Server setup: Tailscale address instead of a domain, and a no-questions mode

- 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
This commit is contained in:
王性驊 2026-11-05 01:26:33 +08:00
parent 472a004f87
commit 6d6461e4f3
3 changed files with 57 additions and 7 deletions

View File

@ -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"

View File

@ -5,10 +5,17 @@
# ./setup.sh --dry-run ask and write the files, but don't start anything # ./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). # 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 set -u
cd "$(dirname "$0")" cd "$(dirname "$0")"
DRY=0 DRY=0
[ "${1:-}" = "--dry-run" ] && DRY=1 [ "${1:-}" = "--dry-run" ] && DRY=1
AUTO=0
[ -n "${ANYBOT_SETUP_USER:-}" ] && [ -n "${ANYBOT_SETUP_PASSWORD:-}" ] && AUTO=1
# ---------- looks ---------- # ---------- looks ----------
if [ -t 1 ]; then if [ -t 1 ]; then
@ -64,6 +71,8 @@ ask() {
# choose VAR "問題" "選項|說明" ... (↑↓ and Enter) # choose VAR "問題" "選項|說明" ... (↑↓ and Enter)
choose() { 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 __v=$1 title=$2; shift 2
local opts=("$@") sel=0 n=$# key rest i local opts=("$@") sel=0 n=$# key rest i
while :; do while :; do
@ -105,7 +114,7 @@ spin() { # spin "做什麼" command… : runs it, shows a spinner and the newest
return $rc 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; } die() { card_close; printf '\n'; exit 1; }
# ---------- 0. this server ---------- # ---------- 0. this server ----------
@ -141,8 +150,20 @@ fi
if [ "${SKIP:-0}" != 1 ]; then if [ "${SKIP:-0}" != 1 ]; then
# ---------- 1. the address ---------- # ---------- 1. the address ----------
AT=1; MSG="" AT=1; MSG=""; TS=0
while :; do # 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 "手機和其他電腦用哪個網址連到這台?" header; card_open "手機和其他電腦用哪個網址連到這台?"
note "網域要先指到這台伺服器(DNS 的 A 紀錄)。HTTPS 憑證會自動申請、自動更新。" note "網域要先指到這台伺服器(DNS 的 A 紀錄)。HTTPS 憑證會自動申請、自動更新。"
line "" line ""
@ -164,9 +185,11 @@ if [ "${SKIP:-0}" != 1 ]; then
break break
done done
[ "$TS" = 2 ] && TS=0
# ---------- 2. the account ---------- # ---------- 2. the account ----------
AT=2; MSG="" 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 帳號" header; card_open "你的 AnyBot 帳號"
note "手機、電腦都用這個帳號登入。第一次啟動時會自動建立。" note "手機、電腦都用這個帳號登入。第一次啟動時會自動建立。"
line "" line ""
@ -199,6 +222,10 @@ if [ "${SKIP:-0}" != 1 ]; then
q ANTHROPIC_API_KEY "$ANT" q ANTHROPIC_API_KEY "$ANT"
q OPENAI_API_KEY "$OAI" q OPENAI_API_KEY "$OAI"
q GEMINI_API_KEY "$GEM" 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 } >.env
chmod 600 .env chmod 600 .env
umask 022 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)" [ "$mem" -lt 3000 ] && warn "記憶體只有 ${mem} MB:編譯會比較久。被中斷的話先加 2 GB swap(見 docs/install.md)"
fi fi
spin "建置並啟動(第一次約 5–15 分鐘)" docker compose up -d --build || die 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). # The brain joins the account (and makes it, the first time).
f=0; c=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏"); joined="" 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 printf '\r\e[K %s│%s %s%s%s 等 HTTPS 憑證…' "$D" "$N" "$A" "${c[$((f % 10))]}" "$N"; f=$((f + 1)); sleep 1
done done
printf '\r\e[K' 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 card_close
printf '\n' printf '\n'

View File

@ -263,9 +263,11 @@ case $MODE in
find_source find_source
step "設定和啟動" step "設定和啟動"
info "接下來會問網域、帳號、密碼。" info "接下來會問網域、帳號、密碼。"
[ -n "$TTY" ] || die "需要一個終端機回答問題。" "在伺服器上直接執行:cd $SRC && ./install.sh server"
cd "$SRC/deploy/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) app|hands)