WIP: add host SSH setup for Docker-managed host nginx#1686
Draft
Hintay wants to merge 42 commits into
Draft
Conversation
Introduces Host* fields on settings.Nginx and a ControlMode() helper that resolves the active nginx control channel with priority host_via_ssh > external_container > local. RunningInAnotherContainer() is refactored to delegate to ControlMode() so SSH mode is correctly excluded from the docker-exec path. Refs: docs/superpowers/specs/2026-05-21-docker-host-nginx-management-design.md §5
…ment Replaces string literals in nginx_test.go with the exported constants defined in nginx.go so that renaming a constant breaks the test. Removes the internal docs/superpowers/ reference from the source comment.
Adds internal/nginx/runner.go defining a Runner interface that abstracts command execution and path-existence checks across the three control modes. localRunner mirrors the current behavior. Placeholders for newSSHRunner/newDockerRunner keep the package compiling until follow-up tasks replace them with real impls.
Wires the Runner interface to the existing docker.Exec / docker.StatPath helpers for external_container mode. The SSH runner placeholder remains until Task 5.
Introduces internal/host/ssh package with cosy error codes 510001-510009 and a KnownHosts type that wraps a known_hosts file with thread-safe Trust + IsTrusted operations. Used by subsequent Client and verify tasks.
Adds a long-lived SSH client with keepalive + lazy reconnect, command-building with sudo whitelisting and shell escaping, plus the sshRunner wiring into the Runner interface.
- connect() now holds the mutex across dial, preventing concurrent callers from overwriting c.conn and leaking SSH connections. - buildCommand tokenizes and shell-quotes SudoPrefix so a misconfigured setting cannot inject arbitrary commands. - Adds a ResetSSHClient() hook for settings-change invalidation. - Logs a clear warning when password auth is configured (currently unsupported pending crypto package refactor).
execCommand no longer branches on RunningInAnotherContainer directly. The Runner abstraction picks the right backend (local/docker/ssh) based on settings.NginxSettings.ControlMode().
GetPIDPath probing and IsRunning() now branch on ControlMode(): - local: unchanged (os.Stat + gopsutil.PidExists) - external_container: docker.StatPath as before - host_via_ssh: systemctl is-active over SSH with PID-file fallback PID files are visible via the bind-mounted /var/run, so local probing still works in SSH mode; the systemctl path is preferred for authority.
Adds compose / override / docker run / authorized_keys / sudoers / acl_commands templates. They share the SetupParams input model and are rendered by snippets.go in the next task.
Embeds the six templates via embed.FS; RenderAll returns a single struct convenient for both REST JSON and CLI --json output. Golden file tests lock down byte-for-byte output so CLI and Web UI render identical content.
Writes a 0600 OpenSSH-format private key and returns the matching public key in authorized_keys single-line form. Overwrites any existing key file at the target path (matches Web UI 'regenerate' UX).
Steps 0-9 with structured StepOutcome including remediation hints. Linux-only via build tag; non-Linux returns a stub that flags the platform mismatch.
Implements 'nginx-ui host-setup print/keygen/test'. The 'test' action body is intentionally a stub until Task 14 extracts the shared live-client builder helper used by both this CLI and the REST verify handler.
Adds /api/host/setup/{preview,keypair,publickey,verify,known-host} and
wires the CLI 'host-setup test' subcommand through the shared
setup.NewClientFromSettings + setup.Verify helpers introduced in this
commit. Mounted under the authenticated router group.
- Adds ErrPublicKeyParse (510010) so TrustHostKey returns a semantically correct error instead of reusing ErrHostKeyMismatch's expected/got template. - The TrustHostKey HTTP handler now recomputes the SHA256 fingerprint of the submitted public key and rejects requests where the client-confirmed fingerprint does not match. Closes a security gap where a tampered request body could install an unverified key in known_hosts.
When set to 'true', the s6-overlay nginx service blocks on sleep infinity instead of starting nginx, and the init-config script skips the /etc/nginx seed copy. Required by host_via_ssh mode so the container's internal nginx doesn't conflict with the host one.
…rning Renders the address/user/unit form and surfaces a non-blocking warning when the host address is outside the same-host whitelist.
Adds a Wizard.vue that orchestrates the four steps and a control-mode segmented control on NginxSettings.vue that exposes the wizard entry.
…settings, default strict host key Three critical gaps surfaced by the final review: 1. nginx Reload()/restart() in SSH mode now call 'sudo systemctl reload|restart <unit>' instead of 'nginx -s reload', which would send a SIGHUP to a PID that does not exist in the container's PID namespace. 2. The wizard's connection form now persists all 10 Host* fields to the settings store, so the existing Save button in Preference.vue actually saves them. The TypeScript NginxSettings interface gains the matching fields. 3. HostStrictHostKey now defaults to true unless explicitly disabled via NGINX_UI_NGINX_HOST_STRICT_HOST_KEY=false, closing a TOFU security gap on first connection. 4. ResetSSHClient() is now called after SaveSettings so the cached SSH client picks up new credentials without a process restart.
Align known_hosts defaults across setup and runtime, require explicit host key confirmation from the UI, and persist the host SSH wizard settings needed by verification and runner flows.
Normalize Host SSH wizard imports and shell quoting style so frontend lint passes after the host key trust review fixes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds a host SSH control mode for deployments where Nginx UI runs in Docker but needs to manage an nginx instance installed directly on the same host.
Changes
Host SSH control mode
host_via_sshsupport for controlling host nginx from the Nginx UI container.Host setup workflow
nginx-ui host-setupCLI commands for generating snippets, keypairs, and verification checks.Frontend setup wizard
CodeBlockcomponent with copy support for setup snippets.Docker support
NGINX_UI_DISABLE_BUNDLED_NGINXsupport so Docker deployments can disable the bundled nginx service when controlling host nginx.Documentation