dploydb 0.1.0__py3-none-any.whl
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.
- dploydb/__init__.py +1 -0
- dploydb/__main__.py +6 -0
- dploydb/backup.py +455 -0
- dploydb/candidate.py +868 -0
- dploydb/cli.py +1111 -0
- dploydb/config.py +784 -0
- dploydb/cutover.py +326 -0
- dploydb/deploy.py +1163 -0
- dploydb/deployment_dependencies.py +366 -0
- dploydb/deployment_evidence.py +136 -0
- dploydb/diagnostics.py +1020 -0
- dploydb/errors.py +140 -0
- dploydb/health.py +625 -0
- dploydb/locking.py +609 -0
- dploydb/manual_restore.py +825 -0
- dploydb/migration.py +575 -0
- dploydb/models.py +927 -0
- dploydb/recovery.py +1210 -0
- dploydb/redaction.py +229 -0
- dploydb/releases.py +611 -0
- dploydb/restore.py +461 -0
- dploydb/retention.py +165 -0
- dploydb/runners/__init__.py +33 -0
- dploydb/runners/base.py +389 -0
- dploydb/runners/docker_compose.py +590 -0
- dploydb/runners/docker_compose_production.py +966 -0
- dploydb/sqlite_checks.py +136 -0
- dploydb/state.py +604 -0
- dploydb/storage/__init__.py +13 -0
- dploydb/storage/base.py +52 -0
- dploydb/storage/local.py +301 -0
- dploydb/storage/s3.py +737 -0
- dploydb/subprocesses.py +641 -0
- dploydb/traffic.py +165 -0
- dploydb-0.1.0.dist-info/METADATA +583 -0
- dploydb-0.1.0.dist-info/RECORD +40 -0
- dploydb-0.1.0.dist-info/WHEEL +4 -0
- dploydb-0.1.0.dist-info/entry_points.txt +2 -0
- dploydb-0.1.0.dist-info/licenses/LICENSE +201 -0
- dploydb-0.1.0.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dploydb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deployment safety for SQLite applications
|
|
5
|
+
Project-URL: Homepage, https://github.com/recursiveway/dployDB
|
|
6
|
+
Project-URL: Repository, https://github.com/recursiveway/dployDB
|
|
7
|
+
Project-URL: Issues, https://github.com/recursiveway/dployDB/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/recursiveway/dployDB/tree/main/docs
|
|
9
|
+
Author: RecursiveWay
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
License-File: NOTICE
|
|
13
|
+
Keywords: backup,deployment,docker-compose,sqlite
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Database
|
|
21
|
+
Classifier: Topic :: System :: Archiving :: Backup
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Requires-Dist: boto3>=1.40.0
|
|
24
|
+
Requires-Dist: httpx>=0.28.1
|
|
25
|
+
Requires-Dist: pydantic>=2.11.7
|
|
26
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
27
|
+
Requires-Dist: rich>=13.9.4
|
|
28
|
+
Requires-Dist: typer>=0.16.0
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# DployDB
|
|
32
|
+
|
|
33
|
+
[](https://github.com/recursiveway/dployDB/actions/workflows/ci.yml)
|
|
34
|
+
[](https://pypi.org/project/dploydb/)
|
|
35
|
+
[](https://www.python.org/downloads/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
|
|
38
|
+
DployDB is being built as a deployment-safety tool for applications that use one SQLite database on one Linux server.
|
|
39
|
+
|
|
40
|
+
> [!WARNING]
|
|
41
|
+
> DployDB 0.1.0 is Alpha software. Its safety gates are real and extensively
|
|
42
|
+
> tested, but public interfaces may still change in documented `0.x` minor
|
|
43
|
+
> releases. Read the [supported limits](docs/limitations.md), keep independent
|
|
44
|
+
> backups, and prove the complete failure/restore flow on a non-production host
|
|
45
|
+
> before relying on it for production.
|
|
46
|
+
|
|
47
|
+
## Quick start: install and run the real deployment demo
|
|
48
|
+
|
|
49
|
+
This path starts a real v1 Docker Compose application and SQLite database, then
|
|
50
|
+
uses the installed `dploydb` CLI—not the fixture controller—to rehearse, validate,
|
|
51
|
+
and deploy v2. Run it from a checked-out DployDB source tree on Linux with Python
|
|
52
|
+
3.12+, `pipx`, Docker Engine, and the Docker Compose plugin.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python3 --version # must report 3.12 or newer
|
|
56
|
+
pipx --version
|
|
57
|
+
docker version
|
|
58
|
+
docker compose version
|
|
59
|
+
|
|
60
|
+
git clone https://github.com/recursiveway/dployDB.git
|
|
61
|
+
cd dployDB
|
|
62
|
+
pipx install dploydb==0.1.0
|
|
63
|
+
dploydb --no-color version
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Create the known-good v1 application, then generate private absolute-path demo
|
|
67
|
+
configuration. `start-v1` intentionally resets only the selected `quickstart`
|
|
68
|
+
demo instance.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python3 demo/controller.py --instance quickstart --port 4510 start-v1
|
|
72
|
+
python3 -m demo.prepare \
|
|
73
|
+
--instance quickstart --port 4510 --candidate-port 4511
|
|
74
|
+
. demo/.state/quickstart/dploydb.env
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Run the complete host audit and deploy v2 with machine-readable output:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
dploydb --no-color doctor --deep \
|
|
81
|
+
--config demo/.state/quickstart/dploydb.yaml
|
|
82
|
+
dploydb --no-color deploy --version v2 \
|
|
83
|
+
--config demo/.state/quickstart/dploydb.yaml \
|
|
84
|
+
--json --non-interactive > /tmp/dploydb-quickstart-result.json
|
|
85
|
+
python3 -c 'import json; p=json.load(open("/tmp/dploydb-quickstart-result.json")); assert p["ok"] is True and p["outcome"] == "active"; print(p["release_id"])'
|
|
86
|
+
curl --fail-with-body http://127.0.0.1:4510/health
|
|
87
|
+
dploydb releases --config demo/.state/quickstart/dploydb.yaml
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The JSON assertion proves CI can parse the real deployment result. The v1
|
|
91
|
+
database is migrated to schema version 2, the candidate is tested on an isolated
|
|
92
|
+
copy and port, and normal traffic is activated only after final production
|
|
93
|
+
checks. Stop the demo container without deleting its database, backups, or
|
|
94
|
+
release evidence:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
python3 demo/controller.py --instance quickstart --port 4510 stop
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If either port is already occupied, choose two unused distinct ports in all
|
|
101
|
+
three commands. For production setup, continue with the
|
|
102
|
+
[first-run guide](docs/first-run.md), [Nginx hook example](examples/nginx/README.md),
|
|
103
|
+
[security model](docs/security.md), [limitations](docs/limitations.md), and
|
|
104
|
+
[backup-preserving uninstall](docs/uninstall.md).
|
|
105
|
+
|
|
106
|
+
## Current status
|
|
107
|
+
|
|
108
|
+
Milestones 0 through 8 provide:
|
|
109
|
+
|
|
110
|
+
- an installable `dploydb` CLI with help and version commands;
|
|
111
|
+
- strict, duplicate-safe configuration parsing with environment interpolation;
|
|
112
|
+
- in-memory secret registration and output redaction;
|
|
113
|
+
- `dploydb init`, which creates a valid mode-`0600` starter file without overwriting;
|
|
114
|
+
- atomic generic operation manifests and append-only, redacted event logs;
|
|
115
|
+
- a durable `fcntl.flock` deployment lock with atomic owner metadata and stale-owner diagnosis;
|
|
116
|
+
- bounded, redacted subprocess execution with process-group timeout cleanup;
|
|
117
|
+
- `dploydb doctor`, with layered host checks, bounded SQLite verification, and explicit deferred checks;
|
|
118
|
+
- read-only `dploydb status`, including active, interrupted, stale, and recovery-required state;
|
|
119
|
+
- SQLite online backups that remain consistent while the application is writing;
|
|
120
|
+
- immutable mode-`0600` backup databases and metadata under a mode-`0700` directory;
|
|
121
|
+
- `dploydb backup [--upload]` and read-only `dploydb verify <backup-id>` with stable JSON output;
|
|
122
|
+
- S3-compatible off-server replication, including Cloudflare R2, with local
|
|
123
|
+
verification before upload, database-first/metadata-last commit, full remote
|
|
124
|
+
readback verification, bounded requests/retries, and runtime-only credentials;
|
|
125
|
+
- verified remote hydration when a protected local backup is absent, used by
|
|
126
|
+
manual restore and interrupted-operation recovery without accepting corrupt
|
|
127
|
+
local evidence;
|
|
128
|
+
- an internal stopped-application restore engine that creates a verified pre-restore backup and
|
|
129
|
+
restores it automatically if replacement fails;
|
|
130
|
+
- an internal, lock-protected migration rehearsal stage that creates a verified
|
|
131
|
+
snapshot, migrates only a private disposable copy, captures redacted command
|
|
132
|
+
evidence, enforces process-tree timeout cleanup, reruns SQLite checks, and
|
|
133
|
+
records `rehearsal_passed` or a durable failed-safe result;
|
|
134
|
+
- an internal Docker Compose candidate runner that creates an operation-scoped
|
|
135
|
+
one-off project, binds only the configured loopback candidate port, overlays
|
|
136
|
+
the rehearsed SQLite directory at the configured container target, validates
|
|
137
|
+
live mounts/ports/labels before acceptance, captures bounded redacted logs,
|
|
138
|
+
and proves idempotent container/network cleanup;
|
|
139
|
+
- a public `dploydb deploy --version <version>` command that runs verified
|
|
140
|
+
backup, migration rehearsal, isolated candidate checks, controlled
|
|
141
|
+
maintenance, stopped-writer final backup, production migration, final
|
|
142
|
+
application/database checks, and traffic activation under one durable lock;
|
|
143
|
+
- exact previous-container preservation and automatic application/database
|
|
144
|
+
rollback for failures before new traffic activation, with durable release,
|
|
145
|
+
hook, health, checksum, and event evidence;
|
|
146
|
+
- required final-backup replication before production migration, plus
|
|
147
|
+
post-activation local/remote retention that always protects the active and
|
|
148
|
+
immediately previous releases' rehearsal and final backups;
|
|
149
|
+
- read-only `dploydb releases` and `dploydb release show <release-id>` history
|
|
150
|
+
with validated active/previous pointers and preserved failure evidence;
|
|
151
|
+
- `dploydb restore <release-id>`, which previews the protected immediately
|
|
152
|
+
previous release, warns about data loss, and creates a verified backup of the
|
|
153
|
+
current state before any confirmed database replacement;
|
|
154
|
+
- `dploydb recover`, which reconciles durable intent, backups, checksums, exact
|
|
155
|
+
Docker resources, traffic evidence, and the OS lock before offering an
|
|
156
|
+
idempotent recovery plan;
|
|
157
|
+
- real abrupt-process crash tests after maintenance, current-app stop, and
|
|
158
|
+
production migration, plus a real backup-first previous-release restore;
|
|
159
|
+
- a deterministic Docker Compose demo application;
|
|
160
|
+
- working v1 and v2 release fixtures;
|
|
161
|
+
- a deliberately broken migration fixture;
|
|
162
|
+
- a deliberately unhealthy application fixture;
|
|
163
|
+
- a production-only final-health failure fixture used to prove real cutover
|
|
164
|
+
rollback after candidate validation passes;
|
|
165
|
+
- real SQLite reads, writes, and data-preserving migration behavior.
|
|
166
|
+
- explicit ANSI-free `--no-color`/`NO_COLOR` behavior and installed help audits
|
|
167
|
+
for every required command;
|
|
168
|
+
- finite per-operation evidence logs, with byte and record-count limits that
|
|
169
|
+
preserve append-only failure evidence;
|
|
170
|
+
- a clean-Linux-tested installed-CLI quick start, production first-run guide,
|
|
171
|
+
validated Nginx traffic hooks, security/limitations guidance, and an
|
|
172
|
+
uninstall path proven to preserve database, backup, release, and event bytes.
|
|
173
|
+
|
|
174
|
+
The demo controller is **not** the DployDB deployment engine. Migration
|
|
175
|
+
rehearsal and candidate validation are internal stages of the public deployment
|
|
176
|
+
engine. Controlled production cutover, automatic pre-traffic rollback,
|
|
177
|
+
release-aware manual restore, interrupted-operation recovery, verified
|
|
178
|
+
off-server backup, and protected retention are implemented.
|
|
179
|
+
|
|
180
|
+
## Prerequisites
|
|
181
|
+
|
|
182
|
+
- Python 3.12 or newer
|
|
183
|
+
- `pipx` for an isolated end-user installation
|
|
184
|
+
- Docker Engine or Docker Desktop
|
|
185
|
+
- Docker Compose
|
|
186
|
+
|
|
187
|
+
Install the published Alpha CLI:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pipx install dploydb==0.1.0
|
|
191
|
+
dploydb version
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Contributors can install the checked-out source tree with `pipx install .`.
|
|
195
|
+
|
|
196
|
+
Repository development and validation use [uv](https://docs.astral.sh/uv/):
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
uv sync --locked
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Start working v1
|
|
203
|
+
|
|
204
|
+
Reset the default demo database, apply the v1 migration, build the image, start the single Compose application service, and verify HTTP health:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
uv run python demo/controller.py start-v1
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The application is available at `http://127.0.0.1:4510`. Its real SQLite database is stored at:
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
demo/.state/default/data/app.db
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Create and read data through the application:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
curl --fail-with-body \
|
|
220
|
+
--header 'Content-Type: application/json' \
|
|
221
|
+
--data '{"body":"written under v1"}' \
|
|
222
|
+
http://127.0.0.1:4510/notes
|
|
223
|
+
|
|
224
|
+
curl --fail-with-body http://127.0.0.1:4510/notes
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Stop the container without deleting the database, then start v1 against the same data:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
uv run python demo/controller.py stop
|
|
231
|
+
uv run python demo/controller.py start v1
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Running `start-v1` again intentionally resets the default instance to an empty v1 database.
|
|
235
|
+
|
|
236
|
+
## Migrate to working v2
|
|
237
|
+
|
|
238
|
+
Start v1 and create a note first, then stop application writes and apply the v2 fixture migration:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
uv run python demo/controller.py stop
|
|
242
|
+
uv run python demo/controller.py migrate v2
|
|
243
|
+
uv run python demo/controller.py start v2
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The v1 note is preserved with the default category `general`. V2 also accepts an explicit category:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
curl --fail-with-body \
|
|
250
|
+
--header 'Content-Type: application/json' \
|
|
251
|
+
--data '{"body":"written under v2","category":"deployment"}' \
|
|
252
|
+
http://127.0.0.1:4510/notes
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
These commands are deterministic fixture controls, not safe deployment orchestration.
|
|
256
|
+
|
|
257
|
+
## Deploy with DployDB
|
|
258
|
+
|
|
259
|
+
After adapting the generated configuration to the real absolute database,
|
|
260
|
+
Compose, backup, health, migration, and traffic-hook paths, deploy with:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
dploydb deploy --version v2 --config /absolute/path/to/dploydb.yaml
|
|
264
|
+
dploydb deploy --version v2 --config /absolute/path/to/dploydb.yaml \
|
|
265
|
+
--json --non-interactive
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The command does not modify production until the verified snapshot, migration
|
|
269
|
+
rehearsal, and isolated candidate checks pass. During cutover it enables
|
|
270
|
+
maintenance, stops the exact current container, creates and verifies a final
|
|
271
|
+
backup, migrates production, starts and checks the new application while normal
|
|
272
|
+
traffic remains blocked, then activates traffic and records the release.
|
|
273
|
+
When `backup.remote.required` is true, the stopped-writer final backup must be
|
|
274
|
+
committed and read back from remote storage before production migration intent
|
|
275
|
+
is recorded. A failed upload restarts and verifies the previous application
|
|
276
|
+
without migrating or restoring the unchanged database.
|
|
277
|
+
|
|
278
|
+
If a failure occurs before traffic activation, DployDB restores the final
|
|
279
|
+
backup when needed, restarts the exact previous container, activates the old
|
|
280
|
+
target, disables maintenance, verifies the old application/database, and
|
|
281
|
+
returns an `outcome` of `rolled_back`. Once new traffic may be active, DployDB
|
|
282
|
+
does not automatically restore the old database; uncertain routing or
|
|
283
|
+
post-activation failure is reported as `recovery_required` with the evidence
|
|
284
|
+
log and next safe action.
|
|
285
|
+
|
|
286
|
+
## Inspect and restore releases
|
|
287
|
+
|
|
288
|
+
List validated local history or inspect one immutable release record:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
dploydb releases --config /absolute/path/to/dploydb.yaml
|
|
292
|
+
dploydb releases --config /absolute/path/to/dploydb.yaml --json
|
|
293
|
+
dploydb release show release_0123456789abcdef0123456789abcdef \
|
|
294
|
+
--config /absolute/path/to/dploydb.yaml --json
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Manual restore is intentionally limited to the protected immediately previous
|
|
298
|
+
release. Previewing is read-only and prints the exact application, database
|
|
299
|
+
backup, checksum, and data-loss warning:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
dploydb restore release_0123456789abcdef0123456789abcdef \
|
|
303
|
+
--config /absolute/path/to/dploydb.yaml
|
|
304
|
+
dploydb restore release_0123456789abcdef0123456789abcdef \
|
|
305
|
+
--config /absolute/path/to/dploydb.yaml --json
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
After reviewing the preview, confirm interactively or use `--yes` explicitly:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
dploydb restore release_0123456789abcdef0123456789abcdef \
|
|
312
|
+
--config /absolute/path/to/dploydb.yaml --yes
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
DployDB revalidates the selection under the deployment lock, enables
|
|
316
|
+
maintenance, stops the current writer, creates and verifies a `PRE_RESTORE`
|
|
317
|
+
backup, restores the selected database, restarts and checks the selected
|
|
318
|
+
application, switches traffic, and then swaps active/previous pointers. A
|
|
319
|
+
pre-traffic failure restores and verifies the pre-restore database and current
|
|
320
|
+
application. Once selected traffic may have been exposed, DployDB does not
|
|
321
|
+
automatically replace the database again.
|
|
322
|
+
|
|
323
|
+
## Recover an interrupted deployment
|
|
324
|
+
|
|
325
|
+
`status` reports an abrupt or contradictory operation with exit code `60`.
|
|
326
|
+
Diagnose it without mutation:
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
dploydb recover --config /absolute/path/to/dploydb.yaml
|
|
330
|
+
dploydb recover --config /absolute/path/to/dploydb.yaml --json
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
An executable plan lists its exact ordered actions and requires confirmation.
|
|
334
|
+
Run it interactively or acknowledge it explicitly:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
dploydb recover --config /absolute/path/to/dploydb.yaml --yes
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Recovery can return to the verified previous application/database when traffic
|
|
341
|
+
was not activated, or finish an already checked new release when durable hook
|
|
342
|
+
evidence proves activation succeeded. If traffic activation is uncertain,
|
|
343
|
+
backup lineage conflicts, live identities cannot be proven, or unrelated
|
|
344
|
+
operations are unfinished, it refuses automatic mutation with
|
|
345
|
+
`recovery_required`, the evidence log, and the next safe action. Re-running a
|
|
346
|
+
partially interrupted recovery re-inspects live state and skips database
|
|
347
|
+
replacement when the checksum already matches the verified target.
|
|
348
|
+
|
|
349
|
+
## Observe the broken migration
|
|
350
|
+
|
|
351
|
+
Return to a clean v1 database and create data, then stop the application:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
uv run python demo/controller.py start-v1
|
|
355
|
+
curl --fail-with-body \
|
|
356
|
+
--header 'Content-Type: application/json' \
|
|
357
|
+
--data '{"body":"must survive"}' \
|
|
358
|
+
http://127.0.0.1:4510/notes
|
|
359
|
+
uv run python demo/controller.py stop
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Run the broken migration:
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
uv run python demo/controller.py migrate broken-migration
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
It exits nonzero with the stable SQLite reason `no such table: deliberate_missing_table`. The migration is transactional, so the v1 schema and note remain unchanged. Restart v1 to inspect them:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
uv run python demo/controller.py start v1
|
|
372
|
+
curl --fail-with-body http://127.0.0.1:4510/notes
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Observe the broken-health release
|
|
376
|
+
|
|
377
|
+
The broken-health fixture performs a valid v1-to-v2 migration, starts normally, reads SQLite successfully, and then deliberately returns HTTP 503 from `/health`:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
uv run python demo/controller.py start-v1
|
|
381
|
+
uv run python demo/controller.py stop
|
|
382
|
+
uv run python demo/controller.py migrate broken-health
|
|
383
|
+
uv run python demo/controller.py up broken-health
|
|
384
|
+
uv run python demo/controller.py health
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
The last command exits nonzero and reports `fixture_broken_health`. To inspect the response directly:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
curl --include http://127.0.0.1:4510/health
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Instances, ports, and cleanup
|
|
394
|
+
|
|
395
|
+
Use separate state and Compose projects with global options placed before the command:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
uv run python demo/controller.py --instance second --port 4520 start-v1
|
|
399
|
+
uv run python demo/controller.py --instance second --port 4520 stop
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Generated databases live below `demo/.state/<instance>/` and are ignored by Git. `stop` preserves the selected database. `reset` stops the service and recreates a clean migrated v1 database without starting it:
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
uv run python demo/controller.py reset
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## Validation
|
|
409
|
+
|
|
410
|
+
Run the complete repository suite:
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
uv lock --check
|
|
414
|
+
uv sync --locked --check
|
|
415
|
+
uv run pytest -q
|
|
416
|
+
uv run ruff check .
|
|
417
|
+
uv run ruff format --check .
|
|
418
|
+
uv run mypy dploydb
|
|
419
|
+
uv run mypy demo
|
|
420
|
+
uv build
|
|
421
|
+
uv run python scripts/verify_pipx_install.py
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The Docker integration tests require a running Docker daemon and perform their own Compose cleanup.
|
|
425
|
+
|
|
426
|
+
## Configuration example
|
|
427
|
+
|
|
428
|
+
Create a restrictive starter configuration without overwriting an existing path:
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
uv run dploydb init
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
The generated `dploydb.yaml` is strict: duplicate keys, unknown fields, unsafe
|
|
435
|
+
candidate URLs, relative production paths, shell-style command strings, and
|
|
436
|
+
invalid timeout/retention values are rejected. `${VARIABLE}` interpolation is
|
|
437
|
+
resolved only after structural validation. Host and database checks are kept
|
|
438
|
+
out of parsing and performed by `doctor` or the relevant lock-tracked operation.
|
|
439
|
+
Candidate isolation defaults to container port `8080` and database volume target
|
|
440
|
+
`/data`; set `application.candidate_container_port` and
|
|
441
|
+
`application.database_volume_target` explicitly when the Compose service uses
|
|
442
|
+
different container-side values. Compose files may use the reserved
|
|
443
|
+
`DPLOYDB_VERSION` interpolation value; test-mode configuration cannot override it.
|
|
444
|
+
Production deployment additionally requires `application.production_project`,
|
|
445
|
+
`application.production_port`, and `application.production_health_url`, with a
|
|
446
|
+
distinct candidate port. All four traffic commands are bounded argument arrays;
|
|
447
|
+
they must implement maintenance on/off and new/old target activation
|
|
448
|
+
idempotently.
|
|
449
|
+
|
|
450
|
+
`demo/dploydb.yaml` is another valid example for the deterministic fixture. Its
|
|
451
|
+
`/srv/dploydb-demo` paths and placeholder traffic hooks must be adapted before
|
|
452
|
+
host validation or real use.
|
|
453
|
+
|
|
454
|
+
Run the implemented host checks with:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
uv run dploydb doctor --config /absolute/path/to/dploydb.yaml
|
|
458
|
+
uv run dploydb doctor --config /absolute/path/to/dploydb.yaml --deep
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Normal mode checks configuration, required paths and executables, Docker and
|
|
462
|
+
Compose CLI availability, candidate-port availability, lock ownership, durable
|
|
463
|
+
operation state, and bounded read-only SQLite `quick_check` and
|
|
464
|
+
`foreign_key_check` results. Deep mode additionally runs SQLite
|
|
465
|
+
`integrity_check`, cleaned-up write probes, disk-space checks, Docker daemon
|
|
466
|
+
inspection, and Compose service validation. When remote storage is disabled,
|
|
467
|
+
both modes report it as skipped. When enabled, normal mode validates its runtime
|
|
468
|
+
credentials and client configuration; deep mode also performs one bounded,
|
|
469
|
+
read-only, prefix-scoped bucket access probe. Migration execution, application
|
|
470
|
+
health, and traffic execution remain skipped. `doctor` never runs a developer
|
|
471
|
+
migration as a diagnostic; the implemented internal rehearsal stage runs it
|
|
472
|
+
only against a verified disposable copy inside a lock-tracked operation.
|
|
473
|
+
|
|
474
|
+
Inspect current state without creating, repairing, or deleting state files:
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
uv run dploydb status --config /absolute/path/to/dploydb.yaml
|
|
478
|
+
uv run dploydb status --config /absolute/path/to/dploydb.yaml --json
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
`status` exits `0` for coherent idle or active state and `60` for interrupted,
|
|
482
|
+
stale, contradictory, corrupt, or recovery-required state. See
|
|
483
|
+
`IMPLEMENTATION_PLAN.md` for the complete safety requirements and milestone
|
|
484
|
+
order.
|
|
485
|
+
|
|
486
|
+
Create and independently reverify a local backup:
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
uv run dploydb backup --config /absolute/path/to/dploydb.yaml
|
|
490
|
+
uv run dploydb backup --config /absolute/path/to/dploydb.yaml --json
|
|
491
|
+
uv run dploydb verify backup_0123456789abcdef0123456789abcdef \
|
|
492
|
+
--config /absolute/path/to/dploydb.yaml
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Enable an S3-compatible target by naming environment variables rather than
|
|
496
|
+
putting credentials in YAML. Cloudflare R2 uses its account endpoint and the
|
|
497
|
+
`auto` region:
|
|
498
|
+
|
|
499
|
+
```yaml
|
|
500
|
+
backup:
|
|
501
|
+
local_directory: /srv/dploydb/backups/example-app
|
|
502
|
+
keep_last: 10
|
|
503
|
+
remote:
|
|
504
|
+
enabled: true
|
|
505
|
+
required: true
|
|
506
|
+
provider: s3
|
|
507
|
+
bucket: example-backups
|
|
508
|
+
prefix: dploydb/example-app
|
|
509
|
+
region_name: auto
|
|
510
|
+
endpoint_url_env: DPLOYDB_S3_ENDPOINT_URL
|
|
511
|
+
access_key_env: DPLOYDB_S3_ACCESS_KEY_ID
|
|
512
|
+
secret_key_env: DPLOYDB_S3_SECRET_ACCESS_KEY
|
|
513
|
+
timeout_seconds: 30
|
|
514
|
+
max_attempts: 3
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
export DPLOYDB_S3_ENDPOINT_URL='https://ACCOUNT_ID.r2.cloudflarestorage.com'
|
|
519
|
+
export DPLOYDB_S3_ACCESS_KEY_ID='...'
|
|
520
|
+
export DPLOYDB_S3_SECRET_ACCESS_KEY='...'
|
|
521
|
+
dploydb doctor --deep --config /absolute/path/to/dploydb.yaml
|
|
522
|
+
dploydb backup --upload --config /absolute/path/to/dploydb.yaml --json
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Use an R2 S3 access-key pair with access limited to the selected bucket. An R2
|
|
526
|
+
API token is not used by DployDB's S3 adapter. Never commit credentials or pass
|
|
527
|
+
them as command-line arguments. Uploaded database bytes are not considered
|
|
528
|
+
committed until DployDB reads them back and verifies size and SHA-256, then
|
|
529
|
+
publishes and rereads immutable metadata. Restore downloads to a private
|
|
530
|
+
temporary file and repeats size, checksum, and SQLite verification.
|
|
531
|
+
For AWS S3, set `region_name` to the bucket's AWS region instead of `auto`.
|
|
532
|
+
|
|
533
|
+
To validate another S3-compatible service without writing credentials to disk
|
|
534
|
+
or command history, run the acceptance helper and answer its no-echo credential
|
|
535
|
+
prompts. It uses a unique child prefix, proves upload/list/download/SQLite
|
|
536
|
+
restore/delete, and removes only the objects it created:
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
.venv/bin/python scripts/verify_s3_compatibility.py
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
The configured backup directory must be owned privately with mode `0700` when
|
|
543
|
+
it already exists. Backup database and metadata files are written with mode
|
|
544
|
+
`0600`; metadata is published last and is the success marker. `verify` accepts
|
|
545
|
+
only committed backup IDs. Manual restore accepts a protected release ID rather
|
|
546
|
+
than a raw backup ID. After a release and its active pointer are durable,
|
|
547
|
+
retention keeps the newest `keep_last` unprotected backups and additionally
|
|
548
|
+
preserves every rehearsal and final backup referenced by the active and
|
|
549
|
+
immediately previous releases, both locally and remotely.
|
|
550
|
+
|
|
551
|
+
The rehearsal and candidate lifecycle APIs remain internal implementation
|
|
552
|
+
stages of `deploy`. A configured migration command must use `database.path_env`
|
|
553
|
+
for its database target and must not hard-code the production path or perform
|
|
554
|
+
unrelated production side effects; DployDB does not claim to sandbox an
|
|
555
|
+
arbitrary developer-supplied executable.
|
|
556
|
+
|
|
557
|
+
## Security, limitations, and removal
|
|
558
|
+
|
|
559
|
+
- [First production setup](docs/first-run.md)
|
|
560
|
+
- [Security model and trust boundaries](docs/security.md)
|
|
561
|
+
- [Supported scope, rollback boundary, and post-traffic data-loss risk](docs/limitations.md)
|
|
562
|
+
- [Nginx maintenance and fixed-port activation hooks](examples/nginx/README.md)
|
|
563
|
+
- [Uninstalling the CLI without deleting backups or evidence](docs/uninstall.md)
|
|
564
|
+
|
|
565
|
+
## Alpha lifecycle and compatibility
|
|
566
|
+
|
|
567
|
+
DployDB follows Semantic Versioning. Compatible `0.1.x` releases contain Alpha
|
|
568
|
+
fixes; later `0.x` minor releases may make documented breaking changes. `0.9.0`
|
|
569
|
+
is reserved for Beta, `1.0.0rc1` for the first release candidate, and `1.0.0`
|
|
570
|
+
for Stable. Durable state is never silently guessed or rewritten: an upgrade
|
|
571
|
+
must provide a tested migration or stop with an exact safe action.
|
|
572
|
+
|
|
573
|
+
Promotion is based on real deployment, failure-drill, recovery, and soak-time
|
|
574
|
+
evidence—not a calendar date. The exact gates and release procedure are in
|
|
575
|
+
[RELEASING.md](RELEASING.md), and user-visible changes are recorded in
|
|
576
|
+
[CHANGELOG.md](CHANGELOG.md).
|
|
577
|
+
|
|
578
|
+
## Community and license
|
|
579
|
+
|
|
580
|
+
DployDB is licensed under the [Apache License 2.0](LICENSE), copyright 2026
|
|
581
|
+
RecursiveWay. Bug reports and pull requests are welcome under
|
|
582
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) and the [community conduct policy](CODE_OF_CONDUCT.md).
|
|
583
|
+
Report vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
dploydb/__init__.py,sha256=-HhPgby0bxFKah7wZ5CLXEop3bLUdx3lsXwBfsTLTa0,23
|
|
2
|
+
dploydb/__main__.py,sha256=PHLc1laHp1zQcYy8Ga8Okmg-NAAEF9SWQqdguyF38xk,113
|
|
3
|
+
dploydb/backup.py,sha256=DqG1rZM4eRO4J0OAzf3N5pA2LoT-GXwnVhC1CLPi5gY,17772
|
|
4
|
+
dploydb/candidate.py,sha256=JhhJwAl0NLGRnW2yAAEIXypS3gj2lTAoDYWmPO32gwA,32765
|
|
5
|
+
dploydb/cli.py,sha256=A9dzlKCG2ZU0lKC376hCubf3jVIs5bUpP174JQh0LVk,38307
|
|
6
|
+
dploydb/config.py,sha256=BmW4SYmDvHws37k55CtLkJ4VQxWfD3cn_RnFTRJ4wP8,28630
|
|
7
|
+
dploydb/cutover.py,sha256=2fwrmtnwfomCjspcZqppCDFnVg1FHxWWsJlEy2IpYKI,12372
|
|
8
|
+
dploydb/deploy.py,sha256=J9X8VHbCUay_OcxUFVpgjzOBQU_SKbHAb8XppDvf34g,46448
|
|
9
|
+
dploydb/deployment_dependencies.py,sha256=0GqwEhpFkEUs8fYvwwLcYt_ydU4EzDP0olBqTJwl1Hg,11544
|
|
10
|
+
dploydb/deployment_evidence.py,sha256=FBWjgxw24ahgj4LHEx_slAF7aT6PLjHywtOQufKclsE,4736
|
|
11
|
+
dploydb/diagnostics.py,sha256=EfFeFg0KcmSUM-c-4T-5nEOTNkrZ4lU_OKvsqAz7sho,35276
|
|
12
|
+
dploydb/errors.py,sha256=2F-ZA15GgVwfSsVl1rnEJ0fLMW90wWsfiqHVWJojMFw,4367
|
|
13
|
+
dploydb/health.py,sha256=wfoeNIFSxkHUYTVwxn3D5Ymxu3u_brbhok7be6fkTp8,23763
|
|
14
|
+
dploydb/locking.py,sha256=oN0UIafnQ-ZZK0bxn7LuxTxQ2sFKwzyOVrudAAZVO7A,23041
|
|
15
|
+
dploydb/manual_restore.py,sha256=jtFGcaGSlgRNgcOErmWnBKb2OudxEcxd8AsU_TW7kCg,34905
|
|
16
|
+
dploydb/migration.py,sha256=S8EnryyBZ7h0tJmcnbt3E9BGxIqC_bGaAf9hc0uIv-o,23827
|
|
17
|
+
dploydb/models.py,sha256=5BaglCS7fONZ5rVWTDvhJ90o8V1B66VBDN20TJrCUxY,36741
|
|
18
|
+
dploydb/recovery.py,sha256=l3IEQK-KGrABHTydmCOWT3UzWp6q_6mmfHS0PACy5pM,45224
|
|
19
|
+
dploydb/redaction.py,sha256=mYZrEhLWwW15RFA2SmC9gEVjSkBiyF8GJLlXvsf3Alw,6993
|
|
20
|
+
dploydb/releases.py,sha256=_Xx-uAA85BLp7y3EcNlqzUT1JaS-8SSYJbEOVGgIfGs,25325
|
|
21
|
+
dploydb/restore.py,sha256=5Ny1Xo_aaNPKbv89_qM14EWi0XZqvL31sEKUsR8981Q,18210
|
|
22
|
+
dploydb/retention.py,sha256=IiWwXU6Zz48hhkB6iAkVW3dq7yt7dhLVyOuP3wz_QQM,5724
|
|
23
|
+
dploydb/sqlite_checks.py,sha256=HpELyvdRmvdwbVQMevaWc-Ye2RjeJjqVtwZkWseaIo4,4855
|
|
24
|
+
dploydb/state.py,sha256=IHZRUEwF45Ca5NgCE_JAbcCBDSxT2_H0ND1QvUr_pFc,24903
|
|
25
|
+
dploydb/subprocesses.py,sha256=cQJYZZIXB8hzhYwwSFRtaUxnhlfY05z-P2LWKVbtJFw,24666
|
|
26
|
+
dploydb/traffic.py,sha256=2PtkRNp3kALLg6rcrc2i9vXd4-ezeXxHnsVhqINlB8c,4916
|
|
27
|
+
dploydb/runners/__init__.py,sha256=qyOdSQNoo3qmXxR1MzdwE-qu-rPDS_nBUdzavXkjOF0,794
|
|
28
|
+
dploydb/runners/base.py,sha256=xLMKw3TJ0YcX-7wtrNfTrCQqVXKWWl4IECXyY4bT_7Q,10759
|
|
29
|
+
dploydb/runners/docker_compose.py,sha256=dnVrSd5T756-LvhtCLN30XpILNoxFBLlA99oECe4PsE,24449
|
|
30
|
+
dploydb/runners/docker_compose_production.py,sha256=DL-Rva7tzXlRfRRkZuzIrKdJSms_nJEnxDL1GRJEky0,39872
|
|
31
|
+
dploydb/storage/__init__.py,sha256=ZnFFkA5UVyit0L2zDSHa-c6nm8D2ZU5FsMcl988zbMA,364
|
|
32
|
+
dploydb/storage/base.py,sha256=omdwuILs3E13OnV-jK8JbuuEGicJjG-1niPYTpfIepU,1398
|
|
33
|
+
dploydb/storage/local.py,sha256=tcJuZeSPSelzIsseaI3RZHn-pVELGO2x-x6FJXZ0qdQ,12413
|
|
34
|
+
dploydb/storage/s3.py,sha256=WkW8rSKiPfRQGW8YPuRZvNavLNtTrcIdEQDijQtu4KU,28832
|
|
35
|
+
dploydb-0.1.0.dist-info/METADATA,sha256=pErKbmtkoEHI4o50tw9-Njs5qG_KFxdkdhiufH6p7DA,24776
|
|
36
|
+
dploydb-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
37
|
+
dploydb-0.1.0.dist-info/entry_points.txt,sha256=ikcWIHND_jnIPJsMH_0XNUuXmAJmIPdH1sBNq1TY89U,44
|
|
38
|
+
dploydb-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
39
|
+
dploydb-0.1.0.dist-info/licenses/NOTICE,sha256=YMJpi072jVWQDuWPPG4N9GBMOK0NUr6QHKiFMzk2HqI,101
|
|
40
|
+
dploydb-0.1.0.dist-info/RECORD,,
|