4 Commits

Author SHA1 Message Date
Aaron
ed162d3c1d Release prep tweaks and version bump 0.1.4 2026-01-03 12:29:55 -05:00
Aaron
d07fab99a6 Add stable release checklist 2026-01-03 11:56:39 -05:00
Aaron
511978666a Add mock API server for Playwright tests 2026-01-03 11:49:41 -05:00
Aaron
453deac601 Update stable manifest for v0.1.3 2026-01-03 00:18:17 -05:00
7 changed files with 204 additions and 20 deletions

View File

@@ -8,8 +8,10 @@ This documents the *current* workflow and the *target* workflow once profiles +
- Nginx + PiKit dashboard - Nginx + PiKit dashboard
- DietPi dashboard - DietPi dashboard
3) Update the system if needed. 3) Update the system if needed.
4) Run the prep scrub + verify: 4) Run the prep scrub + verify (prep now prompts to shut down):
- `sudo ./pikit-prep.sh` - `sudo ./pikit-prep.sh`
- (optional) `sudo ./pikit-prep.sh --no-shutdown`
- (optional) `sudo ./pikit-prep.sh --shutdown-now`
- `./pikit-smoke-test.sh` - `./pikit-smoke-test.sh`
- (optional) `sudo ./pikit-prep.sh --check-only` - (optional) `sudo ./pikit-prep.sh --check-only`
5) Image the SD card with DietPi Imager. 5) Image the SD card with DietPi Imager.
@@ -25,8 +27,10 @@ This documents the *current* workflow and the *target* workflow once profiles +
4) Add dashboard services using the UI (Add Service modal). 4) Add dashboard services using the UI (Add Service modal).
5) Open any needed ports in ufw (done as part of testing/config): 5) Open any needed ports in ufw (done as part of testing/config):
- `sudo ufw allow from <LAN subnet> to any port <port>` - `sudo ufw allow from <LAN subnet> to any port <port>`
6) Run the prep scrub + verify: 6) Run the prep scrub + verify (prep now prompts to shut down):
- `sudo ./pikit-prep.sh` - `sudo ./pikit-prep.sh`
- (optional) `sudo ./pikit-prep.sh --no-shutdown`
- (optional) `sudo ./pikit-prep.sh --shutdown-now`
- `./pikit-smoke-test.sh` - `./pikit-smoke-test.sh`
- (optional) `sudo ./pikit-prep.sh --check-only` - (optional) `sudo ./pikit-prep.sh --check-only`
7) Image the SD card via the QEMU DietPi VM: 7) Image the SD card via the QEMU DietPi VM:
@@ -54,8 +58,10 @@ This documents the *current* workflow and the *target* workflow once profiles +
- Merges services into `/etc/pikit/services.json` (idempotent). - Merges services into `/etc/pikit/services.json` (idempotent).
5) Run the drift check (planned script): 5) Run the drift check (planned script):
- Confirms services + ports match the profile + base. - Confirms services + ports match the profile + base.
6) Run the prep scrub + verify: 6) Run the prep scrub + verify (prep now prompts to shut down):
- `sudo ./pikit-prep.sh` - `sudo ./pikit-prep.sh`
- (optional) `sudo ./pikit-prep.sh --no-shutdown`
- (optional) `sudo ./pikit-prep.sh --shutdown-now`
- `./pikit-smoke-test.sh` - `./pikit-smoke-test.sh`
- (optional) `sudo ./pikit-prep.sh --check-only` - (optional) `sudo ./pikit-prep.sh --check-only`
7) Image the SD card with DietPi Imager. 7) Image the SD card with DietPi Imager.
@@ -84,8 +90,10 @@ Use the helper:
- dashboard loads - dashboard loads
- firstboot completes - firstboot completes
4) Apply any required profile/services. 4) Apply any required profile/services.
5) Run prep + verify: 5) Run prep + verify (prep now prompts to shut down):
- `sudo ./pikit-prep.sh` - `sudo ./pikit-prep.sh`
- (optional) `sudo ./pikit-prep.sh --no-shutdown`
- (optional) `sudo ./pikit-prep.sh --shutdown-now`
- `./pikit-smoke-test.sh` - `./pikit-smoke-test.sh`
6) Power down cleanly. 6) Power down cleanly.
7) Image the SD card (DietPi Imager via QEMU or ondevice). 7) Image the SD card (DietPi Imager via QEMU or ondevice).
@@ -96,6 +104,48 @@ Use the helper:
9) Smoke test the flashed image on a second SD card: 9) Smoke test the flashed image on a second SD card:
- boot → firstboot → dashboard → services - boot → firstboot → dashboard → services
## 5) Release checklist (stable)
1) Ensure `main` is clean and all changes are pushed.
2) Update `pikit-web/data/version.json` to the new version.
3) Build the web assets:
- `npm --prefix pikit-web run build`
4) Run tests:
- `npm --prefix pikit-web test`
- `./pikit-smoke-test.sh`
5) Commit version bump and push.
6) Tag the release:
- `git tag vX.Y.Z && git push origin vX.Y.Z`
7) Build release bundle + manifest:
- `./tools/release/make-release.sh X.Y.Z https://git.44r0n.cc/44r0n7/pi-kit/releases/download/vX.Y.Z`
8) Generate changelog from git:
- `git log --pretty=format:'- %s (%h)' vPREV..HEAD > out/releases/CHANGELOG-X.Y.Z.txt`
9) Create the Gitea release and upload assets:
- `pikit-X.Y.Z.tar.gz`
- `manifest.json`
- `CHANGELOG-X.Y.Z.txt`
10) Update `manifests/manifest-stable.json` with new version + sha256 and push.
## 5) Release checklist (stable)
1) Ensure `main` is clean and all changes are pushed.
2) Update `pikit-web/data/version.json` to the new version.
3) Build the web assets:
- `npm --prefix pikit-web run build`
4) Run tests:
- `npm --prefix pikit-web test`
- `./pikit-smoke-test.sh`
5) Commit version bump and push.
6) Tag the release:
- `git tag vX.Y.Z && git push origin vX.Y.Z`
7) Build release bundle + manifest:
- `./tools/release/make-release.sh X.Y.Z https://git.44r0n.cc/44r0n7/pi-kit/releases/download/vX.Y.Z`
8) Generate changelog from git:
- `git log --pretty=format:'- %s (%h)' vPREV..HEAD > out/releases/CHANGELOG-X.Y.Z.txt`
9) Create the Gitea release and upload assets:
- `pikit-X.Y.Z.tar.gz`
- `manifest.json`
- `CHANGELOG-X.Y.Z.txt`
10) Update `manifests/manifest-stable.json` with new version + sha256 and push.
## Notes ## Notes
- Profiles are additive to the base image defaults; do not include PiKit or DietPi dashboard entries in profiles. - Profiles are additive to the base image defaults; do not include PiKit or DietPi dashboard entries in profiles.
- Keep `RESCUE.md` in `/root` and `/home/dietpi` only (not in `/var/www`). - Keep `RESCUE.md` in `/root` and `/home/dietpi` only (not in `/var/www`).

