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
This commit is contained in:
parent
11ed58cb3c
commit
472a004f87
|
|
@ -2,6 +2,8 @@
|
|||
name = "anybot"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
# Older Rust stops with a clear message instead of odd errors deep in a dependency.
|
||||
rust-version = "1.95"
|
||||
|
||||
[[bin]]
|
||||
name = "anybot"
|
||||
|
|
|
|||
46
Makefile
46
Makefile
|
|
@ -1,10 +1,14 @@
|
|||
# AnyBot — build and install from source. make help lists everything.
|
||||
#
|
||||
# git clone git@git.30cm.net:daniel.w/AnyBot.git && cd AnyBot
|
||||
# make build (release)
|
||||
# make install install for you: ~/Applications/AnyBot.app on a Mac, ~/.local/bin on Linux
|
||||
# git clone https://code.30cm.net/daniel.w/AnyBot.git && cd AnyBot
|
||||
# make deps everything the build needs (system packages, Rust) — once
|
||||
# make install build and install: ~/Applications/AnyBot.app on a Mac, ~/.local/bin on Linux
|
||||
# make run open it without installing
|
||||
#
|
||||
# The one-step version of all of this is ./install.sh (README.md).
|
||||
|
||||
# Rust installed by rustup lives in ~/.cargo/bin, which a fresh shell may not have on PATH yet.
|
||||
export PATH := $(HOME)/.cargo/bin:$(PATH)
|
||||
CARGO ?= cargo
|
||||
UNAME := $(shell uname -s)
|
||||
VERSION ?= $(shell git describe --tags --always 2>/dev/null | sed 's/^v//' || echo dev)
|
||||
|
|
@ -13,7 +17,7 @@ APPDIR ?= $(HOME)/Applications
|
|||
BIN := $(TARGET)/release/anybot
|
||||
RELAY := $(if $(CARGO_TARGET_DIR),$(CARGO_TARGET_DIR),relay/target)/release/anybot-relay
|
||||
|
||||
.PHONY: all build app relay run serve setup relay-run test deps install uninstall hands server dist clean help
|
||||
.PHONY: all build app relay run serve setup relay-run test deps doctor install uninstall hands server dist clean help check-rust
|
||||
|
||||
all: build
|
||||
|
||||
|
|
@ -24,7 +28,8 @@ help:
|
|||
@echo " make run 直接打開 AnyBot(不安裝)"
|
||||
@echo " make install 安裝:Mac → $(APPDIR)/AnyBot.app;Linux → ~/.local/bin/anybot 和選單"
|
||||
@echo " make uninstall 移除安裝的程式(你的 Bot 和聊天不會刪)"
|
||||
@echo " make deps Linux:安裝系統套件(會問 sudo 密碼)"
|
||||
@echo " make deps 裝好建置需要的東西:系統套件和 Rust(Linux 會問 sudo 密碼)"
|
||||
@echo " make doctor 檢查這台缺什麼"
|
||||
@echo " make test 跑測試"
|
||||
@echo ""
|
||||
@echo " make serve 不開視窗執行(伺服器、一直開著的電腦)"
|
||||
|
|
@ -38,10 +43,14 @@ help:
|
|||
|
||||
build: app relay
|
||||
|
||||
app:
|
||||
check-rust:
|
||||
@command -v $(CARGO) >/dev/null 2>&1 || { echo; echo " ✗ 找不到 Rust(cargo)。先執行:make deps"; echo; exit 1; }
|
||||
@v=$$(rustc --version | sed -n 's/^rustc 1\.\([0-9]*\).*/\1/p'); [ "$${v:-0}" -ge 95 ] || { echo; echo " ✗ Rust 太舊($$(rustc --version)),要 1.95 以上。執行:rustup update stable(或 make deps)"; echo; exit 1; }
|
||||
|
||||
app: check-rust
|
||||
$(CARGO) build --release --bin anybot
|
||||
|
||||
relay:
|
||||
relay: check-rust
|
||||
cd relay && $(CARGO) build --release
|
||||
|
||||
run: app
|
||||
|
|
@ -56,19 +65,26 @@ setup: app
|
|||
relay-run: relay
|
||||
$(RELAY) --port 8080 --data relay-data
|
||||
|
||||
test:
|
||||
test: check-rust
|
||||
$(CARGO) test --bin anybot
|
||||
cd relay && $(CARGO) test
|
||||
|
||||
deps:
|
||||
sh ./install.sh deps
|
||||
|
||||
doctor:
|
||||
@echo "AnyBot $(VERSION) · $(UNAME) $$(uname -m)"
|
||||
@command -v git >/dev/null && echo " ✓ git" || echo " ✗ git:make deps"
|
||||
@command -v cc >/dev/null && echo " ✓ C 編譯器" || echo " ✗ C 編譯器:make deps"
|
||||
@command -v $(CARGO) >/dev/null && echo " ✓ $$(rustc --version)" || echo " ✗ Rust:make deps"
|
||||
ifeq ($(UNAME),Linux)
|
||||
sh deploy/linux/install.sh --deps-only
|
||||
else ifeq ($(UNAME),Darwin)
|
||||
@xcode-select -p >/dev/null 2>&1 || xcode-select --install
|
||||
@command -v cargo >/dev/null || echo "需要 Rust:curl --proto '=https' -sSf https://sh.rustup.rs | sh"
|
||||
else
|
||||
@echo "Windows:請照 docs/install.md 安裝 Rust 和 Visual Studio Build Tools"
|
||||
@pkg-config --exists alsa 2>/dev/null && echo " ✓ ALSA(libasound2-dev)" || echo " ✗ ALSA 開發套件:make deps"
|
||||
@command -v docker >/dev/null && echo " ✓ $$(docker --version)" || echo " · 沒有 Docker(只有伺服器和 Bot 自己的電腦需要;make server 會自動裝)"
|
||||
endif
|
||||
ifeq ($(UNAME),Darwin)
|
||||
@xcode-select -p >/dev/null 2>&1 && echo " ✓ Xcode Command Line Tools" || echo " ✗ Xcode Command Line Tools:make deps"
|
||||
endif
|
||||
@test -x $(BIN) && echo " ✓ 已建置 $(BIN)" || echo " · 還沒建置:make"
|
||||
|
||||
install: app
|
||||
ifeq ($(UNAME),Darwin)
|
||||
|
|
@ -94,7 +110,7 @@ hands: app
|
|||
sh deploy/linux/hands.sh --deps
|
||||
|
||||
server:
|
||||
cd deploy/server && ./setup.sh
|
||||
sh ./install.sh server
|
||||
|
||||
dist: build
|
||||
ifeq ($(UNAME),Darwin)
|
||||
|
|
|
|||
181
README.md
181
README.md
|
|
@ -1,74 +1,167 @@
|
|||
# AnyBot
|
||||
|
||||
你自己的 AI Bot:可以聊天,也能在它自己的電腦和你的電腦上幫你做事(檔案、指令、瀏覽器、螢幕操作、例行任務)。每個 Bot 可以用不同的 AI 服務:xAI Grok、OpenAI、Claude、Gemini、OpenRouter、OpenCode Go、Ollama(本機),或任何 OpenAI 相容的服務。
|
||||
你自己的 AI Bot。可以聊天,也能幫你動手做事:處理檔案、執行指令、操作瀏覽器和螢幕、跑例行任務。
|
||||
|
||||
- 桌面 App:macOS、Linux(Windows 可以編譯,還沒有安裝包)
|
||||
- 手機:網頁版,可加到主畫面、收推播通知
|
||||
- 多台電腦:一台當**大腦**(保存 Bot、聊天、記憶和排程),其他電腦當**手腳**或**螢幕**
|
||||
- 用 Rust 和 egui 寫成,一個執行檔就是全部(視窗、無視窗伺服器模式、設定工具)
|
||||
- 每個 Bot 可以用不同的 AI:xAI Grok、OpenAI、Claude、Gemini、OpenRouter、OpenCode Go、Ollama(本機),或任何 OpenAI 相容的服務
|
||||
- 桌面 App(macOS、Linux)+手機網頁版(可加到主畫面、收通知)
|
||||
- 多台電腦一起用:一台當**大腦**,其他電腦當它的**手腳**
|
||||
|
||||
## 從原始碼安裝
|
||||
---
|
||||
|
||||
```sh
|
||||
git clone git@git.30cm.net:daniel.w/AnyBot.git
|
||||
cd AnyBot
|
||||
make deps # 只有 Linux 需要:安裝系統套件(會問 sudo 密碼)
|
||||
make # 建置
|
||||
make install # Mac → ~/Applications/AnyBot.app;Linux → ~/.local/bin/anybot 和選單
|
||||
## 安裝
|
||||
|
||||
先決定你要裝哪一種,複製那一行到終端機執行就好。需要的東西(系統套件、Rust、Docker)腳本會自己裝。
|
||||
|
||||
| 我要… | 在哪台執行 | 指令 |
|
||||
|---|---|---|
|
||||
| **桌面 App** | Mac、有螢幕的 Linux | `curl -fsSL https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.sh \| sh` |
|
||||
| **架伺服器**(帳號伺服器+大腦+HTTPS) | 有網域的 Linux 主機 | `curl -fsSL https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.sh \| sh -s -- server` |
|
||||
| **加一台電腦當手腳** | 任何 Linux(有沒有螢幕都可以) | 在 AnyBot 按「加一台電腦」,貼上它給你的那一行 |
|
||||
|
||||
> 想先看腳本做什麼:[install.sh](install.sh)。它只會裝 AnyBot 需要的套件,程式裝在你的使用者資料夾(不用 root),原始碼放在 `~/AnyBot`。
|
||||
|
||||
**第一次要多久?** 腳本會在你的電腦上編譯,第一次大約 5–15 分鐘(看電腦快慢),之後更新只要 1–2 分鐘。
|
||||
|
||||
---
|
||||
|
||||
## 開始使用
|
||||
|
||||
### 只用一台電腦
|
||||
|
||||
1. 裝好桌面 App,打開。
|
||||
2. 設定 › 模型,貼上你的 API 金鑰(例如 xAI、OpenAI)。
|
||||
3. 開始聊天。
|
||||
|
||||
### 手機和多台電腦一起用
|
||||
|
||||
```
|
||||
手機(網頁) 筆電(螢幕) 沒螢幕的 Linux(手腳)
|
||||
\ | /
|
||||
└──────── 帳號伺服器(每台都連過來)─────────┘
|
||||
|
|
||||
大腦:Bot、聊天、記憶、例行任務都在這裡
|
||||
```
|
||||
|
||||
需要 Rust(`curl --proto '=https' -sSf https://sh.rustup.rs | sh`)。Mac 另外需要 Xcode Command Line Tools(`make deps` 會提示)。詳細步驟、各平台需求和常見問題:[docs/install.md](docs/install.md)。
|
||||
**步驟 1:架伺服器**(只做一次)
|
||||
|
||||
常用指令(`make help` 列出全部):
|
||||
需要:一台 Linux 主機(2 GB 以上記憶體)、一個網域,網域的 A 紀錄要指到這台主機,80 和 443 埠要打開。
|
||||
|
||||
```sh
|
||||
curl -fsSL https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.sh | sh -s -- server
|
||||
```
|
||||
|
||||
只會問三件事:**網域、帳號、密碼**。它會自動裝 Docker、申請 HTTPS 憑證、建立帳號、啟動大腦。完成畫面會告訴你網址。
|
||||
|
||||
> 沒有伺服器?也可以先拿一台 Mac 當大腦,見 [docs/multi-device.md](docs/multi-device.md)(做法 B)。
|
||||
|
||||
**步驟 2:手機**:用瀏覽器打開你的網址並登入,再「加入主畫面」。
|
||||
|
||||
**步驟 3:其他電腦**
|
||||
|
||||
- **有螢幕的電腦**:裝好桌面 App,到 設定 › 一般 › 手機與帳號 登入,在「大腦」那一欄選伺服器。
|
||||
- **沒螢幕的 Linux**:在手機 AnyBot 右上角按「⋯ › 加一台電腦」(或在電腦的 設定 › 一般 › 手機與帳號),會出現一行像這樣的指令:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.sh | sh -s -- hands https://你的網域 482913
|
||||
```
|
||||
|
||||
貼到那台電腦執行,就會自動裝好、接上大腦,之後開機也會自動執行。不用帳號密碼,這組碼 10 分鐘內有效、只能用一次。
|
||||
|
||||
---
|
||||
|
||||
## 更新和移除
|
||||
|
||||
| 要做什麼 | 指令 |
|
||||
|---|---|
|
||||
| 更新桌面 App 或手腳 | 再執行一次安裝的那一行 |
|
||||
| 更新伺服器 | 在伺服器上 `cd ~/AnyBot && make server`,選「用現有設定,更新並重新啟動」 |
|
||||
| 移除程式 | `cd ~/AnyBot && make uninstall`(你的 Bot 和聊天會留著) |
|
||||
| 停掉手腳 | `systemctl --user disable --now anybot` |
|
||||
|
||||
---
|
||||
|
||||
## 出問題時
|
||||
|
||||
先執行這個,它會告訴你缺什麼:
|
||||
|
||||
```sh
|
||||
cd ~/AnyBot && make doctor
|
||||
```
|
||||
|
||||
| 看到 | 怎麼辦 |
|
||||
|---|---|
|
||||
| 編譯到一半被中斷、`Killed` 或 `signal: 9` | 記憶體不夠。加 2 GB swap 再試:`sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile` |
|
||||
| `Rust 太舊` 或 `requires rustc 1.95` | `rustup update stable`。用 apt 裝的 rustc 太舊,請改用 rustup |
|
||||
| `alsa` 或 `pkg-config` 找不到 | 少了系統套件,執行 `make deps` |
|
||||
| 伺服器的網址連不到 | 網域是不是指到這台?80、443 埠有沒有被防火牆擋?`cd ~/AnyBot/deploy/server && docker compose logs caddy` |
|
||||
| 大腦登不進帳號 | `cd ~/AnyBot/deploy/server && docker compose logs anybot` |
|
||||
| Mac 說「無法驗證開發者」 | 對 AnyBot 按右鍵 › 打開,只有第一次需要 |
|
||||
| 手機上看到「大腦目前離線」 | 大腦那台關機、睡眠,或 AnyBot 沒開。詳見 [docs/multi-device.md](docs/multi-device.md) 的「連不上時」 |
|
||||
|
||||
還是不行:把 `make doctor` 的結果和錯誤訊息的最後幾行貼給維護的人。
|
||||
|
||||
---
|
||||
|
||||
## 自己從原始碼建置
|
||||
|
||||
適合想改程式的人。一般使用請用上面的一行安裝。
|
||||
|
||||
```sh
|
||||
git clone https://code.30cm.net/daniel.w/AnyBot.git
|
||||
cd AnyBot
|
||||
make deps # 裝好系統套件和 Rust(只要一次;Linux 會問 sudo 密碼)
|
||||
make # 建置
|
||||
make install # 安裝:Mac → ~/Applications/AnyBot.app;Linux → ~/.local/bin/anybot 和應用程式選單
|
||||
```
|
||||
|
||||
| 指令 | 做什麼 |
|
||||
|---|---|
|
||||
| `make run` | 不安裝,直接打開 |
|
||||
| `make test` | 跑測試 |
|
||||
| `make serve` | 不開視窗執行(伺服器、一直開著的電腦) |
|
||||
| `make setup` | 把這台接到你的 AnyBot(終端機裡的引導畫面) |
|
||||
| `make server` | 在伺服器上架好帳號伺服器、大腦和 HTTPS(Docker) |
|
||||
| `make doctor` | 檢查這台缺什麼 |
|
||||
| `make serve` | 不開視窗執行(一直開著的電腦) |
|
||||
| `make setup` | 把這台接到你的大腦(終端機裡的引導畫面) |
|
||||
| `make server` | 在這台架伺服器(同上面的 `install.sh server`) |
|
||||
| `make dist` | 打包發行檔到 `dist/` |
|
||||
| `make uninstall` | 移除程式(Bot 和聊天不會刪) |
|
||||
| `make help` | 列出全部 |
|
||||
|
||||
## 多台電腦一起用
|
||||
需要:Rust 1.95 以上、C 編譯器、git;Linux 另外需要 `pkg-config` 和 ALSA 開發套件(`libasound2-dev`)。`make deps` 會全部裝好。各平台細節:[docs/install.md](docs/install.md)。
|
||||
|
||||
```
|
||||
手機(網頁) 筆電(螢幕) 沒螢幕的 Linux(手腳)
|
||||
\ | /
|
||||
└──── 帳號伺服器(一條一直開著的連線)────┘
|
||||
|
|
||||
大腦:Bot、聊天、記憶、排程
|
||||
```
|
||||
---
|
||||
|
||||
1. **架帳號伺服器和大腦**:在伺服器上執行 `make server`(只問網域、帳號、密碼);或先用一台 Mac 當大腦([docs/multi-device.md](docs/multi-device.md))。
|
||||
2. **其他電腦當螢幕**:打開 AnyBot › 設定 › 手機與帳號,登入同一個帳號,在「大腦」選它。
|
||||
3. **加一台手腳**:手機 AnyBot 右上「⋯ › 加一台電腦」,把出現的那一行貼到那台的終端機:
|
||||
```sh
|
||||
anybot setup https://anybot.example.com 469615
|
||||
```
|
||||
不用帳號密碼,會設成開機自動執行;沒有螢幕就開一個虛擬螢幕。
|
||||
## 部署方式
|
||||
|
||||
| 方式 | 用在 | 說明 |
|
||||
|---|---|---|
|
||||
| **Docker Compose**(預設) | 伺服器 | `install.sh server` 用的就是它:帳號伺服器、大腦、Caddy(自動 HTTPS)三個容器,資料放在 Docker 磁碟區,重開機自動啟動。設定檔:[deploy/server/docker-compose.yml](deploy/server/docker-compose.yml) |
|
||||
| Portainer、Coolify、Dokploy… | 已經在用這些管理工具的伺服器 | 它們都吃標準的 `docker-compose.yml`:把 repo 加進去、指定 `deploy/server/docker-compose.yml`、在環境變數填 `ANYBOT_USER`、`ANYBOT_PASSWORD`、`ANYBOT_RELAY_PUBLIC_URL`。若工具自己處理 HTTPS,就拿掉 `caddy` 那一段,把網域導到 `relay` 的 8080 埠 |
|
||||
| **systemd**(使用者服務) | 手腳、沒螢幕的 Linux 大腦 | `anybot setup` 會自動建立 `anybot.service`;登出後也要繼續跑:`sudo loginctl enable-linger $USER` |
|
||||
| **launchd** | Mac 當大腦或帳號伺服器 | 見 [docs/multi-device.md](docs/multi-device.md) |
|
||||
| Gitea Actions | 推 tag 自動打包發行檔 | 見 [docs/release.md](docs/release.md) |
|
||||
|
||||
不需要另外架資料庫:資料是幾個 JSON 檔,寫入時不會寫一半。要備份伺服器,備份 Docker 磁碟區 `server_relay-data`、`server_anybot-data` 就好。
|
||||
|
||||
---
|
||||
|
||||
## 文件
|
||||
|
||||
| 文件 | 內容 |
|
||||
|---|---|
|
||||
| [docs/install.md](docs/install.md) | 從原始碼建置和安裝(macOS、Linux、Windows)、更新、移除、常見問題 |
|
||||
| [docs/install.md](docs/install.md) | 安裝細節:各平台需求、手動步驟、常見問題 |
|
||||
| [docs/multi-device.md](docs/multi-device.md) | 大腦、手腳、螢幕:怎麼架、怎麼接、連不上時看什麼 |
|
||||
| [docs/architecture.md](docs/architecture.md) | 技術架構:程式分層、資料存放、安全設計、同步怎麼運作 |
|
||||
| [docs/protocol.md](docs/protocol.md) | 帳號伺服器和 AnyBot 之間的介面與長連線格式 |
|
||||
| [docs/release.md](docs/release.md) | 發行:打包、Gitea Actions 自動建置、runner 設定 |
|
||||
| [FEATURES.md](FEATURES.md) | 所有功能的完整說明 |
|
||||
| [relay/README.md](relay/README.md) | 帳號伺服器的架設細節 |
|
||||
| [docs/architecture.md](docs/architecture.md) | 技術架構:程式分層、資料存放、安全設計 |
|
||||
| [docs/protocol.md](docs/protocol.md) | 帳號伺服器的介面與長連線格式 |
|
||||
| [docs/release.md](docs/release.md) | 打包和自動建置 |
|
||||
| [FEATURES.md](FEATURES.md) | 所有功能 |
|
||||
|
||||
## 專案結構
|
||||
|
||||
```
|
||||
src/ AnyBot(桌面 App、無視窗模式、anybot setup)
|
||||
app/ 視窗介面(聊天、設定、電腦、大腦同步的畫面)
|
||||
relay/ 帳號伺服器(獨立的小程式)
|
||||
mobile/ 手機網頁(由 AnyBot 和帳號伺服器提供)
|
||||
install.sh 一鍵安裝(桌面 App、手腳、伺服器)
|
||||
src/ AnyBot:桌面 App、無視窗模式、anybot setup
|
||||
relay/ 帳號伺服器
|
||||
mobile/ 手機網頁
|
||||
box/ Bot 自己的電腦(Docker 容器)
|
||||
deploy/ 安裝與部署:mac/、linux/、server/、ci/
|
||||
deploy/ 各平台的安裝和部署檔:mac/、linux/、server/、ci/
|
||||
docs/ 技術文件
|
||||
```
|
||||
|
|
|
|||
|
|
@ -22,26 +22,16 @@ DEPS_FEDORA="alsa-lib libxkbcommon-x11 mesa-libGL xdg-utils libnotify libsecret
|
|||
DEPS_ARCH="alsa-lib libxkbcommon-x11 mesa xdg-utils libnotify libsecret espeak-ng zenity xclip wl-clipboard ffmpeg docker noto-fonts-cjk"
|
||||
BUILD_DEBIAN="build-essential pkg-config libasound2-dev"
|
||||
|
||||
if [ "$1" = "--deps" ] || [ "$1" = "--deps-only" ]; then
|
||||
if command -v apt-get >/dev/null; then
|
||||
sudo apt-get update && sudo apt-get install -y $DEPS_DEBIAN $BUILD_DEBIAN
|
||||
elif command -v dnf >/dev/null; then
|
||||
sudo dnf install -y $DEPS_FEDORA gcc pkgconf alsa-lib-devel
|
||||
elif command -v pacman >/dev/null; then
|
||||
sudo pacman -S --needed $DEPS_ARCH base-devel
|
||||
else
|
||||
echo "Install these with your package manager: $DEPS_DEBIAN"
|
||||
fi
|
||||
if command -v docker >/dev/null && ! id -nG | grep -qw docker; then
|
||||
echo "Adding you to the docker group (the bot's computer needs it); log out and back in afterwards."
|
||||
sudo usermod -aG docker "$USER"
|
||||
fi
|
||||
# The package lists above are for reference; ../../install.sh installs them (it picks the right
|
||||
# names per release, e.g. libasound2t64 on Ubuntu 24.04, and installs Rust when it's missing).
|
||||
if [ "${1:-}" = "--deps" ] || [ "${1:-}" = "--deps-only" ]; then
|
||||
sh ./install.sh deps
|
||||
fi
|
||||
|
||||
[ "$1" = "--deps-only" ] && exit 0
|
||||
[ "${1:-}" = "--deps-only" ] && exit 0
|
||||
|
||||
OUT="${CARGO_TARGET_DIR:-target}/release/anybot"
|
||||
if [ -x "$OUT" ] && [ "$1" != "--rebuild" ]; then
|
||||
if [ -x "$OUT" ] && [ "${1:-}" != "--rebuild" ]; then
|
||||
echo "Using the existing build ($OUT)."
|
||||
else
|
||||
command -v cargo >/dev/null || { echo "Rust is needed to build: https://rustup.rs"; exit 1; }
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
# AnyBot without a window (`anybot --serve`) on a Linux server: the bots, routines, triggers and
|
||||
# channels run around the clock; the person uses the phone. The bot's own computer is a sibling
|
||||
# container started through the host's Docker (the socket is mounted in docker-compose.yml).
|
||||
# Build from the app/ folder: docker build -f deploy/server/Dockerfile -t anybot-serve .
|
||||
# Build from the repository root: docker build -f deploy/server/Dockerfile -t anybot-serve .
|
||||
# JOBS limits parallel compiles on small servers (setup.sh sets it from the memory).
|
||||
FROM rust:1-bookworm AS build
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends pkg-config libasound2-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
ARG JOBS=
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
RUN cargo build --release --bin anybot
|
||||
# The registry and target caches survive rebuilds, so an update only recompiles what changed.
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/src/target \
|
||||
cargo build --release --bin anybot ${JOBS:+-j $JOBS} \
|
||||
&& cp target/release/anybot /anybot
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl git ripgrep poppler-utils docker.io openssh-client \
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl git ripgrep poppler-utils docker.io openssh-client libasound2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=build /src/target/release/anybot /usr/local/bin/anybot
|
||||
COPY --from=build /anybot /usr/local/bin/anybot
|
||||
ENV ANYBOT_DATA_DIR=/data
|
||||
VOLUME /data
|
||||
CMD ["anybot", "--serve"]
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ services:
|
|||
build:
|
||||
context: ../..
|
||||
dockerfile: relay/Dockerfile
|
||||
args: { JOBS: "${ANYBOT_BUILD_JOBS:-}" }
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# Your public address (for the links of sites the bots publish), e.g. https://anybot.example.com
|
||||
|
|
@ -38,6 +39,7 @@ services:
|
|||
build:
|
||||
context: ../..
|
||||
dockerfile: deploy/server/Dockerfile
|
||||
args: { JOBS: "${ANYBOT_BUILD_JOBS:-}" }
|
||||
restart: unless-stopped
|
||||
depends_on: [relay]
|
||||
environment:
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ choose() {
|
|||
card_open "$title"
|
||||
for i in "${!opts[@]}"; do
|
||||
local name=${opts[$i]%%|*} desc=${opts[$i]#*|}
|
||||
if [ "$i" -eq "$sel" ]; then line "$A❯ ● $B$name$N"; else line " $D○$N $name"; fi
|
||||
if [ "$i" -eq "$sel" ]; then line "${A}❯ ● $B$name$N"; else line " ${D}○$N $name"; fi
|
||||
[ "$desc" != "${opts[$i]}" ] && [ -n "$desc" ] && note " $desc"
|
||||
line ""
|
||||
done
|
||||
|
|
@ -153,12 +153,12 @@ if [ "${SKIP:-0}" != 1 ]; then
|
|||
ips=$(getent ahosts "$DOMAIN" 2>/dev/null | awk '{print $1}' | sort -u | tr '\n' ' ')
|
||||
if [ -z "$ips" ]; then
|
||||
card_close
|
||||
choose GO "「$DOMAIN」還沒有指到任何位址" "重新輸入|" "仍然繼續|DNS 剛設好、還沒生效時可以先繼續;生效後憑證會自動申請"
|
||||
choose GO "「${DOMAIN}」還沒有指到任何位址" "重新輸入|" "仍然繼續|DNS 剛設好、還沒生效時可以先繼續;生效後憑證會自動申請"
|
||||
[ "$GO" = 0 ] && { MSG=""; continue; }
|
||||
else
|
||||
mine=$(hostname -I 2>/dev/null || true)
|
||||
hit=0; for ip in $ips; do case " $mine " in *" $ip "*) hit=1 ;; esac; done
|
||||
if [ $hit = 1 ]; then MSG=$(ok "$DOMAIN → $ips(就是這台)"); else MSG=$(warn "$DOMAIN → $ips(這台的位址:${mine:-不知道};在 NAT 或雲端主機後面時這是正常的)"); fi
|
||||
if [ $hit = 1 ]; then MSG=$(ok "$DOMAIN → ${ips}(就是這台)"); else MSG=$(warn "$DOMAIN → ${ips}(這台的位址:${mine:-不知道};在 NAT 或雲端主機後面時這是正常的)"); fi
|
||||
header; card_open "手機和其他電腦用哪個網址連到這台?"; printf '%s\n' "$MSG"; card_close; sleep 1
|
||||
fi
|
||||
break
|
||||
|
|
@ -212,8 +212,15 @@ if [ $DRY = 1 ]; then
|
|||
warn "試跑(--dry-run):沒有啟動。要啟動:docker compose up -d --build"
|
||||
card_close; exit 0
|
||||
fi
|
||||
if [ "${SKIP:-0}" = 1 ] && [ -d ../../../.git ] && command -v git >/dev/null; then
|
||||
spin "拉下最新版" git -C ../../.. pull --ff-only || true
|
||||
if [ "${SKIP:-0}" = 1 ] && [ -d ../../.git ] && command -v git >/dev/null; then
|
||||
spin "拉下最新版" git -C ../.. pull --ff-only || true
|
||||
fi
|
||||
# Small servers run out of memory compiling many crates at once: one compile per 2 GB.
|
||||
if [ -z "${ANYBOT_BUILD_JOBS:-}" ] && [ -r /proc/meminfo ]; then
|
||||
mem=$(awk '/MemTotal/ {m=$2} /SwapTotal/ {s=$2} END {print int((m + s / 2) / 1024)}' /proc/meminfo)
|
||||
j=$((mem / 2000)); [ "$j" -lt 1 ] && j=1
|
||||
export ANYBOT_BUILD_JOBS=$j
|
||||
[ "$mem" -lt 3000 ] && warn "記憶體只有 ${mem} MB:編譯會比較久。被中斷的話先加 2 GB swap(見 docs/install.md)"
|
||||
fi
|
||||
spin "建置並啟動(第一次約 5–15 分鐘)" docker compose up -d --build || die
|
||||
|
||||
|
|
@ -245,7 +252,7 @@ card_close
|
|||
printf '\n'
|
||||
card_open "$([ -n "$joined" ] && echo "完成了" || echo "差一點")"
|
||||
line "${B}手機${N} 打開 ${A}https://$DOMAIN${N},用 ${B}$USERNAME${N} 登入,可以「加入主畫面」"
|
||||
line "${B}電腦${N} AnyBot › 設定 › 手機與帳號 登入,在「大腦」選「$BRAIN」"
|
||||
line "${B}電腦${N} AnyBot › 設定 › 一般 › 手機與帳號 登入,在「大腦」選「${BRAIN}」"
|
||||
line "${B}AI 金鑰${N} 手機或電腦的 AnyBot › 設定 › 模型(只存在大腦上)"
|
||||
line "${B}加一台電腦${N} 手機 AnyBot 右上 ⋯ › 加一台電腦,把那一行貼到那台的終端機"
|
||||
line ""
|
||||
|
|
|
|||
|
|
@ -1,26 +1,42 @@
|
|||
# 從原始碼建置和安裝
|
||||
# 安裝細節
|
||||
|
||||
## 需要準備
|
||||
一般使用請用 README 的一行安裝(`install.sh`),它會做完下面所有步驟。這份文件說明它做了什麼,以及想自己一步一步來時怎麼做。
|
||||
|
||||
## install.sh 做了什麼
|
||||
|
||||
| 步驟 | 桌面 App(`app`) | 手腳(`hands`) | 伺服器(`server`) |
|
||||
|---|---|---|---|
|
||||
| 1. 系統套件 | 編譯工具、ALSA、視窗和聲音、通知、剪貼簿、ffmpeg、中文字型 | 編譯工具、ALSA、虛擬螢幕(Xvfb)、視窗管理員、輔助使用服務 | curl、git |
|
||||
| 2. 工具 | Rust(rustup,裝在 `~/.cargo`) | Rust | Docker(官方 get.docker.com)和 docker compose |
|
||||
| 3. 原始碼 | 下載到 `~/AnyBot`(已存在就更新) | 同左 | 同左 |
|
||||
| 4. 安裝 | 編譯,放到 `~/Applications`(Mac)或 `~/.local/bin`(Linux) | 編譯、放到 `~/.local/bin`,帶網址和配對碼時接著執行 `anybot setup` | 執行 `deploy/server/setup.sh`:問網域、帳號、密碼,用 Docker 建置並啟動 |
|
||||
|
||||
- 系統套件會依發行版挑對的名字(例如 Ubuntu 24.04 的 `libasound2` 叫 `libasound2t64`)。支援 apt(Ubuntu、Debian)、dnf(Fedora)、pacman(Arch)、zypper(openSUSE)。
|
||||
- 已經有 Rust 但版本低於 1.95:有 rustup 就更新;是系統套件裝的就另外用 rustup 裝新版。
|
||||
- 記憶體小的機器會自動減少同時編譯的數量(每 2 GB 一個)。
|
||||
- 不帶參數時自己判斷:Mac 和有桌面的 Linux 裝桌面 App,沒有桌面的 Linux 裝手腳。
|
||||
|
||||
環境變數:`ANYBOT_DIR`(原始碼放哪,預設 `~/AnyBot`)、`ANYBOT_BRANCH`(預設 `main`)。
|
||||
|
||||
## 自己一步一步來
|
||||
|
||||
### 需要準備
|
||||
|
||||
| 平台 | 需要 |
|
||||
|---|---|
|
||||
| 全部 | Rust(stable,用 rustup 安裝)、git、make |
|
||||
| 全部 | Rust **1.95 以上**(用 rustup 安裝;apt 裝的通常太舊)、git、make |
|
||||
| macOS 12 以後 | Xcode Command Line Tools(`xcode-select --install`) |
|
||||
| Linux | 編譯用:`build-essential pkg-config libasound2-dev`;執行用的系統套件見下面,`make deps` 會一次裝好 |
|
||||
| Linux | `build-essential pkg-config libasound2-dev`;執行用的套件見下面 |
|
||||
| Windows | Visual Studio Build Tools(C++)。可以 `cargo build --release`,安裝包還沒做 |
|
||||
|
||||
安裝 Rust:
|
||||
`make deps` 會把這些都裝好(等於 `./install.sh deps`)。`make doctor` 檢查還缺什麼。
|
||||
|
||||
### 步驟
|
||||
|
||||
```sh
|
||||
curl --proto '=https' -sSf https://sh.rustup.rs | sh
|
||||
```
|
||||
|
||||
## 步驟
|
||||
|
||||
```sh
|
||||
git clone git@git.30cm.net:daniel.w/AnyBot.git
|
||||
git clone https://code.30cm.net/daniel.w/AnyBot.git
|
||||
cd AnyBot
|
||||
make deps # Linux:安裝系統套件(會問 sudo 密碼);Mac:檢查 Xcode 工具
|
||||
make deps # 系統套件和 Rust(Linux 會問 sudo 密碼);Mac:Xcode 工具和 Rust
|
||||
make # 建置 AnyBot 和帳號伺服器(第一次約 5–15 分鐘)
|
||||
make install
|
||||
```
|
||||
|
|
@ -34,11 +50,11 @@ make install
|
|||
|
||||
## Linux 的系統套件
|
||||
|
||||
`make deps` 依發行版(apt、dnf、pacman)安裝這些,全部都是選用的,缺了 App 會說明少了什麼:
|
||||
`make deps` 依發行版(apt、dnf、pacman、zypper)安裝這些。除了第一行,全部都是選用的,缺了 App 會說明少了什麼:
|
||||
|
||||
| 套件 | 用途 |
|
||||
|---|---|
|
||||
| libasound2、libxkbcommon-x11-0、libgl1、xdg-utils | 視窗和聲音(必要) |
|
||||
| libasound2(Ubuntu 24.04 起叫 libasound2t64)、libxkbcommon-x11-0、libgl1、xdg-utils | 視窗和聲音(必要) |
|
||||
| libnotify-bin | 通知 |
|
||||
| libsecret-tools | 保險庫存在系統鑰匙圈 |
|
||||
| espeak-ng | 唸出回覆 |
|
||||
|
|
@ -48,10 +64,12 @@ make install
|
|||
| docker.io | Bot 自己的電腦 |
|
||||
| fonts-noto-cjk | 更多中文字形(內建的粉圓體已經夠用) |
|
||||
|
||||
沒有螢幕的 Linux(只當手腳或大腦):只需要 `libasound2`。要讓 Bot 看得到畫面,再執行 `make hands`,會裝好虛擬螢幕(Xvfb)、視窗管理員和輔助使用服務。
|
||||
沒有螢幕的 Linux(只當手腳或大腦):只需要 `libasound2`;要讓 Bot 看得到畫面,還要虛擬螢幕(Xvfb)、視窗管理員和輔助使用服務。`./install.sh hands` 會一起裝好(自己來:`make hands`)。
|
||||
|
||||
## 更新
|
||||
|
||||
再執行一次安裝的那一行就好。自己建置的話:
|
||||
|
||||
```sh
|
||||
git pull
|
||||
make install
|
||||
|
|
@ -94,10 +112,24 @@ make uninstall
|
|||
系統設定 › 隱私權與安全性:「螢幕錄製」和「輔助使用」要打開 AnyBot。App 的 設定 › 電腦 有按鈕直接開到那一頁。
|
||||
|
||||
**Linux 啟動時說找不到 libasound.so.2**
|
||||
`sudo apt-get install libasound2`(Ubuntu 24.04 以後叫 `libasound2t64`)。
|
||||
`sudo apt-get install libasound2t64`(Ubuntu 24.04 以後)或 `libasound2`(更早的版本)。
|
||||
|
||||
**編譯時找不到 alsa 或 pkg-config**
|
||||
Linux 少了編譯用的套件:`sudo apt-get install build-essential pkg-config libasound2-dev`。
|
||||
Linux 少了編譯用的套件:`make deps`,或 `sudo apt-get install build-essential pkg-config libasound2-dev`。
|
||||
|
||||
**`make: cargo: No such file or directory` 或「找不到 Rust」**
|
||||
還沒裝 Rust:`make deps`。剛裝好但終端機找不到:`. "$HOME/.cargo/env"`,或開一個新的終端機。
|
||||
|
||||
**`requires rustc 1.95` 或「Rust 太舊」**
|
||||
`rustup update stable`。如果 rustc 是 apt 裝的,先 `sudo apt-get remove rustc cargo`,再 `make deps`。
|
||||
|
||||
**編譯到一半 `Killed`、`signal: 9`、或整台卡住**
|
||||
記憶體不夠(常見於 1–2 GB 的 VPS)。加 2 GB swap 再試:
|
||||
|
||||
```sh
|
||||
sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
|
||||
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab # 重開機後也保留
|
||||
```
|
||||
|
||||
**第一次 `make` 很久**
|
||||
要下載並編譯所有套件,第一次需要 5–15 分鐘,之後只會重編有改的部分。
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
| 角色 | 是什麼 | 怎麼設 |
|
||||
|---|---|---|
|
||||
| 大腦 | 保存所有 Bot、聊天、記憶和例行任務,由它思考和排程。要一直開著 | 登入帳號就是(預設) |
|
||||
| 螢幕 | 有視窗的電腦:看到的、改的都是大腦的 Bot;同時也是大腦的手腳 | 設定 › 手機與帳號 › 大腦 › 「改用它當大腦」 |
|
||||
| 螢幕 | 有視窗的電腦:看到的、改的都是大腦的 Bot;同時也是大腦的手腳 | 設定 › 一般 › 手機與帳號 › 大腦 › 「改用它當大腦」 |
|
||||
| 手腳 | 讓大腦的 Bot 操作這台:畫面、點擊打字、檔案和指令、這台的瀏覽器 | 「加一台電腦」的那一行,或 `anybot setup` |
|
||||
| 手機 | 網頁版,打開帳號伺服器的網址 | 登入同一個帳號 |
|
||||
|
||||
|
|
@ -15,16 +15,15 @@
|
|||
|
||||
### 做法 A:放在伺服器上(推薦,Mac 關機時大腦也還在)
|
||||
|
||||
需要:一台有 Docker 的 Linux 主機,以及一個指到它的網域。
|
||||
需要:一台 Linux 主機(2 GB 以上記憶體;更小的請先加 swap,見 [install.md](install.md) 的常見問題)、一個 A 紀錄指到它的網域、打開 80 和 443 埠。Docker 沒裝會自動裝。
|
||||
|
||||
```sh
|
||||
git clone git@git.30cm.net:daniel.w/AnyBot.git && cd AnyBot
|
||||
make server
|
||||
curl -fsSL https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.sh | sh -s -- server
|
||||
```
|
||||
|
||||
只問三件事:網域、帳號、密碼。會自動申請 HTTPS 憑證、建立帳號、啟動大腦。完成畫面會告訴你手機和電腦怎麼連。之後再執行一次 `make server`,可以選「用現有設定,更新並重新啟動」。
|
||||
只問三件事:網域、帳號、密碼。會自動申請 HTTPS 憑證、建立帳號、啟動大腦。完成畫面會告訴你手機和電腦怎麼連。之後在伺服器上執行 `cd ~/AnyBot && make server`,可以選「用現有設定,更新並重新啟動」。
|
||||
|
||||
不想在伺服器上編譯:下載發行檔 `AnyBot-<版本>-server-x86_64.tar.gz`,解開後 `cd deploy/server && ./setup.sh`,用的是已經編好的程式。
|
||||
有發行檔時(推 `v*` tag 後由 Gitea Actions 產生,見 [release.md](release.md)),也可以不在伺服器上編譯:下載 `AnyBot-<版本>-server-<架構>.tar.gz`,解開後 `cd deploy/server && ./setup.sh`。
|
||||
|
||||
### 做法 B:先用一台 Mac 當大腦(家裡用 Tailscale)
|
||||
|
||||
|
|
@ -37,7 +36,7 @@ make server
|
|||
launchctl load ~/Library/LaunchAgents/com.anybot.relay.plist
|
||||
```
|
||||
2. 用 Tailscale 給它 HTTPS 網址:`tailscale serve --bg 8780`(網址像 `https://mac-mini.xxxx.ts.net`)。
|
||||
3. 打開 AnyBot › 設定 › 手機與帳號,填這個網址,建立帳號並登入。這台就是大腦。
|
||||
3. 打開 AnyBot › 設定 › 一般 › 手機與帳號,填這個網址,建立帳號並登入。這台就是大腦。
|
||||
|
||||
只有同一個 Tailscale 網路裡的手機和電腦連得到。
|
||||
|
||||
|
|
@ -45,7 +44,7 @@ make server
|
|||
|
||||
### 有視窗的電腦(螢幕)
|
||||
|
||||
打開 AnyBot › 設定 › 手機與帳號,登入同一個帳號,在「大腦」那一欄按某一台旁邊的「改用它當大腦」。
|
||||
打開 AnyBot › 設定 › 一般 › 手機與帳號,登入同一個帳號,在「大腦」那一欄按某一台旁邊的「改用它當大腦」。
|
||||
|
||||
- 這台原本的 Bot 會留著不刪,可以按「把這台的 Bot 搬過去」複製到大腦(連同聊天、金鑰、技能和連接器;已經在大腦上的不會重複搬)。
|
||||
- 隨時可以按「改回用這台自己的 Bot」。
|
||||
|
|
@ -53,16 +52,16 @@ make server
|
|||
|
||||
### 沒有螢幕的 Linux(手腳)
|
||||
|
||||
1. 在手機 AnyBot 右上「⋯ › 加一台電腦」,或電腦 AnyBot 的 設定 › 手機與帳號 › 大腦 › 加一台電腦 › 產生配對碼。
|
||||
1. 在手機 AnyBot 右上「⋯ › 加一台電腦」,或電腦 AnyBot 的 設定 › 一般 › 手機與帳號 › 大腦 › 加一台電腦 › 產生配對碼。
|
||||
2. 會出現一行(10 分鐘內有效,只能用一次):
|
||||
```sh
|
||||
anybot setup https://anybot.example.com 469615
|
||||
curl -fsSL https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.sh | sh -s -- hands https://anybot.example.com 469615
|
||||
```
|
||||
3. 在那台電腦裝好 AnyBot(`make install`,或發行檔 `AnyBot-<版本>-headless-x86_64.tar.gz` 裡的 `./install.sh`),再貼上這一行執行。
|
||||
3. 在那台電腦的終端機貼上這一行執行。
|
||||
|
||||
它會:加入帳號(不用密碼)、只讓產生配對碼的那個大腦使用、設成開機自動執行(systemd 使用者服務 `anybot`)、沒有螢幕時開一個 1440×900 的虛擬螢幕,最後確認大腦看得到它。大腦約一分鐘內就會把它列進 Bot 可以用的電腦。
|
||||
它會:還沒裝 AnyBot 就先裝好(系統套件、Rust、編譯,第一次約 5–15 分鐘;已經裝好就直接下一步)、加入帳號(不用密碼)、只讓產生配對碼的那個大腦使用、設成開機自動執行(systemd 使用者服務 `anybot`)、沒有螢幕時開一個 1440×900 的虛擬螢幕,最後確認大腦看得到它。大腦約一分鐘內就會把它列進 Bot 可以用的電腦。
|
||||
|
||||
想讓 Bot 看得到這台的畫面,先執行 `make hands`(裝 Xvfb、視窗管理員、輔助使用服務)。
|
||||
虛擬螢幕(Xvfb)、視窗管理員和輔助使用服務會一起裝好,Bot 看得到這台的畫面。
|
||||
|
||||
只打 `anybot setup` 會出現一個畫面:填網址和配對碼(或改用帳號密碼)。給程式用:
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,315 @@
|
|||
#!/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 "接下來會問網域、帳號、密碼。"
|
||||
[ -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
|
||||
;;
|
||||
|
||||
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"
|
||||
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
|
||||
|
|
@ -230,7 +230,7 @@ header .sub{font-size:12px;color:var(--text3);font-weight:400}
|
|||
|
||||
<div class="sheet-bg" id="pair"><div class="sheet pair">
|
||||
<div class="pair-t">加一台電腦</div>
|
||||
<div class="note">在那台電腦的終端機貼上這一行執行,它就會變成「<b id="pair-for"></b>」的手腳,開機自動執行。不用帳號密碼。</div>
|
||||
<div class="note">在那台電腦(Linux 或 Mac)的終端機貼上這一行執行:還沒裝 AnyBot 會先自動裝好,接著變成「<b id="pair-for"></b>」的手腳,開機自動執行。不用帳號密碼。</div>
|
||||
<div class="pair-code" id="pair-code">······</div>
|
||||
<code class="pair-line" id="pair-line"></code>
|
||||
<button class="btn main" id="pair-copy">複製這一行</button>
|
||||
|
|
@ -555,7 +555,7 @@ $("m-pair").onclick = async () => {
|
|||
$("pair").classList.add("on");
|
||||
try {
|
||||
const v = await api("relay/pair", {json: {for: S.host}});
|
||||
const line = `anybot setup ${location.origin} ${v.code}`;
|
||||
const line = `curl -fsSL https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.sh | sh -s -- hands ${location.origin} ${v.code}`;
|
||||
$("pair-code").textContent = v.code.slice(0, 3) + " " + v.code.slice(3);
|
||||
$("pair-line").textContent = line;
|
||||
const end = Date.now() + (v.expires_in || 600) * 1000;
|
||||
|
|
@ -569,7 +569,7 @@ $("m-pair").onclick = async () => {
|
|||
};
|
||||
$("pair-copy").onclick = async () => {
|
||||
const line = $("pair-line").textContent;
|
||||
if (!line.startsWith("anybot")) return;
|
||||
if (!line.startsWith("curl")) return;
|
||||
try { await navigator.clipboard.writeText(line); toast("已複製"); } catch (_) { toast("請長按上面那一行複製"); }
|
||||
};
|
||||
$("pair-close").onclick = () => { clearInterval(S.pairTimer); $("pair").classList.remove("on"); };
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@ COPY src/httpd.rs src/httpd.rs
|
|||
COPY src/wire.rs src/wire.rs
|
||||
COPY mobile mobile
|
||||
COPY relay relay
|
||||
RUN cd relay && cargo build --release
|
||||
ARG JOBS=
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/src/relay/target \
|
||||
cd relay && cargo build --release ${JOBS:+-j $JOBS} && cp target/release/anybot-relay /anybot-relay
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
RUN useradd -m relay && mkdir /data && chown relay /data
|
||||
COPY --from=build /src/relay/target/release/anybot-relay /usr/local/bin/anybot-relay
|
||||
COPY --from=build /anybot-relay /usr/local/bin/anybot-relay
|
||||
USER relay
|
||||
VOLUME /data
|
||||
EXPOSE 8080
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ impl App {
|
|||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(RichText::new(t("加一台電腦")).size(13.5).strong());
|
||||
help_mark(ui, "讓另一台電腦(例如沒有螢幕的 Linux)也當大腦的手腳:在那台的終端機貼上這一行就好,不用帳號密碼、不用回答問題。它會設成開機自動執行,只讓這個大腦使用。", p);
|
||||
help_mark(ui, "讓另一台電腦(例如沒有螢幕的 Linux)也當大腦的手腳:在那台的終端機貼上這一行就好:還沒裝 AnyBot 會先自動裝好,不用帳號密碼、不用回答問題。它會設成開機自動執行,只讓這個大腦使用。", p);
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
if ui.add(secondary_button("產生配對碼")).clicked() {
|
||||
let (s, slot) = (self.state.remote.clone(), self.mir.pair.clone());
|
||||
|
|
@ -161,7 +161,7 @@ impl App {
|
|||
*slot.lock().unwrap() = (t("正在產生…"), false, None);
|
||||
std::thread::spawn(move || {
|
||||
*slot.lock().unwrap() = match crate::remote::pair_code(&s, &for_host) {
|
||||
Ok(code) => (format!("anybot setup {} {code}", s.relay_url), true, Some(Instant::now())),
|
||||
Ok(code) => (crate::remote::pair_line(&s.relay_url, &code), true, Some(Instant::now())),
|
||||
Err(e) => (e, false, None),
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1250,3 +1250,12 @@ mod tests {
|
|||
assert_eq!(req.query["since"], "4");
|
||||
}
|
||||
}
|
||||
|
||||
/// Where the one-step installer lives (it also installs AnyBot on a computer that has nothing yet).
|
||||
pub const INSTALL_URL: &str = "https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.sh";
|
||||
|
||||
/// The line 「加一台電腦」 shows: pasted into any Linux or Mac terminal, it installs AnyBot when it
|
||||
/// isn't there yet and joins this computer as hands (`anybot setup 網址 配對碼`).
|
||||
pub fn pair_line(relay_url: &str, code: &str) -> String {
|
||||
format!("curl -fsSL {INSTALL_URL} | sh -s -- hands {} {code}", relay_url.trim_end_matches('/'))
|
||||
}
|
||||
|
|
|
|||
21
src/setup.rs
21
src/setup.rs
|
|
@ -496,11 +496,13 @@ impl Wiz {
|
|||
|
||||
fn type_text(&mut self, s: &str) {
|
||||
let s: String = s.chars().filter(|c| !c.is_control()).collect();
|
||||
// A pasted 「anybot setup 網址 配對碼」 line fills both.
|
||||
let words: Vec<&str> = s.split_whitespace().filter(|w| *w != "anybot" && *w != "setup").collect();
|
||||
if words.len() == 2 && words[1].chars().all(|c| c.is_ascii_digit()) && !self.password {
|
||||
self.url = words[0].to_string();
|
||||
self.code = words[1].to_string();
|
||||
// A pasted 「anybot setup 網址 配對碼」 line (or the 「curl … | sh -s -- hands 網址 配對碼」 one)
|
||||
// fills both.
|
||||
let words: Vec<&str> = s.split_whitespace().collect();
|
||||
let n = words.len();
|
||||
if n >= 2 && words[n - 1].chars().all(|c| c.is_ascii_digit()) && words[n - 2].contains("://") && !self.password {
|
||||
self.url = words[n - 2].to_string();
|
||||
self.code = words[n - 1].to_string();
|
||||
self.focus = 1;
|
||||
return;
|
||||
}
|
||||
|
|
@ -687,7 +689,7 @@ impl Plan {
|
|||
// 2. Start with the computer.
|
||||
say(1, Mark::Run, "");
|
||||
let started = if !self.env.linux {
|
||||
say(1, Mark::Warn, "Mac 和 Windows:打開 AnyBot App 就會用這些設定連上(設定 › 手機與帳號)。");
|
||||
say(1, Mark::Warn, "Mac 和 Windows:打開 AnyBot App 就會用這些設定連上(設定 › 一般 › 手機與帳號)。");
|
||||
false
|
||||
} else if !self.service {
|
||||
say(1, Mark::Warn, "這台沒有 systemd:請用「anybot --serve」啟動,或把它加到開機執行。");
|
||||
|
|
@ -742,7 +744,7 @@ impl Plan {
|
|||
let mut tips = vec![];
|
||||
match self.role {
|
||||
Role::Hands => tips.push(format!("在大腦上跟 Bot 說「用 {} 的瀏覽器打開…」或「看一下 {} 的畫面」試試。", r.host_name, r.host_name)),
|
||||
Role::Brain => tips.push("其他電腦:AnyBot › 設定 › 手機與帳號,登入同一個帳號,在「大腦」選這台。沒有視窗的 Linux:anybot setup 選「手腳」。".into()),
|
||||
Role::Brain => tips.push("其他電腦:AnyBot › 設定 › 一般 › 手機與帳號,登入同一個帳號,在「大腦」選這台。沒有視窗的 Linux:anybot setup 選「手腳」。".into()),
|
||||
}
|
||||
if started {
|
||||
tips.push(String::new());
|
||||
|
|
@ -1150,5 +1152,10 @@ mod tests {
|
|||
w.url.clear();
|
||||
w.type_text("anybot setup https://anybot.example.com 482913");
|
||||
assert_eq!((w.url.as_str(), w.code.as_str()), ("https://anybot.example.com", "482913"));
|
||||
let mut w = Wiz::new(Role::Hands);
|
||||
w.focus = 0;
|
||||
w.url.clear();
|
||||
w.type_text(&crate::remote::pair_line("https://anybot.example.com/", "135790"));
|
||||
assert_eq!((w.url.as_str(), w.code.as_str()), ("https://anybot.example.com", "135790"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue