Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@
"filename": "scripts/init_system.sh",
"hashed_secret": "96183ea4ff07d786ed3233777364ddbf14eb74cc",
"is_verified": false,
"line_number": 19
"line_number": 23
}
]
},
"generated_at": "2026-02-24T14:14:56Z"
"generated_at": "2026-03-13T14:03:33Z"
}
272 changes: 174 additions & 98 deletions scripts/init_system.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
# Initialize host prerequisites for OpenCode + A2A (idempotent).
set -euo pipefail

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./init_system_uv_release_manifest.sh
source "${SCRIPT_DIR}/init_system_uv_release_manifest.sh"

OPENCODE_CORE_DIR="/opt/.opencode"
SHARED_WRAPPER_DIR="/opt/opencode-a2a"
OPENCODE_A2A_DIR="${SHARED_WRAPPER_DIR}/opencode-a2a-server"
Expand Down Expand Up @@ -165,10 +169,10 @@ install_packages() {
log_done "Package installation completed."
}

download_script() {
download_file() {
local url="$1"
local dest="$2"
if ! curl -fL "$url" -o "$dest"; then
if ! curl -fsSL "$url" -o "$dest"; then
return 1
fi
if [[ ! -s "$dest" ]]; then
Expand All @@ -177,15 +181,6 @@ download_script() {
return 0
}

validate_script_contains() {
local path="$1"
local pattern="$2"
if ! grep -Eqi "$pattern" "$path"; then
return 1
fi
return 0
}

verify_file_checksum() {
local path="$1"
local expected="$2"
Expand All @@ -208,6 +203,151 @@ verify_file_checksum() {
return 0
}

detect_linux_libc() {
local output=""
if command -v ldd >/dev/null 2>&1; then
output="$(ldd --version 2>&1 || true)"
fi
if printf '%s' "$output" | grep -qi "musl"; then
printf 'musl\n'
return 0
fi
if getconf GNU_LIBC_VERSION >/dev/null 2>&1; then
printf 'gnu\n'
return 0
fi
if printf '%s' "$output" | grep -Eqi "glibc|gnu libc"; then
printf 'gnu\n'
return 0
fi
return 1
}

resolve_uv_release_artifact() {
local arch="$1"
local libc="$2"

case "${arch}:${libc}" in
x86_64:gnu)
printf '%s|%s\n' "$UV_TARBALL_X86_64_GNU" "$UV_TARBALL_X86_64_GNU_SHA256"
;;
x86_64:musl)
printf '%s|%s\n' "$UV_TARBALL_X86_64_MUSL" "$UV_TARBALL_X86_64_MUSL_SHA256"
;;
aarch64:gnu)
printf '%s|%s\n' "$UV_TARBALL_AARCH64_GNU" "$UV_TARBALL_AARCH64_GNU_SHA256"
;;
aarch64:musl)
printf '%s|%s\n' "$UV_TARBALL_AARCH64_MUSL" "$UV_TARBALL_AARCH64_MUSL_SHA256"
;;
*)
return 1
;;
esac
}

node_manual_install_hint() {
warn "Install Node.js >= ${NODE_MAJOR} manually from a trusted distro repository or internal mirror, then rerun init_system.sh with INSTALL_NODE=false."
}

ensure_node_from_package_manager() {
case "$PKG_MANAGER" in
apt)
$SUDO apt-get update
if ! $SUDO apt-get install -y nodejs npm; then
return 1
fi
;;
dnf)
if ! $SUDO dnf install -y nodejs npm && ! $SUDO dnf install -y nodejs; then
return 1
fi
;;
yum)
if ! $SUDO yum install -y nodejs npm && ! $SUDO yum install -y nodejs; then
return 1
fi
;;
pacman)
if ! $SUDO pacman -Syu --noconfirm nodejs npm; then
return 1
fi
;;
*)
return 1
;;
esac
}

install_uv_from_release() {
local os_name
local arch
local libc
local resolved
local asset
local checksum
local tmpdir
local archive
local extract_dir

os_name="$(uname -s)"
if [[ "$os_name" != "Linux" ]]; then
warn "Unsupported OS for pinned uv bootstrap: $os_name"
return 1
fi

arch="$(uname -m)"
case "$arch" in
x86_64|amd64) arch="x86_64" ;;
aarch64|arm64) arch="aarch64" ;;
*)
warn "Unsupported architecture for pinned uv bootstrap: $arch"
return 1
;;
esac

if ! libc="$(detect_linux_libc)"; then
warn "Unable to detect Linux libc for uv bootstrap."
return 1
fi

if ! resolved="$(resolve_uv_release_artifact "$arch" "$libc")"; then
warn "No pinned uv release artifact configured for ${arch}/${libc}."
return 1
fi

