| 1 | #!/usr/bin/env bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | if [[ "${EUID}" -ne 0 ]]; then |
| 5 | echo "Run as root: sudo bash scripts/tencent-lighthouse/bootstrap-ubuntu.sh" >&2 |
| 6 | exit 1 |
| 7 | fi |
| 8 | |
| 9 | CODEWHALE_USER="${CODEWHALE_USER:-${DEEPSEEK_USER:-codewhale}}" |
| 10 | CODEWHALE_ROOT="${CODEWHALE_ROOT:-${DEEPSEEK_ROOT:-/opt/codewhale}}" |
| 11 | WHALEBRO_ROOT="${WHALEBRO_ROOT:-/opt/whalebro}" |
| 12 | REPO_URL="${CODEWHALE_REPO_URL:-${DEEPSEEK_REPO_URL:-https://github.com/Hmbown/CodeWhale.git}}" |
| 13 | WHALEBRO_EXTRA_REPOS="${WHALEBRO_EXTRA_REPOS:-}" |
| 14 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 15 | SOURCE_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" |
| 16 | SOURCE_BRANCH="$(git -C "${SOURCE_ROOT}" branch --show-current 2>/dev/null || true)" |
| 17 | REPO_BRANCH="${CODEWHALE_REPO_BRANCH:-${DEEPSEEK_REPO_BRANCH:-${SOURCE_BRANCH:-main}}}" |
| 18 | |
| 19 | apt-get update |
| 20 | apt-get install -y \ |
| 21 | ca-certificates \ |
| 22 | curl \ |
| 23 | git \ |
| 24 | iproute2 \ |
| 25 | openssh-client \ |
| 26 | build-essential \ |
| 27 | pkg-config \ |
| 28 | libdbus-1-dev \ |
| 29 | libssl-dev \ |
| 30 | nodejs \ |
| 31 | npm \ |
| 32 | rsync \ |
| 33 | tmux \ |
| 34 | fail2ban \ |
| 35 | ufw |
| 36 | |
| 37 | node_major="$(node -p "Number(process.versions.node.split('.')[0])")" |
| 38 | if (( node_major < 18 )); then |
| 39 | echo "Node.js 18+ is required for the phone bridges; install a newer Node.js before running install-services.sh." >&2 |
| 40 | fi |
| 41 | |
| 42 | if ! id -u "${CODEWHALE_USER}" >/dev/null 2>&1; then |
| 43 | useradd --create-home --shell /bin/bash "${CODEWHALE_USER}" |
| 44 | fi |
| 45 | |
| 46 | install -d -o "${CODEWHALE_USER}" -g "${CODEWHALE_USER}" "${CODEWHALE_ROOT}" |
| 47 | install -d -o "${CODEWHALE_USER}" -g "${CODEWHALE_USER}" "${CODEWHALE_ROOT}/bridge" |
| 48 | install -d -o "${CODEWHALE_USER}" -g "${CODEWHALE_USER}" "${CODEWHALE_ROOT}/telegram-bridge" |
| 49 | install -d -o "${CODEWHALE_USER}" -g "${CODEWHALE_USER}" "${WHALEBRO_ROOT}" |
| 50 | install -d -o "${CODEWHALE_USER}" -g "${CODEWHALE_USER}" "${WHALEBRO_ROOT}/worktrees" |
| 51 | install -d -m 0750 -o root -g "${CODEWHALE_USER}" /etc/codewhale |
| 52 | install -d -m 0700 -o "${CODEWHALE_USER}" -g "${CODEWHALE_USER}" /var/lib/codewhale-feishu-bridge |
| 53 | install -d -m 0700 -o "${CODEWHALE_USER}" -g "${CODEWHALE_USER}" /var/lib/codewhale-telegram-bridge |
| 54 | |
| 55 | if [[ ! -d "${WHALEBRO_ROOT}/codewhale/.git" ]]; then |
| 56 | sudo -u "${CODEWHALE_USER}" git clone --branch "${REPO_BRANCH}" "${REPO_URL}" "${WHALEBRO_ROOT}/codewhale" |
| 57 | fi |
| 58 | |
| 59 | for repo_spec in ${WHALEBRO_EXTRA_REPOS}; do |
| 60 | repo_name="${repo_spec%%=*}" |
| 61 | repo_url="${repo_spec#*=}" |
| 62 | if [[ -z "${repo_name}" || -z "${repo_url}" || "${repo_name}" == "${repo_url}" ]]; then |
| 63 | echo "Skipping malformed WHALEBRO_EXTRA_REPOS entry: ${repo_spec}" >&2 |
| 64 | continue |
| 65 | fi |
| 66 | if [[ ! -d "${WHALEBRO_ROOT}/${repo_name}/.git" ]]; then |
| 67 | sudo -u "${CODEWHALE_USER}" git clone "${repo_url}" "${WHALEBRO_ROOT}/${repo_name}" || { |
| 68 | echo "Warning: failed to clone optional repo ${repo_name} from ${repo_url}" >&2 |
| 69 | } |
| 70 | fi |
| 71 | done |
| 72 | |
| 73 | if [[ ! -f /etc/codewhale/runtime.env ]]; then |
| 74 | cat >/etc/codewhale/runtime.env <<'EOF' |
| 75 | CODEWHALE_RUNTIME_TOKEN=replace-with-long-random-token |
| 76 | CODEWHALE_RUNTIME_PORT=7878 |
| 77 | CODEWHALE_RUNTIME_WORKERS=2 |
| 78 | CODEWHALE_PROVIDER=deepseek |
| 79 | DEEPSEEK_API_KEY=replace-with-provider-key |
| 80 | RUST_LOG=info |
| 81 | EOF |
| 82 | chown root:"${CODEWHALE_USER}" /etc/codewhale/runtime.env |
| 83 | chmod 0640 /etc/codewhale/runtime.env |
| 84 | fi |
| 85 | |
| 86 | if [[ ! -f /etc/codewhale/feishu-bridge.env ]]; then |
| 87 | cat >/etc/codewhale/feishu-bridge.env <<'EOF' |
| 88 | FEISHU_APP_ID=cli_xxxxxxxxxxxxxxxx |
| 89 | FEISHU_APP_SECRET=replace-with-app-secret |
| 90 | FEISHU_DOMAIN=feishu |
| 91 | CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878 |
| 92 | CODEWHALE_RUNTIME_TOKEN=replace-with-same-token-as-runtime-env |
| 93 | CODEWHALE_WORKSPACE=/opt/whalebro |
| 94 | CODEWHALE_MODEL=auto |
| 95 | CODEWHALE_MODE=agent |
| 96 | CODEWHALE_ALLOW_SHELL=true |
| 97 | CODEWHALE_TRUST_MODE=false |
| 98 | CODEWHALE_AUTO_APPROVE=false |
| 99 | CODEWHALE_CHAT_ALLOWLIST= |
| 100 | CODEWHALE_ALLOW_UNLISTED=false |
| 101 | FEISHU_THREAD_MAP_PATH=/var/lib/codewhale-feishu-bridge/thread-map.json |
| 102 | FEISHU_ALLOW_GROUPS=false |
| 103 | FEISHU_REQUIRE_PREFIX_IN_GROUP=true |
| 104 | FEISHU_GROUP_PREFIX=/cw |
| 105 | FEISHU_MAX_REPLY_CHARS=3500 |
| 106 | CODEWHALE_TURN_TIMEOUT_MS=900000 |
| 107 | EOF |
| 108 | chown root:"${CODEWHALE_USER}" /etc/codewhale/feishu-bridge.env |
| 109 | chmod 0640 /etc/codewhale/feishu-bridge.env |
| 110 | fi |
| 111 | |
| 112 | ufw allow OpenSSH |
| 113 | ufw --force enable |
| 114 | |
| 115 | cat <<EOF |
| 116 | |
| 117 | Base server setup complete. |
| 118 | |
| 119 | Next: |
| 120 | 1. Install Rust 1.88+ for ${CODEWHALE_USER}; rustup is the usual path. |
| 121 | 2. Build/install both binaries: |
| 122 | sudo -iu ${CODEWHALE_USER} |
| 123 | cd ${WHALEBRO_ROOT}/codewhale |
| 124 | cargo install --path crates/cli --locked --force |
| 125 | cargo install --path crates/tui --locked --force |
| 126 | 3. Copy integrations/feishu-bridge or integrations/telegram-bridge to ${CODEWHALE_ROOT} and run npm install. |
| 127 | 4. Edit /etc/codewhale/runtime.env and the selected bridge env file. |
| 128 | 5. Install systemd units with scripts/tencent-lighthouse/install-services.sh. |
| 129 | 6. After the env files are edited and services are started, run: |
| 130 | sudo bash scripts/tencent-lighthouse/doctor.sh |
| 131 | |
| 132 | EOF |
| 133 |