biblio-uplift 0.1.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 (72) hide show
  1. biblio_uplift-0.1.0/LICENSE +21 -0
  2. biblio_uplift-0.1.0/PKG-INFO +348 -0
  3. biblio_uplift-0.1.0/README.md +308 -0
  4. biblio_uplift-0.1.0/pyproject.toml +90 -0
  5. biblio_uplift-0.1.0/setup.cfg +4 -0
  6. biblio_uplift-0.1.0/src/biblio_uplift/__init__.py +1 -0
  7. biblio_uplift-0.1.0/src/biblio_uplift/__main__.py +4 -0
  8. biblio_uplift-0.1.0/src/biblio_uplift/cli/__init__.py +0 -0
  9. biblio_uplift-0.1.0/src/biblio_uplift/cli/main.py +1167 -0
  10. biblio_uplift-0.1.0/src/biblio_uplift/config/__init__.py +3 -0
  11. biblio_uplift-0.1.0/src/biblio_uplift/config/loader.py +38 -0
  12. biblio_uplift-0.1.0/src/biblio_uplift/config/schema.py +105 -0
  13. biblio_uplift-0.1.0/src/biblio_uplift/core/__init__.py +0 -0
  14. biblio_uplift-0.1.0/src/biblio_uplift/core/pipeline.py +258 -0
  15. biblio_uplift-0.1.0/src/biblio_uplift/core/ssh.py +239 -0
  16. biblio_uplift-0.1.0/src/biblio_uplift/core/state.py +62 -0
  17. biblio_uplift-0.1.0/src/biblio_uplift/core/steps/__init__.py +36 -0
  18. biblio_uplift-0.1.0/src/biblio_uplift/core/steps/backup.py +145 -0
  19. biblio_uplift-0.1.0/src/biblio_uplift/core/steps/cleanup.py +267 -0
  20. biblio_uplift-0.1.0/src/biblio_uplift/core/steps/docker.py +72 -0
  21. biblio_uplift-0.1.0/src/biblio_uplift/core/steps/git.py +113 -0
  22. biblio_uplift-0.1.0/src/biblio_uplift/core/steps/healthcheck.py +104 -0
  23. biblio_uplift-0.1.0/src/biblio_uplift/core/steps/hooks.py +38 -0
  24. biblio_uplift-0.1.0/src/biblio_uplift/core/steps/preflight.py +134 -0
  25. biblio_uplift-0.1.0/src/biblio_uplift/core/steps/system.py +94 -0
  26. biblio_uplift-0.1.0/src/biblio_uplift/core/tools/__init__.py +42 -0
  27. biblio_uplift-0.1.0/src/biblio_uplift/core/tools/docker.py +551 -0
  28. biblio_uplift-0.1.0/src/biblio_uplift/core/tools/network.py +109 -0
  29. biblio_uplift-0.1.0/src/biblio_uplift/core/tools/security.py +75 -0
  30. biblio_uplift-0.1.0/src/biblio_uplift/core/tools/system.py +135 -0
  31. biblio_uplift-0.1.0/src/biblio_uplift/core/tools/users.py +119 -0
  32. biblio_uplift-0.1.0/src/biblio_uplift/examples/__init__.py +0 -0
  33. biblio_uplift-0.1.0/src/biblio_uplift/examples/example.yml +24 -0
  34. biblio_uplift-0.1.0/src/biblio_uplift/history/__init__.py +10 -0
  35. biblio_uplift-0.1.0/src/biblio_uplift/history/audit.py +376 -0
  36. biblio_uplift-0.1.0/src/biblio_uplift/paths.py +51 -0
  37. biblio_uplift-0.1.0/src/biblio_uplift/settings.py +180 -0
  38. biblio_uplift-0.1.0/src/biblio_uplift/tui/__init__.py +0 -0
  39. biblio_uplift-0.1.0/src/biblio_uplift/tui/app.py +113 -0
  40. biblio_uplift-0.1.0/src/biblio_uplift/tui/css/app.tcss +307 -0
  41. biblio_uplift-0.1.0/src/biblio_uplift/tui/screens/__init__.py +0 -0
  42. biblio_uplift-0.1.0/src/biblio_uplift/tui/screens/about.py +186 -0
  43. biblio_uplift-0.1.0/src/biblio_uplift/tui/screens/backups.py +177 -0
  44. biblio_uplift-0.1.0/src/biblio_uplift/tui/screens/cleanup.py +195 -0
  45. biblio_uplift-0.1.0/src/biblio_uplift/tui/screens/config_edit.py +456 -0
  46. biblio_uplift-0.1.0/src/biblio_uplift/tui/screens/dashboard.py +132 -0
  47. biblio_uplift-0.1.0/src/biblio_uplift/tui/screens/history.py +61 -0
  48. biblio_uplift-0.1.0/src/biblio_uplift/tui/screens/server_status.py +268 -0
  49. biblio_uplift-0.1.0/src/biblio_uplift/tui/screens/settings.py +138 -0
  50. biblio_uplift-0.1.0/src/biblio_uplift/tui/screens/tools.py +181 -0
  51. biblio_uplift-0.1.0/src/biblio_uplift/tui/screens/upgrade.py +226 -0
  52. biblio_uplift-0.1.0/src/biblio_uplift/tui/widgets/__init__.py +0 -0
  53. biblio_uplift-0.1.0/src/biblio_uplift/tui/widgets/sidebar.py +142 -0
  54. biblio_uplift-0.1.0/src/biblio_uplift.egg-info/PKG-INFO +348 -0
  55. biblio_uplift-0.1.0/src/biblio_uplift.egg-info/SOURCES.txt +70 -0
  56. biblio_uplift-0.1.0/src/biblio_uplift.egg-info/dependency_links.txt +1 -0
  57. biblio_uplift-0.1.0/src/biblio_uplift.egg-info/entry_points.txt +2 -0
  58. biblio_uplift-0.1.0/src/biblio_uplift.egg-info/requires.txt +17 -0
  59. biblio_uplift-0.1.0/src/biblio_uplift.egg-info/top_level.txt +1 -0
  60. biblio_uplift-0.1.0/tests/test_audit_sqlite.py +257 -0
  61. biblio_uplift-0.1.0/tests/test_cli.py +1303 -0
  62. biblio_uplift-0.1.0/tests/test_config.py +196 -0
  63. biblio_uplift-0.1.0/tests/test_coverage_gaps.py +1036 -0
  64. biblio_uplift-0.1.0/tests/test_history.py +54 -0
  65. biblio_uplift-0.1.0/tests/test_main_entry.py +8 -0
  66. biblio_uplift-0.1.0/tests/test_paths.py +53 -0
  67. biblio_uplift-0.1.0/tests/test_pipeline.py +312 -0
  68. biblio_uplift-0.1.0/tests/test_settings.py +43 -0
  69. biblio_uplift-0.1.0/tests/test_ssh.py +213 -0
  70. biblio_uplift-0.1.0/tests/test_state.py +33 -0
  71. biblio_uplift-0.1.0/tests/test_steps.py +457 -0
  72. biblio_uplift-0.1.0/tests/test_tools.py +90 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cody Lusk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,348 @@
