From 4bf811a38301f0d6c9a68e7e8241a887f7e5f43d Mon Sep 17 00:00:00 2001 From: dtoro Date: Wed, 8 Jul 2026 21:15:02 +0200 Subject: [PATCH] docker: alpine base with openssh-client, mount SSH key + NET_RAW for scheduler - Switch Dockerfile from distroless/static to alpine:3.21 - Install openssh-client-default in runtime image - Mount SSH key in scheduler service (docker-compose) - Add NET_RAW capability for ping checks - Wire OIKOS_SSH_KEY_PATH and OIKOS_SSH_USER env vars in scheduler - sshExec uses configured key path with StrictHostKeyChecking=no --- compose/oikos/Dockerfile | 6 ++++-- docker-compose.yml | 5 +++++ internal/scheduler/scheduler.go | 23 ++++++++++++++++++----- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/compose/oikos/Dockerfile b/compose/oikos/Dockerfile index bd9f5ed..a5acf25 100644 --- a/compose/oikos/Dockerfile +++ b/compose/oikos/Dockerfile @@ -23,8 +23,10 @@ COPY --from=ui-builder /web/dist ./web/dist RUN CGO_ENABLED=0 go build -o /oikos -tags timetzdata -ldflags="-s -w" ./cmd/oikos -# --- Runtime: distroless static --- -FROM gcr.io/distroless/static:nonroot +# --- Runtime: alpine with SSH + ping for scheduler checks --- +FROM alpine:3.21 + +RUN apk add --no-cache ca-certificates openssh-client-default COPY --from=builder /oikos /oikos COPY --from=builder /build/seeds /seeds diff --git a/docker-compose.yml b/docker-compose.yml index 6a35dbc..9f8e868 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -83,6 +83,11 @@ services: OIKOS_DATABASE_URL: postgres://oikos:${OIKOS_DB_PASSWORD:-oikos_dev}@postgres:5432/oikos?sslmode=disable OIKOS_DEBUG: "true" OIKOS_SCHEDULER_INTERVAL: "30s" + OIKOS_SSH_KEY_PATH: /etc/oikos/ssh_key + volumes: + - ${OIKOS_SSH_KEY_PATH:-~/.ssh/id_ed25519}:/etc/oikos/ssh_key:ro + cap_add: + - NET_RAW command: ["scheduler"] stop_signal: SIGTERM stop_grace_period: 30s diff --git a/internal/scheduler/scheduler.go b/internal/scheduler/scheduler.go index a302f70..05757b1 100644 --- a/internal/scheduler/scheduler.go +++ b/internal/scheduler/scheduler.go @@ -26,6 +26,11 @@ import ( "golang.org/x/sys/unix" ) +var ( + sshKeyPath string + sshUser string +) + // Run starts the scheduler loop. Blocks until ctx is cancelled. func Run(ctx context.Context, pool *db.Pool, cfg config.Config) { slog.Info("scheduler: starting", "interval", cfg.SchedulerInterval) @@ -34,6 +39,12 @@ func Run(ctx context.Context, pool *db.Pool, cfg config.Config) { interval = 30 * time.Second } + sshKeyPath = cfg.SSHKeyPath + sshUser = cfg.SSHUser + if sshUser == "" { + sshUser = "root" + } + ticker := time.NewTicker(interval) defer ticker.Stop() @@ -537,7 +548,7 @@ func checkSSHScript(ctx context.Context, cd sqlcgen.ListEnabledCheckDefsRow) che cfg.Port = 22 } if cfg.User == "" { - cfg.User = "root" + cfg.User = sshUser } if !allowlistedScript(cfg.Script) { @@ -609,12 +620,14 @@ func allowlistedScript(name string) bool { func sshExec(ctx context.Context, addr, user, cmd string, timeout time.Duration) ([]byte, error) { args := []string{ "-o", "ConnectTimeout=" + strconv.Itoa(int(timeout.Seconds())), - "-o", "StrictHostKeyChecking=yes", + "-o", "StrictHostKeyChecking=no", "-o", "BatchMode=yes", - "-l", user, - addr, - cmd, + "-o", "UserKnownHostsFile=/dev/null", } + if sshKeyPath != "" { + args = append(args, "-i", sshKeyPath) + } + args = append(args, "-l", user, addr, cmd) c := exec.CommandContext(ctx, "ssh", args...) return c.Output() }