Files
main/pi-env-backup.sh
2026-07-14 06:07:16 +05:00

216 lines
8.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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