chore: back up Pi environment

This commit is contained in:
2026-07-14 00:26:08 +05:00
commit 10dcbedcc2
75 changed files with 1410 additions and 0 deletions

215
pi-env-backup.sh Executable file
View File

@@ -0,0 +1,215 @@
#!/usr/bin/env bash
set -Eeuo pipefail
# Portable backup/restore for Pi Coding Agent.
# Usage:
# ./pi-env-backup.sh backup [destination-dir]
# ./pi-env-backup.sh list <archive>
# ./pi-env-backup.sh restore <archive>
PROGRAM=${0##*/}
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
log() { printf '[pi-backup] %s\n' "$*"; }
fail() { printf '[pi-backup] ERROR: %s\n' "$*" >&2; exit 1; }
need() { command -v "$1" >/dev/null 2>&1 || fail "Не найдено: $1"; }
usage() {
cat <<'EOF'
Portable Pi Coding Agent backup
Создать бэкап:
./pi-env-backup.sh backup [папка-для-архива]
Посмотреть содержимое:
./pi-env-backup.sh list <pi-backup-*.tar.zst|*.tar.gz>
Восстановить на ноутбуке/WSL:
./pi-env-backup.sh restore <архив>
Переменные:
PI_BACKUP_INCLUDE_SESSIONS=1 включить историю Pi-сессий (по умолчанию 0)
PI_BACKUP_INCLUDE_CONTEXT=1 включить context-mode index/cache (по умолчанию 0)
PI_BACKUP_INCLUDE_AYU=1 включить ayu/checkpoints (по умолчанию 0; обычно не нужно)
PI_BACKUP_ALLOW_RUNNING=1 не предупреждать о запущенном Pi
EOF
}
running_processes() {
pgrep -af '(^|/)(pi|pi-coding-agent)( |$)' 2>/dev/null || true
}
make_manifest() {
local file=$1
{
echo "created_at=$(date -Is)"
echo "source_user=$USER"
echo "source_home=$HOME"
echo "hostname=$(hostname)"
echo "uname=$(uname -srmo)"
echo "pi_version=$(pi --version 2>/dev/null || echo unavailable)"
echo "node_version=$(node --version 2>/dev/null || echo unavailable)"
echo "npm_version=$(npm --version 2>/dev/null || echo unavailable)"
echo "shell=${SHELL:-unknown}"
echo
echo '[installed-pi-packages]'
pi list 2>/dev/null || true
echo
echo '[external-tools]'
for tool in pi node npm npx bun pnpm python3 pipx git gh jj rg fd docker zstd; do
if command -v "$tool" >/dev/null 2>&1; then
printf '%s: ' "$tool"
case "$tool" in
pi) pi --version 2>&1 || true ;;
node|bun|pnpm|python3|git|gh|jj|rg|fd|docker|zstd) "$tool" --version 2>&1 || true ;;
npm|npx|pipx) "$tool" --version 2>&1 || true ;;
esac
fi
done
echo
echo '[paths]'
find "$HOME/.pi/agent" "$HOME/.agents" -maxdepth 2 -type d 2>/dev/null | sort || true
} > "$file"
}
backup() {
local dest=${1:-$PWD}
mkdir -p "$dest"
need tar
need pi
if [[ "${PI_BACKUP_ALLOW_RUNNING:-0}" != 1 ]] && [[ -n "$(running_processes)" ]]; then
fail "Pi запущен. Закрой Pi и повтори. Для принудительного режима: PI_BACKUP_ALLOW_RUNNING=1"
fi
local stamp archive ext compressor tmp root
stamp=$(date +%Y%m%d-%H%M%S)
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' RETURN
root="$tmp/pi-backup"
mkdir -p "$root/meta"
make_manifest "$root/meta/MANIFEST.txt"
cp "$SELF_DIR/pi-env-backup.sh" "$root/meta/pi-env-backup.sh"
chmod 755 "$root/meta/pi-env-backup.sh"
cat > "$root/meta/RESTORE.md" <<'EOF'
# Pi Coding Agent backup
Этот архив содержит пользовательское состояние Pi Coding Agent:
- `~/.pi/agent/settings.json` — настройки, модели и список packages;
- `~/.pi/agent/auth.json` — авторизация/API keys;
- `~/.pi/agent/mcp.json` и `mcp-oauth/` — MCP;
- `~/.pi/agent/extensions/`, `skills/`, `npm/`, `git/` — плагины и пакеты;
- `~/.pi/agent/AGENTS.md`, `keybindings.json`, `trust.json`;
- `~/.pi/agent/sessions/` — история Pi;
- `~/.pi/agent/pi-hermes-memory/`, `projects-memory/` — память;
- `~/.pi/context-mode/` — локальный индекс context-mode;
- `~/.agents/` — внешние пользовательские skills.
## Восстановление
Внутри WSL установите Node.js/npm, затем выполните из распакованного архива:
```bash
bash meta/pi-env-backup.sh restore pi-backup.tar.zst
```
Или передайте архив и этот скрипт на ноутбук:
```bash
bash pi-env-backup.sh restore pi-backup.tar.zst
```
Скрипт НЕ переносит бинарник Pi и внешние системные программы. После восстановления
он переустанавливает Pi через npm и проверяет состояние. Авторизация из `auth.json`
переносится, но некоторые OAuth-провайдеры могут попросить повторный `/login`.
EOF
# Store paths relative to $HOME. Never archive unrelated parts of the home directory.
local -a paths=(.pi/agent .agents)
[[ -d "$HOME/.pi/local-docs" ]] && paths+=(.pi/local-docs)
[[ "${PI_BACKUP_INCLUDE_CONTEXT:-0}" == 1 && -d "$HOME/.pi/context-mode" ]] && paths+=(.pi/context-mode)
if [[ "${PI_BACKUP_INCLUDE_SESSIONS:-0}" != 1 ]]; then
# handled after copying by tar exclusions
log 'История сессий будет исключена (PI_BACKUP_INCLUDE_SESSIONS=0)'
fi
if command -v zstd >/dev/null 2>&1; then
archive="$dest/pi-backup-$stamp.tar.zst"
compressor=(zstd -T0 -19 -q)
else
archive="$dest/pi-backup-$stamp.tar.gz"
compressor=(gzip -9)
log 'zstd не найден; используется gzip. Для меньшего архива установи zstd.'
fi
log "Архивирую: ${paths[*]}"
# Build one tar with relative paths only, then compress it. No absolute paths leak.
# По умолчанию не переносим историю, context-mode cache и ayu checkpoints.
# Они не нужны для работы плагинов, но могут занимать десятки гигабайт.
tar_args=(--exclude=.pi/agent/.locks --exclude=.pi/agent/*.lock)
[[ "${PI_BACKUP_INCLUDE_SESSIONS:-0}" != 1 ]] && tar_args+=(--exclude=.pi/agent/sessions)
[[ "${PI_BACKUP_INCLUDE_CONTEXT:-0}" != 1 ]] && tar_args+=(--exclude=.pi/context-mode)
[[ "${PI_BACKUP_INCLUDE_AYU:-0}" != 1 ]] && tar_args+=(--exclude=.pi/agent/ayu)
tar -C "$root" -cf "$tmp/all.tar" meta
tar "${tar_args[@]}" -C "$HOME" -rf "$tmp/all.tar" "${paths[@]}" 2>/dev/null || true
if [[ "$archive" == *.zst ]]; then
zstd -T0 -19 -q "$tmp/all.tar" -o "$archive"
else
gzip -9 -c "$tmp/all.tar" > "$archive"
fi
chmod 600 "$archive"
sha256sum "$archive" | tee "$archive.sha256"
log "Готово: $archive"
log "Размер: $(du -h "$archive" | awk '{print $1}')"
log 'Архив содержит секреты. Передавай его только по защищённому каналу.'
}
list_archive() {
local archive=$1
[[ -f "$archive" ]] || fail "Архив не найден: $archive"
if [[ "$archive" == *.zst ]]; then
need zstd; zstd -dc "$archive" | tar -tvf -
else
tar -tzvf "$archive"
fi
}
restore() {
local archive=$1
[[ -f "$archive" ]] || fail "Архив не найден: $archive"
need tar
if [[ "$archive" == *.zst ]]; then need zstd; fi
if [[ -e "$HOME/.pi/agent/settings.json" ]]; then
log 'Обнаружен существующий ~/.pi/agent. Будет создан backup перед заменой.'
mv "$HOME/.pi/agent" "$HOME/.pi/agent.before-restore-$(date +%Y%m%d-%H%M%S)"
fi
mkdir -p "$HOME"
log 'Распаковываю состояние Pi в домашний каталог...'
if [[ "$archive" == *.zst ]]; then
zstd -dc "$archive" | tar -xpf - -C "$HOME"
else
tar -xpf "$archive" -C "$HOME"
fi
chmod 700 "$HOME/.pi" "$HOME/.pi/agent" "$HOME/.agents" 2>/dev/null || true
chmod 600 "$HOME/.pi/agent/auth.json" "$HOME/.pi/agent/mcp.json" 2>/dev/null || true
if ! command -v pi >/dev/null 2>&1; then
need npm
log 'Pi не найден; устанавливаю текущую версию через npm...'
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
fi
log "Pi: $(pi --version 2>/dev/null || echo 'не удалось определить версию')"
log 'Проверка настроек и установленных packages:'
pi list 2>&1 || true
log 'Восстановление завершено. Если OAuth не подхватился, запусти Pi и выполни /login.'
}
case "${1:-}" in
backup) backup "${2:-$PWD}" ;;
list) [[ $# -ge 2 ]] || { usage; exit 2; }; list_archive "$2" ;;
restore) [[ $# -ge 2 ]] || { usage; exit 2; }; restore "$2" ;;
*) usage; exit 2 ;;
esac