# 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.lua` → **`programs/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: ```lua 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): ```lua local MARKER = '/.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.lua` → `programs/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.lua` — `explicit` flag, `api.prune`, autoprune in `api.uninstall`, empty-state cleanup. - `programs/ccpm.lua` — `prune` 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.lua` → `tests/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.