eggpool 0.1.7__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {eggpool-0.1.7 → eggpool-0.2.0}/AGENTS.md +3 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/CHANGELOG.md +23 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/PKG-INFO +26 -1
- {eggpool-0.1.7 → eggpool-0.2.0}/README.md +25 -0
- eggpool-0.2.0/docs/backup-restore.md +221 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/docs/deployment.md +3 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/pyproject.toml +1 -1
- {eggpool-0.1.7 → eggpool-0.2.0}/scripts/install.sh +1 -1
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/cli.py +284 -1
- eggpool-0.2.0/src/eggpool/lifecycle/__init__.py +75 -0
- eggpool-0.2.0/src/eggpool/lifecycle/backup.py +391 -0
- eggpool-0.2.0/src/eggpool/lifecycle/uninstall.py +522 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/uv.lock +1 -1
- eggpool-0.1.7/docs/backup-restore.md +0 -137
- {eggpool-0.1.7 → eggpool-0.2.0}/.env.example +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/.github/workflows/ci.yml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/.github/workflows/release.yml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/.gitignore +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/LICENSE +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/architecture/README.md +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/config-examples/claude-code.env +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/config-examples/opencode.jsonc +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/config.example.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/deploy/eggpool-logrotate.conf +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/deploy/eggpool.service +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/deploy/env.example +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/docs/filesystem-layout.md +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/docs/firewall.md +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/docs/model-limits.md +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/docs/providers.md +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/docs/proxy.md +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/docs/raspberry-pi.md +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/scripts/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/scripts/check_database.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/scripts/install_prompt.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/scripts/smoke_test.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/scripts/verify_upstream_auth.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/__main__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/_share/.env.example +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/_share/config.example.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/accounts/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/accounts/registry.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/accounts/state.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/chat_completions.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/errors.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/messages.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/models.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/proxy_request.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/stats.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/app.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/auth.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/background/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/background/cleanup.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/cache.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/fetcher.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/limits.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/normalizer.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/pricing.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/protocols.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/service.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/config.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/constants.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/_resources.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/escape.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/render.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/routes.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/static/chart.umd.min.js +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/static/dashboard.css +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/static/favicon.svg +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/theme.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Booberry.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Catppuccin Latte.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Catppuccin Macchiato.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Catppuccin Mocha.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Cyber Red.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Cyberpunk.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Dark Green.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Discord (80_ Saturation).toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Discord.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Dracula.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Ferra Light.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Flexor Dark.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Gruvbox.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Halcyon Dark.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/IntelliJ Light.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Kanagawa.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Macaw Dark.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Macaw Light.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Matrix.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Noctis Lilac.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Nord.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Nostromo Terminal.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/One Dark.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Oxocarbon.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Rose Pine Dawn.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Rose Pine Moon.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Rose Pine.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Solarized Dark.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Sonokai.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Tokyo Night Storm.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/VESPER.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Zenburn.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/acton.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/bam.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/base16-atelier-forest-light.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/berlin.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/black but with important highlights.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/broc.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/cork.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/ferra.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/forest.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/lisbon.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/midnight.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/oslo.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/plum.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/portland.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/sunset.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/tofino.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/vanimo.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/vik.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/connection.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/migrations.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/repositories.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0001_initial.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0002_indexes.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0003_request_attempts.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0004_integration_hardening.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0005_price_microdollars.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0006_correct_price_microdollars.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0007_price_cache_rates.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0008_proxy_request_identity.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0009_model_protocol_source.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0010_health_probe.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0011_model_resolution_status.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0013_request_attempts_account_id_index.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0014_bandwidth_tracking.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0015_multi_provider.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0016_requests_provider_id.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0017_price_snapshots_provider_id.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0018_provider_pings.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0019_client_ip.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0020_performance_indexes.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0021_provider_model_metadata.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0022_dashboard_indexes.sql +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/checksums.json +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/deploy/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/errors.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/health/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/health/circuit_breaker.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/health/health_manager.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/integrations/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/integrations/opencode.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/logging.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/models/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/models/api.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/models/config.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/models/database.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/models/domain.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/onboard.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/_templates.toml +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/auth.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/client_pool.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/connect.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/contract.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/pproxy_transport.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/proxy/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/proxy/client.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/proxy/sse_observer.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/proxy/usage.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/py.typed +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/quota/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/quota/estimation.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/quota/reservation.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/quota/scorer.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/attempt_finalizer.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/body.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/coordinator.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/finalizer.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/limits.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/retry/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/retry/classification.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/routing/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/routing/eligibility.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/routing/provider.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/routing/router.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/security/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/security/redaction.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/stats/__init__.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/stats/queries.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/stats/service.py +0 -0
- {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/toml_edit.py +0 -0
|
@@ -106,6 +106,9 @@ Use the hierarchy in `errors.py`. Chain exceptions with `raise ... from err` or
|
|
|
106
106
|
| `eggpool models refresh` | Refresh model catalog from upstream |
|
|
107
107
|
| `eggpool configsetup opencode` | Print OpenCode provider config JSON with model limits |
|
|
108
108
|
| `eggpool db vacuum` | Reclaim SQLite space |
|
|
109
|
+
| `eggpool backup` | Create a timestamped `.zip` backup of config, `.env`, and database |
|
|
110
|
+
| `eggpool recover [path]` | Restore from a backup archive (interactive menu if no path) |
|
|
111
|
+
| `eggpool uninstall` | Remove binary, config, database, and shell PATH entries |
|
|
109
112
|
|
|
110
113
|
All commands accept `--config /path/to/config.toml` (defaults to `config.toml`).
|
|
111
114
|
Running `eggpool` with no arguments prints the help message.
|
|
@@ -5,6 +5,29 @@ All notable changes to EggPool are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2026-06-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `eggpool backup` CLI command that bundles `config.toml`, `.env`, and the
|
|
15
|
+
SQLite database (with `-wal`/`-shm`) into a timestamped `.zip` archive.
|
|
16
|
+
Default location is `~/backups/eggpool/`; override with `--output-dir`.
|
|
17
|
+
Honor `XDG_BACKUP_HOME` for the default.
|
|
18
|
+
- `eggpool recover [path]` CLI command that restores a backup archive. With
|
|
19
|
+
no path, opens an interactive `TerminalMenu` selector over the default
|
|
20
|
+
backup directory. Stages restored files alongside the current ones and
|
|
21
|
+
rolls back on failure.
|
|
22
|
+
- `eggpool uninstall` CLI command that detects the install method
|
|
23
|
+
(`pipx` / `uv tool` / `source` / `manual`) and removes the binary,
|
|
24
|
+
active config, `.env`, database, and shell-rc entries. Supports
|
|
25
|
+
`--yes`, `--keep-config`, `--keep-data`, and `--keep-path`. Prints
|
|
26
|
+
instructions for manual removal of systemd, logrotate, and cron
|
|
27
|
+
artifacts (these are never removed automatically).
|
|
28
|
+
- New `eggpool.lifecycle` module (`backup`, `uninstall`, `__init__`)
|
|
29
|
+
housing the lifecycle helpers.
|
|
30
|
+
|
|
8
31
|
## [0.1.7] - 2026-06-24
|
|
9
32
|
|
|
10
33
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eggpool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A lightweight proxy that aggregates multiple LLM provider accounts behind one OpenAI-compatible endpoint
|
|
5
5
|
Project-URL: Homepage, https://github.com/eggstack/eggpool
|
|
6
6
|
Project-URL: Repository, https://github.com/eggstack/eggpool
|
|
@@ -112,6 +112,28 @@ export PATH="$HOME/.local/bin:$PATH"
|
|
|
112
112
|
|
|
113
113
|
Run `eggpool deploy` for options on running the server on startup. See docs/deployment.md for more info and configs.
|
|
114
114
|
|
|
115
|
+
### Backup and uninstall
|
|
116
|
+
|
|
117
|
+
EggPool ships lifecycle commands that mirror the install flow:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Backup config + .env + database to ~/backups/eggpool/
|
|
121
|
+
eggpool backup
|
|
122
|
+
|
|
123
|
+
# Restore from a specific archive (or omit the path for an interactive menu)
|
|
124
|
+
eggpool recover ~/backups/eggpool/eggpool-backup-20260624-120000.zip
|
|
125
|
+
|
|
126
|
+
# Uninstall: detects pipx / uv tool / source / manual and cleans up
|
|
127
|
+
eggpool uninstall --yes
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The uninstall command removes the binary, active config, `.env`,
|
|
131
|
+
database, and `eggpool` shell-rc entries. It prints manual commands
|
|
132
|
+
for removing systemd, logrotate, and cron artifacts but never removes
|
|
133
|
+
them automatically. Existing backups under `~/backups/eggpool/` are
|
|
134
|
+
left in place. See [docs/backup-restore.md](docs/backup-restore.md)
|
|
135
|
+
for the full backup/restore workflow.
|
|
136
|
+
|
|
115
137
|
### Option 3: Manual install
|
|
116
138
|
|
|
117
139
|
```bash
|
|
@@ -188,6 +210,9 @@ uv run eggpool connect list
|
|
|
188
210
|
| `eggpool deploy logrotate` | Print the logrotate config + install instructions |
|
|
189
211
|
| `eggpool deploy cron` | Print the daily-backup cron entry + install instructions |
|
|
190
212
|
| `eggpool deploy all` | Print every deployment snippet in sequence |
|
|
213
|
+
| `eggpool backup` | Create a timestamped `.zip` backup (default `~/backups/eggpool/`) |
|
|
214
|
+
| `eggpool recover [path]` | Restore from a backup archive (interactive if no path) |
|
|
215
|
+
| `eggpool uninstall` | Remove binary, config, database, and shell PATH entries |
|
|
191
216
|
|
|
192
217
|
All commands accept `--config /path/to/config.toml` (defaults to `config.toml`).
|
|
193
218
|
Running `eggpool` with no arguments prints the help message.
|
|
@@ -68,6 +68,28 @@ export PATH="$HOME/.local/bin:$PATH"
|
|
|
68
68
|
|
|
69
69
|
Run `eggpool deploy` for options on running the server on startup. See docs/deployment.md for more info and configs.
|
|
70
70
|
|
|
71
|
+
### Backup and uninstall
|
|
72
|
+
|
|
73
|
+
EggPool ships lifecycle commands that mirror the install flow:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Backup config + .env + database to ~/backups/eggpool/
|
|
77
|
+
eggpool backup
|
|
78
|
+
|
|
79
|
+
# Restore from a specific archive (or omit the path for an interactive menu)
|
|
80
|
+
eggpool recover ~/backups/eggpool/eggpool-backup-20260624-120000.zip
|
|
81
|
+
|
|
82
|
+
# Uninstall: detects pipx / uv tool / source / manual and cleans up
|
|
83
|
+
eggpool uninstall --yes
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The uninstall command removes the binary, active config, `.env`,
|
|
87
|
+
database, and `eggpool` shell-rc entries. It prints manual commands
|
|
88
|
+
for removing systemd, logrotate, and cron artifacts but never removes
|
|
89
|
+
them automatically. Existing backups under `~/backups/eggpool/` are
|
|
90
|
+
left in place. See [docs/backup-restore.md](docs/backup-restore.md)
|
|
91
|
+
for the full backup/restore workflow.
|
|
92
|
+
|
|
71
93
|
### Option 3: Manual install
|
|
72
94
|
|
|
73
95
|
```bash
|
|
@@ -144,6 +166,9 @@ uv run eggpool connect list
|
|
|
144
166
|
| `eggpool deploy logrotate` | Print the logrotate config + install instructions |
|
|
145
167
|
| `eggpool deploy cron` | Print the daily-backup cron entry + install instructions |
|
|
146
168
|
| `eggpool deploy all` | Print every deployment snippet in sequence |
|
|
169
|
+
| `eggpool backup` | Create a timestamped `.zip` backup (default `~/backups/eggpool/`) |
|
|
170
|
+
| `eggpool recover [path]` | Restore from a backup archive (interactive if no path) |
|
|
171
|
+
| `eggpool uninstall` | Remove binary, config, database, and shell PATH entries |
|
|
147
172
|
|
|
148
173
|
All commands accept `--config /path/to/config.toml` (defaults to `config.toml`).
|
|
149
174
|
Running `eggpool` with no arguments prints the help message.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Backup and Restore
|
|
2
|
+
|
|
3
|
+
## What to Back Up
|
|
4
|
+
|
|
5
|
+
| File | Description | Frequency |
|
|
6
|
+
|------|-------------|-----------|
|
|
7
|
+
| `/etc/eggpool/config.toml` | Configuration | On change |
|
|
8
|
+
| `/etc/eggpool/env` | API keys | On change |
|
|
9
|
+
| `/var/lib/eggpool/usage.sqlite3*` | Database | Daily |
|
|
10
|
+
|
|
11
|
+
## Backup
|
|
12
|
+
|
|
13
|
+
### Using the CLI (recommended)
|
|
14
|
+
|
|
15
|
+
EggPool ships with `eggpool backup` that bundles `config.toml`, `.env`,
|
|
16
|
+
and the SQLite database (plus `-wal`/`-shm` if present) into a single
|
|
17
|
+
timestamped `.zip` archive.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Default location: ~/backups/eggpool/
|
|
21
|
+
eggpool backup
|
|
22
|
+
|
|
23
|
+
# Custom location
|
|
24
|
+
eggpool backup --output-dir /var/backups/eggpool
|
|
25
|
+
|
|
26
|
+
# Override config path
|
|
27
|
+
eggpool --config /etc/eggpool/config.toml backup
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Output filenames follow `eggpool-backup-YYYYMMDD-HHMMSS.zip`. The
|
|
31
|
+
active config file is used to discover the database path, so no extra
|
|
32
|
+
flags are required in the common case. The archive layout is:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
eggpool-backup-20260624-120000.zip
|
|
36
|
+
├── META (format version, install method, member list)
|
|
37
|
+
├── config.toml
|
|
38
|
+
├── usage.sqlite3
|
|
39
|
+
└── .env (if present)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Manual backup
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Create backup directory
|
|
46
|
+
BACKUP_DIR="/var/backups/eggpool/$(date +%Y%m%d-%H%M%S)"
|
|
47
|
+
sudo mkdir -p "$BACKUP_DIR"
|
|
48
|
+
|
|
49
|
+
# Backup configuration
|
|
50
|
+
sudo cp /etc/eggpool/config.toml "$BACKUP_DIR/"
|
|
51
|
+
sudo cp /etc/eggpool/env "$BACKUP_DIR/"
|
|
52
|
+
|
|
53
|
+
# Backup database (using SQLite backup for consistency)
|
|
54
|
+
sudo -u eggpool sqlite3 /var/lib/eggpool/usage.sqlite3 ".backup '$BACKUP_DIR/usage.sqlite3'"
|
|
55
|
+
|
|
56
|
+
# Create archive
|
|
57
|
+
sudo tar czf "$BACKUP_DIR.tar.gz" -C /var/backups/eggpool "$(date +%Y%m%d-%H%M%S)"
|
|
58
|
+
sudo rm -rf "$BACKUP_DIR"
|
|
59
|
+
|
|
60
|
+
echo "Backup saved to $BACKUP_DIR.tar.gz"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Automated backup (cron)
|
|
64
|
+
|
|
65
|
+
Create `/etc/cron.d/eggpool-backup`:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
# Backup eggpool database daily at 2 AM
|
|
69
|
+
0 2 * * * root /usr/local/bin/eggpool-backup
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Create `/usr/local/bin/eggpool-backup`:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
#!/bin/bash
|
|
76
|
+
set -euo pipefail
|
|
77
|
+
|
|
78
|
+
BACKUP_DIR="/var/backups/eggpool"
|
|
79
|
+
KEEP_DAYS=30
|
|
80
|
+
|
|
81
|
+
mkdir -p "$BACKUP_DIR"
|
|
82
|
+
|
|
83
|
+
/usr/local/bin/eggpool backup --output-dir "$BACKUP_DIR"
|
|
84
|
+
|
|
85
|
+
find "$BACKUP_DIR" -name "eggpool-backup-*.zip" -mtime +$KEEP_DAYS -delete
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Make it executable:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
sudo chmod +x /usr/local/bin/eggpool-backup
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Restore
|
|
95
|
+
|
|
96
|
+
### Using the CLI (recommended)
|
|
97
|
+
|
|
98
|
+
`eggpool recover` reads an archive produced by `eggpool backup` and
|
|
99
|
+
restores its contents to the locations recorded in `META`.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Interactive: pick from ~/backups/eggpool/
|
|
103
|
+
eggpool recover
|
|
104
|
+
|
|
105
|
+
# Explicit path
|
|
106
|
+
eggpool recover ~/backups/eggpool/eggpool-backup-20260624-120000.zip
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The CLI stops the running server (if any), stages the restored files
|
|
110
|
+
alongside the current ones, swaps them into place, and restarts the
|
|
111
|
+
server on success. If any restore step fails, the original files are
|
|
112
|
+
preserved under `<data-dir>/rollback-<timestamp>/` and the server is
|
|
113
|
+
left stopped so the operator can intervene.
|
|
114
|
+
|
|
115
|
+
### Manual restore
|
|
116
|
+
|
|
117
|
+
#### Stop the service
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
sudo systemctl stop eggpool
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### Restore configuration
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
sudo cp backup/config.toml /etc/eggpool/config.toml
|
|
127
|
+
sudo cp backup/env /etc/eggpool/env
|
|
128
|
+
sudo chown root:eggpool /etc/eggpool/config.toml /etc/eggpool/env
|
|
129
|
+
sudo chmod 640 /etc/eggpool/config.toml /etc/eggpool/env
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Restore database
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Remove current database
|
|
136
|
+
sudo rm -f /var/lib/eggpool/usage.sqlite3*
|
|
137
|
+
|
|
138
|
+
# Restore from backup
|
|
139
|
+
sudo cp backup/usage.sqlite3 /var/lib/eggpool/usage.sqlite3
|
|
140
|
+
sudo chown eggpool:eggpool /var/lib/eggpool/usage.sqlite3
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Start the service
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
sudo systemctl start eggpool
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Verify
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Check service status
|
|
153
|
+
sudo systemctl status eggpool
|
|
154
|
+
|
|
155
|
+
# Check health
|
|
156
|
+
curl -s http://localhost:11300/v1/healthz
|
|
157
|
+
|
|
158
|
+
# Check dashboard
|
|
159
|
+
curl -s http://localhost:11300/
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Database Migration
|
|
163
|
+
|
|
164
|
+
After restoring a backup from an older version, run migrations:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
sudo systemctl stop eggpool
|
|
168
|
+
sudo -u eggpool /opt/eggpool/.venv/bin/eggpool --config /etc/eggpool/config.toml migrate
|
|
169
|
+
sudo systemctl start eggpool
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Migrations are idempotent and safe to run multiple times.
|
|
173
|
+
|
|
174
|
+
## Uninstall
|
|
175
|
+
|
|
176
|
+
To remove EggPool entirely, run:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
eggpool uninstall
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The command interactively confirms each removal step:
|
|
183
|
+
|
|
184
|
+
1. Removes the binary via the install method that was detected
|
|
185
|
+
(`pipx uninstall eggpool`, `uv tool uninstall eggpool`, or local
|
|
186
|
+
cleanup for source/manual installs).
|
|
187
|
+
2. Deletes the active `config.toml`, `.env`, and the SQLite database.
|
|
188
|
+
3. Removes `eggpool` entries from the user's shell rc (`~/.zshrc`,
|
|
189
|
+
`~/.bashrc`, etc.), including the marker block installed by
|
|
190
|
+
`scripts/install.sh` and any `uv tool update-shell` lines.
|
|
191
|
+
4. Leaves existing backups under `~/backups/eggpool/` in place.
|
|
192
|
+
|
|
193
|
+
After uninstall completes, the CLI prints the commands needed to
|
|
194
|
+
remove systemd, logrotate, and cron artifacts (these are **not**
|
|
195
|
+
removed automatically):
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
sudo systemctl disable --now eggpool
|
|
199
|
+
sudo rm -f /etc/systemd/system/eggpool.service
|
|
200
|
+
sudo rm -f /etc/logrotate.d/eggpool
|
|
201
|
+
crontab -l 2>/dev/null | grep -v 'eggpool' | crontab -
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Flags:
|
|
205
|
+
|
|
206
|
+
| Flag | Effect |
|
|
207
|
+
|------|--------|
|
|
208
|
+
| `--yes` | Skip every confirmation prompt |
|
|
209
|
+
| `--keep-config` | Leave `config.toml` and `.env` in place |
|
|
210
|
+
| `--keep-data` | Leave the SQLite database in place |
|
|
211
|
+
| `--keep-path` | Skip the shell-rc cleanup step |
|
|
212
|
+
|
|
213
|
+
Examples:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# Fully automated uninstall (no prompts)
|
|
217
|
+
eggpool uninstall --yes
|
|
218
|
+
|
|
219
|
+
# Keep backups and config, only remove the binary and shell PATH entries
|
|
220
|
+
eggpool uninstall --keep-config --keep-data --yes
|
|
221
|
+
```
|
|
@@ -241,4 +241,7 @@ sudo journalctl -u eggpool -n 50 --no-pager
|
|
|
241
241
|
| `eggpool deploy cron --install` | Write backup script + cron entry |
|
|
242
242
|
| `eggpool deploy all` | Print all snippets |
|
|
243
243
|
| `eggpool deploy all --install` | Install everything |
|
|
244
|
+
| `eggpool backup` | Create a timestamped backup archive |
|
|
245
|
+
| `eggpool recover [path]` | Restore from a backup archive (interactive if no path) |
|
|
246
|
+
| `eggpool uninstall` | Remove binary, config, database, and shell PATH entries |
|
|
244
247
|
| `eggpool croncheck` | Check if server is running (exit 0/1) |
|
|
@@ -81,7 +81,7 @@ echo "Checking for existing eggpool install..."
|
|
|
81
81
|
if command -v eggpool >/dev/null 2>&1; then
|
|
82
82
|
echo "Existing eggpool install detected: $(command -v eggpool)"
|
|
83
83
|
echo "Using existing install. Run 'eggpool update' to upgrade."
|
|
84
|
-
eggpool
|
|
84
|
+
eggpool version
|
|
85
85
|
fi
|
|
86
86
|
|
|
87
87
|
# Check for pipx (invoke via detected Python to ensure correct version)
|
|
@@ -7,7 +7,10 @@ import contextlib
|
|
|
7
7
|
import os
|
|
8
8
|
import sys
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any, NoReturn, cast
|
|
10
|
+
from typing import TYPE_CHECKING, Any, NoReturn, cast
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Callable
|
|
11
14
|
|
|
12
15
|
import click
|
|
13
16
|
|
|
@@ -17,6 +20,20 @@ from eggpool.db.connection import Database
|
|
|
17
20
|
from eggpool.db.migrations import MigrationRunner
|
|
18
21
|
from eggpool.db.repositories import AccountRepository, ProviderRepository
|
|
19
22
|
from eggpool.errors import AggregatorError
|
|
23
|
+
from eggpool.lifecycle import (
|
|
24
|
+
InstallMethod,
|
|
25
|
+
default_backup_dir,
|
|
26
|
+
list_backups,
|
|
27
|
+
read_backup_contents,
|
|
28
|
+
resolve_uninstall_paths,
|
|
29
|
+
restore_backup,
|
|
30
|
+
select_backup,
|
|
31
|
+
verify_binary_removed,
|
|
32
|
+
)
|
|
33
|
+
from eggpool.lifecycle import (
|
|
34
|
+
uninstall as do_uninstall,
|
|
35
|
+
)
|
|
36
|
+
from eggpool.lifecycle.backup import BackupContents, create_backup
|
|
20
37
|
from eggpool.logging import configure_logging
|
|
21
38
|
from eggpool.models.config import AppConfig
|
|
22
39
|
from eggpool.providers.client_pool import ProviderClientPool
|
|
@@ -1836,6 +1853,272 @@ def croncheck(ctx: click.Context) -> None:
|
|
|
1836
1853
|
sys.exit(1)
|
|
1837
1854
|
|
|
1838
1855
|
|
|
1856
|
+
@cli.command()
|
|
1857
|
+
@click.option(
|
|
1858
|
+
"--output-dir",
|
|
1859
|
+
"output_dir",
|
|
1860
|
+
default=None,
|
|
1861
|
+
help="Override the default backup directory.",
|
|
1862
|
+
type=click.Path(file_okay=False, dir_okay=True),
|
|
1863
|
+
)
|
|
1864
|
+
@click.pass_context
|
|
1865
|
+
def backup(ctx: click.Context, output_dir: str | None) -> None:
|
|
1866
|
+
"""Create a timestamped zip backup of the config and database.
|
|
1867
|
+
|
|
1868
|
+
The backup includes the live config, the optional ``.env`` file next
|
|
1869
|
+
to it, the SQLite database (with its WAL and SHM sidecars when
|
|
1870
|
+
present), and a META block describing the archive's contents.
|
|
1871
|
+
|
|
1872
|
+
Backups are stored under ``$XDG_BACKUP_HOME/eggpool`` or, when that
|
|
1873
|
+
variable is unset, ``$HOME/backups/eggpool``. The filename follows
|
|
1874
|
+
the pattern ``eggpool-backup-YYYYMMDD-HHMMSS.zip`` so that
|
|
1875
|
+
``eggpool recover`` can list and select from them.
|
|
1876
|
+
"""
|
|
1877
|
+
config_path: str = ctx.obj["config_path"]
|
|
1878
|
+
|
|
1879
|
+
try:
|
|
1880
|
+
config = AppConfig.from_toml(config_path)
|
|
1881
|
+
except AggregatorError as exc:
|
|
1882
|
+
click.echo(f"Error: {exc}", err=True)
|
|
1883
|
+
sys.exit(1)
|
|
1884
|
+
|
|
1885
|
+
env_path = Path(config_path).parent / ".env"
|
|
1886
|
+
contents = BackupContents(
|
|
1887
|
+
config_path=Path(config_path).resolve(),
|
|
1888
|
+
db_path=Path(config.database.path).expanduser().resolve(),
|
|
1889
|
+
env_path=env_path if env_path.exists() else None,
|
|
1890
|
+
install_method=_detect_install_method(),
|
|
1891
|
+
)
|
|
1892
|
+
|
|
1893
|
+
target_dir = Path(output_dir) if output_dir else default_backup_dir()
|
|
1894
|
+
|
|
1895
|
+
try:
|
|
1896
|
+
archive = create_backup(contents, output_dir=target_dir)
|
|
1897
|
+
except OSError as exc:
|
|
1898
|
+
click.echo(f"Error: could not write backup: {exc}", err=True)
|
|
1899
|
+
sys.exit(1)
|
|
1900
|
+
|
|
1901
|
+
click.echo(f"Wrote backup: {archive}")
|
|
1902
|
+
if contents.env_path is not None:
|
|
1903
|
+
click.echo(f" included: env ({contents.env_path})")
|
|
1904
|
+
for member in sorted(contents.member_names()):
|
|
1905
|
+
if member == "env":
|
|
1906
|
+
continue
|
|
1907
|
+
click.echo(f" included: {member}")
|
|
1908
|
+
|
|
1909
|
+
|
|
1910
|
+
@cli.group(invoke_without_command=True)
|
|
1911
|
+
@click.argument("source", required=False, type=click.Path())
|
|
1912
|
+
@click.pass_context
|
|
1913
|
+
def recover(ctx: click.Context, source: str | None) -> None:
|
|
1914
|
+
"""Restore a backup taken with ``eggpool backup``.
|
|
1915
|
+
|
|
1916
|
+
Without SOURCE, the command lists existing backups (newest first)
|
|
1917
|
+
and lets the operator choose one through the same selector used by
|
|
1918
|
+
``eggpool connect`` / ``eggpool logout``.
|
|
1919
|
+
|
|
1920
|
+
With SOURCE, the command restores from the supplied path instead
|
|
1921
|
+
of prompting. SOURCE can be either a full archive path or a
|
|
1922
|
+
relative name resolved against the default backup directory.
|
|
1923
|
+
"""
|
|
1924
|
+
archive: Path | None = None
|
|
1925
|
+
if source is not None:
|
|
1926
|
+
candidate = Path(source).expanduser()
|
|
1927
|
+
if not candidate.is_absolute():
|
|
1928
|
+
candidate = default_backup_dir() / candidate
|
|
1929
|
+
if not candidate.exists():
|
|
1930
|
+
click.echo(f"Error: backup not found: {candidate}", err=True)
|
|
1931
|
+
sys.exit(1)
|
|
1932
|
+
archive = candidate
|
|
1933
|
+
else:
|
|
1934
|
+
backups = list_backups()
|
|
1935
|
+
if not backups:
|
|
1936
|
+
click.echo("No backups found in the default backup directory.")
|
|
1937
|
+
click.echo(f" Default location: {Path.home() / 'backups' / 'eggpool'}")
|
|
1938
|
+
click.echo(" Pass an explicit path: eggpool recover <path>")
|
|
1939
|
+
return
|
|
1940
|
+
try:
|
|
1941
|
+
chosen = select_backup(backups)
|
|
1942
|
+
except KeyboardInterrupt:
|
|
1943
|
+
return
|
|
1944
|
+
if chosen is None:
|
|
1945
|
+
return
|
|
1946
|
+
archive = chosen.path
|
|
1947
|
+
|
|
1948
|
+
try:
|
|
1949
|
+
contents = read_backup_contents(archive)
|
|
1950
|
+
except (OSError, ValueError) as exc:
|
|
1951
|
+
click.echo(f"Error: could not read backup {archive}: {exc}", err=True)
|
|
1952
|
+
sys.exit(1)
|
|
1953
|
+
|
|
1954
|
+
click.echo(f"Restoring from: {archive}")
|
|
1955
|
+
click.echo(f" config: {contents.config_path}")
|
|
1956
|
+
if contents.env_path is not None:
|
|
1957
|
+
click.echo(f" env: {contents.env_path}")
|
|
1958
|
+
click.echo(f" db: {contents.db_path}")
|
|
1959
|
+
|
|
1960
|
+
if not click.confirm("Overwrite current configuration and database?"):
|
|
1961
|
+
click.echo("Aborted.")
|
|
1962
|
+
return
|
|
1963
|
+
|
|
1964
|
+
# Stop the running server before swapping files out from under it.
|
|
1965
|
+
_stop_running_server()
|
|
1966
|
+
|
|
1967
|
+
try:
|
|
1968
|
+
restore_backup(archive, contents=contents)
|
|
1969
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
1970
|
+
click.echo(f"Error: restore failed: {exc}", err=True)
|
|
1971
|
+
sys.exit(1)
|
|
1972
|
+
|
|
1973
|
+
click.echo("Restore complete. Restart the server to load the new config.")
|
|
1974
|
+
_print_install_hint()
|
|
1975
|
+
|
|
1976
|
+
|
|
1977
|
+
def _prompt_yes_no(message: str, *, default: bool = False) -> bool:
|
|
1978
|
+
"""Prompt the user with a y/N question.
|
|
1979
|
+
|
|
1980
|
+
Reuses Click's :func:`click.confirm` but forces the answer to come
|
|
1981
|
+
from the controlling terminal. We don't pass ``default`` through
|
|
1982
|
+
because uninstall is intentionally "no by default".
|
|
1983
|
+
"""
|
|
1984
|
+
return bool(click.confirm(message, default=default))
|
|
1985
|
+
|
|
1986
|
+
|
|
1987
|
+
@cli.command()
|
|
1988
|
+
@click.option(
|
|
1989
|
+
"--yes",
|
|
1990
|
+
"assume_yes",
|
|
1991
|
+
is_flag=True,
|
|
1992
|
+
help="Skip the interactive confirmation prompts.",
|
|
1993
|
+
)
|
|
1994
|
+
@click.option(
|
|
1995
|
+
"--keep-data",
|
|
1996
|
+
is_flag=True,
|
|
1997
|
+
help="Keep the XDG data directory (SQLite database, log files).",
|
|
1998
|
+
)
|
|
1999
|
+
@click.option(
|
|
2000
|
+
"--keep-config",
|
|
2001
|
+
is_flag=True,
|
|
2002
|
+
help="Keep the configuration file (and adjacent .env if present).",
|
|
2003
|
+
)
|
|
2004
|
+
@click.option(
|
|
2005
|
+
"--keep-path",
|
|
2006
|
+
is_flag=True,
|
|
2007
|
+
help="Skip PATH / shell-rc cleanup.",
|
|
2008
|
+
)
|
|
2009
|
+
@click.pass_context
|
|
2010
|
+
def uninstall(
|
|
2011
|
+
ctx: click.Context,
|
|
2012
|
+
assume_yes: bool,
|
|
2013
|
+
keep_data: bool,
|
|
2014
|
+
keep_config: bool,
|
|
2015
|
+
keep_path: bool,
|
|
2016
|
+
) -> None:
|
|
2017
|
+
"""Uninstall EggPool from this machine.
|
|
2018
|
+
|
|
2019
|
+
The command detects the install method (pipx / uv tool / source /
|
|
2020
|
+
manual), asks for confirmation, then reverses the install: it stops
|
|
2021
|
+
any running server, removes the binary via the matching installer
|
|
2022
|
+
(or directly, for source installs), deletes the configuration and
|
|
2023
|
+
SQLite database, and scrubs eggpool-related PATH entries from the
|
|
2024
|
+
user's shell rc files.
|
|
2025
|
+
|
|
2026
|
+
System-level deployment artifacts (systemd unit, logrotate config,
|
|
2027
|
+
cron entry) are not touched automatically; the command prints the
|
|
2028
|
+
manual commands for those at the end.
|
|
2029
|
+
"""
|
|
2030
|
+
config_path: str = ctx.obj["config_path"]
|
|
2031
|
+
|
|
2032
|
+
paths = resolve_uninstall_paths(Path(config_path))
|
|
2033
|
+
|
|
2034
|
+
click.echo("EggPool uninstall plan:")
|
|
2035
|
+
click.echo(f" install method: {paths.install_method.value}")
|
|
2036
|
+
click.echo(f" config: {paths.config_path}")
|
|
2037
|
+
if paths.env_path is not None:
|
|
2038
|
+
click.echo(f" env: {paths.env_path}")
|
|
2039
|
+
click.echo(f" database: {paths.db_path}")
|
|
2040
|
+
click.echo(f" data dir: {paths.data_dir}")
|
|
2041
|
+
if paths.binary_path is not None:
|
|
2042
|
+
click.echo(f" binary: {paths.binary_path}")
|
|
2043
|
+
if paths.eggpool_dir is not None:
|
|
2044
|
+
click.echo(f" source dir: {paths.eggpool_dir}")
|
|
2045
|
+
|
|
2046
|
+
if paths.install_method is InstallMethod.MANUAL and paths.binary_path is None:
|
|
2047
|
+
click.echo(
|
|
2048
|
+
"Error: cannot locate the eggpool binary on PATH, and the "
|
|
2049
|
+
"install method is 'manual'. Run from inside a verified "
|
|
2050
|
+
"eggpool checkout, or remove the binary by hand before "
|
|
2051
|
+
"re-running.",
|
|
2052
|
+
err=True,
|
|
2053
|
+
)
|
|
2054
|
+
sys.exit(1)
|
|
2055
|
+
if paths.install_method is InstallMethod.SOURCE and paths.eggpool_dir is None:
|
|
2056
|
+
click.echo(
|
|
2057
|
+
"Error: source install detected, but the eggpool project root "
|
|
2058
|
+
"could not be verified. Re-run from inside the cloned "
|
|
2059
|
+
"checkout so verification can succeed.",
|
|
2060
|
+
err=True,
|
|
2061
|
+
)
|
|
2062
|
+
sys.exit(1)
|
|
2063
|
+
|
|
2064
|
+
click.echo("")
|
|
2065
|
+
click.echo(
|
|
2066
|
+
"This will remove the eggpool binary, the configuration, "
|
|
2067
|
+
"and the SQLite database."
|
|
2068
|
+
)
|
|
2069
|
+
click.echo("Existing backups under ~/backups/eggpool are NOT removed.")
|
|
2070
|
+
|
|
2071
|
+
if not assume_yes and not _prompt_yes_no(
|
|
2072
|
+
"Continue with uninstall?",
|
|
2073
|
+
default=False,
|
|
2074
|
+
):
|
|
2075
|
+
click.echo("Aborted.")
|
|
2076
|
+
return
|
|
2077
|
+
|
|
2078
|
+
def _auto_confirm(_msg: str) -> bool:
|
|
2079
|
+
return True
|
|
2080
|
+
|
|
2081
|
+
confirm: Callable[[str], bool] = _auto_confirm if assume_yes else _prompt_yes_no
|
|
2082
|
+
|
|
2083
|
+
try:
|
|
2084
|
+
do_uninstall(
|
|
2085
|
+
paths=paths,
|
|
2086
|
+
confirm=confirm,
|
|
2087
|
+
cleanup_data=not keep_data,
|
|
2088
|
+
cleanup_config=not keep_config,
|
|
2089
|
+
cleanup_path=not keep_path,
|
|
2090
|
+
)
|
|
2091
|
+
except RuntimeError as exc:
|
|
2092
|
+
click.echo(f"Error: {exc}", err=True)
|
|
2093
|
+
sys.exit(1)
|
|
2094
|
+
|
|
2095
|
+
# Verify the binary is actually gone. Pipx and uv tool usually
|
|
2096
|
+
# remove the symlink, but verify in case they fail silently.
|
|
2097
|
+
leftover = verify_binary_removed()
|
|
2098
|
+
if leftover:
|
|
2099
|
+
click.echo(
|
|
2100
|
+
"Warning: eggpool binary still reachable on PATH:",
|
|
2101
|
+
err=True,
|
|
2102
|
+
)
|
|
2103
|
+
for path in leftover:
|
|
2104
|
+
click.echo(f" {path}", err=True)
|
|
2105
|
+
click.echo(
|
|
2106
|
+
" Remove it manually if it was not created by pipx/uv tool.",
|
|
2107
|
+
err=True,
|
|
2108
|
+
)
|
|
2109
|
+
else:
|
|
2110
|
+
click.echo("Verified: eggpool binary is no longer on PATH.")
|
|
2111
|
+
|
|
2112
|
+
# Print cleanup instructions for system-level deploy artifacts.
|
|
2113
|
+
click.echo("")
|
|
2114
|
+
click.echo("To finish removing system-level deploy artifacts, run:")
|
|
2115
|
+
click.echo(" sudo systemctl disable --now eggpool 2>/dev/null || true")
|
|
2116
|
+
click.echo(" sudo rm -f /etc/systemd/system/eggpool.service")
|
|
2117
|
+
click.echo(" sudo rm -f /etc/logrotate.d/eggpool")
|
|
2118
|
+
click.echo(" crontab -l 2>/dev/null | grep -v 'eggpool' | crontab -")
|
|
2119
|
+
click.echo("Existing backups under ~/backups/eggpool were preserved.")
|
|
2120
|
+
|
|
2121
|
+
|
|
1839
2122
|
def main() -> NoReturn:
|
|
1840
2123
|
"""Main entry point."""
|
|
1841
2124
|
cli(obj={}) # type: ignore[call-arg]
|