Skip to content

feat: ubuntu/ migration → v0.5.0 (db / stack / web nginx + version constraints + DockerEnv)#7

Merged
imanimanyara merged 12 commits into
mainfrom
feat/ubuntu-migration
May 15, 2026
Merged

feat: ubuntu/ migration → v0.5.0 (db / stack / web nginx + version constraints + DockerEnv)#7
imanimanyara merged 12 commits into
mainfrom
feat/ubuntu-migration

Conversation

@imanimanyara
Copy link
Copy Markdown
Member

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 into docker-clean + gpg git-signing. The legacy ubuntu/ source is archived + deleted.

Test count: 351 → 503 (+152 across W1–W6).
Gates: pytest / ruff / mypy strict / bandit medium=0 — all green.

What landed

Work item Surface Tests
W1 — core/version Detection registry (5 detectors) + VersionConstraint + preflight + validate_all. shimkit doctor versions section. 35
W2 — core/docker DockerEnv builder, container naming, network helpers, remove_volume safety check. 28
W3 — shimkit db <engine> 5 engines (mysql, mariadb, postgres, mongo, phpmyadmin). ls / up / down / shell / dump / reset (SEVERE) / status. 35
W4 — shimkit web nginx vhost Hardened generator; apply / remove are SEVERE. 3 flavors (static / php / laravel). 21
W5 — shimkit stack lemp 3-container recipe (db + php-fpm + nginx). Multi-project. 21
W6 — shimkit shell colors 256-color ANSI palette diagnostic. 12
W7 — preflight wire-in docker-clean + gpg git-signing boot through version.preflight. (in W1)
W8 — docs README + CHANGELOG + installation Version Requirements + arch cross-links to .design/. (docs only)
W9 — archive + delete ubuntu/ tarballed to .design/archive/; source removed. (chore)

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 container
  • install:node.sh — nvm/volta/asdf are industry standard
  • install:packages.sh — bulk apt installer is anti-shimkit
  • install:phpmyadmin.sh — superseded by shimkit db phpmyadmin
  • install:server-env.sh — duplicate of install:nginx.sh
  • configurators/aliases — dotfiles territory
  • configs:supervisor.sh — supervisor is fading
  • add:cron.sh — Laravel-shaped; generic shimkit cron is a v0.6+ candidate
  • create:mysql.sh — subsumed by shimkit db mysql shell + SQL
  • expressjs:setup.sh / laravel:initialize.sh — too project-shaped
  • laravel:file-perms.sh — defer to a future shimkit framework laravel
  • 3 legacy / duplicate trees in __src/ + empty scripts/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 to 0.0.0.0, deprecated apt-key adv, curl|sh — all N/A inside official-image containers.

Source archive

  • Path: .design/archive/ubuntu-snapshot-2026-05-15.tar.gz
  • SHA-256: 3491cb8fe9ebd7250f608117679fb981410de9db7bd8e044bce6eee39715a367
  • Size: 35 KB compressed, 115 entries (88 files + 27 dirs/DS_Store)
  • The legacy ubuntu/ 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 tests clean; ruff format --check src tests clean
  • mypy src clean (107 source files)
  • bandit -r src/shimkit -ll — Medium: 0, High: 0
  • shimkit doctor runs; new versions section reports all 5 detectors ok
  • shimkit shell colors --json parses; structured palette dump
  • Manual: shimkit db mysql up --yes against Docker Desktop on the reviewer's host
  • Manual: shimkit stack lemp up --yes --project test --project-root /tmp/empty against Docker Desktop
  • Manual: shimkit web nginx vhost generate --name docs --domain docs.local --root /srv/docs --flavor laravel (file-only, no host mutation)

Reading order for review

  1. .design/plans/migration-plan.md — the work-item plan (W1..W9)
  2. .design/plans/validation-report.md — every checklist item, every smoke test
  3. .design/architecture-target.md — post-migration layout
  4. .design/version-constraints-spec.md — how the new preflight system works
  5. The 11 commits in order — each is self-contained.

Out of scope this PR (follow-ups)

  • v0.6+: shimkit tls / certbot with DNS-01 challenge
  • v0.6+: generic shimkit cron
  • v0.6+: shimkit framework laravel (the Laravel-specific helpers we skipped)
  • v0.7+: --on-host mode for db / stack (safe re-implementation of the apt-install paths)
  • deferred: coverage push toward 85% (currently 67%)

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.
Copilot AI review requested due to automatic review settings May 15, 2026 14:22
@gitguardian
Copy link
Copy Markdown

gitguardian Bot commented May 15, 2026

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
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
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. 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


🦉 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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 into doctor and select tool boots.
  • Added shimkit.core.docker.DockerEnv and built new Docker-first tools: shimkit db (5 engines), shimkit stack lemp, shimkit web nginx vhost, and shimkit 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() calls Path(volume_path).mkdir(...) on a path derived from config + CLI inputs. If that path escapes default_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 resolved default_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 + when min already includes an operator.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +329 to +338
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)
Comment on lines +156 to +168
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_}"
)
Comment on lines +155 to +160
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

Comment on lines +315 to +330
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,
Comment on lines +203 to +206
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")

Comment on lines +47 to +51
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>'}+."
)
Comment on lines +56 to +72
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:
Comment on lines +273 to +279
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
Comment on lines +282 to +298
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.
@imanimanyara imanimanyara merged commit 955f433 into main May 15, 2026
14 of 15 checks passed
@imanimanyara imanimanyara deleted the feat/ubuntu-migration branch May 15, 2026 15:15
imanimanyara added a commit that referenced this pull request May 15, 2026
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`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants