CXT - Enjoy Life | 生活、技术、交友、分享 CXT - Enjoy Life | 生活、技术、交友、分享
  • 首页
  • 特色专题
    • 一键网络重装系统 - 魔改版(适用于Linux / Windows)
    • 精英IDC计划 - 千万IDC计划(从入门到跑路)
    • CXT裸机系统部署平台(自定义安装任意系统)
    • OpenWRT-Virtualization-Servers
  • 分类目录
    • 站点公告
    • 技术分享
    • 生活感悟
  • 更多(More)
    • 浏览记录(Historical-Record)
    • 支付捐赠(Payment-Donation)
    • 隐私政策(Privacy-Policy)
    • 服务状态(Server-Status)
    • 友情链接(Link)
    • 联系我们(Contact-US)
    • 关于我们(About-Me)
Home › 技术分享 › 在低配VPS 上安全部署 PicoClaw:个人微信 iLink、受限 Shell、systemd 与备份还原、1C 1G 5G配置[picoclaw-debian13-wechat-ilink-deployment]
  • 0

在低配VPS 上安全部署 PicoClaw:个人微信 iLink、受限 Shell、systemd 与备份还原、1C 1G 5G配置[picoclaw-debian13-wechat-ilink-deployment]

CXT
July 13, 2026
241 views

简介

适用对象:希望在低配 Debian VPS 上运行个人微信 AI Agent,并使用远端 OpenAI 兼容模型 API 的用户。
本文目标:不使用 Docker、不在 VPS 编译、不开放 PicoClaw 公网端口,以普通用户运行,并将 Agent 的文件与命令活动限制在专用工作区。

一、最终架构与边界

本文采用的架构如下:

个人微信 iLink
        │(长轮询出站连接,无需公网 Webhook)
        ▼
PicoClaw(systemd)
  ├─ 普通用户:picoclaw
  ├─ 白名单:仅自己的 @im.wechat 用户 ID
  ├─ 工作区:/home/picoclaw/workspace
  ├─ 本地 Gateway:127.0.0.1:18790 / [::1]:18790
  ├─ 内存上限:480 MiB
  └─ CPU 上限:单核 95%
        │
        ▼
OpenAI 兼容远端模型 API

安全策略

  • 不以 root 运行 Agent:仅 root 负责安装、更新、启停 systemd 服务。
  • 不开放 PicoClaw 公网端口:微信 iLink 使用出站长轮询;管理仍使用 SSH、Tailscale 或 ZeroTier。
  • 单一微信白名单:只有指定 @im.wechat ID 可以使用 Agent。
  • C-轻量模式:允许 Agent 在工作区内使用 Shell 和用户态工具,但不允许 sudo、apt、systemctl、Docker、网络管理等系统级操作。
  • systemd 强制限制:限制权限、设备、临时目录、内核设置、系统目录写入、内存和 CPU。
  • 敏感凭证单独保存:模型 API Key 不写进主配置,也不提交 Git。

[!WARNING] 允许远程 Shell 后,即使 Agent 是普通用户,也仍有提示注入、恶意网页、恶意下载脚本等风险。本文的配置能阻止系统级修改,但无法让一个“可以执行任意用户态命令”的进程完全看不到它自身需要使用的凭证。请只允许可信微信账号使用,不要把 Bot 分享给他人或加入群聊。

在低配VPS 上安全部署 PicoClaw:个人微信 iLink、受限 Shell、systemd 与备份还原、1C 1G 5G配置[picoclaw-debian13-wechat-ilink-deployment]-CXT - Enjoy Life | 生活、技术、交友、分享

二、资源要求与容量规划

本文实测配置适用于:

项目建议值
系统Debian 13(x86_64)
CPU1 vCPU
内存1 GB
Swap至少 512 MB,建议 1 GB
磁盘5 GB 起步,建议 10 GB+ 更从容
模型云端 API;不运行 Ollama 或本地模型

低配 VPS 的关键不是 PicoClaw 二进制本身,而是避免同时运行:

  • Docker / 多容器沙箱;
  • 本地大模型;
  • 浏览器自动化;
  • 多 Agent 并发;
  • 大型向量数据库;
  • 大量附件处理、下载和日志积累。

建议首先检查:

uname -m
cat /etc/os-release
free -h
df -h /

三、部署前检查

1. 检查端口和已有服务

ss -lntup
systemctl --type=service --state=running --no-pager

PicoClaw 的本地 Gateway 默认使用 18790。它应只绑定到回环地址,不应监听:

0.0.0.0:18790
[::]:18790

2. 检查微信 iLink 出网

getent hosts ilinkai.weixin.qq.com
curl -I --max-time 10 https://ilinkai.weixin.qq.com

