Skip to content

Commit a158a89

Browse files
committed
feat!: make guided setup reviewed and verifiable
- Embed the pinned lego provider catalog and verify DNS credentials by issuing one wildcard certificate reused by every database route - Support local and rclone Restic repositories with preflight and safe non-root execution - Preserve configurable Postgres identities across runtime, backups, PgBouncer, and connection details - Add append-only review and edit flows with contextual output and help BREAKING CHANGE: Postgres roles must declare username and database fields. DNS settings must match the bundled provider catalog. Rclone repositories must use 'rclone:REMOTE:PATH', while local repositories must omit 'rclone_config'.
1 parent e964739 commit a158a89

73 files changed

Lines changed: 6417 additions & 383 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,3 @@
88
- Never commit `.env`, backup files, dumps, rclone config, or resolved secrets.
99
- Source config contains `op://` references only.
1010
- Tests use disposable containers and local Restic repositories only.
11-
- Do not run deployment, Restic, Docker, cron, or systemd changes on `montreal-01` while
12-
implementing `build-db-system`.
13-
- Production migration is a separate OpenSpec change.

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ integration-collect:
4848

4949
binary:
5050
$(PYINSTALLER) --clean --noconfirm --onefile --name evdb --paths src \
51+
--add-data src/evdb/dns_providers.json:evdb \
5152
--add-data src/evdb/units:evdb/units src/evdb/__main__.py
5253

5354
require-db:

README.md

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,30 @@ Choose a host ID and base domain. For a host ID of `example-01` and a base domai
3939
`storage.example.com`, arrange for `*.example-01.storage.example.com` to resolve to the server from
4040
your clients. evdb uses DNS-01 to issue certificates but does not create DNS records.
4141

42-
Create a private file containing the environment variables Traefik requires for your DNS provider,
43-
one `KEY=VALUE` per line. Keep its path for initialization.
42+
Guided initialization includes every DNS provider supported by the pinned Traefik release. It shows the
43+
provider's documented variables and help URL, accepts credentials with masked input, and verifies them by
44+
obtaining one certificate for `*.<host-id>.<base-domain>`. For non-interactive setup, prepare a private
45+
file containing documented `KEY=VALUE` lines and pass it with `--dns-file`.
4446

45-
Configure your backup remote as the normal non-root user who will own remote access:
47+
For remote backups, configure rclone as the normal non-root user who will own remote access:
4648

4749
```sh
4850
rclone config
4951
rclone config file
5052
rclone lsd remote:
5153
```
5254

53-
Replace `remote` with the configured remote name. Set the reported rclone configuration file to mode
54-
`0600`; evdb requires its absolute path and uses it without copying it. A typical Restic repository is
55-
`rclone:remote:evdb/example-01`.
55+
Set the reported rclone configuration file to mode `0600`; evdb uses it in place. Guided setup reads its
56+
configured remotes and asks you to select one, then defaults the repository path to `evdb/<host-id>`.
57+
58+
For a local Restic repository, create only its parent as the non-root backup user. For example:
59+
60+
```sh
61+
install -d -m 0700 "$HOME/restic"
62+
```
63+
64+
Guided setup can then use an absent child such as `$HOME/restic/example-01`. Local mode does not require
65+
rclone and derives the Restic process identity from that safe parent.
5666

5767
## Install evdb
5868