IFS='|' read -r asset checksum <<< "$resolved"
tmpdir="$(mktemp -d)"
archive="${tmpdir}/${asset}"
extract_dir="${tmpdir}/${asset%.tar.gz}"

if ! download_file "${UV_RELEASE_BASE_URL}/${asset}" "$archive"; then
rm -rf "$tmpdir"
return 1
fi
if ! verify_file_checksum "$archive" "$checksum"; then
rm -rf "$tmpdir"
return 1
fi
if ! tar -xzf "$archive" -C "$tmpdir"; then
rm -rf "$tmpdir"
return 1
fi
if [[ ! -x "${extract_dir}/uv" ]]; then
warn "Pinned uv archive is missing the uv binary: ${extract_dir}/uv"
rm -rf "$tmpdir"
return 1
fi

$SUDO install -d -m 755 /usr/local/bin
$SUDO install -m 755 "${extract_dir}/uv" /usr/local/bin/uv
if [[ -x "${extract_dir}/uvx" ]]; then
$SUDO install -m 755 "${extract_dir}/uvx" /usr/local/bin/uvx
fi
rm -rf "$tmpdir"
return 0
}

ensure_group() {
local group="$1"
if [[ -z "$group" ]]; then
Expand Down Expand Up @@ -335,81 +475,28 @@ if is_truthy "$INSTALL_NODE"; then
if [[ -n "$node_major" && "$node_major" -ge "$NODE_MAJOR" ]]; then
log_done "Node.js already installed: ${node_version}"
else
log_start "Installing Node.js ${NODE_MAJOR}.x..."
case "$PKG_MANAGER" in
apt)
nodesource_script="$(mktemp)"
log_start "Downloading NodeSource setup script..."
if ! download_script "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" "$nodesource_script"; then
warn "Failed to download NodeSource setup script."
INCOMPLETE=1
else
log_done "NodeSource setup script downloaded."
log_start "Validating NodeSource setup script..."
if ! validate_script_contains "$nodesource_script" "nodesource|NodeSource"; then
warn "NodeSource setup script validation failed."
INCOMPLETE=1
else
log_done "NodeSource setup script validated."
$SUDO -E bash "$nodesource_script"
fi
fi
rm -f "$nodesource_script"
$SUDO apt-get install -y nodejs
;;
dnf)
nodesource_script="$(mktemp)"
log_start "Downloading NodeSource setup script..."
if ! download_script "https://rpm.nodesource.com/setup_${NODE_MAJOR}.x" "$nodesource_script"; then
warn "Failed to download NodeSource setup script."
INCOMPLETE=1
else
log_done "NodeSource setup script downloaded."
log_start "Validating NodeSource setup script..."
if ! validate_script_contains "$nodesource_script" "nodesource|NodeSource"; then
warn "NodeSource setup script validation failed."
INCOMPLETE=1
else
log_done "NodeSource setup script validated."
$SUDO -E bash "$nodesource_script"
fi
fi
rm -f "$nodesource_script"
$SUDO dnf install -y nodejs
;;
yum)
nodesource_script="$(mktemp)"
log_start "Downloading NodeSource setup script..."
if ! download_script "https://rpm.nodesource.com/setup_${NODE_MAJOR}.x" "$nodesource_script"; then
warn "Failed to download NodeSource setup script."
INCOMPLETE=1
else
log_done "NodeSource setup script downloaded."
log_start "Validating NodeSource setup script..."
if ! validate_script_contains "$nodesource_script" "nodesource|NodeSource"; then
warn "NodeSource setup script validation failed."
INCOMPLETE=1
else
log_done "NodeSource setup script validated."
$SUDO -E bash "$nodesource_script"
fi
fi
rm -f "$nodesource_script"
$SUDO yum install -y nodejs
;;
pacman)
$SUDO pacman -Syu --noconfirm nodejs npm
;;
*)
warn "Unsupported package manager for Node.js install: $PKG_MANAGER"
INCOMPLETE=1
;;
esac
log_start "Installing Node.js from trusted package manager repositories..."
if ! ensure_node_from_package_manager; then
warn "Node.js install via package manager failed."
node_manual_install_hint
INCOMPLETE=1
fi
if command -v node >/dev/null 2>&1; then
node_version="$(node -v 2>/dev/null || true)"
log_done "Node.js installed: ${node_version}"
node_major=""
if [[ "$node_version" =~ ^v([0-9]+) ]]; then
node_major="${BASH_REMATCH[1]}"
fi
if [[ -n "$node_major" && "$node_major" -ge "$NODE_MAJOR" ]]; then
log_done "Node.js installed: ${node_version}"
else
warn "Installed Node.js version does not satisfy NODE_MAJOR=${NODE_MAJOR}: ${node_version:-unknown}"
node_manual_install_hint
INCOMPLETE=1
fi
else
warn "Node.js installation failed."
node_manual_install_hint
INCOMPLETE=1
fi
fi
Expand Down Expand Up @@ -465,23 +552,12 @@ if is_truthy "$INSTALL_UV"; then
INCOMPLETE=1
else
log_start "Installing uv..."
uv_script="$(mktemp)"
log_start "Downloading uv install script..."
if ! download_script "https://astral.sh/uv/install.sh" "$uv_script"; then
warn "Failed to download uv install script."
log_start "Downloading pinned uv release tarball..."
if ! install_uv_from_release; then
warn "Pinned uv release installation failed."
warn "Install uv ${UV_VERSION} manually from a trusted release artifact, then rerun init_system.sh with INSTALL_UV=false."
INCOMPLETE=1
else
log_done "uv install script downloaded."
log_start "Validating uv install script..."
if ! validate_script_contains "$uv_script" "astral|uv"; then
warn "uv install script validation failed."
INCOMPLETE=1
else
log_done "uv install script validated."
UV_PYTHON_INSTALL_DIR="$UV_PYTHON_INSTALL_DIR" sh "$uv_script"
fi
fi
rm -f "$uv_script"
if ! command -v uv >/dev/null 2>&1; then
if [[ -x "$HOME/.local/bin/uv" ]]; then
log_start "Moving uv into /usr/local/bin."
Expand Down Expand Up @@ -618,7 +694,7 @@ else
if [[ -n "$OPENCODE_INSTALL_CMD" ]]; then
opencode_install_script="$(mktemp)"
log_start "Downloading OpenCode installer..."
if ! download_script "$OPENCODE_INSTALLER_URL" "$opencode_install_script"; then
if ! download_file "$OPENCODE_INSTALLER_URL" "$opencode_install_script"; then
warn "Failed to download OpenCode installer."
INCOMPLETE=1
elif ! verify_file_checksum "$opencode_install_script" "$OPENCODE_INSTALLER_SHA256"; then
Expand Down
21 changes: 19 additions & 2 deletions scripts/init_system_readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ The script does not accept runtime arguments. Adjust defaults by editing constan
## What It Does