405 Method Not Allowed 对 curl -I 来说通常仍表示网络连接成功:该端点不接受 HEAD 请求,但 HTTPS 已可达。

3. 选择模型 API

本文假设服务商同时支持 OpenAI Chat Completions 风格接口:

https://YOUR-API-HOST/v1/chat/completions

PicoClaw 配置中使用的 Base URL 应为:

https://YOUR-API-HOST/v1

不要在文章、聊天记录、截图或 Git 仓库中粘贴真实 API Key。


四、创建受限运行用户与目录

所有以下系统操作以 root 执行。

if ! id picoclaw >/dev/null 2>&1; then
  useradd --create-home --user-group --shell /bin/bash picoclaw
fi

# 锁定密码;root 仍可通过 runuser 切换到该用户。
usermod -L picoclaw
gpasswd -d picoclaw sudo 2>/dev/null || true

install -d -o picoclaw -g picoclaw -m 0700 \
  /home/picoclaw/.picoclaw \
  /home/picoclaw/workspace \
  /home/picoclaw/workspace/bin \
  /home/picoclaw/workspace/downloads

id picoclaw

预期:picoclaw 不应属于 sudo 组。


五、安装 PicoClaw 预编译二进制

PicoClaw 是 Go 二进制程序。对 x86_64 Linux,可使用官方 Release 的预编译包;不需要在 VPS 或 WSL 编译。

发布资产名称可能随项目版本变化。以下是当前常见命名方式;若下载出现 404,请先在官方 Release 页面确认资产名。

TMPDIR="$(mktemp -d)"

curl -fL --retry 3 --retry-delay 2 \
  -o "${TMPDIR}/picoclaw.tar.gz" \
  "https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_x86_64.tar.gz"

tar -C "${TMPDIR}" -xzf "${TMPDIR}/picoclaw.tar.gz"

BIN_PATH="$(find "${TMPDIR}" -type f -name picoclaw -executable -print -quit)"
test -n "${BIN_PATH}"

install -o root -g root -m 0755 "${BIN_PATH}" /usr/local/bin/picoclaw
rm -rf -- "${TMPDIR}"

/usr/local/bin/picoclaw version

六、写入 PicoClaw 主配置

创建 /home/picoclaw/.picoclaw/config.json:

cat > /home/picoclaw/.picoclaw/config.json <<'EOF'
{
  "agents": {
    "defaults": {
      "workspace": "/home/picoclaw/workspace",
      "model_name": "my-openai-compatible-model",
      "max_tokens": 12288,
      "max_tool_iterations": 5,
      "max_parallel_turns": 1,
      "restrict_to_workspace": true
    }
  },

  "model_list": [
    {
      "model_name": "my-openai-compatible-model",
      "provider": "openai",
      "model": "YOUR_MODEL_NAME",
      "api_base": "https://YOUR-API-HOST/v1"
    }
  ],

  "channel_list": {
    "weixin": {
      "enabled": true,
      "type": "weixin",
      "allow_from": ["blocked-until-owner-id-is-set"]
    }
  },

  "tools": {
    "web": {
      "duckduckgo": {
        "enabled": true,
        "max_results": 5
      }
    },
    "exec": {
      "allow_remote": false,
      "enable_deny_patterns": true,
      "custom_deny_patterns": [
        "(?i)\\b(sudo|su|apt|apt-get|dpkg|systemctl|service|journalctl|useradd|usermod|passwd|visudo|mount|umount|iptables|nft|ufw|docker|podman|tailscale|zerotier-cli|caddy|reboot|shutdown|poweroff)\\b"
      ]
    },
    "cron": {
      "enabled": false
    }
  },

  "heartbeat": {
    "enabled": false
  }
}
EOF

chown picoclaw:picoclaw /home/picoclaw/.picoclaw/config.json
chmod 600 /home/picoclaw/.picoclaw/config.json

配置重点说明

字段建议说明
restrict_to_workspacetrue让 Agent 的操作范围聚焦在工作区
max_tokens12288单次模型输出上限,适合代码、长说明和多工具任务
max_parallel_turns11C/1G 与单用户微信场景下保持顺序处理
max_tool_iterations5单次任务最多工具循环次数;复杂任务可拆分消息
allow_remote初始 false白名单确认后再启用 C-轻量 Shell
allow_from初始占位值初始状态拒绝所有微信用户,避免误开放

max_tokens 主要限制每次模型输出,不是整个会话总上下文。12288 是远端模型输出上限,主要影响 API 成本、单次响应长度和等待时间,不会让 VPS 本地进行模型推理。


七、私密写入模型 API Key

不要把 API Key 放在 config.json。使用独立安全文件:

runuser -l picoclaw

进入 picoclaw 用户后执行:

umask 077

read -rsp "Paste API key: " PICO_KEY
echo

cat > ~/.picoclaw/.security.yml <<EOF
model_list:
  my-openai-compatible-model:
    api_keys:
      - "${PICO_KEY}"
EOF

unset PICO_KEY
chmod 600 ~/.picoclaw/.security.yml
exit

测试模型连接:

runuser -l picoclaw -c \
  '/usr/local/bin/picoclaw agent -m "Reply with: model connection successful"'

模型返回正常后,再继续微信接入。


八、个人微信 iLink 扫码登录

以 root 执行:

runuser -l picoclaw -c '/usr/local/bin/picoclaw auth weixin'

终端会显示二维码。使用微信扫码并确认授权。

成功后,凭证由 picoclaw 用户保存;不要查看、复制、公开或提交这些凭证。


九、安全获取个人微信 User ID 并设置白名单

1. 先启动“拒绝所有人”的服务

本节先使用占位白名单:

blocked-until-owner-id-is-set

这样即使微信 Bot 在线,也不会接受任何用户请求。

2. 向微信 Bot 发送探测消息

在微信中发送:

owner-id-probe

PicoClaw 的微信 context token 通常保存在:

/home/picoclaw/.picoclaw/channels/weixin/context-tokens/

文件名可能是内部哈希,不应直接当作用户 ID。可以只从 context-token 文件中提取形如 @im.wechat 的 ID:

CTX=/home/picoclaw/.picoclaw/channels/weixin/context-tokens

grep -RahoE '[A-Za-z0-9_-]+@im\.wechat' "$CTX" 2>/dev/null | sort -u

如果得到一条唯一输出,例如:

[email protected]

将它作为 allow_from 的白名单值。

如果出现多条,向 Bot 再发送一条唯一探测文本,然后仅检查修改时间最新的 context-token 文件:

find "$CTX" -maxdepth 1 -type f -printf '%T@ %p\n' | sort -n | tail -n 1

3. 用真实 ID 替换占位白名单

read -rp "Enter your own Weixin User ID: " WX_USER_ID

CFG=/home/picoclaw/.picoclaw/config.json
BACKUP="${CFG}.before-owner-allowlist.$(date +%Y%m%d-%H%M%S)"

cp -a -- "$CFG" "$BACKUP"

awk -v id="$WX_USER_ID" '
  /"weixin"[[:space:]]*:[[:space:]]*\{/ {
    in_weixin = 1
  }

  in_weixin && /"allow_from"[[:space:]]*:/ {
    print "      \"allow_from\": [\"" id "\"],"
    next
  }

  in_weixin && /^[[:space:]]*}[[:space:]]*,?[[:space:]]*$/ {
    in_weixin = 0
  }

  { print }
' "$CFG" > "${CFG}.new"

mv -- "${CFG}.new" "$CFG"
unset WX_USER_ID

chown picoclaw:picoclaw "$CFG"
chmod 600 "$CFG"

白名单验证后,微信中发送:

Please reply: whitelist verified

确认 Bot 正常回复后才继续开启 Shell。


十、英文版 AGENT.md 安全规则

为了避免终端区域设置导致中文乱码,建议 Agent 指令文件使用 ASCII 英文。

cat > /home/picoclaw/workspace/AGENT.md <<'EOF'
---
name: restricted-home-agent
description: Personal WeChat assistant restricted to its workspace.
tools:
  - read_file
  - write_file
  - edit_file
  - append_file
  - list_dir
  - web_search
  - web_fetch
  - send_file
  - exec
---

# Mandatory boundaries

- Work only inside /home/picoclaw/workspace and its subdirectories.
- Never access /root, /etc, /usr, /opt, /var, /boot, or other users' homes.
- Never access or reveal API keys, tokens, credentials, login state, or files
  under /home/picoclaw/.picoclaw.
- Never run sudo, su, apt, apt-get, dpkg, systemctl, service, docker, podman,
  caddy, tailscale, zerotier-cli, nft, iptables, ufw, reboot, shutdown, or poweroff.
- Keep downloaded files, scripts, and portable user tools inside the workspace.
- Run commands only with /home/picoclaw/workspace as the working directory.
- Read-only web requests are allowed. Before uploading data, submitting forms,
  downloading executables, executing downloaded scripts, or contacting an API
  other than the configured model provider, explain the impact and ask first.
- Before deleting files, overwriting existing files, changing Agent instructions,
  or creating scheduled tasks, explain the impact and ask first.
- Do not create or modify cron jobs, heartbeat tasks, service files, or configs.
- If a request conflicts with these rules, refuse it and explain why.
EOF

