From d5587958e8d7d28d1473f2684378bfb29359e7f5 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Wed, 27 May 2026 16:32:33 -0400 Subject: [PATCH 1/3] feat: seed SSH users from UPTOP_ADMIN_KEY env and UPTOP_KEYS file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On startup, reads SSH public keys from two sources: - UPTOP_ADMIN_KEY env var (single key, quick setup) - UPTOP_KEYS file path (authorized_keys format, team setup) Keys already in the database are skipped (idempotent across restarts). All seeded users get admin role. Username parsed from key comment. Dockerfile already sets UPTOP_KEYS=/data/authorized_keys — Docker users just drop their keys file in ./data/ and start the container. --- cmd/uptop/main.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 2 ++ 2 files changed, 81 insertions(+) diff --git a/cmd/uptop/main.go b/cmd/uptop/main.go index 659d509..4827a06 100644 --- a/cmd/uptop/main.go +++ b/cmd/uptop/main.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "context" "errors" "flag" @@ -10,6 +11,7 @@ import ( "os" "os/signal" "strconv" + "strings" "sync" "syscall" "time" @@ -348,6 +350,8 @@ func runServe(args []string) { seedDemoData(s) } + seedKeysFromEnv(s) + if *importKuma != "" { kb, err := importer.LoadKumaFile(*importKuma) if err != nil { @@ -563,3 +567,78 @@ func (c *keyCache) IsAllowed(incomingKey ssh.PublicKey) bool { } return false } + +func seedKeysFromEnv(s store.Store) { + var keys []string + + if v := os.Getenv("UPTOP_ADMIN_KEY"); v != "" { + keys = append(keys, strings.TrimSpace(v)) + } + + if path := os.Getenv("UPTOP_KEYS"); path != "" { + f, err := os.Open(path) + if err == nil { + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + keys = append(keys, line) + } + f.Close() + } + } + + if len(keys) == 0 { + return + } + + existing, err := s.GetAllUsers() + if err != nil { + fmt.Fprintf(os.Stderr, "warning: could not check existing users: %v\n", err) + return + } + + existingKeys := make(map[string]bool) + for _, u := range existing { + existingKeys[u.PublicKey] = true + } + + added := 0 + for i, key := range keys { + if existingKeys[key] { + continue + } + + username := usernameFromKey(key, i, len(existing)+added) + if err := s.AddUser(username, key, "admin"); err != nil { + fmt.Fprintf(os.Stderr, "warning: failed to seed user %q: %v\n", username, err) + continue + } + fmt.Printf("Seeded admin user %q from %s\n", username, seedSource(i, len(keys), os.Getenv("UPTOP_ADMIN_KEY") != "")) + added++ + } +} + +func usernameFromKey(key string, index, totalExisting int) string { + parts := strings.Fields(key) + if len(parts) >= 3 { + comment := parts[2] + if at := strings.Index(comment, "@"); at > 0 { + return comment[:at] + } + return comment + } + if index == 0 && totalExisting == 0 { + return "admin" + } + return fmt.Sprintf("user-%d", totalExisting+1) +} + +func seedSource(index, total int, hasEnvKey bool) string { + if hasEnvKey && index == 0 { + return "UPTOP_ADMIN_KEY" + } + return "UPTOP_KEYS" +} diff --git a/docker-compose.yml b/docker-compose.yml index 9c71634..19452af 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,5 +14,7 @@ services: - UPTOP_HTTP_PORT=8080 - UPTOP_STATUS_ENABLED=true - UPTOP_STATUS_TITLE=System Status + # SSH access: add your public key via env var or authorized_keys file + # - UPTOP_ADMIN_KEY=ssh-ed25519 AAAA... you@host volumes: - ./data:/data -- 2.52.0 From 64b49187f54e1feb521cd6e72e0b5b43688202af Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Wed, 27 May 2026 16:45:57 -0400 Subject: [PATCH 2/3] fix(lint): handle file close error in key seeding --- cmd/uptop/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/uptop/main.go b/cmd/uptop/main.go index 4827a06..da8df6a 100644 --- a/cmd/uptop/main.go +++ b/cmd/uptop/main.go @@ -586,7 +586,7 @@ func seedKeysFromEnv(s store.Store) { } keys = append(keys, line) } - f.Close() + _ = f.Close() } } -- 2.52.0 From c8e9893b5968ef16b4e96834ededd830d495bffb Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Wed, 27 May 2026 16:59:02 -0400 Subject: [PATCH 3/3] fix(lint): sanitize UPTOP_KEYS path for gosec G703 --- cmd/uptop/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/uptop/main.go b/cmd/uptop/main.go index da8df6a..65ad2f4 100644 --- a/cmd/uptop/main.go +++ b/cmd/uptop/main.go @@ -10,6 +10,7 @@ import ( "net/url" "os" "os/signal" + "path/filepath" "strconv" "strings" "sync" @@ -576,7 +577,7 @@ func seedKeysFromEnv(s store.Store) { } if path := os.Getenv("UPTOP_KEYS"); path != "" { - f, err := os.Open(path) + f, err := os.Open(filepath.Clean(path)) if err == nil { scanner := bufio.NewScanner(f) for scanner.Scan() { -- 2.52.0