Made on 27 Sep 2026, at M1. What it marks as planned was the design then. M2 to M5 have been built since (the watcher, the checkers, the rescue path and the .deb package; tag m5 on 2026-10-09) and differ from this design in places; M6 and M7 are not built. Where things stand now: MILESTONES.md.

System map · 27 Sep 2026

SmartConfig

A safety net for the config files that take a Linux machine down. Snapshot a file before it changes, warn before a bad edit lands, explain what broke, and put one file back from the rescue shell.

milestone 1 of 7 built 1 static binary, 6.1 MB 20 Go tests 7 commits acceptance run passed as root
Roadmap

Seven milestones, built one at a time

  1. M1builtStore + CLIsnapshot, log, cat, diff, restore
  2. M2nextWatcherscd daemon, inotify, auto-snapshots
  3. M3Checkersvalidators, rules, sc edit and sc check
  4. M4Rescue pathGRUB entry, rescue.target, boot-ok
  5. M5Package.deb (built with dpkg-deb), clean-VM install
  6. M6Incident factorybroken configs and an eval set
  7. M7Local modelsc why with llama.cpp, opt-in
Where we started

How milestone 1 was built, commit by commit

We started from an empty repo containing only go.mod. Click any step, or replay the build and watch the repository fill in.

    7commits
    20tests
    24files
    The whole system

    Where every part sits, and which ones exist today

    Show Play a story
    built in M1 planned (milestone on the box) already on the machine working today planned connection
    runs sc edits files inotify events auto-snapshot on every write reads atomic write blob + row warns before a bad edit diffs + history findings restores one file replays broken configs to test them Youshell · editor · apt scdwatcher daemonM2 /etcfstab · hosts · grubyour system scsnapshot → restoreM1 · BUILT Storeblobs + changes.dbM1 · BUILT Incident factorybroken configs → evalsM6 Rescue bootGRUB → rescue shellM4 Checkersvalidators + rulesM3 sc whylocal model explainsM7
    Click any box to see what it does and which milestone builds it.
    What exists today · M1

    One static binary, three layers, two places on disk

    cmd/sc

    the CLI, built on cobra
    initsnapshotlogcatdiffrestore

    Thin. Any error becomes one line, sc: … on stderr, and exit code 1.

    calls

    internal/store

    history of every file
    InitOpenSnapshotListGetBlobRestoreUnifiedDiff

    Content-addressed blobs plus one SQLite row per change. Resolves id prefixes and verifies each blob's checksum before use.

    reads and writes files through

    internal/fsutil

    the only code that touches files
    ReadWithMetaWriteAtomic

    Refuses symlinks, non-regular files and anything over 8 MB. Writes a temp file, sets owner and mode, fsyncs, then renames it over the original.

    on disk

    /etc/…

    The live config files. Only ever replaced by rename, never truncated in place.

    $SC_HOME

    /var/lib/smartconfig/  0700
    ├─ objects/
    │  ├─ c2/c2646361…  0600
    │  └─ 97/97acb3b7…  0600
    └─ changes.db  SQLite
    One row in changes.db
    id      733bd9
    ts      1790469360  (unix seconds)
    path    /etc/hosts
    blob    c2646361…  (sha256)
    size    273
    mode    0644 (+setuid/gid/sticky)
    uid/gid 0 / 0
    origin  manual | pre-restore | restore
    intent  "baseline"
    How an id is made
    sha256(path + "\n" + ts + "\n" + blob)[:6]

    Two rows for the same file, content and second would get the same id. When an id is taken, \n1, \n2… is appended and it is hashed again. This happened in the real acceptance run: d2b20b and 0beb81 hold the same bytes, saved in the same second.

    Flow 1 · animated

    What happens on sc snapshot

    Scenario
    yes no, it changed You runsc snapshot Read + checkno symlink, ≤ 8 MB sha256of the bytes Same asnewest? Nothing savedunchanged since … Store blobobjects/97/97acb3… Insert roworigin manual, new id Printsnapshot d2b20b …

    What sc knows so far

    A snapshot only saves something when the file changed. Each unique content is written once; the row records mode, owner and size alongside it so a restore can put all of them back.
    Flow 2 · animated

    What happens on sc restore 733bd9

    sc store fsutil /etc/hosts $SC_HOME 1 2 3 4 5 6 7 8 9 10 restore "733bd9" read blob, check its sha256 read the current file, mode and owner save it as pre-restore 0beb81 (undo point) WriteAtomic(0644, root:root) write temp file in /etc chown · chmod · fsync rename over /etc/hosts insert row: restore 460e5a done, previous = 0beb81

    /etc on disk

    changes.db, newest first

    Terminal

    Step 8 is the only moment /etc/hosts changes, and it is atomic. Until the rename the original is untouched; if steps 5 to 7 fail, the temp file is deleted and nothing happened. Step 4 means a restore you regret is one more sc restore 0beb81 away.
    Try it yourself

    A copy of sc running in this page

    The same logic as the real tool, rewritten in JavaScript: real sha256 hashes, the same id formula and collision rule, the same messages. It works on a copy of this VM's /etc/hosts. Edit the file directly, type commands, or follow the numbered buttons.

    /etc/hosts273 bytes · 0644 root:root

    changes.db

    IDOriginSizeWhat
    objects/: 0 blobs

    Terminal

    Commands: sc snapshot /etc/hosts [-m "why"] [-q], sc log [-n N], sc cat <id>, sc diff <id> [<id2>], sc restore <id>, clear. Id prefixes work.

    Proof

    The acceptance run on this VM, as root

    The script breaks the real /etc/hosts by adding a line, then fixes it with one command. This is the sc log /etc/hosts it ended with, newest first.

    IDWhenOriginSizeWhat
    460e5a2026-09-27 00:36restore273restored from 733bd9
    0beb812026-09-27 00:36pre-restore297before restoring 733bd9
    d2b20b2026-09-27 00:36manual297added smoke line
    733bd92026-09-27 00:36manual273baseline