多台電腦(大腦、手腳、螢幕)、一行加電腦、文件與 make #2
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Push a version tag (git tag v1.2.0 && git push origin v1.2.0) and every build lands on that
|
||||||
|
# tag's release: macOS (the app), Ubuntu (with a window), Linux without a screen, and the server.
|
||||||
|
#
|
||||||
|
# Runners (see deploy/ci/README.md):
|
||||||
|
# anybot-linux a Linux computer with Docker; jobs run in anybot-builder:22.04
|
||||||
|
# macos-arm64 a Mac (runs directly on it)
|
||||||
|
name: Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
|
||||||
|
env:
|
||||||
|
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
linux:
|
||||||
|
runs-on: anybot-linux
|
||||||
|
env:
|
||||||
|
# Kept between releases (a Docker volume on the runner): only what changed is compiled.
|
||||||
|
CARGO_TARGET_DIR: /ci-target
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cargo build --release --bin anybot
|
||||||
|
cd relay && cargo build --release
|
||||||
|
- name: Test
|
||||||
|
run: cargo test --release --bin anybot -- --skip live 2>&1 | tail -3
|
||||||
|
- name: Package
|
||||||
|
run: sh deploy/ci/package-linux.sh "${GITHUB_REF_NAME#v}"
|
||||||
|
- name: Upload
|
||||||
|
run: sh deploy/ci/upload-release.sh "$GITHUB_REF_NAME" dist/*.tar.gz
|
||||||
|
|
||||||
|
macos:
|
||||||
|
runs-on: macos-arm64
|
||||||
|
env:
|
||||||
|
CARGO_TARGET_DIR: ${{ github.workspace }}/../anybot-target
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --release --bin anybot
|
||||||
|
- name: Package
|
||||||
|
run: sh deploy/mac/bundle.sh "${GITHUB_REF_NAME#v}"
|
||||||
|
- name: Upload
|
||||||
|
run: sh deploy/ci/upload-release.sh "$GITHUB_REF_NAME" dist/*.zip
|
||||||
|
|
@ -14,3 +14,6 @@
|
||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Release packages (deploy/ci, deploy/mac/bundle.sh)
|
||||||
|
/dist/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
# The Linux build environment for the release workflow (.gitea/workflows/release.yml).
|
||||||
|
# Ubuntu 22.04 (glibc 2.35), so the binaries also run on newer Ubuntu, Debian 12 and the like.
|
||||||
|
# Built once on the runner's computer: docker build -t anybot-builder:22.04 -f deploy/ci/Dockerfile.linux deploy/ci
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates curl git build-essential pkg-config cmake clang libclang-dev python3 nodejs xz-utils zip \
|
||||||
|
libasound2-dev libxkbcommon-dev libwayland-dev libx11-dev libxtst-dev libdbus-1-dev \
|
||||||
|
libxcb1-dev libxcb-randr0-dev libxcb-shm0-dev libxdo-dev libpipewire-0.3-dev libgbm-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo PATH=/usr/local/cargo/bin:$PATH
|
||||||
|
RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.1 && chmod -R a+w $RUSTUP_HOME $CARGO_HOME
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# AnyBot (the brain, no window) from the release's binary.
|
||||||
|
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 libasound2 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY bin/anybot /usr/local/bin/anybot
|
||||||
|
ENV ANYBOT_DATA_DIR=/data
|
||||||
|
VOLUME /data
|
||||||
|
CMD ["anybot", "--serve"]
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# The account server from the release's binary (no compiling on the server).
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
RUN useradd -m relay && mkdir /data && chown relay /data
|
||||||
|
COPY bin/anybot-relay /usr/local/bin/anybot-relay
|
||||||
|
USER relay
|
||||||
|
VOLUME /data
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["anybot-relay", "--port", "8080", "--data", "/data"]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Install AnyBot from this package for the current user (no root needed).
|
||||||
|
# ./install.sh the app (menu entry and icon too when this computer has a desktop)
|
||||||
|
# Then: open AnyBot from the menu, or on a computer without a screen: anybot setup
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
BIN="$HOME/.local/bin"
|
||||||
|
mkdir -p "$BIN"
|
||||||
|
install -m 755 anybot "$BIN/anybot"
|
||||||
|
if [ -f anybot.desktop ]; then
|
||||||
|
APPS="$HOME/.local/share/applications"; ICONS="$HOME/.local/share/icons/hicolor"
|
||||||
|
mkdir -p "$APPS" "$ICONS/scalable/apps" "$ICONS/256x256/apps"
|
||||||
|
install -m 644 anybot.desktop "$APPS/anybot.desktop"
|
||||||
|
install -m 644 anybot.svg "$ICONS/scalable/apps/anybot.svg"
|
||||||
|
"$BIN/anybot" --write-icon "$ICONS/256x256/apps/anybot.png" 256 2>/dev/null || true
|
||||||
|
command -v update-desktop-database >/dev/null && update-desktop-database "$APPS" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
echo "AnyBot 已安裝在 $BIN/anybot"
|
||||||
|
case ":$PATH:" in *":$BIN:"*) ;; *) echo "(把 $BIN 加到 PATH,才能直接打 anybot)" ;; esac
|
||||||
|
if ! ldconfig -p 2>/dev/null | grep -q libasound.so.2; then
|
||||||
|
echo "還需要 libasound2:sudo apt-get install -y libasound2(或 libasound2t64)"
|
||||||
|
fi
|
||||||
|
echo "接到你的大腦:在手機或電腦的 AnyBot 按「加一台電腦」,把那一行貼到這裡執行。"
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Package the Linux builds for a release (run from app/ after the release builds):
|
||||||
|
# dist/AnyBot-<version>-ubuntu-x86_64.tar.gz the app with its window (menu entry, icon)
|
||||||
|
# dist/AnyBot-<version>-headless-x86_64.tar.gz for a computer without a screen (hands, or a brain)
|
||||||
|
# dist/AnyBot-<version>-server-x86_64.tar.gz account server + brain on one server (Docker)
|
||||||
|
set -e
|
||||||
|
VERSION=${1:?version}
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
OUT=dist
|
||||||
|
rm -rf "$OUT" && mkdir -p "$OUT"
|
||||||
|
BIN=${CARGO_TARGET_DIR:-target}/release/anybot
|
||||||
|
RELAY=${CARGO_TARGET_DIR:-relay/target}/release/anybot-relay
|
||||||
|
strip "$BIN" "$RELAY" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Ubuntu (with a window)
|
||||||
|
D="$OUT/AnyBot-$VERSION-ubuntu-$ARCH"
|
||||||
|
mkdir -p "$D"
|
||||||
|
cp "$BIN" deploy/linux/anybot.desktop icons/anybot.svg "$D/"
|
||||||
|
cp deploy/ci/linux-install.sh "$D/install.sh"
|
||||||
|
printf 'AnyBot %s(Ubuntu 22.04 以後、Debian 12 以後)\n\n ./install.sh 安裝,之後從選單打開 AnyBot\n' "$VERSION" > "$D/讀我.txt"
|
||||||
|
tar -C "$OUT" -czf "$D.tar.gz" "$(basename "$D")"
|
||||||
|
|
||||||
|
# Without a screen (hands, or a brain)
|
||||||
|
D="$OUT/AnyBot-$VERSION-headless-$ARCH"
|
||||||
|
mkdir -p "$D"
|
||||||
|
cp "$BIN" "$D/"
|
||||||
|
cp deploy/ci/linux-install.sh "$D/install.sh"
|
||||||
|
cp deploy/linux/hands.sh "$D/hands.sh"
|
||||||
|
printf 'AnyBot %s:沒有螢幕的 Linux\n\n ./install.sh\n 然後在手機或電腦的 AnyBot 按「加一台電腦」,把那一行貼到這台執行。\n 想讓 Bot 看這台的畫面(虛擬螢幕):先執行 ./hands.sh --deps\n' "$VERSION" > "$D/讀我.txt"
|
||||||
|
tar -C "$OUT" -czf "$D.tar.gz" "$(basename "$D")"
|
||||||
|
|
||||||
|
# Server: account server + brain, from these binaries (no compiling on the server)
|
||||||
|
D="$OUT/AnyBot-$VERSION-server-$ARCH"
|
||||||
|
mkdir -p "$D/bin" "$D/deploy/server" "$D/relay"
|
||||||
|
cp "$BIN" "$RELAY" "$D/bin/"
|
||||||
|
cp deploy/server/setup.sh deploy/server/.env.example "$D/deploy/server/"
|
||||||
|
cp deploy/ci/server-compose.yml "$D/deploy/server/docker-compose.yml"
|
||||||
|
cp deploy/ci/Dockerfile.server-relay deploy/ci/Dockerfile.server-anybot "$D/deploy/server/"
|
||||||
|
cp relay/Caddyfile "$D/relay/Caddyfile"
|
||||||
|
printf 'AnyBot %s 伺服器(帳號伺服器 + 大腦 + HTTPS,用 Docker)\n\n cd deploy/server && ./setup.sh\n' "$VERSION" > "$D/讀我.txt"
|
||||||
|
tar -C "$OUT" -czf "$D.tar.gz" "$(basename "$D")"
|
||||||
|
|
||||||
|
find "$OUT" -maxdepth 1 -type d -name 'AnyBot-*' -exec rm -rf {} +
|
||||||
|
ls -la "$OUT"
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# gitea-runner settings for the Linux build computer (~/.config/gitea-runner/config.yaml).
|
||||||
|
log:
|
||||||
|
level: info
|
||||||
|
runner:
|
||||||
|
file: /home/daniel/.config/gitea-runner/.runner
|
||||||
|
capacity: 1
|
||||||
|
timeout: 3h
|
||||||
|
labels:
|
||||||
|
- "anybot-linux:docker://anybot-builder:22.04"
|
||||||
|
cache:
|
||||||
|
enabled: true
|
||||||
|
container:
|
||||||
|
# anybot-builder is built on this computer, not pulled.
|
||||||
|
force_pull: false
|
||||||
|
# Downloads and compiled pieces kept between releases (much faster second builds).
|
||||||
|
valid_volumes:
|
||||||
|
- anybot-cargo-registry
|
||||||
|
- anybot-ci-target
|
||||||
|
options: "-v anybot-cargo-registry:/usr/local/cargo/registry -v anybot-ci-target:/ci-target"
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
# The server from a release package (prebuilt binaries in ../../bin). Made from deploy/server/docker-compose.yml.
|
||||||
|
# Everything on one small server: the account server (relay), HTTPS (Caddy), and AnyBot itself.
|
||||||
|
# Easiest: ./setup.sh (asks for the domain, account and keys, writes .env and Caddyfile, starts
|
||||||
|
# everything and checks it). By hand:
|
||||||
|
# 1. Point your domain at the server; copy ../../relay/Caddyfile here and put the domain in it.
|
||||||
|
# 2. Put your account name and password in .env (see .env.example).
|
||||||
|
# 3. docker compose up -d
|
||||||
|
# 4. On the phone open https://your-domain, create the account (first time), and use it.
|
||||||
|
# AI keys: set them in .env, or set ANYBOT_RESTORE_FROM_ACCOUNT=1 to start from the newest backup
|
||||||
|
# your Mac made (bots, chats, routines and the keys you set there).
|
||||||
|
#
|
||||||
|
# 大腦: this AnyBot is the brain of the account. It keeps the bots, chats, memory and routines,
|
||||||
|
# and works even while your computers sleep. On each of your computers: 設定 › 手機與帳號 ›
|
||||||
|
# 大腦 › 「改用它當大腦」. Those computers then show these bots, and the bots can use them as hands
|
||||||
|
# (screen, files, browser), asking you first as usual. A Linux computer without a screen can be
|
||||||
|
# hands too: run `anybot setup` there and pick this brain.
|
||||||
|
services:
|
||||||
|
relay:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: deploy/server/Dockerfile.server-relay
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
# Your public address (for the links of sites the bots publish), e.g. https://anybot.example.com
|
||||||
|
ANYBOT_RELAY_PUBLIC_URL: "${ANYBOT_RELAY_PUBLIC_URL:-}"
|
||||||
|
ANYBOT_RELAY_INVITE: "${ANYBOT_RELAY_INVITE:-}"
|
||||||
|
volumes:
|
||||||
|
- relay-data:/data
|
||||||
|
caddy:
|
||||||
|
image: caddy:2
|
||||||
|
restart: unless-stopped
|
||||||
|
ports: ["80:80", "443:443"]
|
||||||
|
volumes:
|
||||||
|
# Written by setup.sh (your domain); ../../relay/Caddyfile is the template.
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- caddy-data:/data
|
||||||
|
depends_on: [relay]
|
||||||
|
anybot:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: deploy/server/Dockerfile.server-anybot
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on: [relay]
|
||||||
|
environment:
|
||||||
|
ANYBOT_ACCOUNT: "http://relay:8080|${ANYBOT_USER}"
|
||||||
|
ANYBOT_ACCOUNT_PASSWORD: "${ANYBOT_PASSWORD}"
|
||||||
|
ANYBOT_HOST_NAME: "${ANYBOT_HOST_NAME:-雲端 AnyBot}"
|
||||||
|
# First start on a new server: make the account (the first one needs no invite).
|
||||||
|
ANYBOT_ACCOUNT_CREATE: "1"
|
||||||
|
ANYBOT_RELAY_INVITE: "${ANYBOT_RELAY_INVITE:-}"
|
||||||
|
XAI_API_KEY: "${XAI_API_KEY:-}"
|
||||||
|
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
|
||||||
|
OPENAI_API_KEY: "${OPENAI_API_KEY:-}"
|
||||||
|
GEMINI_API_KEY: "${GEMINI_API_KEY:-}"
|
||||||
|
ANYBOT_RESTORE_FROM_ACCOUNT: "${ANYBOT_RESTORE_FROM_ACCOUNT:-}"
|
||||||
|
volumes:
|
||||||
|
- anybot-data:/data
|
||||||
|
# The bot's own computer runs as a container next to this one.
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
volumes:
|
||||||
|
relay-data:
|
||||||
|
caddy-data:
|
||||||
|
anybot-data:
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Attach files to this tag's release on Gitea (made if it isn't there yet).
|
||||||
|
# deploy/ci/upload-release.sh <tag> <file>…
|
||||||
|
# Uses GITHUB_SERVER_URL, GITHUB_REPOSITORY and RELEASE_TOKEN (set by the workflow).
|
||||||
|
set -e
|
||||||
|
TAG=${1:?tag}; shift
|
||||||
|
API="$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY"
|
||||||
|
AUTH="Authorization: token $RELEASE_TOKEN"
|
||||||
|
json() { python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('$1','') if isinstance(d,dict) else '')"; }
|
||||||
|
ID=$(curl -fsS -H "$AUTH" "$API/releases/tags/$TAG" 2>/dev/null | json id || true)
|
||||||
|
if [ -z "$ID" ]; then
|
||||||
|
# Two jobs may get here at once: whoever is second finds the first one's release.
|
||||||
|
ID=$(curl -fsS -H "$AUTH" -H "Content-Type: application/json" -X POST "$API/releases" \
|
||||||
|
-d "{\"tag_name\":\"$TAG\",\"name\":\"AnyBot $TAG\",\"body\":\"自動建置:macOS、Ubuntu、沒有螢幕的 Linux、伺服器。\"}" | json id) \
|
||||||
|
|| ID=$(curl -fsS -H "$AUTH" "$API/releases/tags/$TAG" | json id)
|
||||||
|
fi
|
||||||
|
[ -n "$ID" ] || { echo "找不到也建不了 $TAG 的 release" >&2; exit 1; }
|
||||||
|
for f in "$@"; do
|
||||||
|
name=$(basename "$f")
|
||||||
|
# Built again: replace the old file.
|
||||||
|
old=$(curl -fsS -H "$AUTH" "$API/releases/$ID/assets" | python3 -c "import json,sys; print(next((str(a['id']) for a in json.load(sys.stdin) if a['name']=='$name'),''))")
|
||||||
|
[ -n "$old" ] && curl -fsS -H "$AUTH" -X DELETE "$API/releases/$ID/assets/$old" >/dev/null
|
||||||
|
curl -fsS -H "$AUTH" -F "attachment=@$f" "$API/releases/$ID/assets?name=$name" >/dev/null
|
||||||
|
echo "已上傳 $name"
|
||||||
|
done
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Make AnyBot.app from the release build and zip it (run from app/ on a Mac, after
|
||||||
|
# `cargo build --release`): deploy/mac/bundle.sh 1.2.0 → dist/AnyBot-1.2.0-macos-<arch>.zip
|
||||||
|
# Signed ad hoc (no Apple developer account): the first time, right-click › 打開.
|
||||||
|
set -e
|
||||||
|
VERSION=${1:?version}
|
||||||
|
BIN=${CARGO_TARGET_DIR:-target}/release/anybot
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
OUT=dist
|
||||||
|
APP="$OUT/AnyBot.app"
|
||||||
|
rm -rf "$APP" && mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
|
||||||
|
cp "$BIN" "$APP/Contents/MacOS/AnyBot"
|
||||||
|
|
||||||
|
# The icon: every size the system asks for.
|
||||||
|
SET=$(mktemp -d)/AnyBot.iconset
|
||||||
|
mkdir -p "$SET"
|
||||||
|
for s in 16 32 128 256 512; do
|
||||||
|
"$BIN" --write-icon "$SET/icon_${s}x${s}.png" $s
|
||||||
|
"$BIN" --write-icon "$SET/icon_${s}x${s}@2x.png" $((s * 2))
|
||||||
|
done
|
||||||
|
iconutil -c icns "$SET" -o "$APP/Contents/Resources/AnyBot.icns"
|
||||||
|
|
||||||
|
cat > "$APP/Contents/Info.plist" <<PLIST
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0"><dict>
|
||||||
|
<key>CFBundleName</key><string>AnyBot</string>
|
||||||
|
<key>CFBundleDisplayName</key><string>AnyBot</string>
|
||||||
|
<key>CFBundleIdentifier</key><string>net.30cm.anybot</string>
|
||||||
|
<key>CFBundleExecutable</key><string>AnyBot</string>
|
||||||
|
<key>CFBundleIconFile</key><string>AnyBot</string>
|
||||||
|
<key>CFBundlePackageType</key><string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key><string>$VERSION</string>
|
||||||
|
<key>CFBundleVersion</key><string>$VERSION</string>
|
||||||
|
<key>LSMinimumSystemVersion</key><string>12.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key><true/>
|
||||||
|
<key>NSMicrophoneUsageDescription</key><string>語音聊天時,聽你說話。</string>
|
||||||
|
<key>NSAppleEventsUsageDescription</key><string>讓 Bot 在你允許時操作其他 App。</string>
|
||||||
|
</dict></plist>
|
||||||
|
PLIST
|
||||||
|
codesign --force --deep -s - "$APP"
|
||||||
|
ditto -c -k --norsrc --keepParent "$APP" "$OUT/AnyBot-$VERSION-macos-$ARCH.zip"
|
||||||
|
rm -rf "$APP"
|
||||||
|
ls -la "$OUT"
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
FROM rust:1-bookworm AS build
|
FROM rust:1-bookworm AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY src/httpd.rs src/httpd.rs
|
COPY src/httpd.rs src/httpd.rs
|
||||||
|
COPY src/wire.rs src/wire.rs
|
||||||
COPY mobile mobile
|
COPY mobile mobile
|
||||||
COPY relay relay
|
COPY relay relay
|
||||||
RUN cd relay && cargo build --release
|
RUN cd relay && cargo build --release
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue