feat(v0.4): add session Lease type with JSON round-trip
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Lease is the on-disk format of .ctask/session.json. It identifies the ctask
|
||||
// process holding this workspace open and records a heartbeat timestamp so
|
||||
// that crashed sessions can be detected and cleaned up by a later session.
|
||||
type Lease struct {
|
||||
SessionID string `json:"session_id"`
|
||||
PID int `json:"pid"`
|
||||
Hostname string `json:"hostname"`
|
||||
Username string `json:"username"`
|
||||
Agent string `json:"agent"`
|
||||
Mode string `json:"mode"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
LastHeartbeatAt time.Time `json:"last_heartbeat_at"`
|
||||
Terminal string `json:"terminal"`
|
||||
}
|
||||
|
||||
// LeasePath returns the absolute path of the lease file for the given workspace.
|
||||
func LeasePath(wsDir string) string {
|
||||
return filepath.Join(wsDir, ".ctask", "session.json")
|
||||
}
|
||||
|
||||
// WriteLease marshals a lease to JSON and writes it to path.
|
||||
// The .ctask/ directory is created if needed.
|
||||
func WriteLease(path string, l *Lease) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.MarshalIndent(l, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
// ReadLease reads and parses a lease file. A missing file returns the
|
||||
// stdlib os.ErrNotExist (via os.ReadFile), which callers should detect
|
||||
// with errors.Is.
|
||||
func ReadLease(path string) (*Lease, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var l Lease
|
||||
if err := json.Unmarshal(data, &l); err != nil {
|
||||
return nil, fmt.Errorf("parsing lease: %w", err)
|
||||
}
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
// currentUsername returns the current OS username, falling back to "unknown"
|
||||
// on error. Never panics.
|
||||
func currentUsername() string {
|
||||
u, err := user.Current()
|
||||
if err != nil || u == nil {
|
||||
return "unknown"
|
||||
}
|
||||
if u.Username == "" {
|
||||
return "unknown"
|
||||
}
|
||||
return u.Username
|
||||
}
|
||||
|
||||
// currentHostname returns the current hostname, falling back to "unknown".
|
||||
func currentHostname() string {
|
||||
h, err := os.Hostname()
|
||||
if err != nil || h == "" {
|
||||
return "unknown"
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// currentTerminal is a best-effort terminal identifier based on common env vars.
|
||||
// Returns "unknown" if none are set.
|
||||
func currentTerminal() string {
|
||||
for _, k := range []string{"TERM_PROGRAM", "WT_SESSION", "TERM"} {
|
||||
if v := os.Getenv(k); v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
Reference in New Issue
Block a user