#!/usr/bin/env bash # bootstrap-dev-lxc.sh — provision a fresh dev LXC. # Run from the PVE host as root. Idempotent. # # Usage: # ./bootstrap-dev-lxc.sh [gitea_token_file] # # Assumes: # - CT already exists and is started # - You have local copies of claude-code (in /tmp/claude-staging) and tea # (in /tmp/tea-staging) and credentials (/tmp/claude-creds-staging.json) # # This is intentionally not a full image builder — it's the manual recipe # that documents what dev-01 was bootstrapped with. Codify later. set -euo pipefail CTID="${1:?usage: $0 [gitea_token_file]}" TOKEN_FILE="${2:-/tmp/gitea-token-staging}" [[ -r /tmp/claude-staging ]] || { echo "missing /tmp/claude-staging (claude-code tree)" >&2; exit 1; } [[ -r /tmp/tea-staging ]] || { echo "missing /tmp/tea-staging (tea binary)" >&2; exit 1; } [[ -r /tmp/claude-creds-staging.json ]] || { echo "missing /tmp/claude-creds-staging.json" >&2; exit 1; } [[ -r "$TOKEN_FILE" ]] || { echo "missing token file $TOKEN_FILE" >&2; exit 1; } # --- packages --- pct exec "$CTID" -- bash -c ' apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ git curl ca-certificates jq openssh-server xz-utils sudo \ nodejs npm build-essential python3 python3-pip mkdir -p /root/.claude /root/.local/bin /root/.local/share /root/.ssh /etc/agent /var/agent/workspaces /var/agent/logs chmod 700 /root/.ssh /root/.claude # Non-root agent user (claude refuses bypassPermissions as root) id agent &>/dev/null || useradd -m -s /bin/bash agent chown agent:agent /etc/agent /var/agent /var/agent/workspaces /var/agent/logs chmod 700 /etc/agent grep -q "/.local/bin" /home/agent/.profile 2>/dev/null || \ echo "export PATH=\$HOME/.local/bin:/usr/local/bin:\$PATH" >> /home/agent/.profile ' # --- claude-code --- tar -czf /tmp/claude-bundle.tgz -C /tmp claude-staging pct push "$CTID" /tmp/claude-bundle.tgz /tmp/claude-bundle.tgz pct push "$CTID" /tmp/claude-creds-staging.json /home/agent/.claude/.credentials.json --perms 600 pct exec "$CTID" -- bash -c ' set -e install -d -o agent -g agent /home/agent/.local/share /home/agent/.local/bin /home/agent/.claude tar -xzf /tmp/claude-bundle.tgz -C /home/agent/.local/share/ mv /home/agent/.local/share/claude-staging /home/agent/.local/share/claude CLAUDE_VERSION=$(ls /home/agent/.local/share/claude/versions/ | sort -V | tail -1) ln -sf /home/agent/.local/share/claude/versions/$CLAUDE_VERSION /home/agent/.local/bin/claude chown -R agent:agent /home/agent/.local /home/agent/.claude chmod 700 /home/agent/.claude chmod 600 /home/agent/.claude/.credentials.json rm -f /tmp/claude-bundle.tgz ' rm -f /tmp/claude-bundle.tgz # --- tea --- pct push "$CTID" /tmp/tea-staging /usr/local/bin/tea --perms 755 # --- gitea token --- pct push "$CTID" "$TOKEN_FILE" /etc/agent/gitea-token --perms 600 pct exec "$CTID" -- chown agent:agent /etc/agent/gitea-token # --- SSH authorized_keys for agent user --- pct push "$CTID" /tmp/agent-authorized-keys /home/agent/.ssh/authorized_keys --perms 600 2>/dev/null || \ echo "(skip: place orchestrator pubkey at /tmp/agent-authorized-keys before bootstrap to enable SSH)" pct exec "$CTID" -- bash -c ' install -d -o agent -g agent -m 700 /home/agent/.ssh [ -f /home/agent/.ssh/authorized_keys ] && chown agent:agent /home/agent/.ssh/authorized_keys ' # --- enable sshd --- pct exec "$CTID" -- systemctl enable --now ssh echo "Bootstrap complete for CT $CTID. Versions:" pct exec "$CTID" -- su - agent -c 'claude --version; tea --version | head -2; node --version'