status cmd
This commit is contained in:
150
main.go
150
main.go
@@ -78,6 +78,7 @@ func defaultCommands() []command {
|
||||
{name: "dev", description: "run the PocketBase binary locally", action: runDev},
|
||||
{name: "setup", description: "provision the remote server and install PocketBase", action: runSetup},
|
||||
{name: "deploy", description: "sync migrations/hooks/static assets (runs setup if needed)", action: runDeploy},
|
||||
{name: "status", description: "show status for the remote PocketBase service", action: runStatus},
|
||||
{name: "logs", description: "show PocketBase logs", action: runLogs},
|
||||
{name: "secrets", description: "manage deployment secrets", action: runSecrets},
|
||||
}
|
||||
@@ -897,9 +898,12 @@ const (
|
||||
totalSetupSteps = 5
|
||||
remoteWindowSize = 10
|
||||
remoteIndent = " "
|
||||
remoteLineColor = "\033[96m"
|
||||
headerColor = "\033[95m"
|
||||
localTimeColor = "\033[92m"
|
||||
remoteLineColor = ""
|
||||
headerColor = "\033[1;37m"
|
||||
localTimeColor = "\033[0;32m"
|
||||
statusActiveColor = "\033[92m"
|
||||
statusWarnColor = "\033[93m"
|
||||
statusFailColor = "\033[91m"
|
||||
remoteColorReset = "\033[0m"
|
||||
)
|
||||
|
||||
@@ -1112,6 +1116,146 @@ func runDeploy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func runStatus() error {
|
||||
ctx, err := buildDeploymentContext()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeSSHControlMaster(ctx.serverIP)
|
||||
|
||||
printInfo("Service", fmt.Sprintf("pb@%s", ctx.serviceName), remoteLineColor)
|
||||
printInfo("Server", ctx.serverIP, remoteLineColor)
|
||||
printInfo("Domain", ctx.domain, remoteLineColor)
|
||||
printInfo("Port", fmt.Sprintf("%d", ctx.port), remoteLineColor)
|
||||
|
||||
props, err := querySystemdProperties(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to query PocketBase service: %w", err)
|
||||
}
|
||||
|
||||
state := strings.TrimSpace(props["ActiveState"])
|
||||
if state == "" {
|
||||
state = "unknown"
|
||||
}
|
||||
statusLine := state
|
||||
if sub := strings.TrimSpace(props["SubState"]); sub != "" {
|
||||
statusLine = fmt.Sprintf("%s (%s)", state, sub)
|
||||
}
|
||||
printInfo("Status", statusLine, statusColorFor(state))
|
||||
|
||||
if pid := strings.TrimSpace(props["ExecMainPID"]); pid != "" && pid != "0" {
|
||||
printInfo("PID", pid, remoteLineColor)
|
||||
}
|
||||
|
||||
if started := strings.TrimSpace(props["ActiveEnterTimestamp"]); started != "" {
|
||||
printInfo("Active since", started, localTimeColor)
|
||||
}
|
||||
|
||||
if uptime := computeUptime(ctx.serverIP, props["ActiveEnterTimestampMonotonic"]); uptime > 0 {
|
||||
printInfo("Uptime", formatDuration(uptime), localTimeColor)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printInfo(label, value, valueColor string) {
|
||||
if valueColor == "" {
|
||||
valueColor = remoteLineColor
|
||||
}
|
||||
fmt.Printf("%s%s:%s %s%s%s\n", headerColor, label, remoteColorReset, valueColor, value, remoteColorReset)
|
||||
}
|
||||
|
||||
func statusColorFor(state string) string {
|
||||
switch strings.ToLower(state) {
|
||||
case "active":
|
||||
return statusActiveColor
|
||||
case "activating", "deactivating":
|
||||
return statusWarnColor
|
||||
case "failed", "inactive":
|
||||
return statusFailColor
|
||||
default:
|
||||
return remoteLineColor
|
||||
}
|
||||
}
|
||||
|
||||
// querySystemdProperties asks systemd for a few service properties.
|
||||
func querySystemdProperties(ctx *deploymentContext) (map[string]string, error) {
|
||||
script := fmt.Sprintf(`set -euo pipefail
|
||||
systemctl show pb@%s -p ActiveState -p SubState -p ActiveEnterTimestamp -p ActiveEnterTimestampMonotonic -p ExecMainPID
|
||||
`, ctx.serviceName)
|
||||
output, err := runSSHCollect(ctx.serverIP, script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseKeyValueLines(output), nil
|
||||
}
|
||||
|
||||
func computeUptime(server, startMicro string) time.Duration {
|
||||
startMicro = strings.TrimSpace(startMicro)
|
||||
if startMicro == "" {
|
||||
return 0
|
||||
}
|
||||
start, err := strconv.ParseInt(startMicro, 10, 64)
|
||||
if err != nil || start <= 0 {
|
||||
return 0
|
||||
}
|
||||
now, err := remoteMonotonicMicro(server)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: unable to compute uptime: %v\n", err)
|
||||
return 0
|
||||
}
|
||||
diff := now - start
|
||||
if diff <= 0 {
|
||||
return 0
|
||||
}
|
||||
return time.Duration(diff) * time.Microsecond
|
||||
}
|
||||
|
||||
func remoteMonotonicMicro(server string) (int64, error) {
|
||||
output, err := runSSHOutput(server, `awk '{printf "%d", $1*1000000}' /proc/uptime`)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseInt(strings.TrimSpace(output), 10, 64)
|
||||
}
|
||||
|
||||
func parseKeyValueLines(input string) map[string]string {
|
||||
props := make(map[string]string)
|
||||
scanner := bufio.NewScanner(strings.NewReader(input))
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if idx := strings.Index(line, "="); idx > 0 {
|
||||
key := strings.TrimSpace(line[:idx])
|
||||
value := strings.TrimSpace(line[idx+1:])
|
||||
props[key] = value
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
func formatDuration(d time.Duration) string {
|
||||
if d < time.Second {
|
||||
return d.Round(time.Millisecond).String()
|
||||
}
|
||||
parts := make([]string, 0, 3)
|
||||
if hours := d / time.Hour; hours > 0 {
|
||||
parts = append(parts, fmt.Sprintf("%dh", hours))
|
||||
d -= hours * time.Hour
|
||||
}
|
||||
if mins := d / time.Minute; mins > 0 {
|
||||
parts = append(parts, fmt.Sprintf("%dm", mins))
|
||||
d -= mins * time.Minute
|
||||
}
|
||||
seconds := int64(d / time.Second)
|
||||
if seconds > 0 || len(parts) == 0 {
|
||||
parts = append(parts, fmt.Sprintf("%ds", seconds))
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func runLogs() error {
|
||||
ctx, err := buildDeploymentContext()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user