feat: seed SSH users from env var and authorized_keys file (#31)
## Summary Docker onboarding was broken — no way to add first SSH user without `docker attach` to TUI. Now reads SSH public keys from two sources on startup: - `UPTOP_ADMIN_KEY` env var — single key for quick single-user setup - `UPTOP_KEYS` file path — authorized_keys format for team setup Dockerfile already sets `UPTOP_KEYS=/data/authorized_keys` and compose mounts `./data:/data`, so the flow is: ``` echo "ssh-ed25519 AAAA... me@host" > ./data/authorized_keys docker compose up -d ssh -p 23234 localhost ``` ### Behavior - Skips keys already in DB (idempotent across restarts) - All seeded users get admin role - Username parsed from key comment (e.g. `tyler@macbook` → `tyler`) - Comments and blank lines in keys file are ignored ### Tested - UPTOP_ADMIN_KEY seeds single admin user - UPTOP_KEYS file seeds multiple users with correct usernames - Second startup skips existing keys (no duplicates) - Build and all tests pass Reviewed-on: lerko/uptop#31
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
@@ -9,7 +10,9 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -348,6 +351,8 @@ func runServe(args []string) {
|
||||
seedDemoData(s)
|
||||
}
|
||||
|
||||
seedKeysFromEnv(s)
|
||||
|
||||
if *importKuma != "" {
|
||||
kb, err := importer.LoadKumaFile(*importKuma)
|
||||
if err != nil {
|
||||
@@ -563,3 +568,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(filepath.Clean(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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user