chown picoclaw:picoclaw /home/picoclaw/workspace/AGENT.md
chmod 600 /home/picoclaw/workspace/AGENT.md

AGENT.md 是行为边界,不是 Linux 沙箱。真正的强制安全边界来自普通用户、白名单、systemd 和文件权限。


十一、创建 systemd 服务

将所有资源限制和安全限制统一写在一个主服务文件中,便于维护。

cat > /etc/systemd/system/picoclaw.service <<'EOF'
[Unit]
Description=PicoClaw WeChat Agent (restricted user service)
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=picoclaw
Group=picoclaw

WorkingDirectory=/home/picoclaw/workspace
Environment=HOME=/home/picoclaw
Environment=PATH=/home/picoclaw/workspace/bin:/usr/local/bin:/usr/bin:/bin
Environment=PICOCLAW_HEARTBEAT_ENABLED=false

ExecStart=/usr/local/bin/picoclaw gateway

Restart=on-failure
RestartSec=8
TimeoutStopSec=30

# Resource limits for 1C / 1GB VPS
MemoryHigh=400M
MemoryMax=480M
CPUQuota=95%
TasksMax=128
LimitNOFILE=1024

# Privilege isolation
UMask=0077
NoNewPrivileges=true
CapabilityBoundingSet=
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
RestrictSUIDSGID=true
LockPersonality=true
RestrictNamespaces=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

# Writable area for PicoClaw only
ReadWritePaths=/home/picoclaw

# Explicitly hide sensitive host locations
InaccessiblePaths=/root
InaccessiblePaths=/etc/caddy
InaccessiblePaths=/var/lib/tailscale
InaccessiblePaths=/var/lib/zerotier-one

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemd-analyze verify /etc/systemd/system/picoclaw.service
systemctl enable --now picoclaw.service

验证服务

systemctl is-active picoclaw.service

ss -lntp | grep picoclaw || true

systemctl show picoclaw.service \
  -p User \
  -p MemoryCurrent \
  -p MemoryHigh \
  -p MemoryMax \
  -p CPUQuotaPerSecUSec \
  -p NoNewPrivileges \
  -p ProtectSystem

预期:

active
127.0.0.1:18790
[::1]:18790
User=picoclaw
MemoryHigh=419430400
MemoryMax=503316480
CPUQuotaPerSecUSec=950ms
NoNewPrivileges=yes
ProtectSystem=strict

若出现:

0.0.0.0:18790
[::]:18790

应立即停止服务并检查配置:

systemctl stop picoclaw.service

十二、确认 Cron 与 Heartbeat 没有实际任务

runuser -l picoclaw -c '/usr/local/bin/picoclaw cron list'

预期:

No scheduled jobs.

日志中即使出现:

Cron service started
Heartbeat service started

也可能只是内部调度组件初始化。只要 Cron 列表为空、PICOCLAW_HEARTBEAT_ENABLED=false 已注入 systemd 环境、且没有创建 HEARTBEAT.md 任务,就不会主动执行计划任务。


十三、开启 C-轻量 Shell 模式

仅在满足以下条件后开启:

  1. 白名单已经只包含你自己的 @im.wechat ID;
  2. 微信回复测试成功;
  3. Gateway 仅监听回环地址;
  4. cron list 没有任务;
  5. Agent 以普通用户运行。

开启 allow_remote

CFG=/home/picoclaw/.picoclaw/config.json
BACKUP="${CFG}.before-c-mode.$(date +%Y%m%d-%H%M%S)"

cp -a -- "$CFG" "$BACKUP"

awk '
  /"allow_remote"[[:space:]]*:[[:space:]]*false/ {
    sub(/false/, "true")
  }
  { print }
' "$CFG" > "${CFG}.new"

mv -- "${CFG}.new" "$CFG"

chown picoclaw:picoclaw "$CFG"
chmod 600 "$CFG"

systemctl restart picoclaw.service

Shell 验证

在微信中发送:

Only run pwd and reply with its output.

预期:

/home/picoclaw/workspace

然后测试工作区写入:

Create c-mode-check.txt in the workspace with the content C-MODE-OK. Reply only with done.

服务器侧验证:

cat /home/picoclaw/workspace/c-mode-check.txt

十四、日常运维与紧急降级

常用命令

# 状态
systemctl status picoclaw.service --no-pager

# 最近日志
journalctl -u picoclaw.service -n 100 --no-pager

# 实时日志
journalctl -u picoclaw.service -f

# 重启 / 停止 / 启动
systemctl restart picoclaw.service
systemctl stop picoclaw.service
systemctl start picoclaw.service

# 资源使用
systemctl show picoclaw.service \
  -p MemoryCurrent -p MemoryHigh -p MemoryMax \
  -p CPUUsageNSec -p TasksCurrent

