Compare commits

..

2 Commits

Author SHA1 Message Date
ec5e77f796 Prompt for backup location during re-onboard instead of skipping
When step 4 finds an existing backup repo, show the current location
and ask to reconfigure with the existing path as default, rather than
silently skipping the step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:51:24 -05:00
3be9586238 Fix design issues and add comprehensive test suite
Guard doSetup() against re-initialization, route welcome to onboard
wizard, fix import confirmation logic, remove invalid syncthing
generate flags, and show full export path. Add shell-based integration
test suite (123 tests) at ~/src/test/pal/ covering all commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:50:04 -05:00
3 changed files with 58 additions and 35 deletions

View File

@@ -339,7 +339,7 @@ fn showInteractiveWelcome(): Unit with {Console, Process} = {
"3" => showHelp(), "3" => showHelp(),
_ => { _ => {
print("") print("")
doSetup() doOnboard()
} }
} }
} }
@@ -371,7 +371,28 @@ fn createDirectories(): Unit with {Process} = {
ensureDir(base + "/server") ensureDir(base + "/server")
} }
fn doSetup(): Unit with {Process} = { fn doSetup(): Unit with {Console, Process} = {
print("")
if isInitialized() then {
printWarn("pal is already initialized on this device.")
print("")
let rerun = askConfirm("Re-run setup? This will overwrite your config.", false)
if rerun == false then {
print("")
printHint("No changes made.")
print("")
()
} else {
print("")
doSetupSteps()
}
} else {
doSetupSteps()
}
}
fn doSetupSteps(): Unit with {Process} = {
print("") print("")
printHeader("Setting up pal") printHeader("Setting up pal")
@@ -403,7 +424,7 @@ fn doSetup(): Unit with {Process} = {
if hasCommand("syncthing") then { if hasCommand("syncthing") then {
let hasConfig = execQuiet("test -f " + palDir() + "/syncthing/config/config.xml && echo yes || echo no") let hasConfig = execQuiet("test -f " + palDir() + "/syncthing/config/config.xml && echo yes || echo no")
if hasConfig |> isNo then { if hasConfig |> isNo then {
let ignore3 = Process.exec("syncthing generate --home=" + palDir() + "/syncthing/config --no-default-folder --gui-listen=127.0.0.1:8385 2>&1") let ignore3 = Process.exec("syncthing generate --home=" + palDir() + "/syncthing/config 2>&1")
printStep("Initializing sync", "done") printStep("Initializing sync", "done")
} else { } else {
printStep("Initializing sync", "ready") printStep("Initializing sync", "ready")
@@ -508,7 +529,7 @@ fn doSyncSetup(): Unit with {Process} = {
let hasConfig = execQuiet("test -f " + palDir() + "/syncthing/config/config.xml && echo yes || echo no") let hasConfig = execQuiet("test -f " + palDir() + "/syncthing/config/config.xml && echo yes || echo no")
if hasConfig |> isNo then { if hasConfig |> isNo then {
printStepPending("Creating config") printStepPending("Creating config")
let ignore = Process.exec("syncthing generate --home=" + palDir() + "/syncthing/config --no-default-folder --gui-listen=127.0.0.1:8385 2>&1") let ignore = Process.exec("syncthing generate --home=" + palDir() + "/syncthing/config 2>&1")
printStep("Creating config", "done") printStep("Creating config", "done")
} else { } else {
printStep("Creating config", "exists") printStep("Creating config", "exists")
@@ -779,7 +800,7 @@ fn doExport(): Unit with {Console, Process} = {
let fileSize = execQuiet("du -h " + exportPath + " | cut -f1") let fileSize = execQuiet("du -h " + exportPath + " | cut -f1")
printStep("Creating archive", fileSize) printStep("Creating archive", fileSize)
print("") print("")
printSuccess("Export created: " + exportFile) printSuccess("Export created: " + exportPath)
print("") print("")
printHint("Restore with:") printHint("Restore with:")
printCmd("pal import " + exportFile) printCmd("pal import " + exportFile)
@@ -814,11 +835,7 @@ fn doImport(archivePath: String): Unit with {Console, Process} = {
print("") print("")
let confirmed = askConfirm("Continue?", false) let confirmed = askConfirm("Continue?", false)
if confirmed == false then { if confirmed then {
print("")
printHint("Import cancelled.")
print("")
} else {
print("") print("")
printHeader("Importing") printHeader("Importing")
@@ -844,6 +861,10 @@ fn doImport(archivePath: String): Unit with {Console, Process} = {
printErr("Import failed - config not found in archive") printErr("Import failed - config not found in archive")
} }
print("") print("")
} else {
print("")
printHint("Import cancelled.")
print("")
} }
} else { } else {
// No existing data, proceed without confirmation // No existing data, proceed without confirmation
@@ -1339,7 +1360,7 @@ fn doOnboardSteps(): Unit with {Console, Process} = {
let hasConfig = execQuiet("test -f " + palDir() + "/syncthing/config/config.xml && echo yes || echo no") let hasConfig = execQuiet("test -f " + palDir() + "/syncthing/config/config.xml && echo yes || echo no")
if hasConfig |> isNo then { if hasConfig |> isNo then {
printStepPending("Creating sync config") printStepPending("Creating sync config")
let ignore11 = Process.exec("syncthing generate --home=" + palDir() + "/syncthing/config --no-default-folder --gui-listen=127.0.0.1:8385 2>&1") let ignore11 = Process.exec("syncthing generate --home=" + palDir() + "/syncthing/config 2>&1")
printStep("Creating sync config", "done") printStep("Creating sync config", "done")
} else { } else {
printStep("Creating sync config", "exists") printStep("Creating sync config", "exists")
@@ -1390,21 +1411,24 @@ fn doOnboardSteps(): Unit with {Console, Process} = {
printHint("Install restic and run 'pal backup init <repo>' later.") printHint("Install restic and run 'pal backup init <repo>' later.")
} else { } else {
let existingRepo = getBackupRepo() let existingRepo = getBackupRepo()
if String.length(existingRepo) > 0 then { let setupBackup = if String.length(existingRepo) > 0 then {
printStep("Backup repository", "already configured") printHint("Current repository: " + existingRepo)
printHint("Repository: " + existingRepo)
} else {
let setupBackup = askConfirm("Set up backups?", true)
print("") print("")
if setupBackup then { askConfirm("Reconfigure backup location?", false)
printHint("Enter a path or URL where backup snapshots will be stored.") } else {
print("") askConfirm("Set up backups?", true)
let ignore15b = Process.exec("printf ' \\033[36mLocal directory\\033[0m /mnt/backup, /media/usb/backups\\n' >&2") }
let ignore15c = Process.exec("printf ' \\033[36mSFTP (SSH)\\033[0m sftp:user@host:/backups\\n' >&2") print("")
let ignore15d = Process.exec("printf ' \\033[36mBackblaze B2\\033[0m b2:bucket-name:path\\n' >&2") if setupBackup then {
let ignore15e = Process.exec("printf ' \\033[36mAmazon S3\\033[0m s3:bucket-name/path\\n' >&2") let defaultRepo = existingRepo
print("") printHint("Enter a path or URL where backup snapshots will be stored.")
let backupRepo = askInput("Backup location", "") print("")
let ignore15b = Process.exec("printf ' \\033[36mLocal directory\\033[0m /mnt/backup, /media/usb/backups\\n' >&2")
let ignore15c = Process.exec("printf ' \\033[36mSFTP (SSH)\\033[0m sftp:user@host:/backups\\n' >&2")
let ignore15d = Process.exec("printf ' \\033[36mBackblaze B2\\033[0m b2:bucket-name:path\\n' >&2")
let ignore15e = Process.exec("printf ' \\033[36mAmazon S3\\033[0m s3:bucket-name/path\\n' >&2")
print("")
let backupRepo = askInput("Backup location", defaultRepo)
if String.length(backupRepo) == 0 then { if String.length(backupRepo) == 0 then {
printHint("Skipping backup setup.") printHint("Skipping backup setup.")
printCmd("pal backup init <repo>") printCmd("pal backup init <repo>")
@@ -1433,11 +1457,10 @@ fn doOnboardSteps(): Unit with {Console, Process} = {
printCmd("pal backup init " + backupRepo) printCmd("pal backup init " + backupRepo)
} }
} }
} else { } else {
printHint("Skipping backup setup.") printHint("Skipping backup setup.")
printHint("Set this up later with:") printHint("Set this up later with:")
printCmd("pal backup init <repo>") printCmd("pal backup init <repo>")
}
} }
} }

8
flake.lock generated
View File

@@ -47,13 +47,13 @@
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {
"lastModified": 1771221263, "lastModified": 1771638380,
"narHash": "sha256-Av4s4pelV+ueIMSY61aHuT8KjKZ6ekXtJsnjVc89gtQ=", "narHash": "sha256-RLGfahDSlYi8ec50DtmfOZn9q8JpF2xBTcUb8K2ZQ3Q=",
"path": "/home/blu/src/lux", "path": "/home/blu/src/lux/lang",
"type": "path" "type": "path"
}, },
"original": { "original": {
"path": "/home/blu/src/lux", "path": "/home/blu/src/lux/lang",
"type": "path" "type": "path"
} }
}, },

View File

@@ -15,7 +15,7 @@
}; };
lux = { lux = {
url = "path:/home/blu/src/lux"; url = "path:/home/blu/src/lux/lang";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };