| 1 | #!/usr/bin/env bash |
| 2 | # EXPERIMENTAL — one-shot CodeWhale + Telegram bridge setup for a fresh |
| 3 | # AWS Lightsail Ubuntu 24.04 VM (issue #1990 smoke lane). |
| 4 | # |
| 5 | # Run ON THE VM as root: |
| 6 | # sudo SECRETS_FILE=/tmp/cw-secrets.env bash setup-vm.sh |
| 7 | # |
| 8 | # SECRETS_FILE is a chmod-600 env file you scp'd up, containing: |
| 9 | # TELEGRAM_BOT_TOKEN=... # from @BotFather |
| 10 | # CODEWHALE_PROVIDER=deepseek # or arcee / xiaomi-mimo / ... |
| 11 | # PROVIDER_KEY_NAME=DEEPSEEK_API_KEY |
| 12 | # PROVIDER_KEY_VALUE=... |
| 13 | # TELEGRAM_CHAT_ALLOWLIST=123456789 # optional; empty = first-pairing mode |
| 14 | # The file is shredded after the values land in /etc/codewhale/*.env. |
| 15 | # |
| 16 | # Reuses the repo's existing provider-agnostic scripts: |
| 17 | # scripts/tencent-lighthouse/bootstrap-ubuntu.sh |
| 18 | # scripts/tencent-lighthouse/install-services.sh (CODEWHALE_BRIDGE=telegram) |
| 19 | # scripts/tencent-lighthouse/doctor.sh |
| 20 | # Uses prebuilt release binaries instead of a Rust build. |
| 21 | set -euo pipefail |
| 22 | |
| 23 | RELEASE_TAG="${RELEASE_TAG:-v0.9.4}" |
| 24 | REPO_URL="${REPO_URL:-https://github.com/Hmbown/CodeWhale.git}" |
| 25 | REPO_BRANCH="${REPO_BRANCH:-main}" |
| 26 | SECRETS_FILE="${SECRETS_FILE:-/tmp/cw-secrets.env}" |
| 27 | |
| 28 | [[ "$EUID" -eq 0 ]] || { echo "run as root (sudo)" >&2; exit 1; } |
| 29 | [[ -f "$SECRETS_FILE" ]] || { echo "SECRETS_FILE not found: $SECRETS_FILE" >&2; exit 1; } |
| 30 | |
| 31 | # shellcheck disable=SC1090 |
| 32 | . "$SECRETS_FILE" |
| 33 | : "${TELEGRAM_BOT_TOKEN:?missing in SECRETS_FILE}" |
| 34 | : "${CODEWHALE_PROVIDER:?missing in SECRETS_FILE}" |
| 35 | : "${PROVIDER_KEY_NAME:?missing in SECRETS_FILE}" |
| 36 | : "${PROVIDER_KEY_VALUE:?missing in SECRETS_FILE}" |
| 37 | TELEGRAM_CHAT_ALLOWLIST="${TELEGRAM_CHAT_ALLOWLIST:-}" |
| 38 | |
| 39 | echo "== [1/8] clone repo (${REPO_BRANCH}) ==" |
| 40 | apt-get update -q |
| 41 | apt-get install -y -q git curl ca-certificates |
| 42 | if [[ ! -d /tmp/codewhale/.git ]]; then |
| 43 | git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" /tmp/codewhale |
| 44 | fi |
| 45 | |
| 46 | echo "== [2/8] bootstrap (user, dirs, packages, ufw, env skeletons) ==" |
| 47 | CODEWHALE_REPO_URL="$REPO_URL" CODEWHALE_REPO_BRANCH="$REPO_BRANCH" \ |
| 48 | bash /tmp/codewhale/scripts/tencent-lighthouse/bootstrap-ubuntu.sh |
| 49 | |
| 50 | echo "== [3/8] install prebuilt ${RELEASE_TAG} binaries (no Rust build) ==" |
| 51 | # The systemd unit hardcodes /home/codewhale/.cargo/bin/codewhale, so we put |
| 52 | # the release binaries exactly there. |
| 53 | BIN_DIR=/home/codewhale/.cargo/bin |
| 54 | install -d -o codewhale -g codewhale "$BIN_DIR" |
| 55 | BASE="https://github.com/Hmbown/CodeWhale/releases/download/${RELEASE_TAG}" |
| 56 | TMP=$(mktemp -d) |
| 57 | curl -fsSL -o "$TMP/codewhale" "$BASE/codewhale-linux-x64" |
| 58 | curl -fsSL -o "$TMP/codewhale-tui" "$BASE/codewhale-tui-linux-x64" |
| 59 | curl -fsSL -o "$TMP/sha256.txt" "$BASE/codewhale-artifacts-sha256.txt" |
| 60 | ( cd "$TMP" |
| 61 | grep -E ' (codewhale|codewhale-tui)-linux-x64$' sha256.txt \ |
| 62 | | sed 's/codewhale-linux-x64/codewhale/; s/codewhale-tui-linux-x64/codewhale-tui/' \ |
| 63 | | sha256sum -c - ) |
| 64 | install -m 0755 -o codewhale -g codewhale "$TMP/codewhale" "$BIN_DIR/codewhale" |
| 65 | install -m 0755 -o codewhale -g codewhale "$TMP/codewhale-tui" "$BIN_DIR/codewhale-tui" |
| 66 | rm -rf "$TMP" |
| 67 | sudo -u codewhale "$BIN_DIR/codewhale" --version |
| 68 | sudo -u codewhale "$BIN_DIR/codewhale-tui" --version |
| 69 | |
| 70 | echo "== [4/8] install services (telegram bridge) ==" |
| 71 | CODEWHALE_BRIDGE=telegram bash /tmp/codewhale/scripts/tencent-lighthouse/install-services.sh |
| 72 | |
| 73 | echo "== [5/8] write secrets into /etc/codewhale/*.env ==" |
| 74 | RUNTIME_ENV=/etc/codewhale/runtime.env |
| 75 | BRIDGE_ENV=/etc/codewhale/telegram-bridge.env |
| 76 | RUNTIME_TOKEN="dst_$(openssl rand -hex 24)" |
| 77 | |
| 78 | set_kv() { # file key value (replace or append; never echoes the value) |
| 79 | local file="$1" key="$2" value="$3" |
| 80 | if grep -qE "^${key}=" "$file"; then |
| 81 | # use | delimiter; tokens never contain | |
| 82 | sed -i "s|^${key}=.*|${key}=${value}|" "$file" |
| 83 | else |
| 84 | printf '%s=%s\n' "$key" "$value" >> "$file" |
| 85 | fi |
| 86 | } |
| 87 | set_kv "$RUNTIME_ENV" CODEWHALE_RUNTIME_TOKEN "$RUNTIME_TOKEN" |
| 88 | set_kv "$RUNTIME_ENV" CODEWHALE_PROVIDER "$CODEWHALE_PROVIDER" |
| 89 | set_kv "$RUNTIME_ENV" "$PROVIDER_KEY_NAME" "$PROVIDER_KEY_VALUE" |
| 90 | set_kv "$BRIDGE_ENV" CODEWHALE_RUNTIME_TOKEN "$RUNTIME_TOKEN" |
| 91 | set_kv "$BRIDGE_ENV" TELEGRAM_BOT_TOKEN "$TELEGRAM_BOT_TOKEN" |
| 92 | if [[ -n "$TELEGRAM_CHAT_ALLOWLIST" ]]; then |
| 93 | set_kv "$BRIDGE_ENV" TELEGRAM_CHAT_ALLOWLIST "$TELEGRAM_CHAT_ALLOWLIST" |
| 94 | set_kv "$BRIDGE_ENV" TELEGRAM_ALLOW_UNLISTED false |
| 95 | else |
| 96 | echo "[warn] no TELEGRAM_CHAT_ALLOWLIST given: enabling first-pairing mode" |
| 97 | echo "[warn] (TELEGRAM_ALLOW_UNLISTED=true). DM the bot /status, copy the" |
| 98 | echo "[warn] chat_id into TELEGRAM_CHAT_ALLOWLIST, set ALLOW_UNLISTED=false," |
| 99 | echo "[warn] then: systemctl restart codewhale-telegram-bridge" |
| 100 | set_kv "$BRIDGE_ENV" TELEGRAM_ALLOW_UNLISTED true |
| 101 | fi |
| 102 | chmod 0640 "$RUNTIME_ENV" "$BRIDGE_ENV" |
| 103 | chown root:codewhale "$RUNTIME_ENV" "$BRIDGE_ENV" |
| 104 | shred -u "$SECRETS_FILE" |
| 105 | echo "secrets written; $SECRETS_FILE shredded" |
| 106 | |
| 107 | echo "== [5b/8] install gh CLI (for autonomous agent PR workflow) ==" |
| 108 | if ! command -v gh &>/dev/null; then |
| 109 | apt-get install -y -q software-properties-common |
| 110 | # cli.github.com recommends the official APT repo for Ubuntu |
| 111 | (type -p wget &>/dev/null || apt-get install -y -q wget) |
| 112 | mkdir -p -m 755 /etc/apt/keyrings |
| 113 | wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg \ |
| 114 | | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg >/dev/null |
| 115 | chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg |
| 116 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ |
| 117 | | tee /etc/apt/sources.list.d/github-cli.list >/dev/null |
| 118 | apt-get update -q |
| 119 | apt-get install -y -q gh |
| 120 | fi |
| 121 | |
| 122 | echo "== [5c/8] create 4G swapfile (idempotent) ==" |
| 123 | if [[ ! -f /swapfile ]]; then |
| 124 | fallocate -l 4G /swapfile |
| 125 | chmod 600 /swapfile |
| 126 | mkswap /swapfile |
| 127 | swapon /swapfile |
| 128 | echo '/swapfile none swap sw 0 0' >> /etc/fstab |
| 129 | echo "swapfile created and activated" |
| 130 | else |
| 131 | echo "swapfile already exists, skipping" |
| 132 | fi |
| 133 | |
| 134 | echo "== [6/8] pre-create runtime ReadWritePaths (unit fails without them) ==" |
| 135 | install -d -o codewhale -g codewhale -m 0700 \ |
| 136 | /home/codewhale/.codewhale /home/codewhale/.deepseek |
| 137 | |
| 138 | echo "== [7/8] validate config ==" |
| 139 | sudo -u codewhale node /opt/codewhale/telegram-bridge/scripts/validate-config.mjs \ |
| 140 | --env "$BRIDGE_ENV" --runtime-env "$RUNTIME_ENV" \ |
| 141 | --workspace-root /opt/whalebro --check-filesystem |
| 142 | |
| 143 | echo "== [8/8] start + doctor ==" |
| 144 | systemctl start codewhale-runtime |
| 145 | for _ in $(seq 1 20); do |
| 146 | curl -fsS --max-time 2 http://127.0.0.1:7878/health >/dev/null 2>&1 && break |
| 147 | sleep 1 |
| 148 | done |
| 149 | curl -fsS --max-time 3 http://127.0.0.1:7878/health; echo |
| 150 | systemctl start codewhale-telegram-bridge |
| 151 | sleep 3 |
| 152 | CODEWHALE_BRIDGE=telegram bash /tmp/codewhale/scripts/tencent-lighthouse/doctor.sh |
| 153 | |
| 154 | echo |
| 155 | echo "== Setup complete. Phone smoke checklist (docs/REMOTE_VM_US.md): ==" |
| 156 | echo " 1. DM the bot: /status" |
| 157 | echo " 2. /menu (tappable controls)" |
| 158 | echo " 3. prompt: summarize git status in /opt/whalebro/codewhale" |
| 159 | echo " 4. /threads then a Resume button" |
| 160 | echo " 5. trigger a shell approval; test Allow/Deny buttons and /allow|/deny" |
| 161 | echo " 6. /interrupt during an active turn" |
| 162 | echo " 7. sudo reboot; confirm both services return: systemctl status codewhale-runtime codewhale-telegram-bridge" |
| 163 |