# 系统磁盘与内存
free -h
df -h /

# PicoClaw 数据、工作区和日志容量
du -sh /home/picoclaw/.picoclaw /home/picoclaw/workspace
journalctl --disk-usage

紧急关闭 C 模式

若发现异常命令、提示注入或可疑下载,先停止服务:

systemctl stop picoclaw.service

然后关闭远程 Exec:

CFG=/home/picoclaw/.picoclaw/config.json

awk '
  /"allow_remote"[[:space:]]*:[[:space:]]*true/ {
    sub(/true/, "false")
  }
  { print }
' "$CFG" > "${CFG}.new"

mv -- "${CFG}.new" "$CFG"
chown picoclaw:picoclaw "$CFG"
chmod 600 "$CFG"

systemctl start picoclaw.service

此后 PicoClaw 回到 B 模式:微信聊天可用,但远程 Shell 被禁用。


十五、备份与还原

配套脚本:picoclaw-backup.sh。

#!/usr/bin/env bash
# PicoClaw backup / restore helper for a single-host systemd deployment.
# Run this script as root on the PicoClaw server.

set -Eeuo pipefail
shopt -s nullglob

SERVICE_NAME="picoclaw.service"
PICO_USER="picoclaw"
PICO_HOME="/home/${PICO_USER}"
SERVICE_UNIT="/etc/systemd/system/${SERVICE_NAME}"
PICO_BINARY="/usr/local/bin/picoclaw"
DEFAULT_BACKUP_DIR="/root/picoclaw-backups"

BACKUP_DIR="$DEFAULT_BACKUP_DIR"
LAST_ARCHIVE=""

log()  { printf '[INFO] %s\n' "$*"; }
warn() { printf '[WARN] %s\n' "$*" >&2; }
die()  { printf '[ERROR] %s\n' "$*" >&2; exit 1; }

usage() {
  cat <<'EOF'
PicoClaw backup / restore helper

Usage:
  picoclaw-backup.sh backup [label]
  picoclaw-backup.sh list
  picoclaw-backup.sh verify <archive.tar.gz>
  picoclaw-backup.sh restore <archive.tar.gz> --yes
  picoclaw-backup.sh help

Options:
  --backup-dir <path>  Override the default backup directory for this invocation.
  --yes                Required for restore; confirms that files under / will be replaced.

Examples:
  sudo ./picoclaw-backup.sh backup before-upgrade
  sudo ./picoclaw-backup.sh list
  sudo ./picoclaw-backup.sh verify /root/picoclaw-backups/picoclaw-20260713-120000.tar.gz
  sudo ./picoclaw-backup.sh restore /root/picoclaw-backups/picoclaw-20260713-120000.tar.gz --yes

Security note:
  Archives contain the model API key and WeChat login credentials. Keep them private.
EOF
}

require_root() {
  [[ "${EUID}" -eq 0 ]] || die "Run this script as root."
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
}

check_dependencies() {
  require_command tar
  require_command gzip
  require_command sha256sum
  require_command systemctl
  require_command find
}

check_live_paths() {
  [[ -d "${PICO_HOME}/.picoclaw" ]] || die "Missing PicoClaw config directory: ${PICO_HOME}/.picoclaw"
  [[ -d "${PICO_HOME}/workspace" ]] || die "Missing PicoClaw workspace: ${PICO_HOME}/workspace"
  [[ -f "$SERVICE_UNIT" ]] || die "Missing systemd service file: $SERVICE_UNIT"
  [[ -x "$PICO_BINARY" ]] || die "Missing PicoClaw binary: $PICO_BINARY"
}

sanitize_label() {
  local input="${1:-manual}"
  input="${input//[^A-Za-z0-9._-]/-}"
  input="${input#-}"
  input="${input%-}"
  [[ -n "$input" ]] || input="manual"
  printf '%s' "$input"
}

service_is_active() {
  systemctl is-active --quiet "$SERVICE_NAME"
}

stop_service_if_active() {
  SERVICE_WAS_ACTIVE=0
  if service_is_active; then
    SERVICE_WAS_ACTIVE=1
    log "Stopping ${SERVICE_NAME} for a consistent archive..."
    systemctl stop "$SERVICE_NAME"
  else
    log "${SERVICE_NAME} is already inactive."
  fi
}

restore_service_state() {
  if [[ "${SERVICE_WAS_ACTIVE:-0}" -eq 1 ]]; then
    log "Starting ${SERVICE_NAME}..."
    systemctl start "$SERVICE_NAME"
  fi
}