- installs base tooling and `gh`
- installs Node.js >= 20 (`npm`/`npx`)
- installs `uv` and pre-downloads Python `3.10/3.11/3.12/3.13` (if missing)
- installs Node.js >= 20 (`npm`/`npx`) from the distro package manager only
- installs pinned `uv` release binaries and pre-downloads Python `3.10/3.11/3.12/3.13` (if missing)
- creates shared directories and applies permissions
- clones this repository to shared path (HTTPS by default)
- creates A2A virtualenv via `uv sync --all-extras`
Expand All @@ -30,13 +30,30 @@ The script does not accept runtime arguments. Adjust defaults by editing constan
- Toggles: `INSTALL_PACKAGES`, `INSTALL_UV`, `INSTALL_GH`, `INSTALL_NODE`
- Versions: `NODE_MAJOR`, `UV_PYTHON_VERSIONS`
- Installer pinning: `OPENCODE_INSTALLER_URL`, `OPENCODE_INSTALLER_VERSION`, `OPENCODE_INSTALLER_SHA256`, `OPENCODE_INSTALL_CMD`
- uv release pinning: [`init_system_uv_release_manifest.sh`](./init_system_uv_release_manifest.sh)

## Current Trust Model

- `gh`: installed from distro package manager or package-manager-backed vendor repo configuration
- Node.js: installed only from the distro package manager; the script no longer executes NodeSource setup scripts
- `uv`: installed from a pinned upstream GitHub release tarball, with static asset/checksum data sourced from `init_system_uv_release_manifest.sh`
- OpenCode: installer is still a remote script, but it is version-pinned and checksum-verified before execution
- repo bootstrap: clone still trusts the configured git remote + branch head

## Recommended Secure Mode

- keep installer pinning/checksum verification enabled
- keep `init_system_uv_release_manifest.sh` aligned with the exact `uv` release assets you intend to trust
- prefer distro package mirrors or internal mirrors for Node.js instead of adding ad-hoc third-party setup scripts
- keep `/opt/uv-python` in controlled group mode (`770` -> `750` hardening flow)
- set `UV_PYTHON_DIR_GROUP` to a controlled group and add runtime users intentionally

## Failure Fallback

- If `uv` bootstrap reports an unsupported Linux architecture/libc, install the pinned `uv` release manually and rerun `./scripts/init_system.sh` with `INSTALL_UV=false`.
- If the distro package manager cannot provide `Node.js >= NODE_MAJOR`, install Node.js manually from a trusted distro or internal mirror and rerun with `INSTALL_NODE=false`.
- If OpenCode installer checksum validation fails, stop and refresh the pinned installer metadata before retrying.

## Next Step

After bootstrap, deploy a project instance with [`deploy_readme.md`](./deploy_readme.md).
Loading
Loading