@@ -72,17 +82,18 @@ Start guided initialization:
7282
sudo evdb init
7383
```
7484

75-
Enter the host ID, base domain, ACME email, Traefik DNS provider name, Restic repository, DNS
76-
credential file, and absolute rclone configuration path prepared above. Leave the initial Restic
77-
password blank to generate one.
85+
The append-only setup flow validates each answer, searches supported DNS providers, collects documented
86+
credentials, selects rclone or local storage, and shows a redacted review before Apply. Leave the initial
87+
Restic password blank to generate one. Setup waits for the wildcard certificate and verifies or creates
88+
the Restic repository before enabling automatic backups.
7889

7990
Initialization stores configuration under `/etc/evdb`, managed data under `/var/lib/evdb`, starts the
8091
TLS router, initializes the Restic repository, and enables automatic daily backups.
8192

8293
## Create your first database
8394

84-
Project names must end in `-dev-N`, `-test-N`, or `-prod-N`. Create Postgres and retrieve its
85-
TLS-secured connection URL:
95+
Project names must end in `-dev-N`, `-test-N`, or `-prod-N`. Create Postgres with a generated managed
96+
login and retrieve its TLS-secured connection URL:
8697

8798
```sh
8899
sudo evdb database add notes-prod-01 postgres
@@ -96,6 +107,18 @@ postgresql://default:<password>@notes-prod-01.example-01.storage.example.com:543
96107
Use the URL with any standard Postgres client. `database info` prints complete credentials and
97108
therefore requires a terminal.
98109

110+
Guided creation has an Advanced option for preserving an existing username, database name, and password
111+
when you import data yourself. Automation can provide the same creation-only identity without exposing
112+
the password in process arguments:
113+
114+
```sh
115+
sudo evdb database add imported-prod-01 postgres \
116+
--username app_user --database-name app_db --password-file /private/postgres-password
117+
```
118+
119+
evdb does not import the source database or rotate that identity after creation; use standard Postgres
120+
dump and restore tools for the data transfer.
121+
99122
## CLI usage
100123

101124
Run `sudo evdb` for the guided terminal interface. Direct commands execute immediately and are useful

openspec/changes/archive/2026-07-25-build-db-system/design.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## Context
22

3-
`montreal-01` currently has 25 managed database instances under `/home/ubuntu/databases`: 14 Postgres, 7 Dragonfly, and 4 Redis. The current scripts back up only 6 Postgres and 4 KV instances. They delete prior local files before upload is known to be good, handle each engine as one batch, do not check Restic results well enough, and cannot produce a safe Dragonfly RDB.
3+
`production-host` currently has 25 managed database instances under `/home/ubuntu/databases`: 14 Postgres, 7 Dragonfly, and 4 Redis. The current scripts back up only 6 Postgres and 4 KV instances. They delete prior local files before upload is known to be good, handle each engine as one batch, do not check Restic results well enough, and cannot produce a safe Dragonfly RDB.
44

55
Read-only Docker inspection confirmed that the managed database, PgBouncer, HTTP sidecar, and native Traefik containers have no Docker CPU, memory, cpuset, or PID limits. Source config records that current resource contract as `unlimited`, and generated Compose preserves it by adding no resource limit keys.
66

@@ -27,7 +27,7 @@ Production uses Ubuntu 22.04 ARM64, Python 3.10, Docker 29, Compose 2.40, Restic
2727

2828
**Non-Goals:**
2929

30-
- Deploying any part of this repository to `montreal-01`.
30+
- Deploying any part of this repository to `production-host`.
3131
- Replacing cron, changing Compose ownership, restarting databases, changing images, upgrading Restic, rotating secrets, or touching the OneDrive repositories.
3232
- Changing any running Redis instance to Dragonfly.
3333
- Moving database data or redesigning Traefik.
@@ -65,7 +65,7 @@ src/evdb/
6565
Related deployment and test files use the same names:
6666

