# Dockwatch v9.6.0 > Go module: `git.send.nrw/sendnrw/dockwatch` Dockwatch is a single-binary Go control plane for Docker Compose, Docker resources and uptime monitoring. It combines Dockge/Dockhand-style operational workflows with Uptime-Kuma-style monitoring while keeping its own implementation and UI. The same binary runs as `standalone`, `master` or `agent`. SQLite uses `modernc.org/sqlite`, so the application itself builds with `CGO_ENABLED=0`. ## v9.6.0 System-Updates - neuer Admin-Bereich **System-Updates** für den lokalen Host und ausgewählte Master/Agent-Nodes - Update-Erkennung und Installation über apt, dnf/yum, pacman sowie weiterhin zypper/apk als bereits unterstützte Paketmanagerfamilien - Arch-Abfragen nutzen bevorzugt `checkupdates` aus `pacman-contrib`, damit keine isolierte `pacman -Sy`-Synchronisierung und damit kein Partial-Upgrade-Risiko entsteht - Arch-Systemupdates werden ausschließlich als vollständiges `pacman -Syu --noconfirm` ausgeführt - AUR-Unterstützung erkennt `paru` (bevorzugt) oder `yay`; `HOST_AUR_USER` legt den Nicht-root-Benutzer fest, unter dem AUR-Abfrage/Build läuft - AUR-Installation fordert die Paket-Eskalation des Helpers non-interaktiv an (`--sudoflags=-n`); Hosts müssen diesen Benutzer entsprechend für den Paketmanager autorisieren - neue Master/Agent-Endpunkte für Update-Liste, Paketmetadaten-Refresh und Installation; alle Mutationen bleiben Admin-only und laufen durch Origin-Schutz/Audit-Logging - Browser kann weiterhin keine freien Paketnamen oder Shell-Kommandos einreichen, sondern nur die vordefinierten Scopes `system`, `aur` oder `all` - statische Assets verwenden `v=9.6.0` zur sicheren Cache-Aktualisierung ## v9.5.3 deutsche Weboberfläche - die Weboberfläche ist nun durchgängig deutsch beschriftet; interne API-/Compose-Werte bleiben unverändert - Navigation, Compose-Designer, Monitoring, Dienste, Statusseiten, Docker-Ressourcen, Git, Benachrichtigungen und Host-Sicherheit wurden sprachlich vereinheitlicht - Status- und Rollenwerte werden im UI deutsch dargestellt, ohne die API-Werte (`up`, `down`, `admin`, `operator` usw.) zu verändern - die öffentliche Statusseite ist ebenfalls deutsch - häufige serverseitige Diagnosemeldungen werden für die Anzeige im WebUI verständlich ins Deutsche übertragen - statische Assets verwenden `v=9.5.3` zur sicheren Cache-Aktualisierung ## v9.5.1 UI / Compose editor fixes - Compose scalar edits no longer re-render the complete visual tree after every keystroke, so text/port fields keep focus while typing. Structural changes (add/remove/type changes) still re-parse the YAML AST. - Compose designer controls now use the application theme instead of browser-default white form controls in dark mode. - Stack container cards format Compose publisher JSON into readable port mappings and wrap long image/command values safely. - Docker inventory tables now have horizontal overflow handling, sensible column widths, compact label summaries, and a sticky action column so buttons are no longer squeezed off-screen. - Static assets are cache-busted to `v9.5.1`. ## v9.5 firewall-provider layer - Host Security firewall management now uses a provider model: **Auto detect / UFW / firewalld / native nftables** - Auto detect adopts an already-active UFW or firewalld frontend instead of refusing all changes simply because one exists - multiple truly competing active frontends are still treated as a hard conflict - UFW rules are added with deterministic `dockwatch:` comments and Dockwatch removes only those managed rules; it never runs `ufw reset` - firewalld uses the selected native zone and managed rich rules while preserving unrelated services, ports, sources and rich rules - native nftables keeps the isolated `table inet dockwatch` model and never flushes the global ruleset - existing provider state/rules are displayed read-only in the firewall editor before changes are applied - provider selection, default inbound management, firewalld zone selection, ALLOW/DENY/REJECT/LIMIT rules and rule reordering are available in the unified UI - provider changes retain the timed rollback/explicit commit safety model; rollback restores the previous Dockwatch-managed policy and provider default snapshot where applicable - package installation/upgrade now installs the selected firewall frontend (`ufw`, `firewalld` or `nftables`) rather than always installing nftables ## v9.4.1 fixes - fixed monitor creation when no monitor is selected (`state.monitor == null`) - static UI assets are served with `Cache-Control: no-store`, preventing stale frontend code after upgrades - Compose visual-editor patches now use a real queue so rapid edits on different fields are not lost - the **Volumes** section now reconciles every service mount with top-level `volumes:` declarations - named volumes missing a top-level declaration can be added individually or with **Add missing declarations** - bind mounts, anonymous mounts and dynamic mounts are shown in the reconciliation view but are never silently converted into named volumes - unused top-level volume declarations are highlighted and can be removed explicitly without deleting the Docker volume itself ## What is included ### Compose / stack management - multiple Compose stacks per environment - local Docker plus remote agents - staged `docker compose config --quiet` validation before a save - atomic managed-file writes and rollback on save failure - full bidirectional Compose designer backed by a YAML AST - recursive editing of all YAML maps/arrays/scalars, including unknown/future Compose fields - `compose.yaml`, root `.env`, `envs/`, `secrets/` and `configs/` - Up / Start / Stop / Restart / Down / Pull / Update / Force recreate - service/container status - live SSE logs with pause, follow, filter and download - interactive PTY/WebSocket `docker compose exec` terminal - normalized service/dependency/network/volume graph from `docker compose config --format json` - registry digest based image-update checks - safe stack deletion: only Dockwatch-managed definition files are removed by default - explicit, typed-name confirmation for full stack-folder purge The editor keeps unsaved drafts in browser storage. Background refresh never overwrites a dirty editor. ### Git-backed stacks Git sources support: - local or remote-agent environment - repository, branch, workdir and Compose filename - manual sync - optional deploy after sync - signed webhooks (`X-Hub-Signature-256`, `X-Gitlab-Token` or `X-Webhook-Token`) - encrypted webhook secret at rest - last commit / last sync / last error A Git sync is now **managed-file based**. Dockwatch stores a small `.dockwatch/git-manifest.json` in the stack directory and only replaces/removes files that belong to the Git checkout. Unrelated runtime or bind-mount data beside the Compose project is preserved. Syncs of the same source are serialized and a failed apply rolls back the touched file set. Symlink stack destinations and symlink paths inside Git-managed writes are rejected. ### Docker resources **Containers** - list/filter - start / stop / restart / force-remove - operator-only inspect - one-shot CPU/memory/network/block stats ### Container identity / host UID-GID checks Container rows include an **Identity** action. Dockwatch inspects the selected container and reports: - configured Compose/image user and the effective runtime UID/GID where resolvable - whether the process currently runs as UID 0 - a conservative root assessment based on `privileged`, Docker socket mounts, passed-through devices and added Linux capabilities - bind-mount sources and their host UID/GID ownership when host access is configured - single-container detail checks plus a throttled **Identity audit** across all containers in the selected environment - whether the container UID/GID already maps to a local host account/group Dockwatch deliberately does **not** claim that a root container can always be converted to non-root. Application-internal filesystem permissions, entrypoints and image-specific `PUID`/`PGID` conventions cannot be proven from Docker metadata alone. It also never rewrites Compose `user:` automatically. A matching local host username is **not required by Docker**. Linux file ownership is numeric; creating a host account can nevertheless make bind-mount ownership, backups and administration easier. Host inspection is opt-in. For read-only inspection, set: ```env HOST_ROOT=/host ALLOW_HOST_USER_MANAGEMENT=false ALLOW_HOST_PERMISSION_MANAGEMENT=false ``` and mount the host root read-only at `/host`. `examples/compose-host-identity.override.yml` shows this setup. Admins may optionally create a locked/non-login local host account using the container's server-side re-resolved UID/GID. This requires both a writable host-root mount and: ```env ALLOW_HOST_USER_MANAGEMENT=true ``` See `examples/compose-host-user-management.override.yml`. The browser cannot supply an arbitrary UID/GID: Dockwatch re-inspects the container immediately before the change and derives the IDs itself. Existing numeric users/groups are reused and UID/name collisions are refused. Docker rootless/user-namespace remapping is detected where possible; automatic same-numbered host-account creation is refused when IDs are remapped. This operation is intentionally admin-only and disabled by default. ### Identity & Bind Mount Permissions Dockwatch can now diagnose the actual bind-mount permission problem instead of stopping at "container runs as UID X". The container **Identity** dialog and each stack's **Permissions** tab show, per bind mount: - effective PID 1 UID/GID and a separate expected **bind UID/GID** - `PUID/PGID` or `USER_ID/GROUP_ID` when the image exposes those paired conventions - host owner UID/GID and POSIX mode bits - static writeability (`w+x` for directories, `w` for files) - extended POSIX ACL detection when `getfacl` is available - an optional non-mutating runtime `test -w` using the expected numeric identity - read-only mounts, rootless/userns remapping and unsafe symlinked host paths as hard repair blockers The repair flow is **Analyze → Preview → Repair → Verify**. For recursive ownership repair Dockwatch scans the tree first and shows how many files/directories differ. Automatic recursive repair is refused above 200,000 entries. Symlinks are never followed or chowned. `:ro` mounts are never repaired automatically. Permission repair is a separate high-trust opt-in from host-user creation: ```env HOST_ROOT=/host ALLOW_HOST_PERMISSION_MANAGEMENT=true ``` and `/` must intentionally be mounted read-write at `/host`. See `examples/compose-host-permissions.override.yml`. The browser supplies only the container and its mount destination (for example `/config`); the backend re-reads `docker inspect` and resolves the real host source itself. Arbitrary host paths and arbitrary UID/GID values cannot be submitted for repair. Ownership repair can operate on only the bind root or recursively. `chmod` is separate, optional, explicit, and only applies to the bind root; Dockwatch never automatically applies `chmod 777` and never recursively rewrites modes. After a repair Dockwatch re-runs the ownership/writeability analysis and reports the result. **Images** - list/filter - pull / remove / prune - registry login/logout - persistent Docker auth via `DOCKER_CONFIG=/data/docker-config` **Volumes** - list/filter - create / remove / prune - driver and labels **Networks** - list/filter - create / remove / prune - driver, labels, `internal`, `attachable` Docker positional arguments and Compose service names are validated before invoking the CLI so option-like values cannot be interpreted as Docker CLI flags. ### Host Security layer Dockwatch can optionally act as a host-security control plane for the selected local or remote-agent environment. The feature is **disabled by default** and uses three independent capabilities so that audit, configuration changes and package management can be granted separately. The **System → Host Security** page provides a posture overview and managed workflows for: **Firewall (Auto / UFW / firewalld / nftables)** - **Auto detect** adopts the already-active host firewall frontend: UFW first when it is the sole active frontend, firewalld when it is the sole active frontend, otherwise native nftables is used when available - an explicit provider can be selected when you intentionally want to migrate or standardize a host - only genuinely competing active frontends are treated as conflicts; for example UFW + firewalld, or UFW + an already-loaded Dockwatch nftables table - the editor shows installed/active frontends plus the provider's existing runtime rules/state before any mutation - common managed policy supports trusted IPv4/IPv6 CIDRs and ordered TCP/UDP **ALLOW / DENY / REJECT / LIMIT** rules - provider-global default inbound management is an explicit opt-in for UFW/firewalld; nftables keeps its default policy inside Dockwatch's own table - **UFW:** Dockwatch uses native `ufw` commands, tags its rules with deterministic `dockwatch:` comments and removes only those tagged rules; it never runs `ufw reset` and preserves foreign UFW rules - **firewalld:** Dockwatch targets a selected zone, uses native rich rules, and preserves unrelated zone services/ports/sources/rich rules; default zone target changes are opt-in - **nftables:** Dockwatch continues to own only `table inet dockwatch`, validates with `nft -c`, never runs `flush ruleset`, and does not alter Docker NAT/FORWARD chains - when an enabled policy is applied to an inactive UFW/firewalld provider, Dockwatch activates the selected frontend inside the rollback window; rollback restores the previous active state - **timed rollback** (30–600 seconds, UI default 90 seconds) after apply; changes must be explicitly kept after management connectivity is verified - package installation/upgrade uses the selected provider package instead of always installing nftables - browser clients never submit arbitrary firewall command text; the backend renders and executes only the typed policy model **Fail2Ban** - package/runtime/boot status - package installation/upgrade when separately enabled - managed global ban/find/max-retry/backend settings - ignore IP/CIDR list - configurable jails with filter, port, backend, log path and per-jail retry threshold - server-side `fail2ban-client -t` validation before activation/reload - Dockwatch owns only `/etc/fail2ban/jail.d/dockwatch.local`; distro and administrator configuration is preserved **Linux Audit (auditd)** - package/runtime/boot status - managed watches for identity files, sudoers, SSH configuration, Docker socket/configuration, systemd units and kernel-module configuration - additional explicitly configured file watches - path/key/permission validation - `augenrules --check` validation and `augenrules --load` activation - Dockwatch owns only `/etc/audit/rules.d/90-dockwatch.rules`; unrelated audit rules remain untouched **Installation and maintenance** - install or upgrade the selected firewall provider (`ufw`, `firewalld` or `nftables`), `fail2ban` and `auditd`/`audit` using the detected host package manager - supported package-manager families: apt, dnf, yum, zypper, apk and pacman - enable, disable, restart and (where meaningful) reload the corresponding host services - current package/service/config-drift information in the UI - backups of Dockwatch-managed host configuration before replacement - all mutation endpoints are administrator-only and flow through Dockwatch's existing origin/CSRF guard and activity audit log Dockwatch intentionally manages its own drop-in/configuration scope rather than rewriting the host's entire security configuration. It is a focused administration layer, not a replacement for a complete CIS/STIG benchmark, SELinux/AppArmor policy management, an enterprise EDR, or distro-specific security tooling. #### Capability modes Read-only file/configuration audit: ```yaml services: dockwatch: environment: HOST_ROOT: /host HOST_SECURITY_ENABLED: "true" ALLOW_HOST_SECURITY_CHANGES: "false" ALLOW_HOST_PACKAGE_MANAGEMENT: "false" volumes: - /:/host:ro ``` A ready-to-use example is `examples/compose-host-security-audit.override.yml`. In this mode Dockwatch cannot mutate the host. Runtime/service checks that require entering the host namespaces may be unavailable and are reported as such. Full host-security management is intentionally high privilege: ```yaml services: dockwatch: pid: host privileged: true environment: HOST_ROOT: /host HOST_SECURITY_ENABLED: "true" ALLOW_HOST_SECURITY_CHANGES: "true" ALLOW_HOST_PACKAGE_MANAGEMENT: "true" # Arch/AUR optional, e.g. deploy HOST_AUR_USER: "" HOST_SECURITY_HOST_PID: "1" volumes: - /:/host:rw ``` See `examples/compose-host-security.override.yml`. Dockwatch verifies that `HOST_ROOT` and `/proc//root` refer to the same host before enabling its namespace executor. Host commands are executed through `nsenter` in the target host namespaces; the browser can select only predefined operations and cannot submit arbitrary shell commands. Because this mode grants Dockwatch broad host-administration capability, use OIDC, restrict the `admin` role carefully, protect the Dockwatch database/`APP_SECRET`, and do not expose an agent token or the UI to untrusted networks. The same security APIs are available through the existing master/agent relay, so each agent can audit/manage **its own host** when that agent has been configured with the appropriate host-security capability. A master does not silently inherit host privileges on an agent. #### System package updates The **System-Updates** page uses the same selected environment as Docker/Host Security. Read-only update discovery requires the host-security namespace executor. Installing updates additionally requires `ALLOW_HOST_SECURITY_CHANGES=true` and `ALLOW_HOST_PACKAGE_MANAGEMENT=true`. Supported update strategies: - **apt**: simulate `dist-upgrade` for discovery; refresh with `apt-get update`; install with non-interactive `apt-get dist-upgrade -y --no-remove` - **dnf**: `check-update`; install with `dnf upgrade --refresh -y` - **yum**: `check-update`; install with `yum update -y` - **pacman**: prefer `checkupdates` for safe discovery; install official packages only through full `pacman -Syu --noconfirm` - **AUR**: detect `paru` or `yay`, query with `-Qua`, update AUR-only with `-Sua`, or combine repository+AUR updates with `-Syu` - **zypper/apk** remain supported because the Host Security package installer already supports those families For AUR, configure a real non-root host account: ```yaml environment: HOST_AUR_USER: deploy ``` That user must be able to run the helper and perform its package-manager escalation without an interactive password prompt. Dockwatch passes `--sudoflags=-n`; it never stores or submits a sudo password. ### Monitoring Probe types: - HTTP(S) - TCP - DNS - Docker container running state - Docker container running + optional `healthy` requirement Monitor lifecycle: - create / edit / delete - pause / resume - manual or timed maintenance - maintenance notes - manual **Check now** - interval and timeout - HTTP method and accepted status range - request headers/body - keyword / inverted keyword assertion - optional HTTP TLS verification bypass - local or remote-agent execution - heartbeat history - last latency/status message - 24-hour uptime - retention cleanup The scheduler uses a lightweight monitor query and does not recompute 24-hour uptime statistics every two seconds. Manual checks are serialized with scheduled checks and do not accidentally resume paused/maintenance monitors. Shutdown-cancelled checks are not persisted as false outages. ### Services and public status pages Probes can be grouped into user-facing Services. Aggregation is strict: - any probe `down` -> Service `down` - otherwise maintenance -> Service `maintenance` - pending/paused states are retained - otherwise Service `up` Service dialogs can assign/unassign probes directly. Admins can publish selected Services on public pages: ```text /status/ /public/api/status/ ``` The public JSON API uses a dedicated DTO and exposes only public status information (name, status, uptime, latency and last-check timestamp), not targets, node IDs, request headers, bodies or credentials. HTML/JSON status responses are not cached. ### Notifications Providers: - generic JSON webhook - ntfy - Gotify - SMTP SMTP supports: - `STARTTLS` - implicit `SSL/TLS` - `None` - authentication on/off independently of TLS - optional certificate-verification bypass for private/self-signed infrastructure SMTP connections inherit the notification deadline after connect as well, so a stalled server cannot leave a delivery goroutine hanging indefinitely. SMTP/password/token/secret values are AES-GCM encrypted at rest. Notification configuration is admin-only in both API and UI. ### PocketID / OIDC and roles Roles are mapped from OIDC/PocketID groups: - `viewer` – dashboards/status/inventory, sanitized monitor configuration - `operator` – stack configuration/logs/terminal, Docker actions, monitor/service operations, Git stacks - `admin` – environments, notifications, status-page administration and audit log Viewer API responses do not expose monitor request headers/body/keywords. Stack definition detail and live logs require Operator access because `.env`, secrets and runtime logs may contain sensitive data. Required OIDC setup: ```env AUTH_DISABLED=false BASE_URL=https://dockwatch.example.com OIDC_ISSUER=https://id.example.com OIDC_CLIENT_ID=... OIDC_CLIENT_SECRET=... APP_SECRET=replace-with-at-least-32-random-characters OIDC_ADMIN_GROUP=dockwatch-admins OIDC_OPERATOR_GROUP=dockwatch-operators ``` Redirect URI: ```text https://dockwatch.example.com/auth/callback ``` OIDC discovery, token exchange and verification are bounded by `HTTP_TIMEOUT_SECONDS`. Session/state cookies are `HttpOnly`, `SameSite=Lax` and become `Secure` when `BASE_URL` uses HTTPS. ### Master / agent Modes: ```text APP_MODE=standalone APP_MODE=master APP_MODE=agent ``` An agent exposes only `/agent/v1/*` and requires an `AGENT_TOKEN` of at least 24 characters. Agent tokens are AES-GCM encrypted in the master's SQLite database. Remote-capable features include: - stacks and actions - logs and PTY terminal - Compose graph - image update checks - containers/images/networks/volumes - container identity analysis, bind-mount permission repair and optional host-account creation on the agent host - monitoring probes - Git clone/sync/deploy - host-security audit/management when explicitly enabled on that agent See `examples/compose-master.yml` and `examples/compose-agent.yml`. ## Full Compose designer The Compose tab uses two synchronized representations: 1. the raw YAML source of truth 2. a recursive visual designer YAML changes are parsed after a short debounce. Visual changes patch the selected YAML AST path instead of reserializing a simplified Compose model. Unknown keys, `x-*` extensions and comments are therefore preserved where possible. Invalid/incomplete YAML pauses visual synchronization and never overwrites the source editor. The designer is not limited to a hard-coded subset: arbitrary maps, arrays and scalar types remain editable, including current/future Compose fields and long syntax objects. Server-side save validation still uses Docker Compose itself after all related `.env`, secret, env-file and config files have been staged. ## Reliability and security work in v9 / v9.2 The v9.2 identity extension is opt-in, admin-gated and preserves the existing least-surprise rule: diagnostics are read-only by default and no container user or host account is changed automatically. The v9 review includes, among other changes: - fixed fresh-database migration ordering before monitor indexes are created - SQLite WAL/busy-timeout/foreign-key configuration with a small connection pool - strict environment parsing instead of silently accepting invalid numeric/boolean values - graceful HTTP shutdown and sensible server timeouts - security headers and same-origin checks for browser mutations - request JSON size limit and single-value decoding - admin-only audit activity and notification configuration - audit retention cleanup - public-status DTO that does not leak internal monitor configuration - safe IP parsing for audit entries - sensitive viewer responses reduced - Compose save snapshot/rollback - safe stack delete vs. explicit purge - symlink protection on stack/Git file paths - atomic managed-file writes - Git sync serialization and managed-file manifest - Git copy file-descriptor leak fixed - Node URL validation rejects credentials/query/fragment - remote terminal WebSocket URL/query handling fixed - registry/Docker CLI positional-argument validation - scheduler query and shutdown behaviour improved - defensive frontend collection handling (`null` cannot break list pages) - admin-only navigation entries hidden for non-admin users - persisted dark/light theme and collapsible/mobile sidebar - API connectivity indicator and manual refresh - denser Docker/monitor/service tables and better empty/error states ## Configuration `.env.example` contains the defaults. Important values: ```env APP_MODE=standalone LISTEN_ADDR=:8080 BASE_URL=http://localhost:8080 DATA_DIR=/data STACKS_DIR=/stacks AUTH_DISABLED=true APP_SECRET=change-me-change-me-change-me-32bytes CHECK_CONCURRENCY=8 CHECK_RETENTION_DAYS=30 HTTP_TIMEOUT_SECONDS=10 AUDIT_RETENTION_DAYS=180 HOST_ROOT= ALLOW_HOST_USER_MANAGEMENT=false ALLOW_HOST_PERMISSION_MANAGEMENT=false HOST_SECURITY_ENABLED=false ALLOW_HOST_SECURITY_CHANGES=false ALLOW_HOST_PACKAGE_MANAGEMENT=false HOST_AUR_USER= HOST_SECURITY_HOST_PID=1 ``` `AUTH_DISABLED=true` is for local development only. Do not expose that configuration publicly. ## Run with Docker Compose ```bash cp .env.example .env docker compose up -d --build ``` Runtime mounts normally include: ```text /data SQLite, Docker registry config /stacks Compose projects /var/run/docker.sock Docker Engine access ``` Giving Dockwatch access to the Docker socket grants highly privileged control of that Docker host. Protect the UI and agent endpoint accordingly. ## v9.3.1 build-context fix v9.3 accidentally used the broad ignore pattern `dockwatch` in both `.gitignore` and `.dockerignore`. Because patterns without a slash match path components recursively, that could hide `cmd/dockwatch/` from Git and from the Docker build context. v9.3.1 removes that pattern, writes local Makefile builds to `bin/dockwatch`, ignores only `bin/`/`dist/`, and makes the Dockerfile fail early with a clear message if `cmd/dockwatch/main.go` is ever missing from the build context. ## Build from source The pinned OIDC/OAuth2 releases require **Go 1.25**. The Docker build uses `golang:1.25-alpine`. ```bash go mod tidy go test ./... CGO_ENABLED=0 go build ./cmd/dockwatch ``` Build metadata can be injected through the supplied Makefile/Dockerfile (`VERSION`, `COMMIT`, `BUILD_DATE`) and is visible in `/healthz` and the UI. ## Database SQLite database: ```text /data/dockwatch.db ``` `modernc.org/sqlite` is used, so no SQLite CGO binding is required. Migrations are additive and include users/sessions, nodes, monitors/checks/services, public status pages, audit events, notification channels and Git sources. ## Verification for this archive The artifact-building environment cannot reach `proxy.golang.org`, so it cannot download the real external modules or generate a trustworthy `go.sum` here. The repository intentionally does **not** ship fake checksums or test stubs. For quality control, the project is copied into a temporary test workspace where API-compatible local stubs replace only the external dependencies. Those stubs are not included in the ZIP. The checks used for this source snapshot include: ```text # all packages type/compile checked with temporary external-module stubs go test -run='^$' ./... go test ./internal/config ./internal/gitops ./internal/httpapi \ ./internal/monitor ./internal/nodes ./internal/notify ./internal/stacks ./internal/hostsecurity go vet ./... go test -race ./internal/config ./internal/gitops ./internal/httpapi ./internal/monitor ./internal/nodes ./internal/notify ./internal/stacks ./internal/hostsecurity node --check web/app.js ``` The SQLite migration SQL is additionally smoke-tested against Python's SQLite engine because the temporary `modernc.org/sqlite` stub does not implement a real SQL driver. On a normal networked development machine or during `docker build`, run `go mod tidy && go test ./...` once against the real pinned dependencies. ### Module metadata / `go mod tidy` The Docker builder runs `go mod tidy` after the full source tree has been copied and before compiling. This is intentional: Go 1.17+ module graph pruning may require transitive dependencies to be recorded as indirect requirements once the actual imported package graph is known. A checkout with an older/incomplete `go.mod` or without `go.sum` therefore still builds reproducibly in CI instead of failing late with `updates to go.mod needed`. For repository maintenance, run: ```bash go mod tidy git add go.mod go.sum git commit -m "chore: tidy Go modules" ``` CI can use `make verify` to fail when `go.mod`/`go.sum` are not committed in tidy form. ## v9.3.2 module-build fix v9.3.2 fixes Docker/CI builds that stopped at `go: updates to go.mod needed; to update it: go mod tidy`. The builder now runs `go mod tidy` after the complete source tree has been copied and before `go build`, so indirect requirements required by Go module graph pruning are materialized in the build stage. The Makefile also includes `tidy` and `verify` targets for maintaining committed `go.mod`/`go.sum` files. ### Build-context note (v9.3.3) Runtime directories in `.dockerignore` and `.gitignore` are root-anchored (`/stacks/`, `/data/`, `/bin/`, `/dist/`). This is intentional: unanchored patterns such as `stacks/` also match the source package `internal/stacks/` and can make Go try to resolve the project's own internal package as a remote module during `go mod tidy`. ### v9.5.2 UI polish - Firewall rule editor uses a wide responsive modal and keeps long CIDRs, comments and provider output inside the dialog. - Security details and notices wrap long unbroken values safely. - Light mode now has consistent navigation, buttons, inputs, tables, tabs, code/editor surfaces, notices, modals and Host Security cards instead of inheriting dark-only hard-coded colors.