View File

@@ -1,9 +1,9 @@
{ {
"version": "0.1.2", "version": "0.1.3",
"_release_date": "2025-12-10T00:00:00Z", "_release_date": "2026-01-03T05:16:09Z",
"bundle": "https://git.44r0n.cc/44r0n7/pi-kit/releases/download/v0.1.2/pikit-0.1.2.tar.gz", "bundle": "https://git.44r0n.cc/44r0n7/pi-kit/releases/download/v0.1.3/pikit-0.1.3.tar.gz",
"changelog": "https://git.44r0n.cc/44r0n7/pi-kit/releases/download/v0.1.2/CHANGELOG-0.1.2.txt", "changelog": "https://git.44r0n.cc/44r0n7/pi-kit/releases/download/v0.1.3/CHANGELOG-0.1.3.txt",
"files": [ "files": [
{ "path": "bundle.tar.gz", "sha256": "8d2e0f8b260063cab0d52e862cb42f10472a643123f984af0248592479dd613d" } { "path": "bundle.tar.gz", "sha256": "a31db945f08a8cdb0906a913b3b5507cc50225e9ce6b23bef525951d23335865" }
] ]
} }

View File

@@ -14,9 +14,12 @@ PIKIT_SSH_OPTS="${PIKIT_SSH_OPTS:-}"
PIKIT_REMOTE_TMP="${PIKIT_REMOTE_TMP:-/tmp/pikit-prep.sh}" PIKIT_REMOTE_TMP="${PIKIT_REMOTE_TMP:-/tmp/pikit-prep.sh}"
PIKIT_SELF_DELETE="${PIKIT_SELF_DELETE:-0}" PIKIT_SELF_DELETE="${PIKIT_SELF_DELETE:-0}"
PIKIT_FORCE_PASSWORD_CHANGE="${PIKIT_FORCE_PASSWORD_CHANGE:-1}" PIKIT_FORCE_PASSWORD_CHANGE="${PIKIT_FORCE_PASSWORD_CHANGE:-1}"
PIKIT_SHUTDOWN_AFTER_PREP="${PIKIT_SHUTDOWN_AFTER_PREP:-1}"
PIKIT_SHUTDOWN_PROMPT="${PIKIT_SHUTDOWN_PROMPT:-1}"
MODE="both" MODE="both"
LOCAL_ONLY=0 LOCAL_ONLY=0
DID_PREP=0
ERRORS=0 ERRORS=0
WARNINGS=0 WARNINGS=0
@@ -32,10 +35,14 @@ Options:
--prep-only Run prep only (no check) --prep-only Run prep only (no check)
--check-only Run checks only (no prep) --check-only Run checks only (no prep)
--local Force local execution (no SSH copy) --local Force local execution (no SSH copy)
--shutdown-now Shutdown after prep completes without prompting
--no-shutdown Skip shutdown prompt after prep
--help Show this help --help Show this help
Env: Env:
PIKIT_FORCE_PASSWORD_CHANGE=0 Skip forcing a password change (default is on) PIKIT_FORCE_PASSWORD_CHANGE=0 Skip forcing a password change (default is on)
PIKIT_SHUTDOWN_AFTER_PREP=0 Skip shutdown prompt after prep (default on)
PIKIT_SHUTDOWN_PROMPT=0 Skip shutdown prompt (default on)
USAGE USAGE
} }
@@ -70,6 +77,8 @@ parse_args() {
--prep-only) MODE="prep" ;; --prep-only) MODE="prep" ;;
--check-only) MODE="check" ;; --check-only) MODE="check" ;;
--local) LOCAL_ONLY=1 ;; --local) LOCAL_ONLY=1 ;;
--shutdown-now) PIKIT_SHUTDOWN_AFTER_PREP=1; PIKIT_SHUTDOWN_PROMPT=0 ;;
--no-shutdown) PIKIT_SHUTDOWN_AFTER_PREP=0 ;;
--help|-h) usage; exit 0 ;; --help|-h) usage; exit 0 ;;
*) *)
echo "[FAIL] Unknown argument: $arg" >&2 echo "[FAIL] Unknown argument: $arg" >&2
@@ -86,12 +95,16 @@ run_remote() {
[ "$arg" = "--local" ] && continue [ "$arg" = "--local" ] && continue
forward+=("$arg") forward+=("$arg")
done done
local ssh_tty=()
if [ "$PIKIT_SHUTDOWN_AFTER_PREP" -eq 1 ] && [ "$PIKIT_SHUTDOWN_PROMPT" -eq 1 ] && [ -t 0 ]; then
ssh_tty=(-t)
fi
if ! command -v scp >/dev/null 2>&1 || ! command -v ssh >/dev/null 2>&1; then if ! command -v scp >/dev/null 2>&1 || ! command -v ssh >/dev/null 2>&1; then
echo "[FAIL] ssh/scp not available for remote prep" >&2 echo "[FAIL] ssh/scp not available for remote prep" >&2
exit 1 exit 1
fi fi
scp -i "$PIKIT_SSH_KEY" $PIKIT_SSH_OPTS "$SCRIPT_PATH" "${PIKIT_USER}@${PIKIT_HOST}:${PIKIT_REMOTE_TMP}" scp -i "$PIKIT_SSH_KEY" $PIKIT_SSH_OPTS "$SCRIPT_PATH" "${PIKIT_USER}@${PIKIT_HOST}:${PIKIT_REMOTE_TMP}"
ssh -i "$PIKIT_SSH_KEY" $PIKIT_SSH_OPTS "${PIKIT_USER}@${PIKIT_HOST}" \ ssh "${ssh_tty[@]}" -i "$PIKIT_SSH_KEY" $PIKIT_SSH_OPTS "${PIKIT_USER}@${PIKIT_HOST}" \
"sudo PIKIT_SELF_DELETE=1 bash ${PIKIT_REMOTE_TMP} --local ${forward[*]}; rc=\$?; rm -f ${PIKIT_REMOTE_TMP}; exit \$rc" "sudo PIKIT_SELF_DELETE=1 bash ${PIKIT_REMOTE_TMP} --local ${forward[*]}; rc=\$?; rm -f ${PIKIT_REMOTE_TMP}; exit \$rc"
exit $? exit $?
} }
@@ -607,6 +620,33 @@ finalize() {
echo "[OK] Prep/check completed." echo "[OK] Prep/check completed."
} }
maybe_shutdown() {
if [ "$PIKIT_SHUTDOWN_AFTER_PREP" -ne 1 ] || [ "$DID_PREP" -ne 1 ]; then
return
fi
local do_shutdown=1
if [ "$PIKIT_SHUTDOWN_PROMPT" -eq 1 ]; then
if [ -t 0 ]; then
local reply=""
printf '\nShutdown now? [y/N] '
read -r reply || reply=""
case "${reply,,}" in
y|yes) do_shutdown=1 ;;
*) do_shutdown=0 ;;
esac
else
status WARN "no TTY; skipping shutdown (use --shutdown-now to force)"
do_shutdown=0
fi
fi
if [ "$do_shutdown" -eq 1 ]; then
status OK "Shutting down"
shutdown -f now || status FAIL "shutdown"
else
status OK "Shutdown skipped"
fi
}
maybe_self_delete() { maybe_self_delete() {
if [ "$PIKIT_SELF_DELETE" -eq 1 ] && [[ "$SCRIPT_PATH" == /tmp/* ]]; then if [ "$PIKIT_SELF_DELETE" -eq 1 ] && [[ "$SCRIPT_PATH" == /tmp/* ]]; then
rm -f "$SCRIPT_PATH" || true rm -f "$SCRIPT_PATH" || true
@@ -623,15 +663,17 @@ main() {
require_root require_root
case "$MODE" in case "$MODE" in
prep) prep_image ;; prep) prep_image; DID_PREP=1 ;;
check) check_image ;; check) check_image ;;
both) both)
prep_image prep_image
DID_PREP=1
check_image check_image
;; ;;
esac esac
finalize finalize
maybe_shutdown
maybe_self_delete maybe_self_delete
} }

View File

@@ -1,3 +1,3 @@
{ {
"version": "0.1.3" "version": "0.1.4"
} }

View File

@@ -17,7 +17,7 @@ export default defineConfig({
trace: 'retain-on-failure', trace: 'retain-on-failure',
}, },
webServer: { webServer: {
command: 'npm run dev', command: 'node tests/mock-api.js & npm run dev',
url: BASE_URL, url: BASE_URL,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
stdout: 'pipe', stdout: 'pipe',

View File

@@ -0,0 +1,85 @@
import http from 'http';
const port = Number(process.env.PIKIT_MOCK_API_PORT || 4000);
const updatesConfig = {
enabled: false,
scope: 'security',
cleanup: false,
bandwidth_limit_kbps: null,
auto_reboot: false,
reboot_time: '04:30',
reboot_with_users: false,
update_time: '04:00',
upgrade_time: '04:30',
state: { enabled: false },
};
const routes = {
'/health': { ok: true },
'/api/status': {
hostname: 'pikit-test',
uptime_seconds: 0,
load: [0, 0, 0],
memory_mb: { total: 1024, free: 1024 },
disk_mb: { total: 1024, free: 1024 },
cpu_temp_c: 35.0,
lan_ip: '127.0.0.1',
os_version: 'DietPi',
auto_updates_enabled: false,
auto_updates: { enabled: false },
updates_config: updatesConfig,
reboot_required: false,
ready: true,
services: [],
},
'/api/firstboot': {
state: 'done',
steps: [],
current_step: null,
log_tail: '',
error_present: false,
error_path: '/api/firstboot/error',
ca_hash: null,
ca_url: '/assets/pikit-ca.crt',
},
'/api/firstboot/error': { present: false, text: '' },
'/api/services': { services: [] },
'/api/updates/config': updatesConfig,
'/api/updates/auto': { enabled: false, details: { enabled: false } },
'/api/update/status': {
status: 'up_to_date',
current_version: '0.0.0',
latest_version: '0.0.0',
message: 'Mocked',
channel: 'stable',
in_progress: false,
},
'/api/update/releases': { releases: [] },
'/api/diag/log': { entries: [], state: { enabled: false, level: 'normal' } },
};
const server = http.createServer((req, res) => {
const url = (req.url || '/').split('?')[0];
const body = routes[url] || (url.startsWith('/api/') ? {} : null);
if (body === null) {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'not found' }));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(body));
});
server.listen(port, '127.0.0.1', () => {
console.log(`[mock-api] listening on 127.0.0.1:${port}`);
});
const shutdown = () => {
server.close(() => process.exit(0));
};
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);

View File

@@ -3,17 +3,24 @@
# Prints a one-time SSH hardening tip after the forced password change. # Prints a one-time SSH hardening tip after the forced password change.
FLAG="/var/lib/pikit/first-login.notice" FLAG="/var/lib/pikit/first-login.notice"
DONE_FILE=".pikit-first-login.done"
case "$-" in case "$-" in
*i*) interactive=1 ;; *i*) interactive=1 ;;
*) interactive=0 ;; *) interactive=0 ;;
esac esac
if [ "$interactive" -eq 1 ] && [ -f "$FLAG" ]; then USER_NAME="$(id -un 2>/dev/null || echo "")"
echo "" DONE_PATH="${HOME:-}/$DONE_FILE"
echo "Pi-Kit: For better security, set up an SSH key and disable password auth once working."
echo " Example: ssh-keygen -t ed25519" if [ "$interactive" -eq 1 ] && [ -f "$FLAG" ] && [ "$USER_NAME" = "dietpi" ]; then
echo " ssh-copy-id dietpi@pikit.local" if [ -n "${HOME:-}" ] && [ -d "${HOME:-}" ] && [ ! -f "$DONE_PATH" ]; then
echo "" echo ""
rm -f "$FLAG" 2>/dev/null || true echo "Pi-Kit: For better security, set up an SSH key and disable password auth once working."
echo " Run these from your computer (not the Pi):"
echo " ssh-keygen -t ed25519"
echo " ssh-copy-id dietpi@pikit.local"
echo ""
:> "$DONE_PATH" 2>/dev/null || true
fi
fi fi