8.2 KiB
Plan: trapos-create-installer-disk — one-shot TrapOS installer floppies
Context
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
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
latest TrapOS online and clones your .settings onto the new computer, hands-free.
The disk's value is hands-free install + config cloning, not offline file shipping.
Resolved design decisions (from grill session)
- Install method: autorun installs online via the existing
wget runbootstrap. No files are bundled on the disk; the disk stays tiny and never goes stale vs the registry. - Disk boot file: CC only auto-runs
startup/startup.luafrom a floppy at boot. We put the autorun logic directly in<disk>/startup.lua(the name "autorun" is conceptual). - Ordering (copy-then-install, no reboot):
install-trapos.luaends by handing off to the live/startup/servers.luashell, which never returns. So the autorun copies.settingsfirst, callssettings.load(), then runs the installer (whosetrapos-postinstallrespects the already-setsandbox.urland never touchessandbox.password, so the disk's config wins). No changes toinstall-trapos.luaand no reboot here — the boot/reboot rework is a separate effort. - Sentinel: autorun no-ops if
/traposexists. To reinstall:rm /trapos, reinsert, boot. - Drive selection: pick the first eligible drive = present, writable floppy that is
empty (
#fs.list(mountPath) == 0). Empty is preferred over non-empty.0empty → error (distinguish "disk not empty" vs "insert a blank floppy");2+empty → print "multiple blank disks found, remove all but one" and stop. No interactive insert handling. - Missing
/.settingsat creation: warn + still build a valid (settings-less) installer. When/.settingsexists,settings.save()first to flush in-session values, then copy. - Label:
TrapOS Installer. - Code shape: injectable factory
apis/libinstallerdisk.lua+ thin CLIprograms/trapos-create-installer-disk.lua+tests/installer-disk.lua; ship intrapos-core. Mirrorsapis/libccpm.lua+programs/ccpm.lua+tests/ccpm.lua.
Conventions to follow
2-space indent, semicolons, local function (per AGENTS.md). require absolute paths.
Programs support -version/--version and -help/--help via
require('/apis/libversion')().forSelf(). Colored output guarded by term.isColor() (see
printColored in programs/ccpm.lua). Factory opts default to globals (see createCcpm(opts)
in apis/libccpm.lua).
1. New: apis/libinstallerdisk.lua (testable factory)
-- createInstallerDisk(opts) -> api
-- opts.peripheral (default global `peripheral`) -- drive discovery
-- opts.settings (default global `settings`) -- save() flush
-- opts.settingsSourcePath (default '/.settings') -- computer config to clone
-- opts.installerLabel (default 'TrapOS Installer')
-- opts.installUrl (default 'https://os.trapcloud.fr/install')
-- (uses real global `fs`; tests sandbox with isolated real dirs, like tests/ccpm.lua)
Public API:
api.buildAutorun()→ returns thestartup.luasource string (pure, fully unit-testable).api.findTargetDrive()→ok, { name, mountPath } | err. Iteratesperipheral.find('drive')/getNames, keeps present writable floppies (isDiskPresent()andhasData()), filters to empty (#fs.list(getMountPath()) == 0), applies the 0/1/2+ rules above with the distinct error messages.api.create()→ok, result | err. Orchestrates: find drive →setDiskLabel('TrapOS Installer')→ ifsettingsSourcePathexists:settings.save()thenfs.copy(settingsSourcePath, mount/.settings)(delete dest first) else setresult.settingsWarning = true→ writebuildAutorun()tomount/startup.lua.result = { name, mountPath, label, settingsCopied, settingsWarning }.
buildAutorun() returns (note: copy-then-install, locate-own-disk-by-label, no reboot):
-- TrapOS installer disk autorun (generated; do not edit)
local INSTALLER_LABEL = 'TrapOS Installer';
local INSTALL_URL = 'https://os.trapcloud.fr/install';
if fs.exists('/trapos') then return; end -- already installed: leave machine alone
local function installerMount()
for _, name in ipairs(peripheral.getNames()) do
if peripheral.getType(name) == 'drive' then
local drive = peripheral.wrap(name);
if drive.isDiskPresent() and drive.getDiskLabel() == INSTALLER_LABEL then
return drive.getMountPath();
end
end
end
end
local mount = installerMount();
if mount then
local src = fs.combine(mount, '.settings');
if fs.exists(src) then
fs.delete('/.settings');
fs.copy(src, '/.settings');
settings.load();
end
end
print('TrapOS Installer: installing TrapOS...');
shell.run('wget', 'run', INSTALL_URL);
2. New: programs/trapos-create-installer-disk.lua (thin CLI)
- Handles
version/-version/--version(printsv' .. createVersion().forSelf()) andhelp/-help/--help(usage), mirroringprograms/trapos-upgrade.lua. - Calls
createInstallerDisk().create(). On error: print message in red,return. On success: print a green summary (label set,.settingscloned / or the missing-settings warning, which drive), plus a hint: "Insert this disk into a blank computer and reboot it." - No confirmation prompt (target disk is required to be empty → low risk).
3. New: tests/installer-disk.lua
Follow tests/ccpm.lua: local createLibTest = require('/apis/libtest'), isolated real-fs
dirs per case, fake peripheral injected. Cases:
buildAutorun()contains the/trapossentinel, the label, the install URL, copies.settingsbeforewget run, and has noos.reboot.findTargetDrive: zero drives → "insert a blank" error; one non-empty floppy → "not empty" error; one empty + one non-empty → picks the empty; two empty → "remove all but one" error.create: sets label, writesstartup.lua(==buildAutorun()) into the fake mount dir, copies a tempsettingsSourcePathintomount/.settings; missing source →settingsWarningtrue and no copy. Fakesettings.saveis a recording no-op so the real/.settingsis never touched.
Fake peripheral: getNames/getType/wrap returning fake drives whose getMountPath()
points at a real temp dir (e.g. /installer-disk-test/disk-N) so fs.copy/fs.open/fs.list
operate on real fs; isDiskPresent/hasData return true; set/getDiskLabel store a field.
4. Packaging
packages/trapos-core/ccpm.json: append"apis/libinstallerdisk.lua"and"programs/trapos-create-installer-disk.lua"tofiles; bumpversion0.6.3→0.7.0.packages/index.json: mirrortrapos-core→0.7.0.
Verification
just check— luacheck clean (respect.luacheckrclua51+cc) and lychee link check.just test(orruntest tests/installer-disk.lua) — suite prints__TRAPOS_TEST_OK__.- Optional headless probe:
just trapos-exec '<lua creating a periphemu drive + calling createInstallerDisk().create()>'to smoke-test against an emulated drive end-to-end. - In-game (human): run
trapos-create-installer-diskon a configured machine → verify labelTrapOS Installer,startup.lua+.settingspresent on the floppy. Insert into a blank computer, reboot → TrapOS installs and.settingsis cloned. Confirm re-boot with disk still in is a no-op (because/traposexists), and thatrm /trapos+ reboot reinstalls.
Assumptions / caveats
- Target blank computer has
shell.allow_disk_startupenabled (CC defaulttrue) and HTTP enabled withtrapcloud.frreachable/allowed (CC default). - Literal order is copy-then-install (not "install-then-copy"); functionally the disk's
settings still win. The pre-existing "exit the OS shell re-runs
servers.lua" wart and any forced reboot are out of scope — owned by the separate boot/reboot rework.