From c0aae4a562a2cd1965c9f6643d99358117c3fcc7 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 16 May 2026 19:33:19 +0000 Subject: [PATCH] feat(deploy): add SSH access to agent dev containers --- apps/docs/docs/development/dev-stack.md | 6 ++- deploy/docker/agent-dev-container/Dockerfile | 4 +- deploy/scripts/create-agent-dev-container.sh | 50 +++++++++++++++++++ .../test-create-agent-dev-container.sh | 34 +++++++++++++ 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/apps/docs/docs/development/dev-stack.md b/apps/docs/docs/development/dev-stack.md index 1937ca9c..7057776e 100644 --- a/apps/docs/docs/development/dev-stack.md +++ b/apps/docs/docs/development/dev-stack.md @@ -99,7 +99,11 @@ deploy/scripts/create-agent-dev-container.sh ``` The helper reads `.dev/dev.env`, creates one long-lived systemd Ubuntu -container, and runs the normal CT-Ops agent install script inside it. +container, publishes the container SSH service on a random loopback host port, +and runs the normal CT-Ops agent install script inside it. The selected SSH port +is printed after creation and can also be checked with +`docker port 22/tcp`. The helper creates an SSH login user named +`ssh-user` with password `password` by default. Switch back to loopback-only mode with: diff --git a/deploy/docker/agent-dev-container/Dockerfile b/deploy/docker/agent-dev-container/Dockerfile index 94965d69..9760bdab 100644 --- a/deploy/docker/agent-dev-container/Dockerfile +++ b/deploy/docker/agent-dev-container/Dockerfile @@ -6,12 +6,14 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ + openssh-server \ sudo \ systemd \ systemd-sysv \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ - && useradd --system --create-home --shell /usr/sbin/nologin ctops-agent + && useradd --system --create-home --shell /usr/sbin/nologin ctops-agent \ + && systemctl enable ssh.service STOPSIGNAL SIGRTMIN+3 diff --git a/deploy/scripts/create-agent-dev-container.sh b/deploy/scripts/create-agent-dev-container.sh index 5fe0224f..e49d0f43 100755 --- a/deploy/scripts/create-agent-dev-container.sh +++ b/deploy/scripts/create-agent-dev-container.sh @@ -12,6 +12,9 @@ INGEST_ADDRESS="${CT_OPS_AGENT_INGEST_ADDRESS:-ingest-dev:9443}" CONTAINER_NAME="${CT_OPS_AGENT_CONTAINER_NAME:-ctops-agent-$(date -u +%Y%m%d%H%M%S)-${RANDOM}}" ENROLMENT_TOKEN="${CT_OPS_ENROLMENT_TOKEN:-}" DOCKER_NETWORK="${CT_OPS_AGENT_DOCKER_NETWORK:-}" +SSH_BIND_ADDRESS="${CT_OPS_AGENT_SSH_BIND_ADDRESS:-127.0.0.1}" +SSH_USERNAME="${CT_OPS_AGENT_SSH_USERNAME:-ssh-user}" +SSH_PASSWORD="${CT_OPS_AGENT_SSH_PASSWORD:-password}" SKIP_VERIFY="true" INSTALL_AGENT="true" @@ -33,6 +36,12 @@ Options: Default: ${IMAGE_TAG} --network NAME Docker network containing the dev stack. Default: COMPOSE_PROJECT_NAME_default from ${DEV_ENV} + --ssh-bind-address IP Host interface for the random SSH port publish. + Default: ${SSH_BIND_ADDRESS} + --ssh-user USER SSH login user to create in the container. + Default: ${SSH_USERNAME} + --ssh-password PASS Password for the SSH login user. + Default: ${SSH_PASSWORD} --no-skip-verify Do not request TLS skip verification in the install script. --no-install Create the container but do not run the agent installer. -h, --help Show this help. @@ -125,6 +134,23 @@ wait_for_systemd() { die "systemd did not become ready in container ${name}" } +configure_ssh_user() { + local name="$1" + local username="$2" + local password="$3" + + docker exec \ + -e CT_OPS_AGENT_SSH_USERNAME="$username" \ + -e CT_OPS_AGENT_SSH_PASSWORD="$password" \ + "$name" \ + sh -euc ' + if ! id "$CT_OPS_AGENT_SSH_USERNAME" >/dev/null 2>&1; then + useradd --create-home --shell /bin/bash "$CT_OPS_AGENT_SSH_USERNAME" + fi + printf "%s:%s\n" "$CT_OPS_AGENT_SSH_USERNAME" "$CT_OPS_AGENT_SSH_PASSWORD" | chpasswd + ' +} + load_dev_env_defaults while [ "$#" -gt 0 ]; do @@ -159,6 +185,21 @@ while [ "$#" -gt 0 ]; do DOCKER_NETWORK="$2" shift 2 ;; + --ssh-bind-address) + [ "$#" -ge 2 ] || die "--ssh-bind-address requires a value" + SSH_BIND_ADDRESS="$2" + shift 2 + ;; + --ssh-user) + [ "$#" -ge 2 ] || die "--ssh-user requires a value" + SSH_USERNAME="$2" + shift 2 + ;; + --ssh-password) + [ "$#" -ge 2 ] || die "--ssh-password requires a value" + SSH_PASSWORD="$2" + shift 2 + ;; --no-skip-verify) SKIP_VERIFY="false" shift @@ -204,6 +245,7 @@ docker_run_args=( --cgroupns=host --restart unless-stopped --add-host host.docker.internal:host-gateway + --publish "${SSH_BIND_ADDRESS}::22" --tmpfs /run --tmpfs /run/lock --volume /sys/fs/cgroup:/sys/fs/cgroup:rw @@ -215,6 +257,8 @@ docker_run_args+=("$IMAGE_TAG") docker run "${docker_run_args[@]}" >/dev/null wait_for_systemd "$CONTAINER_NAME" +configure_ssh_user "$CONTAINER_NAME" "$SSH_USERNAME" "$SSH_PASSWORD" +ssh_endpoint="$(docker port "$CONTAINER_NAME" 22/tcp 2>/dev/null | head -n1 || true)" if [ "$INSTALL_AGENT" = "true" ]; then install_url="${APP_URL}/api/agent/install?ingest=$(urlencode "$INGEST_ADDRESS")" @@ -233,5 +277,11 @@ fi echo "" echo "Container: ${CONTAINER_NAME}" +if [ -n "$ssh_endpoint" ]; then + echo "SSHD: ${ssh_endpoint} -> 22/tcp" + echo "SSH Login: ssh -p ${ssh_endpoint##*:} ${SSH_USERNAME}@${ssh_endpoint%:*}" +else + echo "SSHD: docker port ${CONTAINER_NAME} 22/tcp" +fi echo "Status: docker exec -it ${CONTAINER_NAME} systemctl status ct-ops-agent" echo "Logs: docker exec -it ${CONTAINER_NAME} journalctl -u ct-ops-agent -f" diff --git a/deploy/scripts/test-create-agent-dev-container.sh b/deploy/scripts/test-create-agent-dev-container.sh index 37e2f307..5153705f 100755 --- a/deploy/scripts/test-create-agent-dev-container.sh +++ b/deploy/scripts/test-create-agent-dev-container.sh @@ -31,6 +31,10 @@ case "${1:-}" in run) exit 0 ;; + port) + echo "127.0.0.1:32768" + exit 0 + ;; exec) if printf '%s\n' "$*" | grep -q 'test -d /run/systemd/system'; then exit 0 @@ -109,11 +113,31 @@ EOF cat "$log" >&2 exit 1 fi + if ! grep -q -- "--publish 127.0.0.1::22" "$log"; then + echo "expected sshd port publish on a random loopback host port" >&2 + cat "$log" >&2 + exit 1 + fi if ! grep -Fq -- "CT_OPS_ENROLMENT_TOKEN=dev_env_token" "$log"; then echo "expected enrolment token from dev env" >&2 cat "$log" >&2 exit 1 fi + if ! grep -Fq -- "CT_OPS_AGENT_SSH_USERNAME=ssh-user" "$log"; then + echo "expected default ssh username" >&2 + cat "$log" >&2 + exit 1 + fi + if ! grep -Fq -- "CT_OPS_AGENT_SSH_PASSWORD=password" "$log"; then + echo "expected default ssh password" >&2 + cat "$log" >&2 + exit 1 + fi + if ! grep -Fq -- "useradd --create-home --shell /bin/bash" "$log"; then + echo "expected ssh user creation" >&2 + cat "$log" >&2 + exit 1 + fi if ! grep -Fq -- "CT_OPS_AGENT_INSTALL_URL=http://dev-env-host:3000/api/agent/install\\?ingest=dev-env-host%3A9443\\&skip_verify=true" "$log"; then echo "expected install URL with encoded ingest address and skip_verify" >&2 cat "$log" >&2 @@ -129,6 +153,16 @@ EOF echo "$output" >&2 exit 1 fi + if [[ "$output" != *"SSHD: 127.0.0.1:32768 -> 22/tcp"* ]]; then + echo "expected sshd port mapping hint" >&2 + echo "$output" >&2 + exit 1 + fi + if [[ "$output" != *"SSH Login: ssh -p 32768 ssh-user@127.0.0.1"* ]]; then + echo "expected ssh login hint" >&2 + echo "$output" >&2 + exit 1 + fi echo "create-agent-dev-container.sh command assembly test passed" }