diff --git a/.env.example b/.env.example index aa11624..058fa70 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ FORGEJO_BASE_URL= FORGEJO_TOKEN= FORGEJO_ORG= PROJECTS_ROOT=$HOME/projects -DEFAULT_RUN_COMMAND=go run . +DEFAULT_RUN_COMMAND=/usr/bin/env bash -lc 'sleep infinity' CADDY_ROUTE_ENABLED=false CADDY_ADMIN_URL=http://localhost:2019 CADDY_SERVER_ID=srv0 diff --git a/README.md b/README.md index a5188b1..be41978 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,6 @@ Notes: - Status is read via `systemctl --user show `. - Logs are read via `journalctl --user -u `. - Both commands are executed as the workspace Unix user via `sudo -n -u ...`. -- The app also auto-runs `loginctl enable-linger ` (via sudo) to keep user systemd available without active login. ## Systemd diff --git a/internal/app/config.go b/internal/app/config.go index b447341..62ea816 100644 --- a/internal/app/config.go +++ b/internal/app/config.go @@ -22,7 +22,7 @@ func loadConfig() (Config, error) { ForgejoToken: getenv("FORGEJO_TOKEN", ""), ForgejoOrg: getenv("FORGEJO_ORG", ""), ProjectRoot: getenv("PROJECTS_ROOT", filepath.Join(userHomeOrDot(), "projects")), - DefaultRunCommand: getenv("DEFAULT_RUN_COMMAND", "go run ."), + DefaultRunCommand: getenv("DEFAULT_RUN_COMMAND", "/usr/bin/env bash -lc 'sleep infinity'"), CaddyAdminURL: strings.TrimRight(getenv("CADDY_ADMIN_URL", "http://localhost:2019"), "/"), CaddyServerID: getenv("CADDY_SERVER_ID", "srv0"), CaddyDomainSuffix: getenv("CADDY_DOMAIN_SUFFIX", ""), diff --git a/internal/app/deploy_service.go b/internal/app/deploy_service.go index 11e9fa0..1dba0cf 100644 --- a/internal/app/deploy_service.go +++ b/internal/app/deploy_service.go @@ -89,5 +89,5 @@ func (a *App) deployProjectFromMain(_ context.Context, p Project) error { return err } } - return runSystemctlUser(p.UnixUser, "restart", p.ServiceName) + return runCmd("sudo", "-n", "-u", p.UnixUser, "systemctl", "--user", "restart", p.ServiceName) } diff --git a/internal/app/systemd_service.go b/internal/app/systemd_service.go index 4c676ed..f0fab29 100644 --- a/internal/app/systemd_service.go +++ b/internal/app/systemd_service.go @@ -81,8 +81,8 @@ WantedBy=default.target if err := os.WriteFile(servicePath, []byte(unit), 0o644); err != nil { return err } - _ = runSystemctlUser(p.UnixUser, "daemon-reload") - _ = runSystemctlUser(p.UnixUser, "enable", "--now", p.ServiceName) + _ = runCmd("sudo", "-n", "-u", p.UnixUser, "systemctl", "--user", "daemon-reload") + _ = runCmd("sudo", "-n", "-u", p.UnixUser, "systemctl", "--user", "enable", "--now", p.ServiceName) return nil } @@ -97,59 +97,6 @@ func homeForUnixUser(username string) (string, error) { return u.HomeDir, nil } -func userRuntimeEnv(username string) (string, string, error) { - u, err := user.Lookup(username) - if err != nil { - return "", "", fmt.Errorf("lookup unix user %q failed: %w", username, err) - } - runtimeDir := filepath.Join("/run/user", u.Uid) - busAddr := "unix:path=" + filepath.Join(runtimeDir, "bus") - return runtimeDir, busAddr, nil -} - -func runSystemctlUser(username string, args ...string) error { - if err := ensureLingerEnabled(username); err != nil { - return err - } - runtimeDir, busAddr, err := userRuntimeEnv(username) - if err != nil { - return err - } - cmdArgs := []string{ - "-n", "-u", username, - "env", - "XDG_RUNTIME_DIR=" + runtimeDir, - "DBUS_SESSION_BUS_ADDRESS=" + busAddr, - "systemctl", "--user", - } - cmdArgs = append(cmdArgs, args...) - return runCmd("sudo", cmdArgs...) -} - -func runJournalctlUser(username string, args ...string) (string, error) { - if err := ensureLingerEnabled(username); err != nil { - return "", err - } - runtimeDir, busAddr, err := userRuntimeEnv(username) - if err != nil { - return "", err - } - cmdArgs := []string{ - "-n", "-u", username, - "env", - "XDG_RUNTIME_DIR=" + runtimeDir, - "DBUS_SESSION_BUS_ADDRESS=" + busAddr, - "journalctl", "--user", - } - cmdArgs = append(cmdArgs, args...) - return runCmdOut("sudo", cmdArgs...) -} - -func ensureLingerEnabled(username string) error { - // Idempotent; safe to call repeatedly. - return runCmd("sudo", "loginctl", "enable-linger", username) -} - type ServiceStatus struct { Unit string `json:"unit"` ActiveState string `json:"active_state"` @@ -161,20 +108,11 @@ type ServiceStatus struct { } func (a *App) getServiceStatus(p Project) (ServiceStatus, error) { - out, err := func() (string, error) { - runtimeDir, busAddr, err := userRuntimeEnv(p.UnixUser) - if err != nil { - return "", err - } - return runCmdOut( - "sudo", "-n", "-u", p.UnixUser, - "env", - "XDG_RUNTIME_DIR="+runtimeDir, - "DBUS_SESSION_BUS_ADDRESS="+busAddr, - "systemctl", "--user", "show", p.ServiceName, "--no-pager", - "--property=ActiveState,SubState,MainPID,ExecMainStatus,Result,FragmentPath", - ) - }() + out, err := runCmdOut( + "sudo", "-n", "-u", p.UnixUser, + "systemctl", "--user", "show", p.ServiceName, "--no-pager", + "--property=ActiveState,SubState,MainPID,ExecMainStatus,Result,FragmentPath", + ) if err != nil { return ServiceStatus{}, err } @@ -204,5 +142,8 @@ func (a *App) getServiceStatus(p Project) (ServiceStatus, error) { } func (a *App) getServiceLogs(p Project, lines int) (string, error) { - return runJournalctlUser(p.UnixUser, "-u", p.ServiceName, "--no-pager", "-n", strconv.Itoa(lines), "-o", "short-iso") + return runCmdOut( + "sudo", "-n", "-u", p.UnixUser, + "journalctl", "--user", "-u", p.ServiceName, "--no-pager", "-n", strconv.Itoa(lines), "-o", "short-iso", + ) }