Files
latchd/legacy/go/session/x11.go
Zane Walker faf5b27328 feat: ship Rust login manager and GitLab release pipeline
Replace the legacy Go implementation as the production path, quarantine the reference tree, add deterministic release packaging and version metadata, and run verification through GitLab CI.
2026-09-25 21:33:46 +05:30

104 lines
2.4 KiB
Go

// session — X11 server setup.
//
// Mirrors vendor/src/post_login/x.rs:setup_x.
package session
import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"
"time"
"latchd/config"
)
func setupX(uid, gid uint32, groups []uint32, cfg *config.Config) (*exec.Cmd, error) {
display := os.Getenv("DISPLAY")
vtnr := os.Getenv("XDG_VTNR")
home := os.Getenv("HOME")
if display == "" || vtnr == "" || home == "" {
return nil, fmt.Errorf("missing DISPLAY/XDG_VTNR/HOME env vars")
}
xauthPath := filepath.Join(home, ".Xauthority")
os.Remove(xauthPath)
cookie, err := mCookie()
if err != nil {
return nil, err
}
xauthCmd := exec.Command(cfg.X11.XauthPath, "add", display, ".", cookie)
xauthCmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{Uid: uid, Gid: gid, Groups: groups},
}
if out, err := xauthCmd.CombinedOutput(); err != nil {
return nil, fmt.Errorf("xauth: %w — %s", err, string(out))
}
os.Setenv("XAUTHORITY", xauthPath)
dd := vtnr
if len(dd) == 1 {
dd = "0" + dd
}
// Ignore SIGUSR1 so Xorg sends it when ready.
signal.Ignore(syscall.SIGUSR1)
cmd := exec.Command(cfg.SystemShell, "-c",
fmt.Sprintf("%s %s vt%s", cfg.X11.ServerPath, display, dd))
if logF := openLog(cfg.X11.ServerLogPath); logF != nil {
lw := &limitWriter{w: logF, max: logLimit}
cmd.Stdout = lw
cmd.Stderr = lw
defer logF.Close()
}
if err := cmd.Start(); err != nil {
signal.Reset(syscall.SIGUSR1)
return nil, fmt.Errorf("X server: %w", err)
}
signal.Reset(syscall.SIGUSR1)
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGUSR1)
defer signal.Stop(ch)
timeout := time.Duration(cfg.X11.ServerTimeoutSecs) * time.Second
deadline := time.After(timeout)
tick := time.NewTicker(100 * time.Millisecond)
defer tick.Stop()
for {
select {
case <-ch:
return cmd, nil
case <-deadline:
if cfg.X11.ServerTimeoutSecs == 0 {
continue
}
cmd.Process.Signal(syscall.SIGTERM)
cmd.Wait()
return nil, fmt.Errorf("X server timed out after %ds", cfg.X11.ServerTimeoutSecs)
case <-tick.C:
var ws syscall.WaitStatus
if pid, err := syscall.Wait4(cmd.Process.Pid, &ws, syscall.WNOHANG, nil); err == nil && pid == cmd.Process.Pid {
return nil, fmt.Errorf("X server exited with status %d", ws.ExitStatus())
}
}
}
}
func mCookie() (string, error) {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
return "", err
}
return hex.EncodeToString(b[:]), nil
}