Compare commits
2 commits
b38c39030b
...
4e6ad3cd26
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e6ad3cd26 | |||
| 7bff7941cd |
5 changed files with 74 additions and 14 deletions
|
|
@ -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=/usr/bin/env bash -lc 'sleep infinity'
|
DEFAULT_RUN_COMMAND=go run .
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ 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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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", "/usr/bin/env bash -lc 'sleep infinity'"),
|
DefaultRunCommand: getenv("DEFAULT_RUN_COMMAND", "go run ."),
|
||||||
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", ""),
|
||||||
|
|
|
||||||
|
|
@ -89,5 +89,5 @@ func (a *App) deployProjectFromMain(_ context.Context, p Project) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return runCmd("sudo", "-n", "-u", p.UnixUser, "systemctl", "--user", "restart", p.ServiceName)
|
return runSystemctlUser(p.UnixUser, "restart", p.ServiceName)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
_ = runCmd("sudo", "-n", "-u", p.UnixUser, "systemctl", "--user", "daemon-reload")
|
_ = runSystemctlUser(p.UnixUser, "daemon-reload")
|
||||||
_ = runCmd("sudo", "-n", "-u", p.UnixUser, "systemctl", "--user", "enable", "--now", p.ServiceName)
|
_ = runSystemctlUser(p.UnixUser, "enable", "--now", p.ServiceName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,6 +97,59 @@ 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"`
|
||||||
|
|
@ -108,11 +161,20 @@ type ServiceStatus struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) getServiceStatus(p Project) (ServiceStatus, error) {
|
func (a *App) getServiceStatus(p Project) (ServiceStatus, error) {
|
||||||
out, err := runCmdOut(
|
out, err := func() (string, error) {
|
||||||
|
runtimeDir, busAddr, err := userRuntimeEnv(p.UnixUser)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return runCmdOut(
|
||||||
"sudo", "-n", "-u", p.UnixUser,
|
"sudo", "-n", "-u", p.UnixUser,
|
||||||
|
"env",
|
||||||
|
"XDG_RUNTIME_DIR="+runtimeDir,
|
||||||
|
"DBUS_SESSION_BUS_ADDRESS="+busAddr,
|
||||||
"systemctl", "--user", "show", p.ServiceName, "--no-pager",
|
"systemctl", "--user", "show", p.ServiceName, "--no-pager",
|
||||||
"--property=ActiveState,SubState,MainPID,ExecMainStatus,Result,FragmentPath",
|
"--property=ActiveState,SubState,MainPID,ExecMainStatus,Result,FragmentPath",
|
||||||
)
|
)
|
||||||
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ServiceStatus{}, err
|
return ServiceStatus{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -142,8 +204,5 @@ 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 runCmdOut(
|
return runJournalctlUser(p.UnixUser, "-u", p.ServiceName, "--no-pager", "-n", strconv.Itoa(lines), "-o", "short-iso")
|
||||||
"sudo", "-n", "-u", p.UnixUser,
|
|
||||||
"journalctl", "--user", "-u", p.ServiceName, "--no-pager", "-n", strconv.Itoa(lines), "-o", "short-iso",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue