chore: save plans
This commit is contained in:
parent
224efda2b1
commit
db3f6a134b
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
Today TrapOS is installed by typing `wget run https://trapos.trapcloud.fr/install` on each
|
Today TrapOS is installed by typing `wget run https://os.trapcloud.fr/install` on each
|
||||||
fresh computer, then manually configuring settings (`sandbox.url`, `sandbox.password`, …) per
|
fresh computer, then manually configuring settings (`sandbox.url`, `sandbox.password`, …) per
|
||||||
machine. We want a **provisioning floppy**: run one program on a configured machine to "bake"
|
machine. We want a **provisioning floppy**: run one program on a configured machine to "bake"
|
||||||
an installer disk, then on any blank machine just insert the disk and boot — it installs the
|
an installer disk, then on any blank machine just insert the disk and boot — it installs the
|
||||||
@ -50,7 +50,7 @@ in `apis/libccpm.lua`).
|
|||||||
-- opts.settings (default global `settings`) -- save() flush
|
-- opts.settings (default global `settings`) -- save() flush
|
||||||
-- opts.settingsSourcePath (default '/.settings') -- computer config to clone
|
-- opts.settingsSourcePath (default '/.settings') -- computer config to clone
|
||||||
-- opts.installerLabel (default 'TrapOS Installer')
|
-- opts.installerLabel (default 'TrapOS Installer')
|
||||||
-- opts.installUrl (default 'https://trapos.trapcloud.fr/install')
|
-- opts.installUrl (default 'https://os.trapcloud.fr/install')
|
||||||
-- (uses real global `fs`; tests sandbox with isolated real dirs, like tests/ccpm.lua)
|
-- (uses real global `fs`; tests sandbox with isolated real dirs, like tests/ccpm.lua)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ Public API:
|
|||||||
```lua
|
```lua
|
||||||
-- TrapOS installer disk autorun (generated; do not edit)
|
-- TrapOS installer disk autorun (generated; do not edit)
|
||||||
local INSTALLER_LABEL = 'TrapOS Installer';
|
local INSTALLER_LABEL = 'TrapOS Installer';
|
||||||
local INSTALL_URL = 'https://trapos.trapcloud.fr/install';
|
local INSTALL_URL = 'https://os.trapcloud.fr/install';
|
||||||
|
|
||||||
if fs.exists('/trapos') then return; end -- already installed: leave machine alone
|
if fs.exists('/trapos') then return; end -- already installed: leave machine alone
|
||||||
|
|
||||||
|
|||||||
161
.plans/trapos-create-disk-plan.md
Normal file
161
.plans/trapos-create-disk-plan.md
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# 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 = '<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.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.
|
||||||
Loading…
Reference in New Issue
Block a user