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.
This commit is contained in:
263
legacy/go/main.go
Normal file
263
legacy/go/main.go
Normal file
@@ -0,0 +1,263 @@
|
||||
// latchd — TUI display/login manager for GNU/Linux and BSD.
|
||||
//
|
||||
// Supports TTY, X11, and Wayland sessions with PAM authentication.
|
||||
// Go rewrite of Lemurs (Rust source under vendor/).
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
||||
"latchd/auth"
|
||||
"latchd/config"
|
||||
"latchd/login"
|
||||
"latchd/session"
|
||||
"latchd/tui/bg"
|
||||
)
|
||||
|
||||
var (
|
||||
Version = "0.1.0"
|
||||
Commit = "unknown"
|
||||
BuildTime = "unknown"
|
||||
)
|
||||
|
||||
const defaultConfig = "/etc/latchd/config.toml"
|
||||
const defaultVars = "/etc/latchd/variables.toml"
|
||||
|
||||
func main() {
|
||||
var (
|
||||
configPath string
|
||||
varsPath string
|
||||
ttyNum int
|
||||
preview bool
|
||||
noLog bool
|
||||
showConf bool
|
||||
printVer bool
|
||||
xsessions string
|
||||
wlsessions string
|
||||
initPath string
|
||||
)
|
||||
flag.StringVar(&configPath, "config", defaultConfig, "")
|
||||
flag.StringVar(&configPath, "c", defaultConfig, "")
|
||||
flag.StringVar(&varsPath, "variables", defaultVars, "")
|
||||
flag.StringVar(&varsPath, "v", defaultVars, "")
|
||||
flag.IntVar(&ttyNum, "tty", 0, "")
|
||||
flag.BoolVar(&preview, "preview", false, "")
|
||||
flag.BoolVar(&noLog, "no-log", false, "")
|
||||
flag.BoolVar(&showConf, "show-config", false, "")
|
||||
flag.BoolVar(&printVer, "version", false, "")
|
||||
flag.BoolVar(&printVer, "V", false, "")
|
||||
flag.StringVar(&xsessions, "xsessions", "", "")
|
||||
flag.StringVar(&wlsessions, "wlsessions", "", "")
|
||||
flag.StringVar(&initPath, "initial-path", "", "")
|
||||
flag.Parse()
|
||||
|
||||
if printVer {
|
||||
fmt.Printf("latchd %s commit %s built %s\n", Version, Commit, BuildTime)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
cfg := config.Default()
|
||||
if initPath != "" {
|
||||
cfg.InitialPath = initPath
|
||||
}
|
||||
if xsessions != "" {
|
||||
cfg.X11.SessionsPath = xsessions
|
||||
}
|
||||
if wlsessions != "" {
|
||||
cfg.Wayland.SessionsPath = wlsessions
|
||||
}
|
||||
vars, _ := config.LoadVariables(varsPath)
|
||||
if err := config.LoadPartial(configPath, vars, &cfg); err != nil {
|
||||
if configPath != defaultConfig {
|
||||
fmt.Fprintf(os.Stderr, "config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if showConf {
|
||||
fmt.Printf("tty = %d\npam_service = %q\nshell = %q\nfocus = %q\n",
|
||||
cfg.TTY, cfg.PAMService, cfg.SystemShell, cfg.FocusBehaviour)
|
||||
os.Exit(0)
|
||||
}
|
||||
if !noLog && !preview {
|
||||
setupLog(cfg.MainLogPath)
|
||||
}
|
||||
if !preview {
|
||||
if _, ok := os.LookupEnv("XDG_SESSION_TYPE"); ok {
|
||||
fmt.Fprintln(os.Stderr, "latchd: already in a session — use --preview")
|
||||
os.Exit(1)
|
||||
}
|
||||
if os.Getuid() != 0 {
|
||||
fmt.Fprintf(os.Stderr, "latchd: must be root (uid %d)\n", os.Getuid())
|
||||
os.Exit(1)
|
||||
}
|
||||
if ttyNum != 0 {
|
||||
cfg.TTY = uint8(ttyNum)
|
||||
}
|
||||
if err := chvt(int(cfg.TTY)); err != nil {
|
||||
log.Printf("chvt %d: %v", cfg.TTY, err)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
res := launchUI(cfg, preview)
|
||||
if res == nil {
|
||||
break
|
||||
}
|
||||
if err := spawnSession(res); err != nil {
|
||||
log.Printf("session: %v", err)
|
||||
}
|
||||
if !preview {
|
||||
chvt(int(cfg.TTY))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func launchUI(cfg config.Config, preview bool) *login.Result {
|
||||
m := login.New(cfg, preview)
|
||||
if cfg.Background.ShowBackground {
|
||||
m.SetAutomata(bg.NewAutomata(80, 24))
|
||||
}
|
||||
p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion())
|
||||
final, err := p.Run()
|
||||
if err != nil {
|
||||
log.Printf("tui: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
lm, ok := final.(*login.Model)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return lm.Result()
|
||||
}
|
||||
|
||||
func spawnSession(res *login.Result) error {
|
||||
if res.Preview {
|
||||
log.Println("preview — skipping real session")
|
||||
return nil
|
||||
}
|
||||
log.Printf("spawning %s → %s (%s)", res.Username, res.Env.Title, res.Env.Kind)
|
||||
|
||||
creds, err := auth.Validate(res.Username, res.Password, res.Config.PAMService)
|
||||
if err != nil {
|
||||
return fmt.Errorf("auth: %w", err)
|
||||
}
|
||||
defer creds.CloseSession()
|
||||
|
||||
if err := creds.OpenSession(); err != nil {
|
||||
log.Printf("pam session warning: %v", err)
|
||||
}
|
||||
|
||||
session.SetBasicVariables(creds.Username, creds.HomeDir, creds.Shell, res.Config.InitialPath)
|
||||
if res.Env.Kind == "x11" {
|
||||
session.SetDisplay(res.Config.X11.Display)
|
||||
}
|
||||
session.RemoveXDG()
|
||||
session.SetSessionParams(res.Env.Kind)
|
||||
session.SetSeatVars(res.Config.TTY)
|
||||
session.SetSessionVars(creds.UID)
|
||||
session.SetXDGCommonPaths(creds.HomeDir)
|
||||
session.SetDesktopVars(res.Env.DesktopNames, res.Env.SessionDesktop)
|
||||
|
||||
sess, err := session.Spawn(&res.Env, creds.UID, creds.PrimaryGID, creds.AllGIDs, &res.Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("spawn: %w", err)
|
||||
}
|
||||
|
||||
utmpx := auth.AddUtmpx(creds.Username, res.Config.TTY, uint32(sess.PID()))
|
||||
defer utmpx.Close()
|
||||
|
||||
err = sess.Wait()
|
||||
if err != nil {
|
||||
log.Printf("session exited: %v", err)
|
||||
}
|
||||
log.Printf("session pid %d ended", sess.PID())
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupLog(path string) {
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "log %s: %v\n", path, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.SetOutput(io.MultiWriter(f, os.Stderr))
|
||||
log.SetFlags(log.LstdFlags)
|
||||
}
|
||||
|
||||
// ── chvt ────────────────────────────────────────────────────────────────
|
||||
|
||||
const vtA = 0x5606
|
||||
const vtW = 0x5607
|
||||
const kbt = 0x4B33
|
||||
const kb1 = 0x02
|
||||
const kb2 = 0x01
|
||||
|
||||
func isCon(fd uintptr) bool {
|
||||
var a int32
|
||||
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, kbt, uintptr(unsafe.Pointer(&a)))
|
||||
return e == 0 && (a == kb1 || a == kb2)
|
||||
}
|
||||
|
||||
func openCon(fn string) (int, error) {
|
||||
for _, fl := range []int{os.O_RDWR, os.O_RDONLY, os.O_WRONLY} {
|
||||
fd, err := syscall.Open(fn, fl, 0)
|
||||
if err != nil {
|
||||
if err == syscall.EACCES {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if isCon(uintptr(fd)) {
|
||||
return fd, nil
|
||||
}
|
||||
syscall.Close(fd)
|
||||
}
|
||||
return 0, fmt.Errorf("openCon: %s", fn)
|
||||
}
|
||||
|
||||
func getCon() (int, error) {
|
||||
for _, p := range []string{"/dev/tty", "/dev/tty0", "/dev/vc/0", "/dev/console"} {
|
||||
if fd, err := openCon(p); err == nil {
|
||||
return fd, nil
|
||||
}
|
||||
}
|
||||
for fd := 0; fd < 3; fd++ {
|
||||
if isCon(uintptr(fd)) {
|
||||
return fd, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("no console")
|
||||
}
|
||||
|
||||
func chvt(n int) error {
|
||||
fd, err := getCon()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
if _, _, e := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), vtA, uintptr(n)); e != 0 {
|
||||
return fmt.Errorf("VT_ACTIVATE")
|
||||
}
|
||||
if _, _, e := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), vtW, uintptr(n)); e != 0 {
|
||||
return fmt.Errorf("VT_WAITACTIVE")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT)
|
||||
go func() {
|
||||
<-ch
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
Reference in New Issue
Block a user