archive_member_list() {
  local -n _members=$1
  _members=(
    "home/${PICO_USER}/.picoclaw"
    "home/${PICO_USER}/workspace/AGENT.md"
    "etc/systemd/system/${SERVICE_NAME}"
    "usr/local/bin/picoclaw"
  )

  local rel
  for rel in \
    "home/${PICO_USER}/workspace/memory" \
    "home/${PICO_USER}/workspace/cron" \
    "home/${PICO_USER}/workspace/state" \
    "home/${PICO_USER}/workspace/sessions"; do
    [[ -e "/${rel}" ]] && _members+=("$rel")
  done
}

validate_archive_paths() {
  local archive="$1"
  [[ -f "$archive" ]] || die "Archive not found: $archive"

  gzip -t "$archive" || die "gzip integrity check failed: $archive"

  local unsafe
  unsafe="$(tar -tzf "$archive" | awk '
    /^\// || /(^|\/)\.\.($|\/)/ { print; exit }
  ')"
  [[ -z "$unsafe" ]] || die "Unsafe archive path detected: $unsafe"
}

verify_archive() {
  local archive="$1"
  validate_archive_paths "$archive"

  local checksum="${archive}.sha256"
  if [[ -f "$checksum" ]]; then
    sha256sum -c "$checksum"
  else
    warn "No checksum sidecar found: $checksum"
    log "gzip and archive-path validation passed."
  fi
}

create_manifest() {
  local archive="$1"
  local manifest="${archive%.tar.gz}.manifest.txt"

  {
    printf 'Created: %s\n' "$(date -Is)"
    printf 'Archive: %s\n\n' "$archive"
    printf 'PicoClaw version:\n'
    "$PICO_BINARY" version 2>/dev/null | tail -n 8 || true
    printf '\nService state after backup: '
    systemctl is-active "$SERVICE_NAME" || true
    printf '\nArchive SHA-256:\n'
    cat "${archive}.sha256"
  } > "$manifest"

  chmod 600 "$manifest"
}

create_backup() {
  local label
  label="$(sanitize_label "${1:-manual}")"

  check_live_paths
  install -d -m 700 "$BACKUP_DIR"

  local stamp archive partial checksum
  stamp="$(date +%Y%m%d-%H%M%S)"
  archive="${BACKUP_DIR}/picoclaw-${stamp}-${label}.tar.gz"
  partial="${archive}.partial"
  checksum="${archive}.sha256"

  local -a members=()
  archive_member_list members

  stop_service_if_active

  log "Creating archive: $archive"
  if ! tar -C / -czf "$partial" "${members[@]}"; then
    warn "Archive creation failed."
    rm -f -- "$partial"
    restore_service_state || warn "Could not restore service state automatically."
    die "Backup failed."
  fi

  if ! gzip -t "$partial"; then
    warn "Archive integrity check failed."
    rm -f -- "$partial"
    restore_service_state || warn "Could not restore service state automatically."
    die "Backup failed."
  fi

  mv -- "$partial" "$archive"
  chmod 600 "$archive"
  sha256sum "$archive" > "$checksum"
  chmod 600 "$checksum"

  restore_service_state || warn "Could not restore service state automatically."

  create_manifest "$archive"
  LAST_ARCHIVE="$archive"

  log "Backup integrity: OK"
  printf '\nCreated files:\n'
  ls -lh "$archive" "$checksum" "${archive%.tar.gz}.manifest.txt"
}

