From 6d6461e4f3a838c461f58a6bdaa898d8546e4ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Thu, 5 Nov 2026 01:26:33 +0800 Subject: [PATCH] Server setup: Tailscale address instead of a domain, and a no-questions mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - setup.sh offers the computer's Tailscale address (HTTPS from tailscale serve, Caddy not started, nothing open to the internet) — docker-compose.tailscale.yml - ANYBOT_SETUP_USER / ANYBOT_SETUP_PASSWORD [/ ANYBOT_SETUP_DOMAIN] answer the questions, for scripts and deployment tools; install.sh server passes them through Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa --- deploy/server/docker-compose.tailscale.yml | 9 ++++ deploy/server/setup.sh | 49 +++++++++++++++++++--- install.sh | 6 ++- 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 deploy/server/docker-compose.tailscale.yml diff --git a/deploy/server/docker-compose.tailscale.yml b/deploy/server/docker-compose.tailscale.yml new file mode 100644 index 0000000..a6dee3e --- /dev/null +++ b/deploy/server/docker-compose.tailscale.yml @@ -0,0 +1,9 @@ +# Tailscale instead of a public domain: only devices in your tailnet reach this server, and +# Tailscale gives it HTTPS (`tailscale serve`), so Caddy isn't started and ports 80/443 stay closed. +# setup.sh turns this on (COMPOSE_FILE in .env) when you pick 「用 Tailscale」. +services: + caddy: + profiles: ["caddy"] + relay: + ports: + - "127.0.0.1:8780:8080" diff --git a/deploy/server/setup.sh b/deploy/server/setup.sh index 3f065ca..b243efc 100755 --- a/deploy/server/setup.sh +++ b/deploy/server/setup.sh @@ -5,10 +5,17 @@ # ./setup.sh --dry-run ask and write the files, but don't start anything # # Run it again any time: it offers to keep the current settings (to update and restart). +# +# Without questions (scripts, Ansible…): give the answers as environment variables. +# ANYBOT_SETUP_USER=me ANYBOT_SETUP_PASSWORD=… ANYBOT_SETUP_DOMAIN=anybot.example.com ./setup.sh +# leave out ANYBOT_SETUP_DOMAIN on a computer with Tailscale to use its Tailscale address. +# An existing setup is kept (updated and restarted). set -u cd "$(dirname "$0")" DRY=0 [ "${1:-}" = "--dry-run" ] && DRY=1 +AUTO=0 +[ -n "${ANYBOT_SETUP_USER:-}" ] && [ -n "${ANYBOT_SETUP_PASSWORD:-}" ] && AUTO=1 # ---------- looks ---------- if [ -t 1 ]; then @@ -64,6 +71,8 @@ ask() { # choose VAR "問題" "選項|說明" ... (↑↓ and Enter) choose() { + # Without questions: the first choice (keep the setup, use Tailscale). + if [ "$AUTO" = 1 ]; then printf -v "$1" '%s' 0; return; fi local __v=$1 title=$2; shift 2 local opts=("$@") sel=0 n=$# key rest i while :; do @@ -105,7 +114,7 @@ spin() { # spin "做什麼" command… : runs it, shows a spinner and the newest return $rc } -pause() { printf ' %s%s%s' "$D" "${1:-按 Enter 繼續}" "$N"; IFS= read -r _; } +pause() { [ "$AUTO" = 1 ] && return; printf ' %s%s%s' "$D" "${1:-按 Enter 繼續}" "$N"; IFS= read -r _; } die() { card_close; printf '\n'; exit 1; } # ---------- 0. this server ---------- @@ -141,8 +150,20 @@ fi if [ "${SKIP:-0}" != 1 ]; then # ---------- 1. the address ---------- - AT=1; MSG="" - while :; do + AT=1; MSG=""; TS=0 + # On a tailnet: offer its address (HTTPS from Tailscale, no domain, nothing open to the internet). + TSNAME=$(tailscale status --json 2>/dev/null | sed -n 's/.*"DNSName": *"\([^"]*\)\.".*/\1/p' | head -1) + if [ "$AUTO" = 1 ] && [ -n "${ANYBOT_SETUP_DOMAIN:-}" ]; then DOMAIN=${ANYBOT_SETUP_DOMAIN#https://}; TSNAME=""; TS=2; fi + if [ "$AUTO" = 1 ] && [ "$TS" = 0 ] && [ -z "$TSNAME" ]; then + header; card_open "網址"; bad "這台沒有 Tailscale:請加上 ANYBOT_SETUP_DOMAIN=你的網域"; die + fi + if [ -n "$TSNAME" ]; then + choose HOW "手機和其他電腦用哪個網址連到這台?" \ + "用 Tailscale:https://$TSNAME|只有你 Tailscale 裡的手機和電腦連得到。不用網域,也不用開 80、443 埠" \ + "用自己的網域|任何地方都連得到(網域要先指到這台)" + if [ "$HOW" = 0 ]; then DOMAIN=$TSNAME; TS=1; fi + fi + while [ "$TS" = 0 ]; do header; card_open "手機和其他電腦用哪個網址連到這台?" note "網域要先指到這台伺服器(DNS 的 A 紀錄)。HTTPS 憑證會自動申請、自動更新。" line "" @@ -164,9 +185,11 @@ if [ "${SKIP:-0}" != 1 ]; then break done + [ "$TS" = 2 ] && TS=0 # ---------- 2. the account ---------- AT=2; MSG="" - while :; do + if [ "$AUTO" = 1 ]; then USERNAME=$ANYBOT_SETUP_USER; PW1=$ANYBOT_SETUP_PASSWORD; BRAIN="雲端大腦"; fi + while [ "$AUTO" = 0 ]; do header; card_open "你的 AnyBot 帳號" note "手機、電腦都用這個帳號登入。第一次啟動時會自動建立。" line "" @@ -199,6 +222,10 @@ if [ "${SKIP:-0}" != 1 ]; then q ANTHROPIC_API_KEY "$ANT" q OPENAI_API_KEY "$OAI" q GEMINI_API_KEY "$GEM" + if [ "$TS" = 1 ]; then + q ANYBOT_TAILSCALE 1 + q COMPOSE_FILE "docker-compose.yml:docker-compose.tailscale.yml" + fi } >.env chmod 600 .env umask 022 @@ -223,6 +250,16 @@ if [ -z "${ANYBOT_BUILD_JOBS:-}" ] && [ -r /proc/meminfo ]; then [ "$mem" -lt 3000 ] && warn "記憶體只有 ${mem} MB:編譯會比較久。被中斷的話先加 2 GB swap(見 docs/install.md)" fi spin "建置並啟動(第一次約 5–15 分鐘)" docker compose up -d --build || die +[ "${ANYBOT_TAILSCALE:-}" = 1 ] && TS=1 +if [ "${TS:-0}" = 1 ]; then + # Tailscale gives the address HTTPS and passes it to the account server (remembered across reboots). + if tailscale serve --bg --https=443 http://127.0.0.1:8780 >/dev/null 2>&1 \ + || sudo tailscale serve --bg --https=443 http://127.0.0.1:8780 >/dev/null 2>&1; then + ok "Tailscale:https://$DOMAIN → 帳號伺服器" + else + bad "沒辦法設定 tailscale serve。請執行:sudo tailscale serve --bg --https=443 http://127.0.0.1:8780" + fi +fi # The brain joins the account (and makes it, the first time). f=0; c=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏"); joined="" @@ -246,7 +283,9 @@ for _ in $(seq 1 120); do printf '\r\e[K %s│%s %s%s%s 等 HTTPS 憑證…' "$D" "$N" "$A" "${c[$((f % 10))]}" "$N"; f=$((f + 1)); sleep 1 done printf '\r\e[K' -if [ -n "$https" ]; then ok "https://$DOMAIN 可以連"; else warn "https://$DOMAIN 還連不到(DNS 還沒生效,或 80/443 埠被防火牆擋住)。看紀錄:docker compose logs caddy"; fi +if [ -n "$https" ]; then ok "https://$DOMAIN 可以連" +elif [ "${TS:-0}" = 1 ]; then warn "https://$DOMAIN 還連不到。Tailscale 的 HTTPS 要在管理頁打開(DNS › HTTPS Certificates),看設定:tailscale serve status" +else warn "https://$DOMAIN 還連不到(DNS 還沒生效,或 80/443 埠被防火牆擋住)。看紀錄:docker compose logs caddy"; fi card_close printf '\n' diff --git a/install.sh b/install.sh index 98826f6..4d2b42b 100755 --- a/install.sh +++ b/install.sh @@ -263,9 +263,11 @@ case $MODE in find_source step "設定和啟動" info "接下來會問網域、帳號、密碼。" - [ -n "$TTY" ] || die "需要一個終端機回答問題。" "在伺服器上直接執行:cd $SRC && ./install.sh server" cd "$SRC/deploy/server" - if [ "${DOCKER_SUDO:-0}" = 1 ]; then exec $SUDO -E ./setup.sh "$@" <"$TTY"; else exec ./setup.sh "$@" <"$TTY"; fi + IN=$TTY + if [ -n "${ANYBOT_SETUP_USER:-}" ] && [ -n "${ANYBOT_SETUP_PASSWORD:-}" ]; then IN=/dev/null # 免問模式 + else [ -n "$TTY" ] || die "需要一個終端機回答問題。" "在伺服器上直接執行:cd $SRC && ./install.sh server(或用 ANYBOT_SETUP_USER、ANYBOT_SETUP_PASSWORD 免問)"; fi + if [ "${DOCKER_SUDO:-0}" = 1 ]; then exec $SUDO -E ./setup.sh "$@" <"$IN"; else exec ./setup.sh "$@" <"$IN"; fi ;; app|hands)