feat: seed SSH users from UPTOP_ADMIN_KEY env and UPTOP_KEYS file
CI / test (pull_request) Successful in 2m59s
CI / lint (pull_request) Failing after 1m6s
CI / vulncheck (pull_request) Successful in 1m6s

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.
This commit is contained in:
2026-05-27 16:32:33 -04:00
parent ea721601ab
commit d5587958e8
2 changed files with 81 additions and 0 deletions
+79
View File
@@ -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"
}
+2
View File
@@ -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