feat: ubuntu/ migration → v0.5.0 (db / stack / web nginx + version constraints + DockerEnv)#7
Conversation
Per the migration task spec. Three pause points; this commit lands
the first three.
Phase 1 (Discovery & Audit):
- `.design/architecture-current.md` — snapshot of shimkit's pre-
migration architecture: 11 tools, 5 load-bearing rules, JSON
configurator, MODERATE/SEVERE prompt model, EX_* exit codes,
test/CI/release layout.
- `.design/plans/_workspace/source-inventory.md` — 88-file walk
of /Users/imanimanyara/Artisan/projects/opensource/simtabi/ubuntu/.
Canonical feature surface lives at
`scripts/initializers/scripts/{installers,configurators}/` (22
scripts). Three legacy/dupe trees identified for archive-only.
- `.design/plans/_workspace/risk-flags.md` — 5 Critical + 7 High
+ 7 Medium + 4 Low. No hardcoded secrets/IPs/keys. Critical items
(apparmor disable, mongo+mysql 0.0.0.0 bind, deprecated apt-key,
curl|sh) all dissolve under the Docker-first redesign.
Phase 2 (Feature Comparison):
- `.design/plans/feature-gap-analysis.md` — feature matrix +
Adopt/Skip/Improve/Defer buckets. Adopt: 4 new tools (db, stack,
web nginx, shell colors). Skip: 15 features (broken / out-of-
charter / dotfiles-territory / better-tools-exist). Defer to
v0.5.x: certbot, generic cron, framework laravel, --on-host
mode. Improve: hardened nginx vhost template.
Phase 3 (Design):
- `.design/architecture-target.md` — post-migration layout. Two
new core primitives (`core/docker.py` for the Docker SDK
chokepoint, `core/version.py` for the constraints subsystem).
Three new tool sub-trees (`db/engines/*`, `stack/lemp.py`,
`web/nginx/`). All five existing rules preserved.
- `.design/version-constraints-spec.md` — config-driven tool
version registry with packaging.SpecifierSet comparison. Three
enforcement points (install docs / runtime preflight / `shimkit
doctor`) and four distinct status outcomes (OK / OUT_OF_RANGE /
MISSING / UNPARSEABLE) with documented exit-code mappings.
- `.design/plans/migration-plan.md` — 9 atomic work items
(W1..W9), dependency graph, must-have/should-have/nice-to-have
tagging, total ≈2,110 LOC + ~153 tests.
Maintainer decisions captured (`shimkit db` naming, port prefix
`:13306/15432/17017`, volume root `~/.shimkit/data/db/`, reuse
`[docker-clean]` extra for Docker SDK).
No code yet. Next checkpoint: maintainer approval of the migration
plan, then begin W1 (version constraints).
W1 of the ubuntu-migration plan. Adds a config-driven version registry consulted at three enforcement points (install docs, runtime preflight, `shimkit doctor`) with four distinct outcomes (OK / OUT_OF_RANGE / MISSING / UNPARSEABLE). Surface (`shimkit.core.version`): - `Status` enum - `ToolVersion(name, version: packaging.Version | None, raw: str)` - `VersionConstraint(min, max, preferred)` — dataclass with `to_specifier_set()` + `check()` - `Detector(argv, parse)` — pure descriptor of how to ask a tool for its version. Five built-in: docker / nginx / git / gpg / python. - `Result(tool, status, tool_version, constraint, remediation)` - `detect(tool)` / `validate(tool)` / `validate_all()` / `preflight(tools, force=)` - `VersionViolationError` — raised by preflight on OUT_OF_RANGE/MISSING (force=False); MISSING raises even with force=True (force can't conjure a binary); UNPARSEABLE never raises (warn-only — we don't brick the user when a vendor changes their version-string format). Config: - `VersionConstraint` + `VersionsConfig` pydantic models in `config/schema.py` (extra=forbid, like the rest of the schema). - `tools.versions` block in `defaults.json` with `min` set for the tools shimkit itself depends on (docker>=20.10, nginx>=1.20, git>=2.30, gpg>=2.2, python>=3.10). `shimkit doctor` extended with a `versions` section that tabulates status + remediation hints per detected tool. Hints are platform- specific (`brew install …` on macOS; `apt-get install …` on Linux) via `_remediation_for()`. Tests: 35 (351 → 386 total). Pure detector-parse fixtures per tool (offline captures), constraint-check matrix (10 cases), detect/validate happy + sad paths for each outcome, preflight exit-code behavior (incl. force=True semantics), config readback. Renamed: VersionViolation → VersionViolationError to satisfy ruff N818 (exception class names should end in Error). Gates: pytest 386 passed, ruff clean, mypy strict clean, bandit medium=0. No new optional extras (uses `packaging` which is a transitive of pydantic, already pinned by shimkit's runtime deps).
… SDK W2 of the ubuntu-migration plan. The new `db` / `stack` tools route all SDK calls through `DockerEnv` so: - The daemon-reachability check happens once at `boot()`, not on every operation. - Container + volume naming conventions are enforced (`shimkit-<scope>-<kind>-<id>`; volumes at `~/.shimkit/data/db/<engine>-<id>/`). - Containers shimkit creates carry a `shimkit.tool` label, so `list_managed()` can enumerate them and `docker-clean` can eventually be smart about preserving them. Surface (`shimkit.core.docker`): - `DockerEnv.create().boot()` — builder pattern; `boot()` exits 69 with a clear remediation message when the SDK isn't installed or the daemon isn't reachable. Idempotent. - `container_name(scope, kind, id_='dev')` / `volume_path(engine, id_='dev')` — naming conventions. - `find(name)` / `run(image, name=, env=, ports=, volumes=, ...)` / `start(name)` / `stop(name, timeout=)` / `remove(name, force=)` / `exec(name, cmd, ...)` / `logs(name, follow=, tail=)` / `list_managed(scope=)` / `remove_volume(path)`. - `ExecOutcome(exit_code, stdout, stderr)` — typed dataclass with an `ok` property. - `DockerNotAvailableError` — raised by `_from_env` boundary; the manager catches and maps to exit 69. Tests mock here, not at the docker package level, so no real daemon access in any test. Safety: - `remove_volume(path)` refuses any path outside `~/.shimkit/data/` via a `Path.relative_to(root)` check. Tests verify the refusal. - `run()` auto-applies `shimkit.tool=<scope>` from the container name. Future `docker-clean` pass can preserve these by default. Tests: 28 (386 → 414 total). Naming conventions (4), boot states (SDK missing / daemon unreachable / idempotent), find/run/start/ stop/remove (10), exec (3: missing + demux'd output + non-tuple output), logs / list_managed (4), remove_volume safety (3). The `docker` SDK was already a transitive dependency via the `[docker-clean]` extra; no new extras required. The existing `docker_clean/client.py` will migrate to `DockerEnv` in a future pass; for now they coexist (chokepoint is additive, not replacing). Gates: pytest 414 passed, ruff clean, mypy strict clean, bandit medium=0.
W3 of the ubuntu-migration plan. First server-class tool to land
under the Docker-first charter expansion. No host-install path —
shimkit's value here is the container lifecycle, not apt-key
wrangling.
Engines (registry pattern, one file per engine):
- mysql (mysql:8.0, 127.0.0.1:13306)
- mariadb (mariadb:10.11, 127.0.0.1:13307)
- postgres (postgres:16, 127.0.0.1:15432)
- mongo (mongo:7, 127.0.0.1:17017)
- phpmyadmin (phpmyadmin:5, 127.0.0.1:18080) — web UI, no shell/dump
Surface:
shimkit db ls
shimkit db <engine> up [--port N] [--bind HOST] [--volume PATH] [--ephemeral]
[--name id] [--password PWD] [--yes]
shimkit db <engine> down [--yes]
shimkit db <engine> shell # interactive
shimkit db <engine> dump [--out PATH] # stdout or file
shimkit db <engine> reset --confirm RESET-DB # SEVERE
shimkit db <engine> status [--json]
Architecture:
- `DbManager` builds on `core.DockerEnv` (W2): every SDK call goes
through the chokepoint. `boot()` first runs
`version.preflight(("docker",))` from W1, so a too-old docker
exits 69 with a structured remediation message.
- Per-engine `Engine` ABC + 5 concrete drivers. The shared
`_EngineBound` holds the lifecycle methods (up/down/shell/dump/
reset/status) so adding a new engine is a single file + a single
registry-dict line.
- Container naming: `shimkit-db-<engine>-<id>` (id default "dev").
Labels: `shimkit.tool=db` so `db ls` and future `docker-clean`
can identify shimkit-managed containers.
- Volume layout: `~/.shimkit/data/db/<engine>-<id>/`. `reset`
refuses to delete anything outside `~/.shimkit/data/` via
Path.relative_to safety check inherited from DockerEnv.
- Default bind is 127.0.0.1 — opt-in to wider exposure via
`--bind 0.0.0.0` (no auto-prompt, but pair with a real
`--password`).
- phpmyadmin special-cases: stateless (no volume), no shell/dump
(returns False on capability flags), routes to backing DB via
host.docker.internal + extra_hosts host-gateway for Linux.
Idempotency:
- `up` of an already-running container → no-op (action=already_running)
- `up` of an existing-but-stopped → `docker start` (action=started)
- `up` of nothing → `docker run` (action=created)
SEVERE-tier `reset`: --confirm RESET-DB (configurable via
`tools.db.reset_severe_token`) is required; container removed
with --force, volume directory deleted recursively (defended by
the relative_to check).
`shimkit db ls` enumerates every container with the
`shimkit.tool=db` label across all engines — useful when juggling
project-specific dev DBs.
Tests: 35 (414 → 449). Engine pure-value-producer tests for all 5
(env / shell argv / dump argv / phpmyadmin capability refusals),
DockerEnv boot states, up matrix (fresh / running / stopped / dry-
run / ephemeral / custom-id), down (stopped + missing), shell
(unsupported engine + missing + happy-path docker-exec argv),
dump (stdout + file + phpmyadmin refuse), reset (without token /
with token + volume cleanup / dry-run), status / ls (empty + with
containers + scope-filter assertion).
Manager wires up `version.preflight(("docker",), force=force)` —
the first wired preflight from W1. `--force` lets the user bypass
an out-of-range docker; missing docker still raises.
Gates: pytest 449 passed, ruff clean, mypy strict clean, bandit
medium=0 (one B104 false-positive on a parser fallback annotated
in-place).
`docs/tools/db.md` added. No new optional extras (reuses the
existing [docker-clean] extra's docker>=7.1).
W4 of the ubuntu-migration plan. Parent `shimkit web` sub-app with
one tool today (nginx). File-only by default; mutating the host is
SEVERE-tier.
Surface:
shimkit web nginx vhost generate \
--name N --domain D --root R [--flavor static|php|laravel]
[--php-version V] [--out PATH] [--json]
shimkit web nginx vhost apply --name N --source PATH \
--confirm APPLY-VHOST # SEVERE
shimkit web nginx vhost remove --name N --confirm REMOVE-VHOST # SEVERE
shimkit web nginx vhost list [--json]
Templates (stdlib `string.Template`, no Jinja dep):
- 3 flavors: static / php / laravel. The php flavor parameterises
the FPM socket version (default 8.3, configurable). Laravel adds
`/public` to the root and fallbacks to /index.php.
- Borrowed security headers from the source ubuntu nginx:host.sh:
X-Frame-Options, X-Content-Type-Options, X-XSS-Protection,
Referrer-Policy, server_tokens off.
- Modern additions: Permissions-Policy: interest-cohort=().
- Dotfile-deny: `location ~ /\.(?!well-known) { deny all; }`.
- HSTS deliberately NOT included by default (footgun on HTTP-only).
- Every flavor begins with the configurable managed-marker
(`# managed-by: shimkit`) so apply/remove can identify and refuse
to clobber non-shimkit vhosts.
apply path:
- Reads source file, refuses without the managed marker.
- Refuses to overwrite an existing target unless IT is shimkit-
managed (no admin-vhost clobbering).
- Atomic write via `sudo install -m 0644 -o root` with a root-
direct-write fallback for container bind-mounts.
- `sudo ln -sfn` to sites-enabled.
- `sudo nginx -s reload`. Failure surfaces the suggestion to run
`nginx -t`.
remove path: same managed-marker safety check; refuses to remove
external vhosts even with the SEVERE token. Unlinks both sides,
reloads.
Tests: 21 (449 → 470). Template fixtures (5: managed-marker, php
socket, laravel-public, php-version substitution, headers-on-every-
flavor + unknown-flavor error), generate (4: stdout / file / json /
unknown flavor), apply (4: severe-token / marker check / happy path
verifies install+symlink+reload / dry-run-no-op / external-vhost
refuse), remove (4: severe-token / unlinks + reloads / external
refuse / missing-noop), list (2: empty / managed-vs-external).
Gates: pytest 470 passed, ruff clean, mypy strict clean, bandit
medium=0. No new optional extras (Python stdlib only).
W5 of the ubuntu-migration plan. Composes the W3 db engines + a
php-fpm + nginx container into a per-project app stack, all on a
shared user-defined bridge network so containers can fastcgi-pass
each other by name. Project root bind-mounted at /srv/app.
Surface:
shimkit stack ls
shimkit stack lemp up [--project N] [--db mysql|mariadb|postgres]
[--port N] [--project-root PATH] [--password PWD]
shimkit stack lemp down [--project N]
shimkit stack lemp status [--project N] [--json]
shimkit stack lemp logs [--project N] [--tail N]
shimkit stack lemp exec [--project N] -- <cmd...>
Naming (per-project):
network: shimkit-stack-lemp-<project>-net
db: shimkit-stack-lemp-<project>-db
php-fpm: shimkit-stack-lemp-<project>-php
nginx: shimkit-stack-lemp-<project>-nginx
Architecture additions:
- `core/docker.py` gains `network_get_or_create(name)` +
`network_remove(name)` so the stack manager can stand up a
user-defined bridge without breaking the Rule 2 chokepoint.
Labelled with `shimkit.tool=stack`; reusable by future
multi-container recipes.
- `tools/stack/lemp.py` is pure orchestration: idempotent up/down
with the W3 engine drivers reused for the db container's env
vars (so `--db postgres` gets POSTGRES_PASSWORD, etc).
- Nginx conf rendered from a `string.Template` per-project (so the
fastcgi_pass target is the project's php container by name) and
bind-mounted at /etc/nginx/conf.d/default.conf in the nginx
container. Carries the same security headers as the W4 vhost
generator.
- `StackManager` mirrors `DbManager`: builder pattern, `boot()`
preflights docker version, recipe accessor (`mgr.lemp()` returns
a bound recipe).
Idempotency:
- `up` of an already-running stack returns `already_running` for
each role; no SDK mutations.
- Stopped containers get `docker start`-ed instead of recreated.
- Bind-mount paths preserved across runs.
`shimkit stack ls` enumerates every container with
`shimkit.tool=stack` and groups them by `shimkit.project` label —
useful for juggling multiple stacks side-by-side.
Tests: 21 (470 → 491). Nginx-conf template fixtures (php_host
substitution + security headers), naming helpers, boot version
preflight, up matrix (fresh / all-running / dry-run / no-input /
unknown-engine), engine selection (--db postgres puts the right
env vars on the db container, image pinned via the W3 config),
down (all 3 + network removed / all missing), status (running /
missing), ls (grouping + empty), exec (right container targeted /
non-zero exit), logs (per-container output).
Gates: pytest 491 passed, ruff clean, mypy strict clean, bandit
medium=0. No new optional extras.
…clean / gpg
W6 (`shimkit shell colors`) — 256-color ANSI palette diagnostic.
Read-only: prints the basic 16, the 6x6x6 cube, and the 24-step
grayscale ramp, each cell labeled with its index. `--json` returns
a structured dump with the Xterm-specified RGB triple per index
(indices 0-15 are theme-defined; we return null rather than guess).
Useful when a new terminal theme makes shimkit's help output
illegible and you want to see what your terminal actually paints.
W7 (preflight wire-in):
- `tools/docker_clean/manager.py::boot()` now calls
`shimkit.core.version.preflight(("docker",))` before the
daemon-reachability check. A missing or out-of-range docker now
exits 69 with a platform-specific install hint sourced from the
W1 remediation table, rather than the bare "Docker daemon is
not reachable" message that fired regardless of root cause. The
existing `--force` semantic (already on stack/db) is plumbed
through.
- `tools/gpg/manager.py::_check_git_or_warn()` — used by
`git_signing_show` and `git_signing_configure`. Same UX upgrade:
a missing git surfaces the install hint from
`core/version._remediation_for`. The check stays as a single
`shutil.which` (no version-detector round-trip) so existing
CommandRunner-stubbing tests don't need to also stub the version
pipeline.
Tests: 8 new (491 -> 503).
- W6: 7 cases — `index_to_rgb` for basic / cube / grayscale /
out-of-range; `_section_for` parametrised; CLI stdout (headers
present); CLI `--json` (256 entries, basic null, cube endpoints,
grayscale section labels).
- W7: existing tests adjusted to bypass the new preflight where
the test wasn't covering boot semantics — one in
`test_tools_docker_clean.py` (compose-down argv assertion) via
the established `version.preflight = no-op` pattern from W3/W5.
Gates: pytest 503 passed, ruff clean, mypy strict clean, bandit
medium=0. No new optional extras.
W8 of the ubuntu-migration plan. Docs catch up to the v0.5.0 surface; no code change. README: - Help-text panel reflects all 14 user commands (was 6). - Tool list extended with the three new server-class entries (`db`, `web nginx vhost`, `stack lemp`) and the small `shell colors` mention, grouped under a "Server-class tools (Docker-first; opt-in to host install)" sub-heading so the charter expansion is visible at a glance. - Two new sections: **Version requirements** points at the registry + spec; **Architecture** cross-links the four `.design/` documents (architecture-current / architecture-target / version-constraints-spec / plans/migration-plan). - Docs table now lists every per-tool doc (was 2; now 13). CHANGELOG `[Unreleased]`: - Migration narrative — captures the audit's headline findings (5 Critical + 7 High in source; Docker-first dissolves every Critical) and points at .design/ for the audit. - Six Added bullets covering core/version, core/docker, `shimkit db`, `shimkit web nginx`, `shimkit stack lemp`, `shimkit shell colors` — each with surface + test counts. - Changed: `shimkit doctor` versions section + preflight wired into docker-clean and gpg git-signing. - Removed: legacy `ubuntu/` source (subject to W9 sign-off); notes the 15 skipped features with the gap-analysis pointer. docs/installation.md: - Version requirements section with the five-row table and the `shimkit doctor` output sample; ends with a user-override example pointing at the registry spec. docs/architecture.md: - Banner at the top pointing at the four .design/ documents so contributors see the deep reference before they read the quick tour. Gates: pytest 503 passed, ruff clean, mypy strict clean. Docs- only — no source changes.
Phase 6 deliverable per the migration task spec. The report is gating Phase 7 (archive + delete of legacy ubuntu/ source). `.design/plans/validation-report.md` contains: - Full validation checklist (9 items, all green). - Adopt-list test coverage matrix (152 new tests across W1-W6). - Migration vs Skip breakdown with reasons per source file. - The 5 Critical + 5 of 7 High security flags from the source audit, each marked as dissolved by the Docker-first redesign. - Follow-up TODOs deferred to v0.6+ (certbot, generic cron, framework laravel, --on-host mode). - The Phase 7 archive + delete plan (gated on maintainer approval). `docs/tools/shell.md` gained a `shimkit shell colors` section (palette diagnostic written for W6 but not previously documented in the per-tool guide). Gates: pytest 503 passed, ruff/mypy/bandit clean. Docs-only — no source changes. The next step is maintainer approval of the validation report; W9 deletion does not proceed without it.
Phase 7 of the migration plan. Maintainer approved the validation
report (.design/plans/validation-report.md), so:
1. Archived /Users/imanimanyara/Artisan/projects/opensource/simtabi/ubuntu/
→ .design/archive/ubuntu-snapshot-2026-05-15.tar.gz
- 35 KB compressed, 115 entries (88 files + 27 dirs/.DS_Store).
- SHA-256: 3491cb8fe9ebd7250f608117679fb981410de9db7bd8e044bce6eee39715a367
- Verified: `tar -tzf` parses; `tar -xzOf .../bash-colors.sh`
extracts the file body.
2. Deleted the source: `rm -rf /Users/imanimanyara/Artisan/projects/opensource/simtabi/ubuntu/`.
The archive committed here is the ONLY recovery path — the source
was not in any git repository. SHA-256 recorded in the validation
report's "Archive details" section.
Adopted features in v0.5.0 (this branch):
- core/version (35 tests)
- core/docker (28 tests)
- shimkit db (35 tests, 5 engines)
- shimkit web nginx vhost (21 tests)
- shimkit stack lemp (21 tests)
- shimkit shell colors (12 tests)
- doctor extended with versions section
- docker-clean + gpg git-signing gain version preflight
Skipped 15 source features (php / node / composer / packages host
install; supervisor; cron; laravel scaffolders; three broken or
duplicate scripts; legacy server-main trees). Reasons in
.design/plans/feature-gap-analysis.md and validation-report.md.
Test count 351 → 503 (+152). Gates: pytest, ruff, mypy strict,
bandit medium=0, all green.
Migration complete.
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 32887486 | Triggered | Generic Password | 5cefb99 | src/shimkit/config/schema.py | View secret |
| 32887486 | Triggered | Generic Password | 5cefb99 | src/shimkit/config/defaults.json | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secrets safely. Learn here the best practices.
- Revoke and rotate these secrets.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
CI macOS matrix cells failed across all 4 Python versions because
the W7 preflight added to DockerCleanManager.boot() consults
shutil.which("docker"), which is absent on GitHub's macOS runner.
Tests that pass on macOS dev machines (where Docker Desktop is
installed) failed in CI for the first time.
Fix: module-level autouse fixture in test_tools_docker_clean.py
monkeypatches `shimkit.core.version.preflight` to a no-op. Every
test in the module now exercises the manager's post-preflight
logic regardless of CI host state — same pattern already used in
test_tools_db.py and test_tools_stack.py.
The two existing boot-exits-69 tests still pass (they hit the
`get_client` / extras-import paths AFTER the bypassed preflight).
The previously-added inline bypass in `test_docker_clean_compose_
down_invokes_docker_compose` is now redundant; removed.
Gates: pytest 503 passed locally, ruff clean, mypy strict clean.
There was a problem hiding this comment.
Pull request overview
Migrates the legacy ubuntu/ provisioning scripts into shimkit as Docker-first tooling for v0.5.0, adding new core primitives for Docker SDK access and external-tool version constraint enforcement, plus new CLI subapps (db, stack, web nginx, shell colors) and corresponding documentation.
Changes:
- Added
shimkit.core.version(detectors + constraints + preflight) and wired intodoctorand select tool boots. - Added
shimkit.core.docker.DockerEnvand built new Docker-first tools:shimkit db(5 engines),shimkit stack lemp,shimkit web nginx vhost, andshimkit shell colors. - Updated config schema/defaults, expanded docs/README/CHANGELOG, and added substantial test coverage for the new modules.
Reviewed changes
Copilot reviewed 65 out of 68 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_tools_ssh.py | Formatting-only updates to existing SSH tool tests. |
| tests/test_tools_shell_colors.py | Adds tests for the new shimkit shell colors command and JSON palette output. |
| tests/test_tools_logs.py | Formatting-only updates to existing logs tool tests. |
| tests/test_tools_gpg.py | Formatting-only updates to existing GPG tool tests. |
| tests/test_tools_docker_clean.py | Adjusts docker-clean tests to bypass new version preflight. |
| tests/test_core_version.py | Adds test coverage for tool version detection/constraints/preflight. |
| tests/test_core_docker.py | Adds test coverage for DockerEnv SDK boundary and helpers. |
| src/shimkit/tools/web/nginx/templates.py | Introduces hardened nginx vhost templates (static/php/laravel). |
| src/shimkit/tools/web/nginx/manager.py | Implements vhost generate/apply/remove/list orchestration. |
| src/shimkit/tools/web/nginx/commands.py | Adds Typer subcommands for shimkit web nginx vhost. |
| src/shimkit/tools/web/nginx/init.py | Exposes WebNginxManager and package docs. |
| src/shimkit/tools/web/commands.py | Adds parent shimkit web Typer app and registers nginx. |
| src/shimkit/tools/web/init.py | Introduces shimkit web package. |
| src/shimkit/tools/stack/manager.py | Adds stack manager with docker preflight and LEMP binding. |
| src/shimkit/tools/stack/lemp.py | Implements 3-container LEMP recipe using DockerEnv. |
| src/shimkit/tools/stack/commands.py | Adds Typer subcommands for shimkit stack and stack lemp. |
| src/shimkit/tools/stack/init.py | Exposes StackManager and package docs. |
| src/shimkit/tools/ssh/scanner.py | Minor formatting adjustments in SSH scanner. |
| src/shimkit/tools/ssh/manager.py | Minor formatting adjustments in SSH manager output. |
| src/shimkit/tools/ssh/commands.py | Minor formatting adjustments in SSH Typer commands. |
| src/shimkit/tools/shell/commands.py | Registers new shell colors command. |
| src/shimkit/tools/shell/colors.py | Implements ANSI 256-color palette rendering + JSON output. |
| src/shimkit/tools/logs/manager.py | Minor formatting adjustment in platform error message. |
| src/shimkit/tools/logs/commands.py | Minor formatting adjustments in logs Typer commands. |
| src/shimkit/tools/gpg/parser.py | Minor formatting alignment in algorithm map. |
| src/shimkit/tools/gpg/models.py | Minor formatting alignment in dataclass fields. |
| src/shimkit/tools/gpg/manager.py | Adds git presence check helper using version remediation hints. |
| src/shimkit/tools/gpg/commands.py | Minor formatting adjustments in GPG Typer commands. |
| src/shimkit/tools/env/manager.py | Minor formatting adjustments in env manager and list pruning. |
| src/shimkit/tools/env/commands.py | Minor formatting adjustments in env Typer commands. |
| src/shimkit/tools/docker_clean/manager.py | Adds docker version constraint preflight to docker-clean boot. |
| src/shimkit/tools/db/models.py | Adds typed DB value objects (UpResult, StatusRow). |
| src/shimkit/tools/db/engines/postgres.py | Adds Postgres engine driver. |
| src/shimkit/tools/db/engines/phpmyadmin.py | Adds phpMyAdmin “engine” driver (web UI container). |
| src/shimkit/tools/db/engines/mysql.py | Adds MySQL engine driver. |
| src/shimkit/tools/db/engines/mongo.py | Adds Mongo engine driver. |
| src/shimkit/tools/db/engines/mariadb.py | Adds MariaDB engine driver. |
| src/shimkit/tools/db/engines/base.py | Adds engine ABC, UpSpec, and unsupported-op error. |
| src/shimkit/tools/db/engines/init.py | Adds DB engine registry and get() accessor. |
| src/shimkit/tools/db/commands.py | Adds shimkit db Typer app and per-engine subapps/commands. |
| src/shimkit/tools/db/init.py | Exposes DbManager/registry from the db package. |
| src/shimkit/tools/db/manager.py | Implements DB lifecycle using DockerEnv (up/down/shell/dump/reset/status). |
| src/shimkit/core/version.py | Adds tool version detection registry, constraints, validate/preflight APIs. |
| src/shimkit/core/docker.py | Adds DockerEnv chokepoint for docker-py plus naming/network helpers. |
| src/shimkit/core/init.py | Re-exports the new core primitives (docker/version types). |
| src/shimkit/config/schema.py | Extends config schema for db/stack/web + version constraints. |
| src/shimkit/config/defaults.json | Adds default config blocks for db/stack/web + tool version minimums. |
| src/shimkit/cli.py | Registers new Typer apps (db/stack/web) and extends doctor with versions. |
| README.md | Updates tool list and adds version requirements section. |
| docs/tools/web.md | Documents shimkit web nginx vhost usage/config/exit codes. |
| docs/tools/stack.md | Documents shimkit stack lemp usage/config/exit codes. |
| docs/tools/shell.md | Documents shimkit shell colors usage and JSON output. |
| docs/tools/db.md | Documents shimkit db engines, lifecycle, and safety model. |
| docs/installation.md | Adds version requirements and override guidance. |
| docs/architecture.md | Adds links to deeper .design/ architecture/spec docs. |
| CHANGELOG.md | Adds v0.5.0 migration summary and new feature entries. |
| .design/version-constraints-spec.md | Adds a detailed spec for the version constraints subsystem. |
| .design/plans/validation-report.md | Adds a validation checklist/report for the migration. |
| .design/plans/migration-plan.md | Adds detailed migration plan and work items W1–W9. |
| .design/plans/_workspace/source-inventory.md | Workspace inventory of legacy ubuntu source (archival context). |
| .design/plans/_workspace/risk-flags.md | Workspace security/risk audit notes for legacy ubuntu scripts. |
| .design/architecture-current.md | Captures pre-v0.5.0 architecture snapshot and conventions. |
Comments suppressed due to low confidence (2)
src/shimkit/tools/db/manager.py:229
DbEngineBound.up()callsPath(volume_path).mkdir(...)on a path derived from config + CLI inputs. If that path escapesdefault_volume_root(via..segments) or points at a symlink, this can write outside the intended shimkit data directory. Before creating the directory, resolve it and ensure it is contained under the resolveddefault_volume_root(and fail closed otherwise).
src/shimkit/tools/docker_clean/manager.py:79- The out-of-range error message prints
{r.constraint.min}+, which can read oddly for explicit specifiers and doesn't include a max bound when configured. Consider rendering the full specifier set (min+max) for clarity, and avoid appending+whenminalready includes an operator.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| root = Path.home() / ".shimkit" / "data" | ||
| try: | ||
| path.relative_to(root) | ||
| except ValueError: | ||
| return False | ||
| if not path.exists(): | ||
| return False | ||
| import shutil | ||
|
|
||
| shutil.rmtree(path) |
| cfg = get_config().tools.db | ||
| id_ = id_ or cfg.default_id | ||
| port = host_port or self._default_port | ||
| bind = bind_host or cfg.default_bind_host | ||
| pwd = password or cfg.default_password | ||
| name = DockerEnv.container_name(SCOPE, self._engine.name, id_) | ||
|
|
||
| if ephemeral: | ||
| volume_path: str | None = None | ||
| else: | ||
| v = volume or ( | ||
| Path(cfg.default_volume_root).expanduser() / f"{self._engine.name}-{id_}" | ||
| ) |
| cfg = get_config().tools.web.nginx | ||
| avail_dir = Path(cfg.sites_available_dir) | ||
| enabled_dir = Path(cfg.sites_enabled_dir) | ||
| avail_path = avail_dir / name | ||
| enabled_path = enabled_dir / name | ||
|
|
| fd, tmp = tempfile.mkstemp(prefix="shimkit-vhost-", suffix=".tmp") | ||
| try: | ||
| with os.fdopen(fd, "w", encoding="utf-8") as f: | ||
| f.write(src.read_text(encoding="utf-8", errors="replace")) | ||
| r = CommandRunner.run( | ||
| [ | ||
| *sudo_prefix(), | ||
| "install", | ||
| "-m", | ||
| "0644", | ||
| "-o", | ||
| "root", | ||
| tmp, | ||
| str(dst), | ||
| ], | ||
| capture_output=True, |
| nginx_conf = render_nginx_conf(php_host=php_name) | ||
| conf_path = Path(tempfile.gettempdir()) / f"shimkit-stack-lemp-{project}-default.conf" | ||
| conf_path.write_text(nginx_conf, encoding="utf-8") | ||
|
|
| elif r.status is _vc.Status.OUT_OF_RANGE and r.tool_version: | ||
| UI.error( | ||
| f"docker {r.tool_version.raw} is out of range — " | ||
| f"shimkit stack requires {r.constraint.min or '<any>'}+." | ||
| ) |
| def boot(self, *, require_daemon: bool = True, force: bool = False) -> DockerCleanManager: | ||
| from shimkit.core import version as _vc | ||
|
|
||
| self._platform = Platform.detect() | ||
| if not (self._platform.is_macos or self._platform.is_linux): | ||
| UI.error(f"shimkit docker-clean: unsupported platform {self._platform.system}.") | ||
| sys.exit(EX_UNAVAILABLE) | ||
| if not _require_optional_extras(): | ||
| sys.exit(EX_UNAVAILABLE) | ||
| # Preflight the docker binary version constraint. Lifts the | ||
| # generic "not found" path to a structured remediation hint | ||
| # sourced from `tools.versions.docker` + the platform-specific | ||
| # install command. | ||
| try: | ||
| _vc.preflight(("docker",), force=force) | ||
| except _vc.VersionViolationError as exc: | ||
| for r in exc.results: |
| return Result( | ||
| tool=tool, | ||
| status=cons.check(tv.version), | ||
| tool_version=tv, | ||
| constraint=cons, | ||
| remediation=_remediation_for(tool) | ||
| if cons.check(tv.version) is Status.OUT_OF_RANGE |
| def _check_git_or_warn() -> bool: | ||
| """Verify ``git`` is present. Surface the platform-specific | ||
| install hint from the version-constraint registry's remediation | ||
| table on failure so the UX matches the other tools. | ||
|
|
||
| Returns True iff git is present. ``git_signing_*`` only invokes | ||
| ``git config``; we don't enforce a version constraint here. | ||
| """ | ||
| if shutil.which("git") is not None: | ||
| return True | ||
| from shimkit.core.version import _remediation_for | ||
|
|
||
| UI.error("`git` is not on PATH.") | ||
| hint = _remediation_for("git") | ||
| if hint: | ||
| UI.dim(f" -> {hint}") | ||
| return False |
CI's smoke + integration jobs install from a freshly-built wheel WITHOUT the `[dev]` extras. The W1 version-constraints subsystem imports from `packaging.version` / `packaging.specifiers`, but `packaging` was previously only a transitive of pydantic — fine for `pip install -e ".[dev]"` (which is what local + test cells do) but broken for the wheel-only install path used by: - smoke (install built wheel + run CLI) — macOS + ubuntu - adguard integration (real AGH on ubuntu) - adguard mutating integration (systemd-resolved in privileged container) All four jobs failed with `ModuleNotFoundError: No module named 'packaging'` on the very first `shimkit --help` invocation. Fix: add `packaging>=23.0` to the explicit `dependencies` block in pyproject.toml. Now `pip install shimkit` (no extras) ships everything needed for the version subsystem to import. Gates: pytest 503 passed locally; no functional change beyond the install-graph.
Eight new commands across three new sub-trees (db / web / stack) plus two cross-cutting primitives (core/version, core/docker) landed via PR #7 (squash-merged as 955f433). Cutting v0.5.0 to publish the migrated surface. Highlights: - `shimkit db <engine>` — container-first databases (mysql, mariadb, postgres, mongo, phpmyadmin). No host-install path. - `shimkit web nginx vhost` — hardened vhost generator with opt-in SEVERE apply. - `shimkit stack lemp` — 3-container LEMP recipe (db + php-fpm + nginx) with bind-mounted project root. - `shimkit shell colors` — 256-color palette diagnostic. - `core/version` — tool-version detection + constraint enforcement (5 detectors: docker, nginx, git, gpg, python). Wired into `docker-clean`, `db`, `stack`, and the `gpg git-signing` paths. - `core/docker.DockerEnv` — shimkit chokepoint for the docker-py SDK; standard container/volume naming + network helpers. Test count: 351 → 503 (+152). Gates: pytest/ruff/mypy/bandit all green. No new optional extras (reuses [docker-clean]). Legacy `ubuntu/` source archived at .design/archive/ and removed; SHA-256 recorded in `.design/plans/validation-report.md`. Skipped features documented in `.design/plans/feature-gap-analysis.md`.
Summary
Migrates the legacy
ubuntu/server-provisioning scripts into shimkit under a Docker-first charter expansion. Adds two cross-cutting primitives (core/version,core/docker), four new sub-apps (shimkit db,shimkit web nginx,shimkit stack lemp,shimkit shell colors), and wires version-constraint preflight intodocker-clean+gpg git-signing. The legacyubuntu/source is archived + deleted.Test count: 351 → 503 (+152 across W1–W6).
Gates: pytest / ruff / mypy strict / bandit medium=0 — all green.
What landed
core/versionVersionConstraint+preflight+validate_all.shimkit doctorversions section.core/dockerDockerEnvbuilder, container naming, network helpers,remove_volumesafety check.shimkit db <engine>ls / up / down / shell / dump / reset (SEVERE) / status.shimkit web nginx vhostapply/removeare SEVERE. 3 flavors (static / php / laravel).shimkit stack lempshimkit shell colorsdocker-clean+gpg git-signingboot throughversion.preflight..design/.ubuntu/tarballed to.design/archive/; source removed.What was skipped (15 source features)
Brief table; full reasons in
.design/plans/feature-gap-analysis.md:install:certbot.sh— deferred to v0.6 (TLS issuance design under Docker is non-trivial)install:composer.sh/install:php.sh/install:php7.sh— composer + php live in the LEMP containerinstall:node.sh— nvm/volta/asdf are industry standardinstall:packages.sh— bulk apt installer is anti-shimkitinstall:phpmyadmin.sh— superseded byshimkit db phpmyadmininstall:server-env.sh— duplicate ofinstall:nginx.shconfigurators/aliases— dotfiles territoryconfigs:supervisor.sh— supervisor is fadingadd:cron.sh— Laravel-shaped; genericshimkit cronis a v0.6+ candidatecreate:mysql.sh— subsumed byshimkit db mysql shell+ SQLexpressjs:setup.sh/laravel:initialize.sh— too project-shapedlaravel:file-perms.sh— defer to a futureshimkit framework laravel__src/+ emptyscripts/security/,docs/Security wins
All 5 Critical and 5 of 7 High flags from the source audit dissolve under the Docker-first redesign (see
.design/plans/_workspace/risk-flags.md): apparmor disable, mongo/mysql bound to0.0.0.0, deprecatedapt-key adv,curl|sh— all N/A inside official-image containers.Source archive
.design/archive/ubuntu-snapshot-2026-05-15.tar.gz3491cb8fe9ebd7250f608117679fb981410de9db7bd8e044bce6eee39715a367ubuntu/source was not in any git repository; the tarball in this branch is the only recovery path.Test plan
pytest -q→ 503 passed (was 351 at v0.4.0)ruff check src testsclean;ruff format --check src testscleanmypy srcclean (107 source files)bandit -r src/shimkit -ll— Medium: 0, High: 0shimkit doctorruns; newversionssection reports all 5 detectorsokshimkit shell colors --jsonparses; structured palette dumpshimkit db mysql up --yesagainst Docker Desktop on the reviewer's hostshimkit stack lemp up --yes --project test --project-root /tmp/emptyagainst Docker Desktopshimkit web nginx vhost generate --name docs --domain docs.local --root /srv/docs --flavor laravel(file-only, no host mutation)Reading order for review
.design/plans/migration-plan.md— the work-item plan (W1..W9).design/plans/validation-report.md— every checklist item, every smoke test.design/architecture-target.md— post-migration layout.design/version-constraints-spec.md— how the new preflight system worksOut of scope this PR (follow-ups)
shimkit tls / certbotwith DNS-01 challengeshimkit cronshimkit framework laravel(the Laravel-specific helpers we skipped)--on-hostmode fordb/stack(safe re-implementation of the apt-install paths)