2026-10-15 20:34:43 +02:00
#!/usr/bin/env bash
#
# Prepares a cloud session of Claude Code: brings the .NET SDK and the Rust toolchain in line with
# metadata.txt, the way the pipeline reads it, and places the stand-ins which Tauri's build script
# needs before any Rust compiles, like the verify job of the pipeline.
#
# The SessionStart hook in .claude/settings.json runs this script at every start of a session. A cloud
# environment installs its toolchain once, in its setup script, and its sessions then start from a
# snapshot which may be days old while metadata.txt moves on. When everything is in place, this script
# is done within seconds. The setup script of the environment may run it as well, for the first
# installation; it sets CLAUDE_CODE_REMOTE itself, since Claude Code has not started yet.
#
# Usage: prepare-cloud-session.sh [repository root]
# Without an argument, it prepares the repository this script lies in.
#
# Local sessions leave at once: there, the developer's own toolchain and the IDEs build.
#
# What this script prints becomes context of the session. The hook ignores the exit code, so that a
# failure reaches Claude as well; the exit code is meant for the setup script, which must not let a
# broken toolchain into the snapshot of its environment.
#
set -uo pipefail
2026-10-16 10:05:56 +02:00
# A grep behind a pipe sends its output to /dev/null instead of using -q: grep -q leaves at the first
# match, the command in front then dies of SIGPIPE while it writes the rest, and pipefail turns the
# found line into a failed check. Without -q, grep reads to the end.
2026-10-15 20:34:43 +02:00
if [ " ${ CLAUDE_CODE_REMOTE :- } " != "true" ] ; then
exit 0
fi
repo = " ${ 1 :-$( cd " $( dirname " ${ BASH_SOURCE [0] } " ) /../.." && pwd ) } "
log_file = /tmp/prepare-cloud-session.log
: > " $log_file "
export DOTNET_ROOT = " ${ DOTNET_ROOT :- /usr/share/dotnet } "
export DOTNET_CLI_TELEMETRY_OPTOUT = 1
export DOTNET_NOLOGO = 1
say() { echo "[prepare-cloud-session] $* " ; }
fail() {
say " $1 The toolchain is not ready, so builds may fail."
if [ -s " $log_file " ] ; then
say "The end of $log_file :"
tail -n 20 " $log_file "
fi
exit 1
}
# The same parsing as in .github/workflows/build-and-release.yml:
dotnet_sdk_version = $( sed -n '4p' " $repo /metadata.txt" | sed 's/[^0-9.]*\([0-9.]*\).*/\1/' )
rust_version = $( sed -n '6p' " $repo /metadata.txt" | sed 's/[^0-9.]*\([0-9.]*\).*/\1/' )
if [ -z " $dotnet_sdk_version " ] || [ -z " $rust_version " ] ; then
fail "Line 4 or line 6 of metadata.txt names no version."
fi
#
# .NET SDK
#
2026-10-16 10:05:56 +02:00
if ! " $DOTNET_ROOT /dotnet" --list-sdks 2>/dev/null | grep "^ ${ dotnet_sdk_version } " >/dev/null; then
2026-10-15 20:34:43 +02:00
say "Installing the .NET SDK $dotnet_sdk_version ..."
{
curl -fsSL --retry 3 https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh &&
bash /tmp/dotnet-install.sh --version " $dotnet_sdk_version " --install-dir " $DOTNET_ROOT " --no-path
} >> " $log_file " 2>& 1 || fail "Installing the .NET SDK $dotnet_sdk_version failed."
fi
ln -sf " $DOTNET_ROOT /dotnet" /usr/local/bin/dotnet
#
# Rust
#
if ! command -v rustup >/dev/null 2>& 1 && [ -x " $HOME /.cargo/bin/rustup" ] ; then
export PATH = " $HOME /.cargo/bin: $PATH "
fi
if ! command -v rustup >/dev/null 2>& 1; then
say "Installing rustup ..."
{
curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs |
sh -s -- -y --profile minimal --default-toolchain none --no-modify-path
} >> " $log_file " 2>& 1 || fail "Installing rustup failed."
export PATH = " $HOME /.cargo/bin: $PATH "
# Ahead of a cargo which the image might keep in /usr/bin:
for tool in rustup cargo rustc rustdoc cargo-clippy clippy-driver; do
ln -sf " $HOME /.cargo/bin/ $tool " "/usr/local/bin/ $tool "
done
fi
2026-10-16 10:05:56 +02:00
if ! rustup component list --installed --toolchain " $rust_version " 2>/dev/null | grep '^clippy' >/dev/null; then
2026-10-15 20:34:43 +02:00
say "Installing Rust $rust_version with Clippy ..."
rustup toolchain install " $rust_version " --profile minimal --component clippy >> " $log_file " 2>& 1 ||
fail "Installing Rust $rust_version failed."
fi
rustup default " $rust_version " >> " $log_file " 2>& 1 || fail "Rust $rust_version cannot become the default."
#
# Stand-ins, as in the verify job of the pipeline: Tauri's build script refuses to run until the .NET
# sidecar and the downloaded libraries exist, and both are products of a full build. Empty files are
# enough to compile, test, and lint the Rust code. Real files are never replaced.
#
host = $( rustc -vV 2>> " $log_file " | sed -n 's/^host: //p' )
if [ -z " $host " ] ; then
fail "rustc does not name its host."
fi
sidecar = " $repo /app/MindWork AI Studio/bin/dist/mindworkAIStudioServer- $host "
if [ ! -e " $sidecar " ] ; then
mkdir -p " $( dirname " $sidecar " ) "
touch " $sidecar "
chmod +x " $sidecar "
say "Placed a stand-in for the .NET sidecar."
fi
libraries = " $repo /runtime/resources/libraries"
if [ -z " $( ls -A " $libraries " 2>/dev/null) " ] ; then
mkdir -p " $libraries "
touch " $libraries /stand-in-for-verify.txt"
say "Placed a stand-in for the downloaded libraries."
fi
# The .gitignore of the repository covers the sidecar, but not this stand-in. The exclude file of the
# clone keeps it out of every commit without touching the repository:
exclude = $( git -C " $repo " rev-parse --git-path info/exclude 2>/dev/null)
if [ -n " $exclude " ] ; then
case " $exclude " in /*) ;; *) exclude = " $repo / $exclude " ;; esac
if ! grep -qxF '/runtime/resources/libraries/stand-in-for-verify.txt' " $exclude " 2>/dev/null; then
mkdir -p " $( dirname " $exclude " ) "
echo '/runtime/resources/libraries/stand-in-for-verify.txt' >> " $exclude "
fi
fi
#
# The Linux packages are the setup script's part, since installing them takes minutes: the ICU library
# without which the .NET SDK does not run, and what Tauri compiles against. Tell right away when they
# are missing, rather than let the first build find out:
#
packages = ""
2026-10-16 10:05:56 +02:00
if ! ldconfig -p 2>/dev/null | grep 'libicuuc\.so' >/dev/null; then
2026-10-15 20:34:43 +02:00
packages = "libicu-dev"
fi
if ! pkg-config --exists webkit2gtk-4.1 2>/dev/null; then
packages = " $packages build-essential pkg-config libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev"
fi
if [ -n " $packages " ] ; then
say "Builds fail until these Linux packages are installed, which the setup script of the environment normally does: ${ packages # } ."
fi
say "Toolchain in line with metadata.txt: .NET SDK $dotnet_sdk_version , $( rustc -V) . This is a cloud session, so \"Cloud sessions\" in AGENTS.md applies."
exit 0