1
+ Metadata-Version: 2.4
2
+ Name: biblio-uplift
3
+ Version: 0.1.0
4
+ Summary: TUI and CLI tool for managing Docker Compose service upgrades on remote servers via SSH
5
+ Author: Cody Lusk
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/cody-bibliocommons/biblio-uplift
8
+ Project-URL: Repository, https://github.com/cody-bibliocommons/biblio-uplift
9
+ Project-URL: Issues, https://github.com/cody-bibliocommons/biblio-uplift/issues
10
+ Keywords: docker,compose,ssh,upgrade,tui,devops
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: System Administrators
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: System :: Systems Administration
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: textual==8.2.5
24
+ Requires-Dist: rich==15.0.0
25
+ Requires-Dist: click==8.3.3
26
+ Requires-Dist: pydantic==2.13.3
27
+ Requires-Dist: pyyaml==6.0.3
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest==9.0.3; extra == "dev"
30
+ Requires-Dist: pytest-cov==7.1.0; extra == "dev"
31
+ Requires-Dist: bandit==1.9.4; extra == "dev"
32
+ Requires-Dist: safety==3.7.0; extra == "dev"
33
+ Requires-Dist: mypy==1.20.2; extra == "dev"
34
+ Requires-Dist: ruff==0.15.12; extra == "dev"
35
+ Requires-Dist: types-PyYAML==6.0.12.20260408; extra == "dev"
36
+ Requires-Dist: yamllint>=1.35.0; extra == "dev"
37
+ Requires-Dist: mdformat>=0.7.0; extra == "dev"
38
+ Requires-Dist: freezegun>=1.5.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue)
42
+ ![Tests](https://img.shields.io/badge/tests-266%20passed-brightgreen)
43
+ ![Coverage](https://img.shields.io/badge/coverage-99%25-brightgreen)
44
+ ![Security](https://img.shields.io/badge/security-bandit%20%7C%20pip--audit%20%7C%20gitleaks-green)
45
+ ![License](https://img.shields.io/badge/license-internal-lightgrey)
46
+ ![Version](https://img.shields.io/badge/version-0.1.0-blue)
47
+ ![Tools](https://img.shields.io/badge/tools-18-blue)
48
+ ![TUI](https://img.shields.io/badge/TUI-Textual-purple)
49
+
50
+ ![Biblio Uplift](https://raw.githubusercontent.com/cody-bibliocommons/biblio-uplift/main/docs/img/banner_211.png)
51
+
52
+ # biblio-uplift
53
+
54
+ TUI and CLI tool for upgrading Docker Compose-based services on remote servers via SSH.
55
+
56
+ ## Install
57
+
58
+ ```bash
59
+ pip install .
60
+ ```
61
+
62
+ ## Quick Start
63
+
64
+ ```bash
65
+ # Launch the interactive TUI
66
+ biblio-uplift
67
+
68
+ # Run an upgrade from the CLI
69
+ biblio-uplift run itops-vaultwarden
70
+
71
+ # Run non-interactively (for cron/automation)
72
+ biblio-uplift run itops-vaultwarden --non-interactive
73
+
74
+ # Dry run (simulate without executing)
75
+ biblio-uplift run itops-vaultwarden --dry-run
76
+
77
+ # Run cleanup (prune docker resources, old logs)
78
+ biblio-uplift cleanup itops-vaultwarden
79
+
80
+ # Check server status
81
+ biblio-uplift status itops-vaultwarden
82
+
83
+ # Restore from a backup
84
+ biblio-uplift restore itops-vaultwarden
85
+ biblio-uplift restore itops-vaultwarden --backup 20260501-030000
86
+
87
+ # Resume an upgrade after reboot (if session was lost)
88
+ biblio-uplift resume
89
+
90
+ # Run upgrades on all projects sequentially
91
+ biblio-uplift run-all --non-interactive
92
+
93
+ # List available backups
94
+ biblio-uplift backup list itops-vaultwarden
95
+
96
+ # Update a single service (pull repo + recreate)
97
+ biblio-uplift service-update my-project haproxy
98
+
99
+ # Check/upgrade Docker Compose install method
100
+ biblio-uplift tool run itops-vaultwarden compose-version
101
+
102
+ # List available tools
103
+ biblio-uplift tool list
104
+
105
+ # Run a tool against a project
106
+ biblio-uplift tool run itops-vaultwarden pending-security-updates
107
+
108
+ # Manage configs
109
+ biblio-uplift config edit itops-vaultwarden
110
+ biblio-uplift config validate itops-vaultwarden
111
+ biblio-uplift config create my-project --host myhost.example.com --project-dir /opt/docker/my-project
112
+ biblio-uplift config delete my-project
113
+
114
+ # View history
115
+ biblio-uplift history --last 10
116
+ ```
117
+
118
+ ## Screenshots
119
+
120
+ ### Dashboard
121
+
122
+ ![Dashboard](docs/screenshots/dashboard.png)
123
+
124
+ ### Upgrade Pipeline
125
+
126
+ ![Upgrade](docs/screenshots/upgrade.png)
127
+
128
+ ### Tools
129
+
130
+ ![Tools](docs/screenshots/tools.png)
131
+
132
+ ## Configuration
133
+
134
+ Project configs live in `configs/` as YAML files. Each config defines a remote server and its Docker Compose project.
135
+
136
+ ```bash
137
+ # List configured projects
138
+ biblio-uplift config list
139
+
140
+ # Show a project's config
141
+ biblio-uplift config show itops-vaultwarden
142
+ ```
143
+
144
+ ### Config Fields
145
+
146
+ | Field | Default | Description |
147
+ |-------|---------|-------------|
148
+ | `name` | required | Project identifier |
149
+ | `ssh_host` | required | Remote server hostname |
150
+ | `ssh_user` | `ansible` | SSH username |
151
+ | `ssh_key` | `~/.ssh/id_ed25519` | Path to SSH private key (must not have a passphrase, or use ssh-agent) |
152
+ | `sudo` | `true` | Prefix remote commands with sudo |
153
+ | `project_dir` | required | Path to the Docker Compose project on the remote server |
154
+ | `compose_files` | `["docker-compose.yml"]` | Compose file(s) to use |
155
+ | `compose_profile` | `null` | Compose profile. Set to `hostname` to use `$(hostname -s)` on the remote |
156
+ | `compose_command` | `docker compose` | Compose binary |
157
+ | `backup_dir` | `/var/backups/itops` | Where to store backups on the remote server (local disk, not NFS) |
158
+ | `backup_retention` | `5` | Number of backups to keep |
159
+ | `volumes` | `[]` | Docker volume names to back up |
160
+ | `extra_backup_paths` | `[]` | Additional paths to include in file backup |
161
+ | `healthcheck_urls` | `[]` | HTTP(S) URLs to check after startup |
162
+ | `healthcheck_timeout` | `120` | Seconds to wait for containers to become healthy |
163
+ | `skip_os_update` | `false` | Skip OS package updates by default |
164
+ | `skip_reboot` | `false` | Skip reboot by default |
165
+ | `ssh_port` | `22` | SSH port |
166
+ | `git_branch` | `main` | Git branch to pull |
167
+ | `maintenance_window` | `null` | Allowed time window for runs (e.g. `"Sun 02:00-06:00"`) |
168
+ | `on_failure_cmd` | `null` | Shell command to run on failure (e.g. Slack webhook) |
169
+ | `on_success_cmd` | `null` | Shell command to run on success |
170
+ | `reboot_timeout` | `300` | Seconds to wait for SSH after reboot |
171
+ | `apt_timeout` | `600` | Seconds to wait for apt operations |
172
+ | `pre_upgrade_hooks` | `[]` | Shell commands to run before upgrade (executed as root via sudo) |
173
+ | `post_upgrade_hooks` | `[]` | Shell commands to run after upgrade |
174
+
175
+ ### Hooks
176
+
177
+ Pre/post upgrade hooks are arbitrary shell commands executed on the remote server. They run as root (via sudo). Use them for things like:
178
+
179
+ ```yaml
180
+ pre_upgrade_hooks:
181
+ - "systemctl stop ci-runner"
182
+ - "/opt/scripts/notify-slack.sh 'Starting upgrade'"
183
+ post_upgrade_hooks:
184
+ - "systemctl start ci-runner"
185
+ - "/opt/scripts/notify-slack.sh 'Upgrade complete'"
186
+ ```
187
+
188
+ ## Upgrade Pipeline
189
+
190
+ The upgrade runs these steps in order:
191
+
192
+ 1. **Preflight** — SSH connectivity, disk space check, backup size estimation
193
+ 1. **Pre-hooks** — Custom pre-upgrade commands
194
+ 1. **Backup files** — Tar project dir + extra paths
195
+ 1. **Backup volumes** — Export Docker volumes via alpine container
196
+ 1. **Backup cleanup** — Remove old backups beyond retention count
197
+ 1. **Docker down** — `docker compose down`
198
+ 1. **Git pull** — Pull latest from remote
199
+ 1. **Docker pull** — Pull latest images
200
+ 1. **OS update** — `apt-get update && upgrade && autoremove --purge`
201
+ 1. **Reboot** — Reboot and wait for SSH to come back
202
+ 1. **Docker up** — `docker compose up -d`
203
+ 1. **Health check** — Wait for containers healthy + HTTP checks
204
+ 1. **Post-hooks** — Custom post-upgrade commands
205
+
206
+ On failure, completed steps are rolled back in reverse order (docker down → docker up, git pull → git checkout previous commit).
207
+
208
+ ## Cleanup Pipeline
209
+
210
+ 1. **Preflight** — SSH connectivity check
211
+ 1. **Docker cleanup** — Prune stopped containers, dangling images, unused volumes, build cache
212
+ 1. **Log cleanup** — Truncate configured log files, vacuum journald
213
+
214
+ Set `aggressive_prune: true` in the cleanup config to remove *all* unused images (not just dangling) during cleanup.
215
+
216
+ ## TUI
217
+
218
+ Running `biblio-uplift` with no command launches the interactive terminal UI. It has 8 panels:
219
+
220
+ - **Dashboard** — Overview of all projects and their last run status
221
+ - **Upgrade** — Run upgrades interactively with live step progress
222
+ - **Cleanup** — Run cleanup pipelines with live output
223
+ - **Server Status** — View remote server health (disk, memory, uptime, containers)
224
+ - **Backups** — Browse and restore backups
225
+ - **Config Editor** — View and edit project configurations
226
+ - **History** — Browse past upgrade runs with filtering
227
+ - **Tools** — Security audits, log rotation, container management tools
228
+
229
+ ## CLI Flags
230
+
231
+ ```
232
+ biblio-uplift [--debug] [--version] COMMAND
233
+
234
+ Global:
235
+ --debug Write verbose logs to logs/debug.log
236
+ --version Show version and exit
237
+
238
+ run PROJECT:
239
+ --non-interactive Skip confirmation prompts
240
+ --skip-reboot Skip the reboot step
241
+ --skip-os-update Skip OS package updates
242
+ --skip-backup Skip all backup steps
243
+ --skip-git Skip git pull
244
+ --dry-run Simulate without executing
245
+ --no-hooks Skip pre/post hooks
246
+ --on-failure TEXT Shell command to run on failure (overrides config)
247
+ --start-from TEXT Skip steps before this one (e.g. docker_pull)
248
+
249
+ cleanup PROJECT:
250
+ --non-interactive Skip confirmation prompts
251
+ --dry-run Show what would be done
252
+
253
+ restore PROJECT:
254
+ --backup TEXT Backup timestamp to restore (defaults to latest)
255
+ --non-interactive Skip confirmation prompts
256
+
257
+ resume:
258
+ Resume an upgrade after reboot if the controlling session was lost.
259
+ No options.
260
+
261
+ status PROJECT:
262
+ Show current status of a project's remote server.
263
+ No options.
264
+
265
+ backup list PROJECT:
266
+ List available backups for a project.
267
+ No options.
268
+
269
+ service-update PROJECT SERVICE:
270
+ Pull repo and recreate a single service.
271
+ --non-interactive Skip confirmation prompts
272
+
273
+ tool list:
274
+ List available tools by category.
275
+
276
+ tool run PROJECT TOOL_NAME:
277
+ --dry-run Preview what would happen
278
+ --non-interactive Skip confirmation prompts
279
+
280
+ run-all:
281
+ --non-interactive Skip confirmation prompts
282
+ --skip-reboot Skip the reboot step
283
+ --skip-os-update Skip OS package updates
284
+ --dry-run Simulate without executing
285
+ --projects TEXT Comma-separated project names (defaults to all)
286
+
287
+ config:
288
+ config list List all project configurations
289
+ config show PROJECT Show configuration details for a project
290
+ config create NAME Create a new project configuration
291
+ --host TEXT SSH hostname (required)
292
+ --project-dir TEXT Remote project directory (required)
293
+ --ssh-user TEXT SSH username
294
+ --ssh-key TEXT Path to SSH key
295
+ config edit PROJECT Open config in $EDITOR
296
+ config validate PROJECT
297
+ Validate config by testing SSH, Docker, and paths
298
+ config delete PROJECT
299
+ Delete a project configuration
300
+ --non-interactive Skip confirmation prompts
301
+
302
+ history:
303
+ --project TEXT Filter by project
304
+ --last INTEGER Show last N entries (default: 20)
305
+ ```
306
+
307
+ ## Audit History
308
+
309
+ Every run is logged to `logs/history.jsonl` with timestamp, project, steps, outcomes, and duration. View with:
310
+
311
+ ```bash
312
+ biblio-uplift history
313
+ biblio-uplift history --project my-project --last 5
314
+ ```
315
+
316
+ ## Concurrent Run Protection
317
+
318
+ A file lock (`/tmp/biblio-uplift-<project>.lock`) prevents multiple upgrades of the same project from running simultaneously.
319
+
320
+ ## Exit Codes
321
+
322
+ | Code | Meaning |
323
+ |------|---------|
324
+ | 0 | Success |
325
+ | 1 | Pipeline failed or error |
326
+
327
+ ## Cron / Unattended Use
328
+
329
+ ```bash
330
+ # Weekly upgrade of vaultwarden, Sundays at 3am
331
+ 0 3 * * 0 cd /path/to/biblio-uplift && biblio-uplift run itops-vaultwarden --non-interactive --on-failure "curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK -d '{\"text\": \"Vaultwarden upgrade failed\"}' " >> logs/cron.log 2>&1
332
+ ```
333
+
334
+ For unattended runs:
335
+
336
+ - Always use `--non-interactive` to skip confirmation prompts
337
+ - Set `on_failure_cmd` in config or use `--on-failure` for failure alerts
338
+ - Set `maintenance_window` in config to prevent accidental runs outside hours
339
+ - Monitor exit codes and `logs/history.jsonl` for audit trail
340
+
341
+ ## Development
342
+
343
+ ```bash
344
+ pipx install --force .
345
+ biblio-uplift --debug run itops-vaultwarden --dry-run
346
+ ```
347
+
348
+ Set `ITOPS_UPGRADE_DIR` to override the project root directory detection.
@@ -0,0 +1,308 @@
1
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue)
2
+ ![Tests](https://img.shields.io/badge/tests-266%20passed-brightgreen)
3
+ ![Coverage](https://img.shields.io/badge/coverage-99%25-brightgreen)
4
+ ![Security](https://img.shields.io/badge/security-bandit%20%7C%20pip--audit%20%7C%20gitleaks-green)
5
+ ![License](https://img.shields.io/badge/license-internal-lightgrey)
6
+ ![Version](https://img.shields.io/badge/version-0.1.0-blue)
7
+ ![Tools](https://img.shields.io/badge/tools-18-blue)
8
+ ![TUI](https://img.shields.io/badge/TUI-Textual-purple)
9
+
10
+ ![Biblio Uplift](https://raw.githubusercontent.com/cody-bibliocommons/biblio-uplift/main/docs/img/banner_211.png)
11
+
12
+ # biblio-uplift
13
+
14
+ TUI and CLI tool for upgrading Docker Compose-based services on remote servers via SSH.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install .
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```bash
25
+ # Launch the interactive TUI
26
+ biblio-uplift
27
+
28
+ # Run an upgrade from the CLI
29
+ biblio-uplift run itops-vaultwarden
30
+
31
+ # Run non-interactively (for cron/automation)
32
+ biblio-uplift run itops-vaultwarden --non-interactive
33
+
34
+ # Dry run (simulate without executing)
35
+ biblio-uplift run itops-vaultwarden --dry-run
36
+
37
+ # Run cleanup (prune docker resources, old logs)
38
+ biblio-uplift cleanup itops-vaultwarden
39
+
40
+ # Check server status
41
+ biblio-uplift status itops-vaultwarden
42
+
43
+ # Restore from a backup
44
+ biblio-uplift restore itops-vaultwarden
45
+ biblio-uplift restore itops-vaultwarden --backup 20260501-030000
46
+
47
+ # Resume an upgrade after reboot (if session was lost)
48
+ biblio-uplift resume
49
+
50
+ # Run upgrades on all projects sequentially
51
+ biblio-uplift run-all --non-interactive
52
+
53
+ # List available backups
54
+ biblio-uplift backup list itops-vaultwarden
55
+
56
+ # Update a single service (pull repo + recreate)
57
+ biblio-uplift service-update my-project haproxy
58
+
59
+ # Check/upgrade Docker Compose install method
60
+ biblio-uplift tool run itops-vaultwarden compose-version
61
+
62
+ # List available tools
63
+ biblio-uplift tool list
64
+
65
+ # Run a tool against a project
66
+ biblio-uplift tool run itops-vaultwarden pending-security-updates
67
+
68
+ # Manage configs
69
+ biblio-uplift config edit itops-vaultwarden
70
+ biblio-uplift config validate itops-vaultwarden
71
+ biblio-uplift config create my-project --host myhost.example.com --project-dir /opt/docker/my-project
72
+ biblio-uplift config delete my-project
73
+
74
+ # View history
75
+ biblio-uplift history --last 10
76
+ ```
77
+
78
+ ## Screenshots
79
+
80
+ ### Dashboard
81
+
82
+ ![Dashboard](docs/screenshots/dashboard.png)
83
+
84
+ ### Upgrade Pipeline
85
+
86
+ ![Upgrade](docs/screenshots/upgrade.png)
87
+
88
+ ### Tools
89
+
90
+ ![Tools](docs/screenshots/tools.png)
91
+
92
+ ## Configuration
93
+
94
+ Project configs live in `configs/` as YAML files. Each config defines a remote server and its Docker Compose project.
95
+
96
+ ```bash
97
+ # List configured projects
98
+ biblio-uplift config list
99
+
100
+ # Show a project's config
101
+ biblio-uplift config show itops-vaultwarden
102
+ ```
103
+
104
+ ### Config Fields
105
+
106
+ | Field | Default | Description |
107
+ |-------|---------|-------------|
108
+ | `name` | required | Project identifier |
109
+ | `ssh_host` | required | Remote server hostname |
110
+ | `ssh_user` | `ansible` | SSH username |
111
+ | `ssh_key` | `~/.ssh/id_ed25519` | Path to SSH private key (must not have a passphrase, or use ssh-agent) |
112
+ | `sudo` | `true` | Prefix remote commands with sudo |
113
+ | `project_dir` | required | Path to the Docker Compose project on the remote server |
114
+ | `compose_files` | `["docker-compose.yml"]` | Compose file(s) to use |
115
+ | `compose_profile` | `null` | Compose profile. Set to `hostname` to use `$(hostname -s)` on the remote |
116
+ | `compose_command` | `docker compose` | Compose binary |
117
+ | `backup_dir` | `/var/backups/itops` | Where to store backups on the remote server (local disk, not NFS) |
118
+ | `backup_retention` | `5` | Number of backups to keep |
119
+ | `volumes` | `[]` | Docker volume names to back up |
120
+ | `extra_backup_paths` | `[]` | Additional paths to include in file backup |
121
+ | `healthcheck_urls` | `[]` | HTTP(S) URLs to check after startup |
122
+ | `healthcheck_timeout` | `120` | Seconds to wait for containers to become healthy |
123
+ | `skip_os_update` | `false` | Skip OS package updates by default |
124
+ | `skip_reboot` | `false` | Skip reboot by default |
125
+ | `ssh_port` | `22` | SSH port |
126
+ | `git_branch` | `main` | Git branch to pull |
127
+ | `maintenance_window` | `null` | Allowed time window for runs (e.g. `"Sun 02:00-06:00"`) |
128
+ | `on_failure_cmd` | `null` | Shell command to run on failure (e.g. Slack webhook) |
129
+ | `on_success_cmd` | `null` | Shell command to run on success |
130
+ | `reboot_timeout` | `300` | Seconds to wait for SSH after reboot |
131
+ | `apt_timeout` | `600` | Seconds to wait for apt operations |
132
+ | `pre_upgrade_hooks` | `[]` | Shell commands to run before upgrade (executed as root via sudo) |
133
+ | `post_upgrade_hooks` | `[]` | Shell commands to run after upgrade |
134
+
135
+ ### Hooks
136
+
137
+ Pre/post upgrade hooks are arbitrary shell commands executed on the remote server. They run as root (via sudo). Use them for things like:
138
+
139
+ ```yaml
140
+ pre_upgrade_hooks:
141
+ - "systemctl stop ci-runner"
142
+ - "/opt/scripts/notify-slack.sh 'Starting upgrade'"
143
+ post_upgrade_hooks:
144
+ - "systemctl start ci-runner"
145
+ - "/opt/scripts/notify-slack.sh 'Upgrade complete'"
146
+ ```
147
+
148
+ ## Upgrade Pipeline
149
+
150
+ The upgrade runs these steps in order:
151
+
152
+ 1. **Preflight** — SSH connectivity, disk space check, backup size estimation
153
+ 1. **Pre-hooks** — Custom pre-upgrade commands
154
+ 1. **Backup files** — Tar project dir + extra paths
155
+ 1. **Backup volumes** — Export Docker volumes via alpine container
156
+ 1. **Backup cleanup** — Remove old backups beyond retention count
157
+ 1. **Docker down** — `docker compose down`
158
+ 1. **Git pull** — Pull latest from remote
159
+ 1. **Docker pull** — Pull latest images
160
+ 1. **OS update** — `apt-get update && upgrade && autoremove --purge`
161
+ 1. **Reboot** — Reboot and wait for SSH to come back
162
+ 1. **Docker up** — `docker compose up -d`
163
+ 1. **Health check** — Wait for containers healthy + HTTP checks
164
+ 1. **Post-hooks** — Custom post-upgrade commands
165
+
166
+ On failure, completed steps are rolled back in reverse order (docker down → docker up, git pull → git checkout previous commit).
167
+
168
+ ## Cleanup Pipeline
169
+
170
+ 1. **Preflight** — SSH connectivity check
171
+ 1. **Docker cleanup** — Prune stopped containers, dangling images, unused volumes, build cache
172
+ 1. **Log cleanup** — Truncate configured log files, vacuum journald
173
+
174
+ Set `aggressive_prune: true` in the cleanup config to remove *all* unused images (not just dangling) during cleanup.
175
+
176
+ ## TUI
177
+
178
+ Running `biblio-uplift` with no command launches the interactive terminal UI. It has 8 panels:
179
+
180
+ - **Dashboard** — Overview of all projects and their last run status
181
+ - **Upgrade** — Run upgrades interactively with live step progress
182
+ - **Cleanup** — Run cleanup pipelines with live output
183
+ - **Server Status** — View remote server health (disk, memory, uptime, containers)
184
+ - **Backups** — Browse and restore backups
185
+ - **Config Editor** — View and edit project configurations
186
+ - **History** — Browse past upgrade runs with filtering
187
+ - **Tools** — Security audits, log rotation, container management tools
188
+
189
+ ## CLI Flags
190
+
191
+ ```
192
+ biblio-uplift [--debug] [--version] COMMAND
193
+
194
+ Global:
195
+ --debug Write verbose logs to logs/debug.log
196
+ --version Show version and exit
197
+
198
+ run PROJECT:
199
+ --non-interactive Skip confirmation prompts
200
+ --skip-reboot Skip the reboot step
201
+ --skip-os-update Skip OS package updates
202
+ --skip-backup Skip all backup steps
203
+ --skip-git Skip git pull
204
+ --dry-run Simulate without executing
205
+ --no-hooks Skip pre/post hooks
206
+ --on-failure TEXT Shell command to run on failure (overrides config)
207
+ --start-from TEXT Skip steps before this one (e.g. docker_pull)
208
+
209
+ cleanup PROJECT:
210
+ --non-interactive Skip confirmation prompts
211
+ --dry-run Show what would be done
212
+
213
+ restore PROJECT:
214
+ --backup TEXT Backup timestamp to restore (defaults to latest)
215
+ --non-interactive Skip confirmation prompts
216
+
217
+ resume:
218
+ Resume an upgrade after reboot if the controlling session was lost.
219
+ No options.
220
+
221
+ status PROJECT:
222
+ Show current status of a project's remote server.
223
+ No options.
224
+
225
+ backup list PROJECT:
226
+ List available backups for a project.
227
+ No options.
228
+
229
+ service-update PROJECT SERVICE:
230
+ Pull repo and recreate a single service.
231
+ --non-interactive Skip confirmation prompts
232
+
233
+ tool list:
234
+ List available tools by category.
235
+
236
+ tool run PROJECT TOOL_NAME:
237
+ --dry-run Preview what would happen
238
+ --non-interactive Skip confirmation prompts
239
+
240
+ run-all:
241
+ --non-interactive Skip confirmation prompts
242
+ --skip-reboot Skip the reboot step
243
+ --skip-os-update Skip OS package updates
244
+ --dry-run Simulate without executing
245
+ --projects TEXT Comma-separated project names (defaults to all)
246
+
247
+ config:
248
+ config list List all project configurations
249
+ config show PROJECT Show configuration details for a project
250
+ config create NAME Create a new project configuration
251
+ --host TEXT SSH hostname (required)
252
+ --project-dir TEXT Remote project directory (required)
253
+ --ssh-user TEXT SSH username
254
+ --ssh-key TEXT Path to SSH key
255
+ config edit PROJECT Open config in $EDITOR
256
+ config validate PROJECT
257
+ Validate config by testing SSH, Docker, and paths
258
+ config delete PROJECT
259
+ Delete a project configuration
260
+ --non-interactive Skip confirmation prompts
261
+
262
+ history:
263
+ --project TEXT Filter by project
264
+ --last INTEGER Show last N entries (default: 20)
265
+ ```
266
+
267
+ ## Audit History
268
+
269
+ Every run is logged to `logs/history.jsonl` with timestamp, project, steps, outcomes, and duration. View with:
270
+
271
+ ```bash
272
+ biblio-uplift history
273
+ biblio-uplift history --project my-project --last 5
274
+ ```
275
+
276
+ ## Concurrent Run Protection
277
+
278
+ A file lock (`/tmp/biblio-uplift-<project>.lock`) prevents multiple upgrades of the same project from running simultaneously.
279
+
280
+ ## Exit Codes
281
+
282
+ | Code | Meaning |
283
+ |------|---------|
284
+ | 0 | Success |
285
+ | 1 | Pipeline failed or error |
286
+
287
+ ## Cron / Unattended Use
288
+
289
+ ```bash
290
+ # Weekly upgrade of vaultwarden, Sundays at 3am
291
+ 0 3 * * 0 cd /path/to/biblio-uplift && biblio-uplift run itops-vaultwarden --non-interactive --on-failure "curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK -d '{\"text\": \"Vaultwarden upgrade failed\"}' " >> logs/cron.log 2>&1
292
+ ```
293
+
294
+ For unattended runs:
295
+
296
+ - Always use `--non-interactive` to skip confirmation prompts
297
+ - Set `on_failure_cmd` in config or use `--on-failure` for failure alerts
298
+ - Set `maintenance_window` in config to prevent accidental runs outside hours
299
+ - Monitor exit codes and `logs/history.jsonl` for audit trail
300
+
301
+ ## Development
302
+
303
+ ```bash
304
+ pipx install --force .
305
+ biblio-uplift --debug run itops-vaultwarden --dry-run
306
+ ```
307
+
308
+ Set `ITOPS_UPGRADE_DIR` to override the project root directory detection.