One-step install: install.sh, fixed Linux and server builds, clearer README
- install.sh (curl | sh): app, hands, server and deps modes; installs system packages
(right names per release, e.g. libasound2t64 on Ubuntu 24.04), Rust via rustup (1.95+),
Docker for the server; limits parallel compiles on small machines
- Server Docker image: add the ALSA build/runtime packages (the build failed), cache the
cargo registry and target between rebuilds, JOBS build arg
- 加一台電腦 now gives a line that installs AnyBot on a blank computer before joining it
- Makefile: make deps installs Rust too, make doctor, clear message when Rust is missing/old
- README rewritten: one line per use, getting started, update/remove, troubleshooting,
deployment options; docs/install.md and multi-device.md follow it
- Tested on fresh ubuntu:24.04 (app) and debian:12 (curl | sh hands) containers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 16:52:10 +00:00
#!/bin/sh
# AnyBot 一鍵安裝。什麼都沒裝的電腦也可以: 會自己裝好需要的東西( 系統套件、Rust、Docker) 。
#
# curl -fsSL https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.sh | sh
# 桌面 App( Mac、有螢幕的 Linux) 。沒有螢幕的 Linux 會自動改成「手腳」
# … | sh -s -- hands [網址 配對碼]
# 沒有螢幕的 Linux: 裝好後接成大腦的手腳( 網址和配對碼在 AnyBot 的「加一台電腦」)
# … | sh -s -- server
# 伺服器:裝 Docker, 架好帳號伺服器 + 大腦 + HTTPS( 只問網域、帳號、密碼)
# … | sh -s -- deps
# 只裝建置需要的東西(之後自己 make)
#
# 已經 clone 下來的話,在資料夾裡執行 ./install.sh [app|hands|server|deps],效果一樣。
# 環境變數: ANYBOT_DIR( 放原始碼的地方, 預設 ~/AnyBot) 、ANYBOT_BRANCH( 預設 main) 。
set -eu
REPO = ${ ANYBOT_REPO :- https : //code.30cm.net/daniel.w/AnyBot.git }
BRANCH = ${ ANYBOT_BRANCH :- main }
DIR = ${ ANYBOT_DIR :- $HOME /AnyBot }
RUST_MIN = 95 # Rust 1.95 以上
MODE = ${ 1 :- auto }
[ $# -gt 0 ] && shift
case $MODE in
app| hands| headless| server| deps| auto) ; ;
-h| --help| help ) sed -n '2,14p' " $0 " 2>/dev/null | sed 's/^# \{0,1\}//' ; exit 0 ; ;
*) echo " 不認得「 ${ MODE } 」。可以用: app、hands、server、deps " ; exit 1 ; ;
esac
[ " $MODE " = headless ] && MODE = hands
# ---------- 外觀 ----------
if [ -t 1 ] ; then B = $( printf '\033[1m' ) ; D = $( printf '\033[90m' ) ; G = $( printf '\033[32m' ) ; Y = $( printf '\033[33m' ) ; R = $( printf '\033[31m' ) ; C = $( printf '\033[36m' ) ; N = $( printf '\033[0m' )
else B = ; D = ; G = ; Y = ; R = ; C = ; N = ; fi
STEP = 0; TOTAL = 4
step( ) { STEP = $(( STEP + 1 )) ; printf '\n%s[%d/%d]%s %s%s%s\n' " $C " " $STEP " " $TOTAL " " $N " " $B " " $1 " " $N " ; }
ok( ) { printf ' %s✓%s %s\n' " $G " " $N " " $1 " ; }
info( ) { printf ' %s%s%s\n' " $D " " $1 " " $N " ; }
warn( ) { printf ' %s!%s %s\n' " $Y " " $N " " $1 " ; }
die( ) { printf '\n %s✗ %s%s\n' " $R " " $1 " " $N " ; [ $# -gt 1 ] && printf ' %s\n' " $2 " ; exit 1; }
have( ) { command -v " $1 " >/dev/null 2>& 1; }
# 互動輸入:用 curl | sh 時標準輸入是腳本本身,改從終端機讀。
TTY = /dev/tty
[ -r " $TTY " ] && [ -w " $TTY " ] && ( exec <" $TTY " ) 2>/dev/null || TTY =
# ---------- 這台是什麼 ----------
OS = $( uname -s) ; ARCH = $( uname -m)
case $OS in
Darwin) PLAT = mac ; ;
Linux) PLAT = linux ; ;
*) die "這個腳本只支援 macOS 和 Linux。" "Windows: 照 docs/install.md 用 cargo build --release。" ; ;
esac
if [ " $MODE " = auto ] ; then
if [ $PLAT = linux ] && [ -z " ${ DISPLAY :- } ${ WAYLAND_DISPLAY :- } " ] && ! have gnome-shell && ! have plasmashell && ! have startxfce4; then
MODE = hands
else
MODE = app
fi
fi
[ $PLAT = mac ] && [ " $MODE " = hands ] && MODE = app
SUDO =
if [ $PLAT = linux ] && [ " $( id -u) " != 0 ] ; then
have sudo || die "需要 sudo 才能安裝系統套件。" "請用 root 執行,或先裝 sudo。"
SUDO = sudo
fi
PM =
if [ $PLAT = linux ] ; then
for p in apt-get dnf pacman zypper; do have $p && { PM = $p ; break; } ; done
fi
case $MODE in
app) TITLE = "桌面 App" ; ;
hands) TITLE = "沒有螢幕的電腦(手腳)" ; ;
server) TITLE = "伺服器(帳號伺服器 + 大腦 + HTTPS) " ; TOTAL = 3 ; ;
deps) TITLE = "只裝建置需要的東西" ; TOTAL = 2 ; ;
esac
printf '\n %s◆ AnyBot 安裝%s %s%s · %s %s%s\n' " $C $B " " $N " " $D " " $TITLE " " $OS " " $ARCH " " $N "
# ---------- 系統套件 ----------
# apt: 只裝這個發行版有的名字( 例如 Ubuntu 24.04 的 libasound2 叫 libasound2t64) 。
apt_pick( ) {
for p in " $@ " ; do
case $p in
*\| *) # 幾個名字選第一個有的
for alt in $( echo " $p " | tr '|' ' ' ) ; do
if apt-cache show " $alt " >/dev/null 2>& 1; then echo " $alt " ; break; fi
done ; ;
*) apt-cache show " $p " >/dev/null 2>& 1 && echo " $p " ; ;
esac
done
}
linux_packages( ) {
# $1: build | app | hands | server
case $PM in
apt-get)
$SUDO apt-get update -qq || warn "apt-get update 有錯誤,繼續試試看"
case $1 in
server) set -- curl ca-certificates git ; ;
build) set -- build-essential pkg-config git curl ca-certificates libasound2-dev ; ;
# 手腳:虛擬螢幕、視窗管理員、輔助使用服務(讀 App 的按鈕) 、session bus, Bot 才看得到這台的畫面。
hands) set -- build-essential pkg-config git curl ca-certificates libasound2-dev "libasound2t64|libasound2" \
xvfb openbox at-spi2-core libatk-adaptor dbus-x11 dbus-user-session xauth fonts-noto-cjk ; ;
app) set -- build-essential pkg-config git curl ca-certificates libasound2-dev "libasound2t64|libasound2" \
libxkbcommon-x11-0 "libgl1|libgl1-mesa-glx" xdg-utils libnotify-bin libsecret-tools espeak-ng zenity \
xclip wl-clipboard ffmpeg fonts-noto-cjk ; ;
esac
pkgs = $( apt_pick " $@ " | tr '\n' ' ' )
info " apt-get install $pkgs "
$SUDO env DEBIAN_FRONTEND = noninteractive apt-get install -y -qq $pkgs >/dev/null || die "安裝系統套件失敗。" " 請看上面的錯誤, 或手動執行: sudo apt-get install $pkgs "
; ;
dnf)
base = "gcc gcc-c++ make pkgconf-pkg-config git curl ca-certificates alsa-lib-devel"
extra = ""
[ " $1 " = app ] && extra = "libxkbcommon-x11 mesa-libGL xdg-utils libnotify libsecret espeak-ng zenity xclip wl-clipboard ffmpeg-free google-noto-sans-cjk-fonts"
[ " $1 " = hands ] && extra = "xorg-x11-server-Xvfb openbox at-spi2-core at-spi2-atk dbus-x11 xorg-x11-xauth google-noto-sans-cjk-fonts"
[ " $1 " = server ] && base = "curl git ca-certificates"
$SUDO dnf install -y -q $base $extra || die "安裝系統套件失敗。" " sudo dnf install $base $extra "
; ;
pacman)
base = "base-devel git curl alsa-lib"
[ " $1 " = app ] && base = " $base libxkbcommon-x11 mesa xdg-utils libnotify libsecret espeak-ng zenity xclip wl-clipboard ffmpeg noto-fonts-cjk "
[ " $1 " = hands ] && base = " $base xorg-server-xvfb openbox at-spi2-core dbus xorg-xauth noto-fonts-cjk "
[ " $1 " = server ] && base = "curl git"
$SUDO pacman -S --needed --noconfirm $base || die "安裝系統套件失敗。" " sudo pacman -S --needed $base "
; ;
zypper)
base = "gcc gcc-c++ make pkg-config git curl alsa-devel"
[ " $1 " = server ] && base = "curl git"
$SUDO zypper -n install $base || die "安裝系統套件失敗。" " sudo zypper install $base "
; ;
*)
die "認不出這台的套件管理程式( apt、dnf、pacman、zypper 都沒有)。" \
"請自己裝: C 編譯器、pkg-config、git、curl、ALSA 開發套件,再執行一次。"
; ;
esac
}
mac_tools( ) {
if xcode-select -p >/dev/null 2>& 1; then ok "Xcode Command Line Tools" ; return ; fi
warn "需要 Xcode Command Line Tools, 正在打開安裝視窗…"
xcode-select --install 2>/dev/null || true
die "裝好之後(約 5 分鐘),再執行一次同一行指令。"
}
# ---------- Rust ----------
rust_ok( ) {
have cargo && have rustc || return 1
minor = $( rustc --version 2>/dev/null | sed -n 's/^rustc 1\.\([0-9]*\).*/\1/p' )
[ -n " $minor " ] && [ " $minor " -ge $RUST_MIN ]
}
ensure_rust( ) {
[ -f " $HOME /.cargo/env " ] && . " $HOME /.cargo/env "
if rust_ok; then ok " Rust $( rustc --version | cut -d' ' -f2) " ; return ; fi
if have rustup; then
info "Rust 太舊, 更新中( rustup update stable) …"
rustup update stable >/dev/null 2>& 1 && rustup default stable >/dev/null 2>& 1 || true
else
if have rustc; then warn " 系統的 Rust $( rustc --version | cut -d' ' -f2) 太舊(要 1. $RUST_MIN 以上),另外用 rustup 裝新版 " ; fi
info "安裝 Rust( rustup, 裝在 ~/.cargo, 不用 root) …"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -q --profile minimal --no-modify-path >/dev/null 2>& 1 \
|| die "安裝 Rust 失敗。" "手動: curl --proto '=https' -sSf https://sh.rustup.rs | sh"
. " $HOME /.cargo/env "
fi
rust_ok || die " Rust 還是太舊: $( rustc --version 2>/dev/null) " "執行 rustup update stable, 或移除系統的 rustc/cargo 套件。"
ok " Rust $( rustc --version | cut -d' ' -f2) "
case " : $PATH : " in *" : $HOME /.cargo/bin: " *) ; ; *)
for rc in " $HOME /.profile " " $HOME /.bashrc " " $HOME /.zshrc " ; do
[ -f " $rc " ] && ! grep -q '.cargo/env' " $rc " && echo '. "$HOME/.cargo/env"' >>" $rc "
done ; ;
esac
}
# ---------- 原始碼 ----------
SRC =
find_source( ) {
here = $( cd " $( dirname " $0 " ) " 2>/dev/null && pwd || true )
if [ -n " $here " ] && [ -f " $here /Cargo.toml " ] && grep -q '^name = "anybot"' " $here /Cargo.toml " ; then
SRC = $here ; ok " 用這份原始碼: $SRC " ; return
fi
have git || die "需要 git。"
if [ -d " $DIR /.git " ] ; then
info " 更新 $DIR … "
git -C " $DIR " pull --ff-only -q || warn "沒辦法更新(你改過裡面的檔案?),用現有的版本"
else
[ -e " $DIR " ] && die " $DIR 已經存在,但不是 AnyBot 的原始碼。 " "換個地方: ANYBOT_DIR=~/別的資料夾 再執行一次。"
info " 下載原始碼到 $DIR … "
git clone -q --depth 1 -b " $BRANCH " " $REPO " " $DIR " || die "下載原始碼失敗。" " 網路連得到 $REPO 嗎? "
fi
SRC = $DIR ; ok " 原始碼: ${ SRC } ( $( git -C " $SRC " log -1 --format= %h) ) "
}
# 記憶體小的機器同時編太多會被系統砍掉:每 2 GB 一個工作。
build_jobs( ) {
mem_mb = 0
if [ -r /proc/meminfo ] ; then
mem_mb = $( awk '/MemTotal/ {m=$2} /SwapTotal/ {s=$2} END {print int((m + s / 2) / 1024)}' /proc/meminfo)
fi
[ " $mem_mb " -gt 0 ] || { echo "" ; return ; }
j = $(( mem_mb / 2000 )) ; [ $j -lt 1 ] && j = 1
cpus = $( nproc 2>/dev/null || echo $j ) ; [ $j -gt " $cpus " ] && j = $cpus
echo $j
if [ " $mem_mb " -lt 3000 ] ; then
warn " 這台記憶體只有 ${ mem_mb } MB, 編譯會比較久( 一次編一個) 。若被中斷, 加 2 GB swap 再試: " >& 2
info " sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile" >& 2
fi
}
build( ) {
jobs = $( build_jobs)
info "編譯中,第一次約 5– 15 分鐘(之後更新只重編改過的部分)…"
log = " $SRC /target/install-build.log " ; mkdir -p " $SRC /target "
if ( cd " $SRC " && cargo build --release --bin anybot ${ jobs : +-j $jobs } ) >" $log " 2>& 1; then
ok "編譯完成"
else
tail -n 25 " $log " | sed 's/^/ /'
die " 編譯失敗(完整紀錄: ${ log } ) " "把紀錄的最後幾行貼給維護的人;常見原因見 docs/install.md 的「常見問題」。"
fi
}
# ---------- Docker( 伺服器) ----------
ensure_docker( ) {
if ! have docker; then
info "安裝 Docker( 官方腳本 get.docker.com) …"
curl -fsSL https://get.docker.com | $SUDO sh >/dev/null || die "安裝 Docker 失敗。" "手動: https://docs.docker.com/engine/install/"
fi
$SUDO systemctl enable --now docker >/dev/null 2>& 1 || true
docker compose version >/dev/null 2>& 1 || $SUDO docker compose version >/dev/null 2>& 1 || {
case $PM in
apt-get) $SUDO apt-get install -y -qq docker-compose-plugin >/dev/null 2>& 1 || $SUDO apt-get install -y -qq docker-compose-v2 >/dev/null 2>& 1 || true ; ;
dnf) $SUDO dnf install -y -q docker-compose-plugin >/dev/null 2>& 1 || true ; ;
esac
}
docker compose version >/dev/null 2>& 1 || $SUDO docker compose version >/dev/null 2>& 1 || die "沒有 docker compose。" "請更新 Docker: https://docs.docker.com/compose/install/"
ok " Docker $( docker --version 2>/dev/null | sed 's/Docker version \([^,]*\).*/\1/' ) "
if ! docker info >/dev/null 2>& 1; then
if [ -n " $SUDO " ] ; then
$SUDO usermod -aG docker " $( id -un) " 2>/dev/null || true
DOCKER_SUDO = 1
info "已把你加進 docker 群組(下次登入後不用 sudo) ; 這次先用 sudo。"
fi
fi
}
# =====================================================================
case $MODE in
deps)
step "系統套件"
if [ $PLAT = mac ] ; then mac_tools; else linux_packages app; ok "系統套件" ; fi
step "Rust"
ensure_rust
printf '\n %s好了。%s在原始碼資料夾裡: %smake%s 建置、%smake install%s 安裝。\n' " $G $B " " $N " " $B " " $N " " $B " " $N "
[ -n " ${ PS1 :- } " ] || info "這個終端機還找不到 cargo 的話:. \"\$HOME/.cargo/env\""
; ;
server)
[ $PLAT = linux ] || die "伺服器模式要在 Linux 上跑。" "Mac 當大腦的做法見 docs/multi-device.md。"
step "系統套件和 Docker"
linux_packages server
ensure_docker
step "原始碼"
find_source
step "設定和啟動"
info "接下來會問網域、帳號、密碼。"
cd " $SRC /deploy/server "
2026-11-04 17:26:33 +00:00
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
One-step install: install.sh, fixed Linux and server builds, clearer README
- install.sh (curl | sh): app, hands, server and deps modes; installs system packages
(right names per release, e.g. libasound2t64 on Ubuntu 24.04), Rust via rustup (1.95+),
Docker for the server; limits parallel compiles on small machines
- Server Docker image: add the ALSA build/runtime packages (the build failed), cache the
cargo registry and target between rebuilds, JOBS build arg
- 加一台電腦 now gives a line that installs AnyBot on a blank computer before joining it
- Makefile: make deps installs Rust too, make doctor, clear message when Rust is missing/old
- README rewritten: one line per use, getting started, update/remove, troubleshooting,
deployment options; docs/install.md and multi-device.md follow it
- Tested on fresh ubuntu:24.04 (app) and debian:12 (curl | sh hands) containers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 16:52:10 +00:00
; ;
app| hands)
step "系統套件"
if [ $PLAT = mac ] ; then mac_tools
elif [ " $MODE " = hands ] && have anybot && [ $# -gt 0 ] ; then ok "AnyBot 已經裝好,跳過"
else linux_packages $MODE ; ok "系統套件" ; fi
if [ " $MODE " = hands ] && have anybot && [ $# -gt 0 ] ; then
STEP = 3; info " 已經有 $( command -v anybot) ,直接接上。(要更新:不帶網址再執行一次) "
else
step "Rust"
ensure_rust
step "下載和編譯"
find_source
build
step "安裝"
if [ $PLAT = mac ] ; then
( cd " $SRC " && make install >/dev/null) || die "安裝失敗。" " 在 $SRC 執行 make install 看錯誤。 "
ok "AnyBot.app 放在 ~/Applications"
else
sh " $SRC /deploy/linux/install.sh " >/dev/null || die "安裝失敗。" " 執行 sh $SRC /deploy/linux/install.sh 看錯誤。 "
ok "程式放在 ~/.local/bin/anybot"
[ " $MODE " = app ] && ok "應用程式選單裡有 AnyBot"
2026-11-04 23:56:19 +00:00
# 更新: the one running in the background (anybot setup's service) switches to the new one.
if systemctl --user is-active --quiet anybot 2>/dev/null; then
systemctl --user restart anybot && ok "重新啟動背景執行的 AnyBot( 換成新版) "
fi
One-step install: install.sh, fixed Linux and server builds, clearer README
- install.sh (curl | sh): app, hands, server and deps modes; installs system packages
(right names per release, e.g. libasound2t64 on Ubuntu 24.04), Rust via rustup (1.95+),
Docker for the server; limits parallel compiles on small machines
- Server Docker image: add the ALSA build/runtime packages (the build failed), cache the
cargo registry and target between rebuilds, JOBS build arg
- 加一台電腦 now gives a line that installs AnyBot on a blank computer before joining it
- Makefile: make deps installs Rust too, make doctor, clear message when Rust is missing/old
- README rewritten: one line per use, getting started, update/remove, troubleshooting,
deployment options; docs/install.md and multi-device.md follow it
- Tested on fresh ubuntu:24.04 (app) and debian:12 (curl | sh hands) containers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 16:52:10 +00:00
fi
fi
PATH = " $HOME /.local/bin: $PATH "
if [ " $MODE " = hands ] ; then
if [ $# -gt 0 ] ; then
printf '\n'
[ -n " $TTY " ] && exec anybot setup " $@ " <" $TTY "
exec anybot setup " $@ "
fi
printf '\n %s裝好了。%s接著把這台接到你的大腦: \n' " $G $B " " $N "
printf ' 1. 在手機或電腦的 AnyBot 按「加一台電腦」,取得配對碼\n'
printf ' 2. 在這裡執行:%sanybot setup 網址 配對碼%s\n' " $B " " $N "
else
printf '\n %s裝好了。%s' " $G $B " " $N "
if [ $PLAT = mac ] ; then printf '打開 ~/Applications/AnyBot( 第一次: 按右鍵 › 打開)。\n'
else printf '從應用程式選單打開 AnyBot, 或在終端機輸入 %sanybot%s。\n' " $B " " $N " ; fi
if ! have docker; then info "Bot 自己的電腦(一個隔離的桌面)需要 Docker, 要用時 AnyBot 會告訴你怎麼裝。" ; fi
info "要更新:再執行一次同一行指令。"
fi
case " : $PATH : " in *" : $HOME /.local/bin: " *) ; ; *) [ $PLAT = linux ] && info "(終端機找不到 anybot 的話,重新登入,或把 ~/.local/bin 加進 PATH) " ; ; esac
; ;
esac