From ae0481c9829e3602178db7c54caef3fe0586809c Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 15 Nov 2025 10:16:30 +0000 Subject: [PATCH] Improve bootstrap token UX with smart environment detection - Added DetectLXCCTID() to internal/system/container.go to detect Proxmox container ID - Extended /api/security/status to expose inContainer and lxcCtid fields - Updated FirstRunSetup to show most relevant command based on detected environment: * LXC with CTID: Shows 'pct exec 171 -- cat /etc/pulse/.bootstrap_token' * Docker: Shows 'docker exec cat /data/.bootstrap_token' * Bare metal: Shows 'cat /etc/pulse/.bootstrap_token' - Collapsed alternative methods behind 'Show other retrieval methods' button This addresses user feedback that showing all options was overwhelming. Now users see the command most likely to work for their setup first, with alternatives hidden but still accessible. --- copy_and_run.sh | 8 ++ .../src/components/FirstRunSetup.tsx | 79 +++++++++++++++---- internal/api/router.go | 5 ++ internal/system/container.go | 52 ++++++++++++ work.sh | 3 + 5 files changed, 131 insertions(+), 16 deletions(-) create mode 100755 copy_and_run.sh create mode 100644 work.sh diff --git a/copy_and_run.sh b/copy_and_run.sh new file mode 100755 index 0000000..1463137 --- /dev/null +++ b/copy_and_run.sh @@ -0,0 +1,8 @@ +cd / +if [ -d /tmp/pulse-copy ]; then + rm -rf /tmp/pulse-copy +fi +mkdir -p /tmp/pulse-copy +cp -R /opt/pulse /tmp/pulse-copy/repo +cd /tmp/pulse-copy/repo +GOCACHE=/tmp/go-cache HOME=/tmp/pulse-copy/repo go test ./... diff --git a/frontend-modern/src/components/FirstRunSetup.tsx b/frontend-modern/src/components/FirstRunSetup.tsx index 7ad0e67..6a4cfe6 100644 --- a/frontend-modern/src/components/FirstRunSetup.tsx +++ b/frontend-modern/src/components/FirstRunSetup.tsx @@ -26,6 +26,9 @@ export const FirstRunSetup: Component<{ force?: boolean; showLegacyBanner?: bool const [isUnlocked, setIsUnlocked] = createSignal(false); const [bootstrapTokenPath, setBootstrapTokenPath] = createSignal(''); const [isDocker, setIsDocker] = createSignal(false); + const [inContainer, setInContainer] = createSignal(false); + const [lxcCtid, setLxcCtid] = createSignal(''); + const [showAlternatives, setShowAlternatives] = createSignal(false); const applyTheme = (mode: 'system' | 'light' | 'dark') => { if (mode === 'light') { @@ -72,6 +75,8 @@ export const FirstRunSetup: Component<{ force?: boolean; showLegacyBanner?: bool if (data.bootstrapTokenPath) { setBootstrapTokenPath(data.bootstrapTokenPath); setIsDocker(data.isDocker || false); + setInContainer(data.inContainer || false); + setLxcCtid(data.lxcCtid || ''); } } } catch (error) { @@ -317,26 +322,68 @@ IMPORTANT: Keep these credentials secure! } > + {/* Show most relevant command based on detected environment */}
- -
# From Docker host:
- docker exec <container-name> cat {bootstrapTokenPath()} -
# For Kubernetes:
- kubectl exec <pod-name> -- cat {bootstrapTokenPath()} -
# For Proxmox LXC running Docker:
- pct exec <ctid> -- docker exec <container-name> cat {bootstrapTokenPath()} -
# Or enter the LXC container first:
- pct enter <ctid> -
# Then inside LXC:
+ {/* LXC with detected CTID */} + +
# Run from Proxmox host:
+ pct exec {lxcCtid()} -- cat {bootstrapTokenPath()}
- -
# For Proxmox LXC, run from Proxmox host:
- pct enter <ctid> -
# Or directly retrieve:
+ + {/* LXC without CTID */} + +
# Run from Proxmox host:
pct exec <ctid> -- cat {bootstrapTokenPath()} -
# Or from inside the LXC container:
- cat {bootstrapTokenPath()} + + {/* Docker (generic) */} + +
# From Docker host:
+ docker exec <container-name> cat {bootstrapTokenPath()} +
+ + {/* Bare metal / inside container */} + +
# On this host:
+ cat {bootstrapTokenPath()} +
+
+ + {/* Collapsible alternatives */} +
+ + +
+ +
+
# For Kubernetes:
+ kubectl exec <pod-name> -- cat {bootstrapTokenPath()} +
+
+
# For Proxmox LXC running Docker:
+ pct exec <ctid> -- docker exec <container-name> cat {bootstrapTokenPath()} +
+
+ +
+
# Enter container then run:
+ pct enter {lxcCtid() || ''} +
+ cat {bootstrapTokenPath()} +
+
+
+
# Direct file access:
+ cat {bootstrapTokenPath()} +
+
+
diff --git a/internal/api/router.go b/internal/api/router.go index 707fbaf..27c0bf8 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -28,6 +28,7 @@ import ( "github.com/rcourtman/pulse-go-rewrite/internal/dockeragent" "github.com/rcourtman/pulse-go-rewrite/internal/models" "github.com/rcourtman/pulse-go-rewrite/internal/monitoring" + "github.com/rcourtman/pulse-go-rewrite/internal/system" "github.com/rcourtman/pulse-go-rewrite/internal/tempproxy" "github.com/rcourtman/pulse-go-rewrite/internal/updates" "github.com/rcourtman/pulse-go-rewrite/internal/utils" @@ -485,6 +486,10 @@ func (r *Router) setupRoutes() { if r.bootstrapTokenHash != "" { status["bootstrapTokenPath"] = r.bootstrapTokenPath status["isDocker"] = os.Getenv("PULSE_DOCKER") == "true" + status["inContainer"] = system.InContainer() + if ctid := system.DetectLXCCTID(); ctid != "" { + status["lxcCtid"] = ctid + } } if r.config.DisableAuthEnvDetected { diff --git a/internal/system/container.go b/internal/system/container.go index 23ff3f1..cf1a644 100644 --- a/internal/system/container.go +++ b/internal/system/container.go @@ -56,6 +56,58 @@ func InContainer() bool { return false } +// DetectLXCCTID attempts to detect the Proxmox LXC container ID. +// Returns empty string if not in an LXC container or CTID cannot be determined. +func DetectLXCCTID() string { + // Method 1: Parse /proc/1/cgroup for LXC container ID + if data, err := os.ReadFile("/proc/1/cgroup"); err == nil { + lines := strings.Split(string(data), "\n") + for _, line := range lines { + // Look for patterns like: 0::/lxc/123 or 0::/lxc.payload.123 + if strings.Contains(line, "/lxc") || strings.Contains(line, "lxc.payload") { + // Extract digits after lxc/ or lxc.payload. + parts := strings.Split(line, "/") + for _, part := range parts { + part = strings.TrimPrefix(part, "lxc.payload.") + part = strings.TrimPrefix(part, "lxc.") + if isNumeric(part) { + return part + } + } + } + // Also check for machine-lxc-NNN pattern + if strings.Contains(line, "machine-lxc") { + fields := strings.Split(line, "-") + for _, field := range fields { + if isNumeric(field) { + return field + } + } + } + } + } + + // Method 2: Check hostname (some LXC containers use CTID as hostname) + if hostname, err := os.Hostname(); err == nil && isNumeric(hostname) { + return hostname + } + + return "" +} + +func isNumeric(s string) bool { + s = strings.TrimSpace(s) + if s == "" { + return false + } + for _, c := range s { + if c < '0' || c > '9' { + return false + } + } + return true +} + func isTruthy(value string) bool { switch strings.ToLower(strings.TrimSpace(value)) { case "1", "true", "t", "yes", "y", "on": diff --git a/work.sh b/work.sh new file mode 100644 index 0000000..4e331a1 --- /dev/null +++ b/work.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd /opt/pulse +git checkout cleanup-auto-uninstall