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.
@@ -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
+ [![CI](https://github.com/recursiveway/dployDB/actions/workflows/ci.yml/badge.svg)](https://github.com/recursiveway/dployDB/actions/workflows/ci.yml)
34
+ [![PyPI](https://img.shields.io/pypi/v/dploydb.svg)](https://pypi.org/project/dploydb/)
35
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/downloads/)
36
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dploydb = dploydb.cli:app