Project: Systems Ops Toolkit

Updated

September 8, 2026

Project: Systems Ops Toolkit

Overview

Extend the systems story with sysops, a second multi-command project focused on runtime ops: deadlines, config reload, limits, locks + jobs, and local control sockets. Builds on 149 systool.

Command Chapter skills
deadline timers, CommandContext
reload file poll + atomic config swap
limits runtime + optional rlimit
cronish ticker + once lock
healthsock UDS HTTP health
rotate simple size-based log copy/truncate (careful)
envcheck required env vars present

Scaffold

mkdir -p sysops/cmd/sysops sysops/internal/app
cd sysops
go mod init example.com/sysops

Use signal.NotifyContext, exit 2 on usage, stderr diagnostics.


1. deadline — run with timeout

func cmdDeadline(ctx context.Context, args []string) error {
    fs := flag.NewFlagSet("deadline", flag.ContinueOnError)
    max := fs.Duration("t", 30*time.Second, "max runtime")
    if err := fs.Parse(args); err != nil {
        return errUsage
    }
    if fs.NArg() < 1 {
        return fmt.Errorf("usage: sysops deadline -t 5s -- cmd args")
    }
    cctx, cancel := context.WithTimeout(ctx, *max)
    defer cancel()
    cmd := exec.CommandContext(cctx, fs.Arg(0), fs.Args()[1:]...)
    cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
    err := cmd.Run()
    if cctx.Err() == context.DeadlineExceeded {
        return fmt.Errorf("deadline exceeded (%s)", *max)
    }
    return err
}
go run ./cmd/sysops deadline -t 1s -- sleep 10

2. reload — demo config watcher

// poll path every 200ms; on change print new contents length
// store last good bytes in atomic.Value
type box struct{ b []byte }

func cmdReload(ctx context.Context, path string) error {
    var v atomic.Value
    load := func() error {
        b, err := os.ReadFile(path)
        if err != nil {
            return err
        }
        if !json.Valid(b) { // example: require JSON
            return fmt.Errorf("invalid json")
        }
        v.Store(box{b: append([]byte(nil), b...)})
        fmt.Fprintf(os.Stderr, "reloaded %d bytes\n", len(b))
        return nil
    }
    _ = load()
    var lastMod time.Time
    var lastSz int64
    t := time.NewTicker(200 * time.Millisecond)
    defer t.Stop()
    for {
        select {
        case <-ctx.Done():
            return nil
        case <-t.C:
            st, err := os.Stat(path)
            if err != nil {
                continue
            }
            if st.ModTime().Equal(lastMod) && st.Size() == lastSz {
                continue
            }
            lastMod, lastSz = st.ModTime(), st.Size()
            if err := load(); err != nil {
                fmt.Fprintln(os.Stderr, "reload keep old:", err)
            }
        }
    }
}

3. limits — print runtime snapshot

func cmdLimits(w io.Writer) {
    fmt.Fprintf(w, "gomaxprocs\t%d\n", runtime.GOMAXPROCS(0))
    fmt.Fprintf(w, "numcpu\t%d\n", runtime.NumCPU())
    fmt.Fprintf(w, "goroutines\t%d\n", runtime.NumGoroutine())
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Fprintf(w, "heap_alloc\t%d\n", m.HeapAlloc)
    fmt.Fprintf(w, "sys\t%d\n", m.Sys)
}

4. cronish — interval runner with flock

sysops cronish -every 1m -lock /tmp/job.lock -- /path/backup.sh
// each tick: try Acquire lock; if busy skip; else run command
// respects ctx cancel between ticks

Prevents stacked cron jobs (chapter 148).


5. healthsock — UDS health for local probes

// Listen unix; GET /healthz returns 200 + limits snapshot
// Chmod 660; remove stale socket on start
// Shutdown on ctx
curl --unix-socket /tmp/sysops.sock http://localhost/healthz

6. rotate — minimal size rotate (teaching only)

// if file size > max: rename to .1 (overwrite), create new file
// NOT a full logrotate replacement — no copytruncate races covered fully
func rotateIfLarge(path string, max int64) error {
    st, err := os.Stat(path)
    if err != nil {
        return err
    }
    if st.Size() < max {
        return nil
    }
    bak := path + ".1"
    _ = os.Remove(bak)
    if err := os.Rename(path, bak); err != nil {
        return err
    }
    f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
    if err != nil {
        return err
    }
    return f.Close()
}

Document that writers must reopen after rotate (or use stderr→journal instead).


7. envcheck — required environment

func cmdEnvCheck(keys []string) error {
    var missing []string
    for _, k := range keys {
        if os.Getenv(k) == "" {
            missing = append(missing, k)
        }
    }
    if len(missing) > 0 {
        return fmt.Errorf("missing env: %s", strings.Join(missing, ", "))
    }
    fmt.Println("ok")
    return nil
}
go run ./cmd/sysops envcheck DATABASE_URL API_TOKEN

Useful as a container entrypoint preflight.


Acceptance checklist

  • deadline -t 1s -- sleep 5 fails with timeout
  • reload keeps last good config on bad JSON
  • cronish skips overlapping runs under lock
  • healthsock + curl works
  • envcheck exits non-zero when vars missing
  • Ctrl-C stops long-running commands promptly

Suggested implementation order

envcheck → limits → deadline → healthsock → reload → cronish → rotate

Stretch

  1. Merge systool + sysops into one binary with command groups
  2. JSON output for limits / healthz
  3. Debounce reload at 300ms
  4. systemd unit file example in README

You now have two project tracks: 149 systool (core Unix hygiene) and 156 sysops (runtime operations)—together a practical systems programming lab in pure Go style.