Project: Systems Ops Toolkit
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/sysopsUse 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 102. reload — demo config watcher
// poll path every 200ms; on change print new contents length
// store last good bytes in atomic.Valuetype 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 ticksPrevents 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 ctxcurl --unix-socket /tmp/sysops.sock http://localhost/healthz6. 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 fullyfunc 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_TOKENUseful as a container entrypoint preflight.
Acceptance checklist
deadline -t 1s -- sleep 5fails with timeout
reloadkeeps last good config on bad JSON
cronishskips overlapping runs under lock
healthsock+ curl works
envcheckexits non-zero when vars missing
- Ctrl-C stops long-running commands promptly
Suggested implementation order
envcheck → limits → deadline → healthsock → reload → cronish → rotate
Stretch
- Merge
systool+sysopsinto one binary with command groups
- JSON output for
limits/healthz
- Debounce reload at 300ms
- 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.