6767
```text
68-
config/montreal-01/{host,postgres,kv}.yml
68+
config/production-host/{host,postgres,kv}.yml
6969
compose/{postgres,redis,dragonfly,traefik}.yml.j2
7070
ansible/{hosts,backup,databases,restore}.yml
7171
ansible/roles/{base,app,backup,postgres,kv,traefik}/
@@ -78,7 +78,7 @@ The alternative flat layout was shorter but would mix engine backup and restore
7878

7979
### Source YAML and runtime JSON
8080

81-
Humans edit three small YAML files for `montreal-01`. Ansible and local config tools render one JSON file per instance. Production Python reads only JSON.
81+
Humans edit three small YAML files for `production-host`. Ansible and local config tools render one JSON file per instance. Production Python reads only JSON.
8282

8383
Each instance keeps two simple sections:
8484

@@ -181,11 +181,11 @@ Compose templates are separate for Postgres, Redis, and Dragonfly. They preserve
181181
Traefik is the only service that publishes host ports 5432 and 6379. Postgres, PgBouncer, Redis, and Dragonfly stay internal.
182182

183183
```text
184-
<instance>.postgres-montreal-01.storage.evanovation.com:5432
184+
<instance>.postgres-production-host.storage.evanovation.com:5432
185185
-> Traefik TLS HostSNI
186186
-> <instance>-pgbouncer-1:5432
187187
188-
<instance>.kv-montreal-01.storage.evanovation.com:6379
188+
<instance>.kv-production-host.storage.evanovation.com:6379
189189
-> Traefik TLS HostSNI
190190
-> <instance>-redis-1:6379
191191
```
@@ -199,7 +199,7 @@ Every enabled KV HTTP sidecar runs in its instance's Compose project, connects t
199199
Public HTTP is owned by an external proxy:
200200

201201
```text
202-
https://<instance>.kv-montreal-01.storage.evanovation.com
202+
https://<instance>.kv-production-host.storage.evanovation.com
203203
-> external proxy :443
204204
-> 127.0.0.1:133xx
205205
-> serverless-redis-http
@@ -214,7 +214,7 @@ The `app` role installs a root-owned release at `/opt/evdb/releases/<git-sha>` a
214214

215215
The service account owns state and secret files and belongs to the Docker group. Documentation calls out that Docker access is root-equivalent.
216216

217-
Production is never the default target. Make and Ansible require an explicit target, and any production apply also requires an explicit production flag. The integration suite creates disposable local inventory and does not use `montreal-01`. HTTP tests exercise only disposable sidecars and never contact an external proxy.
217+
Production is never the default target. Make and Ansible require an explicit target, and any production apply also requires an explicit production flag. The integration suite creates disposable local inventory and does not use `production-host`. HTTP tests exercise only disposable sidecars and never contact an external proxy.
218218

219219
### Systemd jobs and status
220220

openspec/changes/archive/2026-07-25-build-db-system/proposal.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Evanovation's database setup is spread across host files, shared Compose templat
1313
- Add Restic upload, snapshot tracking, retention, prune, and repository check support while keeping repository format v1.
1414
- Add Ansible, Compose templates, and systemd units for the finished system and test them on disposable local hosts.
1515
- Add local integration tests, CI, secret scanning, and operator documentation.
16-
- Keep production unchanged. Deployment to `montreal-01`, replacement of cron, Restic upgrades, image changes, secret rotation, container restarts, and Compose takeover will be handled by a later production move.
16+
- Keep production unchanged. Deployment to `production-host`, replacement of cron, Restic upgrades, image changes, secret rotation, container restarts, and Compose takeover will be handled by a later production move.
1717

1818
## Capabilities
1919

openspec/changes/archive/2026-07-25-build-db-system/specs/config/spec.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,18 @@ Validation SHALL reject duplicate instance ids and Compose projects within the s
5959
- **THEN** validation accepts the shared listeners and checks instance routes by unique domain
6060

6161
### Requirement: HTTP settings
62-
Each KV instance SHALL record whether its serverless HTTP sidecar is enabled, its unique loopback port, intended external domain, pinned image, token reference, and connection limit. The initial `montreal-01` config SHALL enable the sidecar for all 11 KV instances.
62+
Each KV instance SHALL record whether its serverless HTTP sidecar is enabled, its unique loopback port, intended external domain, pinned image, token reference, and connection limit. The initial `production-host` config SHALL enable the sidecar for all 11 KV instances.
6363

6464
#### Scenario: External route data is recorded
6565
- **WHEN** a KV instance defines its intended HTTP domain and loopback port
6666
- **THEN** validation checks the contract without inspecting or changing the external proxy
6767

6868
#### Scenario: Initial HTTP coverage is checked
69-
- **WHEN** the initial `montreal-01` config is validated
69+
- **WHEN** the initial `production-host` config is validated
7070
- **THEN** all 11 KV instances have enabled HTTP settings with unique loopback ports
7171