list_backups() {
  [[ -d "$BACKUP_DIR" ]] || {
    log "No backup directory exists: $BACKUP_DIR"
    return 0
  }

  local archives=("${BACKUP_DIR}"/*.tar.gz)
  if [[ "${#archives[@]}" -eq 0 ]]; then
    log "No archives found in: $BACKUP_DIR"
    return 0
  fi

  printf '%-20s %-12s %s\n' 'DATE' 'SIZE' 'ARCHIVE'
  for archive in "${archives[@]}"; do
    printf '%-20s %-12s %s\n' \
      "$(date -r "$archive" '+%Y-%m-%d %H:%M:%S')" \
      "$(du -h "$archive" | awk '{print $1}')" \
      "$archive"
  done
}

restore_archive() {
  local archive="$1"
  validate_archive_paths "$archive"

  local checksum="${archive}.sha256"
  if [[ -f "$checksum" ]]; then
    sha256sum -c "$checksum" || die "Checksum validation failed. Restore cancelled."
  else
    warn "No SHA-256 sidecar found; continuing after gzip/path validation only."
  fi

  log "Creating a pre-restore backup first..."
  create_backup "pre-restore"
  local pre_restore_archive="$LAST_ARCHIVE"

  stop_service_if_active

  log "Restoring archive into /: $archive"
  if ! tar -C / -xzf "$archive" --numeric-owner; then
    warn "Restore failed. Attempting automatic rollback from: $pre_restore_archive"
    tar -C / -xzf "$pre_restore_archive" --numeric-owner || warn "Automatic rollback also failed."
    systemctl daemon-reload || true
    restore_service_state || true
    die "Restore failed."
  fi

  systemctl daemon-reload

  # Ensure expected ownership if an archive came from the same host but was unpacked differently.
  chown -R "${PICO_USER}:${PICO_USER}" "${PICO_HOME}/.picoclaw" "${PICO_HOME}/workspace"
  chmod 700 "${PICO_HOME}/workspace"
  chmod 600 "${PICO_HOME}/.picoclaw/config.json" "${PICO_HOME}/.picoclaw/.security.yml" 2>/dev/null || true
  chmod 600 "${PICO_HOME}/workspace/AGENT.md" 2>/dev/null || true

  restore_service_state || die "Archive restored, but ${SERVICE_NAME} could not be restarted."

  log "Restore completed."
  printf '\nPost-restore checks:\n'
  systemctl is-active "$SERVICE_NAME" || true
  "$PICO_BINARY" version 2>/dev/null | tail -n 8 || true
}

parse_global_options() {
  while [[ "${1:-}" == "--backup-dir" ]]; do
    [[ $# -ge 2 ]] || die "--backup-dir requires a path"
    BACKUP_DIR="$2"
    shift 2
  done
  REMAINING_ARGS=("$@")
}

main() {
  require_root
  check_dependencies

  [[ $# -ge 1 ]] || { usage; exit 1; }

  parse_global_options "$@"
  set -- "${REMAINING_ARGS[@]}"

  local command="$1"
  shift || true

  case "$command" in
    backup)
      [[ $# -le 1 ]] || die "Usage: backup [label]"
      create_backup "${1:-manual}"
      ;;
    list)
      [[ $# -eq 0 ]] || die "Usage: list"
      list_backups
      ;;
    verify)
      [[ $# -eq 1 ]] || die "Usage: verify <archive.tar.gz>"
      verify_archive "$1"
      ;;
    restore)
      [[ $# -ge 1 ]] || die "Usage: restore <archive.tar.gz> --yes"
      local archive="$1"
      shift
      [[ "${1:-}" == "--yes" && $# -eq 1 ]] || die "Restore requires explicit confirmation: restore <archive.tar.gz> --yes"
      restore_archive "$archive"
      ;;
    help|--help|-help|-h)
      usage
      ;;
    *)
      die "Unknown command: $command"
      ;;
  esac
}

main "$@"

该脚本支持:

  • 一致性备份;
  • 列出备份;
  • gzip 与 SHA-256 校验;
  • 还原前自动创建 pre-restore 备份;
  • 还原失败时尝试自动回滚;
  • 保留服务原本的运行/停止状态。

上传脚本后先处理换行符

如果脚本从 Windows 上传到 Linux,请确保使用 LF 换行且无 BOM。可在服务器执行:

sed -i 's/\r$//' /root/picoclaw-backup.sh
chmod 700 /root/picoclaw-backup.sh
bash -n /root/picoclaw-backup.sh

使用示例

# 查看帮助
/root/picoclaw-backup.sh help

# 创建备份
/root/picoclaw-backup.sh backup manual

# 升级前备份
/root/picoclaw-backup.sh backup before-upgrade

# 列出备份
/root/picoclaw-backup.sh list

# 校验备份
/root/picoclaw-backup.sh verify \
  /root/picoclaw-backups/picoclaw-YYYYMMDD-HHMMSS-manual.tar.gz

# 还原备份(必须显式确认)
/root/picoclaw-backup.sh restore \
  /root/picoclaw-backups/picoclaw-YYYYMMDD-HHMMSS-manual.tar.gz \
  --yes

[!CAUTION] 备份包含模型 API Key、微信登录凭证、会话和白名单。不要提交到 Git、不要上传到公开网盘、不要发送给他人。建议通过 Tailscale、ZeroTier 或 SSH 下载到本地加密保存。


十六、升级 PicoClaw 的建议

不要在低配生产 VPS 上自动追逐最新版本。推荐流程:

  1. 确认近期稳定运行;
  2. 创建 before-upgrade 备份;
  3. 下载官方 Release 预编译二进制;
  4. 先保留旧二进制;
  5. 替换二进制;
  6. systemctl restart picoclaw.service;
  7. 微信发送一条简单测试消息;
  8. 若失败,用备份脚本还原,或恢复旧二进制。

十七、上线验收清单

[ ] PicoClaw 以 picoclaw 普通用户运行
[ ] picoclaw 不属于 sudo 组
[ ] config.json 与 .security.yml 权限为 600
[ ] workspace 权限为 700
[ ] 微信 allow_from 只包含自己的 @im.wechat ID
[ ] Gateway 仅监听 127.0.0.1 / ::1
[ ] 没有开放 18790 公网端口
[ ] max_parallel_turns 为 1
[ ] restrict_to_workspace 为 true
[ ] C 模式前 allow_remote 为 false
[ ] C 模式后已用 pwd 验证工作区 Shell
[ ] cron list 没有任务
[ ] systemd MemoryHigh=400M、MemoryMax=480M、CPUQuota=95%
[ ] 已创建至少一份离线备份
[ ] API Key 与微信凭证未出现在博客、Git、截图或聊天记录中

结语

对于 1C1G5G 的 VPS,PicoClaw 的优势在于:单二进制、云端模型、原生微信 iLink、低常驻资源与清晰的 systemd 管理。只要坚持“单用户白名单 + 普通用户 + 工作区限制 + 无公网 Gateway + 有备份”的原则,它可以成为一个足够轻量且可长期维护的个人微信 Agent。

0

Debian 13 IPv6-only 服务器初始化与组网部署手册【init-debian13-ipv6-server.sh】

Previous

文章目录

Recent Posts

  • 在低配VPS 上安全部署 PicoClaw:个人微信 iLink、受限 Shell、systemd 与备份还原、1C 1G 5G配置[picoclaw-debian13-wechat-ilink-deployment]
  • Debian 13 IPv6-only 服务器初始化与组网部署手册【init-debian13-ipv6-server.sh】
  • Linux 系统从大容量硬盘迁移到小容量硬盘:Rescue 模式下的通用方案[linux-disk-migration-rescue-guide]
  • Hermes Agent(养马)部署和全套过程方案
  • 【大模型API】智谱(GLM)注册全过程

Related posts

DBeaver连接MySQL数据库报错、备份还原数据库的字符集报错。

DBeaver连接MySQL数据库报错、备份还原数据库的字符集报错。

October 11, 2024
7,828 1
【系统镜像】Windows Server 2012 R2 全虚拟化驱动 数据中心 简体中文版 纯净完整版 DD包 v4.29

【系统镜像】Windows Server 2012 R2 全虚拟化驱动 数据中心 简体中文版 纯净完整版 DD包 v4.29

April 10, 2021
376,995 0
WordPress在Docker容器中的部署迁移相关的常见问题

WordPress在Docker容器中的部署迁移相关的常见问题

October 11, 2024
40,951 2
【内测邀请】代号JuCent,首款Golang开发,国产全平台即时通讯软件。可自行搭建托管的即时通讯软件(支持Linux/Windows/Web网页端/Android/iOS/MacOS等)

【内测邀请】代号JuCent,首款Golang开发,国产全平台即时通讯软件。可自行搭建托管的即时通讯软件(支持Linux/Windows/Web网页端/Android/iOS/MacOS等)

September 15, 2020
129,566 0

简介 CXT - Enjoy Life

CXT - Enjoy Life | 生活、技术、交友、分享 - 自天佑之,吉无不利

网站导航

首页 特色专题 一键网络重装系统 - 魔改版(适用于Linux / Windows) 精英IDC计划 - 千万IDC计划(从入门到跑路) CXT裸机系统部署平台(自定义安装任意系统) OpenWRT-Virtualization-Servers 分类目录 站点公告 技术分享 生活感悟 更多(More) 浏览记录(Historical-Record) 支付捐赠(Payment-Donation) 隐私政策(Privacy-Policy) 服务状态(Server-Status) 友情链接(Link) 联系我们(Contact-US) 关于我们(About-Me)

友情链接

CXT | 自天佑之 吉无不利 润隍科技
Copyright © 2026 CXT - Enjoy Life | 生活、技术、交友、分享. Designed by nicetheme.
  • 首页
  • 特色专题
    • 一键网络重装系统 - 魔改版(适用于Linux / Windows)
    • 精英IDC计划 - 千万IDC计划(从入门到跑路)
    • CXT裸机系统部署平台(自定义安装任意系统)
    • OpenWRT-Virtualization-Servers
  • 分类目录
    • 站点公告
    • 技术分享
    • 生活感悟
  • 更多(More)
    • 浏览记录(Historical-Record)
    • 支付捐赠(Payment-Donation)
    • 隐私政策(Privacy-Policy)
    • 服务状态(Server-Status)
    • 友情链接(Link)
    • 联系我们(Contact-US)
    • 关于我们(About-Me)
  • Linux
  • Server
  • PVE
  • Proxmox-VE
  • Windows
  • 系统镜像
  • Proxmox
  • ISO
  • DD
  • CentOS

CXT

Administrator
91
Posts
0
Comments
127
Likes