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.
Files changed (199) hide show
  1. {eggpool-0.1.7 → eggpool-0.2.0}/AGENTS.md +3 -0
  2. {eggpool-0.1.7 → eggpool-0.2.0}/CHANGELOG.md +23 -0
  3. {eggpool-0.1.7 → eggpool-0.2.0}/PKG-INFO +26 -1
  4. {eggpool-0.1.7 → eggpool-0.2.0}/README.md +25 -0
  5. eggpool-0.2.0/docs/backup-restore.md +221 -0
  6. {eggpool-0.1.7 → eggpool-0.2.0}/docs/deployment.md +3 -0
  7. {eggpool-0.1.7 → eggpool-0.2.0}/pyproject.toml +1 -1
  8. {eggpool-0.1.7 → eggpool-0.2.0}/scripts/install.sh +1 -1
  9. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/cli.py +284 -1
  10. eggpool-0.2.0/src/eggpool/lifecycle/__init__.py +75 -0
  11. eggpool-0.2.0/src/eggpool/lifecycle/backup.py +391 -0
  12. eggpool-0.2.0/src/eggpool/lifecycle/uninstall.py +522 -0
  13. {eggpool-0.1.7 → eggpool-0.2.0}/uv.lock +1 -1
  14. eggpool-0.1.7/docs/backup-restore.md +0 -137
  15. {eggpool-0.1.7 → eggpool-0.2.0}/.env.example +0 -0
  16. {eggpool-0.1.7 → eggpool-0.2.0}/.github/workflows/ci.yml +0 -0
  17. {eggpool-0.1.7 → eggpool-0.2.0}/.github/workflows/release.yml +0 -0
  18. {eggpool-0.1.7 → eggpool-0.2.0}/.gitignore +0 -0
  19. {eggpool-0.1.7 → eggpool-0.2.0}/LICENSE +0 -0
  20. {eggpool-0.1.7 → eggpool-0.2.0}/architecture/README.md +0 -0
  21. {eggpool-0.1.7 → eggpool-0.2.0}/config-examples/claude-code.env +0 -0
  22. {eggpool-0.1.7 → eggpool-0.2.0}/config-examples/opencode.jsonc +0 -0
  23. {eggpool-0.1.7 → eggpool-0.2.0}/config.example.toml +0 -0
  24. {eggpool-0.1.7 → eggpool-0.2.0}/deploy/eggpool-logrotate.conf +0 -0
  25. {eggpool-0.1.7 → eggpool-0.2.0}/deploy/eggpool.service +0 -0
  26. {eggpool-0.1.7 → eggpool-0.2.0}/deploy/env.example +0 -0
  27. {eggpool-0.1.7 → eggpool-0.2.0}/docs/filesystem-layout.md +0 -0
  28. {eggpool-0.1.7 → eggpool-0.2.0}/docs/firewall.md +0 -0
  29. {eggpool-0.1.7 → eggpool-0.2.0}/docs/model-limits.md +0 -0
  30. {eggpool-0.1.7 → eggpool-0.2.0}/docs/providers.md +0 -0
  31. {eggpool-0.1.7 → eggpool-0.2.0}/docs/proxy.md +0 -0
  32. {eggpool-0.1.7 → eggpool-0.2.0}/docs/raspberry-pi.md +0 -0
  33. {eggpool-0.1.7 → eggpool-0.2.0}/scripts/__init__.py +0 -0
  34. {eggpool-0.1.7 → eggpool-0.2.0}/scripts/check_database.py +0 -0
  35. {eggpool-0.1.7 → eggpool-0.2.0}/scripts/install_prompt.py +0 -0
  36. {eggpool-0.1.7 → eggpool-0.2.0}/scripts/smoke_test.py +0 -0
  37. {eggpool-0.1.7 → eggpool-0.2.0}/scripts/verify_upstream_auth.py +0 -0
  38. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/__init__.py +0 -0
  39. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/__main__.py +0 -0
  40. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/_share/.env.example +0 -0
  41. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/_share/config.example.toml +0 -0
  42. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/accounts/__init__.py +0 -0
  43. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/accounts/registry.py +0 -0
  44. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/accounts/state.py +0 -0
  45. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/__init__.py +0 -0
  46. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/chat_completions.py +0 -0
  47. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/errors.py +0 -0
  48. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/messages.py +0 -0
  49. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/models.py +0 -0
  50. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/proxy_request.py +0 -0
  51. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/api/stats.py +0 -0
  52. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/app.py +0 -0
  53. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/auth.py +0 -0
  54. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/background/__init__.py +0 -0
  55. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/background/cleanup.py +0 -0
  56. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/__init__.py +0 -0
  57. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/cache.py +0 -0
  58. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/fetcher.py +0 -0
  59. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/limits.py +0 -0
  60. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/normalizer.py +0 -0
  61. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/pricing.py +0 -0
  62. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/protocols.py +0 -0
  63. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/catalog/service.py +0 -0
  64. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/config.py +0 -0
  65. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/constants.py +0 -0
  66. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/__init__.py +0 -0
  67. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/_resources.py +0 -0
  68. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/escape.py +0 -0
  69. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/render.py +0 -0
  70. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/routes.py +0 -0
  71. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/static/chart.umd.min.js +0 -0
  72. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/static/dashboard.css +0 -0
  73. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/static/favicon.svg +0 -0
  74. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/theme.py +0 -0
  75. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Booberry.toml +0 -0
  76. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Catppuccin Latte.toml +0 -0
  77. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Catppuccin Macchiato.toml +0 -0
  78. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Catppuccin Mocha.toml +0 -0
  79. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Cyber Red.toml +0 -0
  80. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Cyberpunk.toml +0 -0
  81. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Dark Green.toml +0 -0
  82. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Discord (80_ Saturation).toml +0 -0
  83. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Discord.toml +0 -0
  84. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Dracula.toml +0 -0
  85. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Ferra Light.toml +0 -0
  86. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Flexor Dark.toml +0 -0
  87. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Gruvbox.toml +0 -0
  88. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Halcyon Dark.toml +0 -0
  89. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/IntelliJ Light.toml +0 -0
  90. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Kanagawa.toml +0 -0
  91. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Macaw Dark.toml +0 -0
  92. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Macaw Light.toml +0 -0
  93. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Matrix.toml +0 -0
  94. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Noctis Lilac.toml +0 -0
  95. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Nord.toml +0 -0
  96. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Nostromo Terminal.toml +0 -0
  97. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/One Dark.toml +0 -0
  98. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Oxocarbon.toml +0 -0
  99. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Rose Pine Dawn.toml +0 -0
  100. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Rose Pine Moon.toml +0 -0
  101. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Rose Pine.toml +0 -0
  102. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Solarized Dark.toml +0 -0
  103. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Sonokai.toml +0 -0
  104. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Tokyo Night Storm.toml +0 -0
  105. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/VESPER.toml +0 -0
  106. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/Zenburn.toml +0 -0
  107. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/acton.toml +0 -0
  108. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/bam.toml +0 -0
  109. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/base16-atelier-forest-light.toml +0 -0
  110. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/berlin.toml +0 -0
  111. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/black but with important highlights.toml +0 -0
  112. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/broc.toml +0 -0
  113. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/cork.toml +0 -0
  114. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/ferra.toml +0 -0
  115. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/forest.toml +0 -0
  116. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/lisbon.toml +0 -0
  117. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/midnight.toml +0 -0
  118. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/oslo.toml +0 -0
  119. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/plum.toml +0 -0
  120. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/portland.toml +0 -0
  121. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/sunset.toml +0 -0
  122. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/tofino.toml +0 -0
  123. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/vanimo.toml +0 -0
  124. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/dashboard/themes/vik.toml +0 -0
  125. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/__init__.py +0 -0
  126. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/connection.py +0 -0
  127. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/migrations.py +0 -0
  128. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/repositories.py +0 -0
  129. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0001_initial.sql +0 -0
  130. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0002_indexes.sql +0 -0
  131. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0003_request_attempts.sql +0 -0
  132. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0004_integration_hardening.sql +0 -0
  133. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0005_price_microdollars.sql +0 -0
  134. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0006_correct_price_microdollars.sql +0 -0
  135. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0007_price_cache_rates.sql +0 -0
  136. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0008_proxy_request_identity.sql +0 -0
  137. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0009_model_protocol_source.sql +0 -0
  138. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0010_health_probe.sql +0 -0
  139. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0011_model_resolution_status.sql +0 -0
  140. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +0 -0
  141. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0013_request_attempts_account_id_index.sql +0 -0
  142. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0014_bandwidth_tracking.sql +0 -0
  143. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0015_multi_provider.sql +0 -0
  144. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0016_requests_provider_id.sql +0 -0
  145. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0017_price_snapshots_provider_id.sql +0 -0
  146. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0018_provider_pings.sql +0 -0
  147. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0019_client_ip.sql +0 -0
  148. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0020_performance_indexes.sql +0 -0
  149. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0021_provider_model_metadata.sql +0 -0
  150. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/0022_dashboard_indexes.sql +0 -0
  151. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/db/schema/checksums.json +0 -0
  152. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/deploy/__init__.py +0 -0
  153. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/errors.py +0 -0
  154. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/health/__init__.py +0 -0
  155. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/health/circuit_breaker.py +0 -0
  156. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/health/health_manager.py +0 -0
  157. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/integrations/__init__.py +0 -0
  158. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/integrations/opencode.py +0 -0
  159. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/logging.py +0 -0
  160. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/models/__init__.py +0 -0
  161. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/models/api.py +0 -0
  162. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/models/config.py +0 -0
  163. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/models/database.py +0 -0
  164. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/models/domain.py +0 -0
  165. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/onboard.py +0 -0
  166. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/__init__.py +0 -0
  167. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/_templates.toml +0 -0
  168. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/auth.py +0 -0
  169. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/client_pool.py +0 -0
  170. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/connect.py +0 -0
  171. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/contract.py +0 -0
  172. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/providers/pproxy_transport.py +0 -0
  173. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/proxy/__init__.py +0 -0
  174. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/proxy/client.py +0 -0
  175. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/proxy/sse_observer.py +0 -0
  176. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/proxy/usage.py +0 -0
  177. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/py.typed +0 -0
  178. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/quota/__init__.py +0 -0
  179. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/quota/estimation.py +0 -0
  180. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/quota/reservation.py +0 -0
  181. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/quota/scorer.py +0 -0
  182. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/__init__.py +0 -0
  183. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/attempt_finalizer.py +0 -0
  184. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/body.py +0 -0
  185. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/coordinator.py +0 -0
  186. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/finalizer.py +0 -0
  187. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/request/limits.py +0 -0
  188. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/retry/__init__.py +0 -0
  189. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/retry/classification.py +0 -0
  190. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/routing/__init__.py +0 -0
  191. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/routing/eligibility.py +0 -0
  192. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/routing/provider.py +0 -0
  193. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/routing/router.py +0 -0
  194. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/security/__init__.py +0 -0
  195. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/security/redaction.py +0 -0
  196. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/stats/__init__.py +0 -0
  197. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/stats/queries.py +0 -0
  198. {eggpool-0.1.7 → eggpool-0.2.0}/src/eggpool/stats/service.py +0 -0
  199. {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.1.7
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) |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "eggpool"
3
- version = "0.1.7"
3
+ version = "0.2.0"
4
4
  description = "A lightweight proxy that aggregates multiple LLM provider accounts behind one OpenAI-compatible endpoint"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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 --version
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]