7272
### Requirement: Standard HTTP domains
73-
Target HTTP domains SHALL use `<instance>.kv-montreal-01.storage.evanovation.com`. Current legacy domains MAY be recorded as current facts, but the target SHALL NOT use `kv-na01.storage.evanovation.com`.
73+
Target HTTP domains SHALL use `<instance>.kv-production-host.storage.evanovation.com`. Current legacy domains MAY be recorded as current facts, but the target SHALL NOT use `kv-na01.storage.evanovation.com`.
7474

7575
#### Scenario: Legacy HTTP domain is recorded
7676
- **WHEN** a current route uses the old `kv-na01.storage.evanovation.com` suffix

openspec/changes/archive/2026-07-25-build-db-system/specs/deploy/spec.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Ansible, rendered Compose, JSON config, and systemd units SHALL be tested on dis
6565
- **THEN** the test host can run config validation and local backup and restore commands without production credentials
6666

6767
### Requirement: Production guard
68-
Production SHALL NOT be a default Ansible target. Any future production run SHALL require the production inventory and an explicit apply flag. This change SHALL NOT run those playbooks against `montreal-01`.
68+
Production SHALL NOT be a default Ansible target. Any future production run SHALL require the production inventory and an explicit apply flag. This change SHALL NOT run those playbooks against `production-host`.
6969

7070
#### Scenario: Deploy command has no target
7171
- **WHEN** a deploy target is omitted

openspec/changes/archive/2026-07-25-build-db-system/specs/http/spec.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ The repository SHALL record each sidecar's intended domain and loopback port as
4343
The missing `oai-co-prod-02` route and two current `kv-na01.storage.evanovation.com` domains SHALL be recorded only as informational follow-ups for the external proxy owner during the later production move.
4444

4545
#### Scenario: Initial HTTP contract is rendered
46-
- **WHEN** all `montreal-01` KV config is rendered
46+
- **WHEN** all `production-host` KV config is rendered
4747
- **THEN** it contains 11 unique intended domains and 11 unique loopback ports without a proxy route plan
4848

4949
### Requirement: HTTP authentication

openspec/changes/archive/2026-07-25-simplify-db-manager/design.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The implementation must continue to use subprocess argument arrays, keep product
2121

2222
**Non-Goals:**
2323

24-
- Deploying this redesign to `montreal-01` or changing any running production container.
24+
- Deploying this redesign to `production-host` or changing any running production container.
2525
- Providing a web dashboard, persistent control-plane service, or public API.
2626
- Managing the external HTTP proxy.
2727
- Purging database data, backup history, or 1Password items.
@@ -41,7 +41,7 @@ This is preferred over YAML anchors, profiles, or inheritance because those mech
4141

4242
### Generated lock and observed state
4343

44-
`host.lock.json` will be a tool-owned, secret-free file beside `host.yml`. It will record the platform-specific digest resolved for each host-level image and stable assigned values that cannot be recomputed safely, initially HTTP loopback ports. Existing Montreal ports will seed the first lock; new ports will use the next free value in the configured range. Operators may review and commit the lock but never edit it manually.
44+
`host.lock.json` will be a tool-owned, secret-free file beside `host.yml`. It will record the platform-specific digest resolved for each host-level image and stable assigned values that cannot be recomputed safely, initially HTTP loopback ports. Existing production ports will seed the first lock; new ports will use the next free value in the configured range. Operators may review and commit the lock but never edit it manually.
4545

4646
Observed Docker facts will not be source configuration. `evdb plan` will obtain the active release manifest and live status over SSH and compare them with the normalized desired model. This replaces the public `current` and `target` sections.
4747

@@ -67,7 +67,7 @@ Items are not deleted when config writes, applies, rollbacks, or retirements fai
6767

6868
`evdb show <database>` will combine derived configuration, active release facts, and live engine status into one detail view. For Postgres it will show engine and image versions, state, hostname, port, username, database name, TLS requirement, 1Password item, data path, container name, backup summary, and a complete `postgresql://` URL. For Redis and Dragonfly it will show the corresponding native TLS `rediss://` URL and, when enabled, the public HTTP endpoint and token.
6969

