cc-libs/.plans/trapos-create-disk-plan.md
2026-06-15 20:58:26 +02:00

7.9 KiB

Plan: trapos-create-disk + ccpm prune/autoprune

Context

Today programs/trapos-create-installer-disk.lua only builds an install floppy disk (its startup.lua installs TrapOS on a blank computer). We want one disk-creation tool that can also produce reinstall and uninstall disks, and we want a real "remove TrapOS" path. ComputerCraft's ccpm uninstall trapos alone is insufficient: it only deletes the trapos meta-package's own files and its dependency guard blocks cascading removal.

The fix is two-sided:

  1. Teach ccpm to remove orphaned dependencies (prune), auto-pruned on uninstall, so ccpm uninstall trapos cleanly wipes the whole tree and the /trapos state dir.
  2. Rename/generalize the disk creator to emit install / reinstall / uninstall disks whose startup.lua performs the matching action on the booted target.

Decisions (settled with user)

  • Program rename: programs/trapos-create-installer-disk.luaprograms/trapos-create-disk.lua. Bare command prints help. Flags select the disk variant: --install, --reinstall, --uninstall. Keep version and help.
  • No separate trapos-uninstall.lua. Everything routes through ccpm uninstall trapos.
  • ccpm gains a new explicit flag per lock entry, a prune command, autoprune on uninstall (--no-prune to disable), interactive y/N confirm with --yes/-y, and state-dir deletion when empty. ccpm never auto-reboots — the disk startup.lua owns reboot/eject.

Part A — ccpm changes

A1. explicit flag (apis/libccpm.lua, api.install ~L397)

When writing a lock entry, set explicit = (item.name == pkg). Deps pulled in get explicit = false. Preserve an existing true (re-installing a dep that was previously explicit must not demote it). Legacy/migration: a lock entry with no explicit field is treated as explicit = true (never auto-prune pre-existing installs).

A2. api.prune(opts) (new, apis/libccpm.lua)

  • Compute the set of packages reachable from every explicit root by walking dependencies (reuse the traversal idea from api.resolve, L314).
  • Orphans = installed packages not in the reachable set.
  • Remove the whole orphan set atomically: delete each orphan's files, drop all their lock entries in one pass, then writeLock + writeOsState. This deliberately bypasses the per-package dependents guard in api.uninstall (orphans depend on each other but none are reachable from a root). Return the list of pruned names.

A3. Autoprune in api.uninstall (apis/libccpm.lua L439)

  • Add opts.prune (default behavior = prune). After removing pkg (existing logic L463-469), if pruning is enabled call api.prune and merge its removed list into the result/log.
  • libccpm.uninstall stays non-interactive (CLI owns the prompt) so tests/ccpm.lua keep working.

A4. State-dir cleanup when empty (apis/libccpm.lua)

  • After any uninstall/prune that leaves lock.packages empty, delete the entire state dir (stateDir, default /trapos) so the machine is blank and the reinstall guard fs.exists('/trapos') passes. Also remove now-empty generated dirs created by the installer (/programs, /apis, /servers, /startup) if empty. Guard the path so a custom test stateDir is still removed safely.

A5. CLI wiring (programs/ccpm.lua)

  • uninstall|remove|rm handler (L102): parse --no-prune and --yes/-y from args. Unless --yes, print what will be removed (pkg + a dry-run prune list) and read a y/N line via a small local confirm() helper (no read() helper exists in the repo — add one using read()/io.read). Pass { prune = not noPrune, log = logLine } to ccpm.uninstall.
  • Add prune command → ccpm.prune({ log = logLine }), printing pruned packages or "nothing to prune".
  • Update printUsage() to list prune, uninstall [--yes] [--no-prune].

Part B — disk creator

B1. Generalize apis/libinstallerdisk.lua

  • create(opts) takes opts.mode = 'install' | 'reinstall' | 'uninstall' (default install).

  • buildAutorun(mode) emits the variant startup.lua. Reuse findTargetDrive, labeling, and DEFAULT_INSTALL_URL (https://os.trapcloud.fr/install). Rename disk label to "TrapOS Disk".

  • install mode: current behavior unchanged — clone /.settings onto the disk, autorun guards if fs.exists('/trapos') then return, restores settings, wget run INSTALL_URL. Only this mode clones settings.

  • uninstall mode autorun:

    if not fs.exists('/trapos') then print('TrapOS already removed.') else
      shell.run('ccpm','uninstall','trapos','--yes')
    end
    print('TrapOS removed. Eject the disk to finish.')
    os.pullEvent('disk_eject'); os.reboot()
    
  • reinstall mode autorun (marker breaks the post-install loop, since install-trapos reboots with the disk still inserted):

    local MARKER = '<diskMount>/.trapos-reinstalled'
    if fs.exists(MARKER) then
      print('Reinstall complete. Eject the disk.')
      os.pullEvent('disk_eject'); os.reboot(); return
    end
    -- write MARKER
    if fs.exists('/trapos') then shell.run('ccpm','uninstall','trapos','--yes') end
    shell.run('wget','run', INSTALL_URL)  -- installer reboots
    

    /.settings is left untouched by uninstall, so the machine keeps its own config — no clone.

B2. Rewrite programs/trapos-create-disk.lua

  • Replace positional command parsing with flag parsing (pattern from programs/ai.lua L4-26).
  • Bare invocation (or help) → printUsage() documenting --install/--reinstall/--uninstall.
  • Keep version via require('/apis/libversion')().forSelf() and the local printColored.
  • Map flag → createInstallerDisk().create({ mode = ... }); reuse existing success/warning output (label, settingsCopied/Warning, insert-and-reboot hint), adjusting copy per mode.

Part C — packaging / metadata

  • packages/trapos-core/ccpm.json: rename programs/trapos-create-installer-disk.luaprograms/trapos-create-disk.lua in files; bump version.
  • packages/index.json: bump trapos-core version to match.
  • Delete the old programs/trapos-create-installer-disk.lua file (currently uncommitted/new).

Files to modify

  • apis/libccpm.luaexplicit flag, api.prune, autoprune in api.uninstall, empty-state cleanup.
  • programs/ccpm.luaprune command, uninstall --yes/--no-prune + confirm helper, usage.
  • apis/libinstallerdisk.lua — mode-parameterized create/buildAutorun, label rename.
  • programs/trapos-create-disk.lua — new (renamed from trapos-create-installer-disk.lua).
  • packages/trapos-core/ccpm.json, packages/index.json — file rename + version bump.

Tests (repo uses apis/libtest, __TRAPOS_TEST_OK__ on pass)

  • tests/ccpm.lua — add:
    • install marks target explicit=true, deps explicit=false.
    • prune removes orphans not reachable from explicit roots; keeps reachable ones.
    • uninstall trapos (with deps) autoprunes the whole tree; --no-prune/{prune=false} removes only the target.
    • uninstalling the last package deletes the (test) state dir.
    • legacy entry without explicit is not pruned.
  • Rename tests/installer-disk.luatests/create-disk.lua; add buildAutorun assertions per mode: install contains settings-copy-before-wget; uninstall contains ccpm uninstall trapos --yes + disk_eject and no wget; reinstall contains marker check + uninstall --yes + wget run.

Verification

  1. Run the suite: tests/ccpm.lua and tests/create-disk.lua (look for __TRAPOS_TEST_OK__).
  2. Manually inspect generated startup.lua for each mode via the new buildAutorun tests.
  3. In-world (if available): build an install disk on a blank computer (unchanged path), then a reinstall disk and an uninstall disk on an installed computer; confirm uninstall wipes /trapos and waits for eject, and reinstall completes then waits for eject.