Compare commits

..

No commits in common. "4e6ad3cd26f996997ff06d7769b12cb15ddf82e1" and "b38c39030ba93b7feb918777cceda4b6bc135987" have entirely different histories.

5 changed files with 14 additions and 74 deletions

View file

@ -10,7 +10,7 @@ FORGEJO_BASE_URL=
FORGEJO_TOKEN= FORGEJO_TOKEN=
FORGEJO_ORG= FORGEJO_ORG=
PROJECTS_ROOT=$HOME/projects PROJECTS_ROOT=$HOME/projects
DEFAULT_RUN_COMMAND=go run . DEFAULT_RUN_COMMAND=/usr/bin/env bash -lc 'sleep infinity'
CADDY_ROUTE_ENABLED=false CADDY_ROUTE_ENABLED=false
CADDY_ADMIN_URL=http://localhost:2019 CADDY_ADMIN_URL=http://localhost:2019
CADDY_SERVER_ID=srv0 CADDY_SERVER_ID=srv0

View file

@ -94,7 +94,6 @@ Notes:
- Status is read via `systemctl --user show <service>`. - Status is read via `systemctl --user show <service>`.
- Logs are read via `journalctl --user -u <service>`. - Logs are read via `journalctl --user -u <service>`.
- Both commands are executed as the workspace Unix user via `sudo -n -u <unix_user> ...`. - Both commands are executed as the workspace Unix user via `sudo -n -u <unix_user> ...`.
- The app also auto-runs `loginctl enable-linger <unix_user>` (via sudo) to keep user systemd available without active login.
## Systemd ## Systemd

View file

@ -22,7 +22,7 @@ func loadConfig() (Config, error) {
ForgejoToken: getenv("FORGEJO_TOKEN", ""), ForgejoToken: getenv("FORGEJO_TOKEN", ""),
ForgejoOrg: getenv("FORGEJO_ORG", ""), ForgejoOrg: getenv("FORGEJO_ORG", ""),
ProjectRoot: getenv("PROJECTS_ROOT", filepath.Join(userHomeOrDot(), "projects")), 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"), "/"), CaddyAdminURL: strings.TrimRight(getenv("CADDY_ADMIN_URL", "http://localhost:2019"), "/"),
CaddyServerID: getenv("CADDY_SERVER_ID", "srv0"), CaddyServerID: getenv("CADDY_SERVER_ID", "srv0"),
CaddyDomainSuffix: getenv("CADDY_DOMAIN_SUFFIX", ""), CaddyDomainSuffix: getenv("CADDY_DOMAIN_SUFFIX", ""),

View file

@ -89,5 +89,5 @@ func (a *App) deployProjectFromMain(_ context.Context, p Project) error {
return err return err
} }
} }
return runSystemctlUser(p.UnixUser, "restart", p.ServiceName) return runCmd("sudo", "-n", "-u", p.UnixUser, "systemctl", "--user", "restart", p.ServiceName)
} }

View file

@ -81,8 +81,8 @@ WantedBy=default.target
if err := os.WriteFile(servicePath, []byte(unit), 0o644); err != nil { if err := os.WriteFile(servicePath, []byte(unit), 0o644); err != nil {
return err return err
} }
_ = runSystemctlUser(p.UnixUser, "daemon-reload") _ = runCmd("sudo", "-n", "-u", p.UnixUser, "systemctl", "--user", "daemon-reload")
_ = runSystemctlUser(p.UnixUser, "enable", "--now", p.ServiceName) _ = runCmd("sudo", "-n", "-u", p.UnixUser, "systemctl", "--user", "enable", "--now", p.ServiceName)
return nil return nil
} }
@ -97,59 +97,6 @@ func homeForUnixUser(username string) (string, error) {
return u.HomeDir, nil 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 { type ServiceStatus struct {
Unit string `json:"unit"` Unit string `json:"unit"`
ActiveState string `json:"active_state"` ActiveState string `json:"active_state"`
@ -161,20 +108,11 @@ type ServiceStatus struct {
} }
func (a *App) getServiceStatus(p Project) (ServiceStatus, error) { func (a *App) getServiceStatus(p Project) (ServiceStatus, error) {
out, err := func() (string, error) { out, err := runCmdOut(
runtimeDir, busAddr, err := userRuntimeEnv(p.UnixUser) "sudo", "-n", "-u", p.UnixUser,
if err != nil { "systemctl", "--user", "show", p.ServiceName, "--no-pager",
return "", err "--property=ActiveState,SubState,MainPID,ExecMainStatus,Result,FragmentPath",
} )
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",
)
}()
if err != nil { if err != nil {
return ServiceStatus{}, err 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) { 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",
)
} }