70-
The command intentionally reveals the full connection URL and HTTP token every time, matching the existing Montreal manager behavior selected by the operator. It will resolve credentials from 1Password on the local controller, percent-encode URL components, and print them directly to the terminal. Credentials will not be requested from the remote host, included in SSH or child-process arguments, emitted to structured logs, or persisted in state. If local 1Password authentication cannot resolve a required field, the command will fail rather than print a misleading partial URL.
70+
The command intentionally reveals the full connection URL and HTTP token every time, matching the existing production manager behavior selected by the operator. It will resolve credentials from 1Password on the local controller, percent-encode URL components, and print them directly to the terminal. Credentials will not be requested from the remote host, included in SSH or child-process arguments, emitted to structured logs, or persisted in state. If local 1Password authentication cannot resolve a required field, the command will fail rather than print a misleading partial URL.
7171

7272
This explicit details command is the only normal output allowed to contain credentials. Status, plan, logs, backup history, release history, and all JSON machine output remain secret-free.
7373

@@ -130,7 +130,7 @@ Commands accept a plain database name when it is unique. If the same name exists
130130
## Migration Plan
131131

132132
1. Add the new source parser and normalizer alongside fixtures that prove concise entries expand to the current 25-instance deployment contract.
133-
2. Convert Montreal source config to one `host.yml`, delete the two instance files, and seed `host.lock.json` with current image digests and HTTP ports.
133+
2. Convert production source config to one `host.yml`, delete the two instance files, and seed `host.lock.json` with current image digests and HTTP ports.
134134
3. Add the local controller, remote JSON protocol, 1Password manager, and status/lifecycle commands against fakes and disposable hosts.
135135
4. Adapt Ansible and Compose rendering to normalized input, then add staged release activation and rollback on disposable Docker projects.
136136
5. Extend isolated restore code with persistent candidates and promotion, including forced failure recovery tests.

openspec/changes/archive/2026-07-25-simplify-db-manager/proposal.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ The current system exposes migration inventory and deployment internals instead
99
- Add a generated lock file for resolved image digests and stable allocated values such as HTTP ports; operators do not edit this file.
1010
- Add a short `evdb` command that runs locally and uses SSH to manage the configured production host.
1111
- Add plan, apply, create, start, stop, restart, status, and log workflows while keeping Ansible as an internal deployment mechanism.
12-
- Add a database details command that shows live metadata, native and HTTP endpoints, and complete usable connection URLs like the existing Montreal manager.
12+
- Add a database details command that shows live metadata, native and HTTP endpoints, and complete usable connection URLs like the existing production manager.
1313
- Manage convention-based database passwords and tokens in 1Password from the local controller without exposing secret values in arguments, config, state, or logs; the details command intentionally reveals requested credentials only in terminal output.
1414
- Present backup creation, backup history, and full restore verification as direct operator workflows.
1515
- Add deployment release history and rollback without changing database data.
@@ -35,9 +35,9 @@ None. The repository has no archived main capability specs; these capabilities s
3535

3636
## Impact
3737

38-
- Replaces the source configuration model and command syntax in `src/evdb/config.py`, `src/evdb/cli.py`, the checked-in Montreal configuration, tests, and documentation.
38+
- Replaces the source configuration model and command syntax in `src/evdb/config.py`, `src/evdb/cli.py`, the checked-in production configuration, tests, and documentation.
3939
- Adds local SSH transport, 1Password item management, desired-versus-deployed planning, Compose lifecycle control, and deployment release manifests.
4040
- Changes Ansible roles and Compose rendering to consume normalized generated configuration rather than three human-authored inventory files.
4141
- Extends existing backup, Restic, status, and isolated restore code instead of replacing their validated data-handling paths.
4242
- Requires a write-capable local 1Password session or service account and SSH/sudo access to the managed host.
43-
- Does not deploy to, restart, or otherwise write to `montreal-01` as part of this change.
43+
- Does not deploy to, restart, or otherwise write to `production-host` as part of this change.

0 commit comments

Comments
 (0)