- CFG_HOTFIX_AUTO (security-breakage|all|off, default security-breakage) seeded in
general_terminal; reaches existing installs via the add-only config reconciler.
- webui_artifact_scan.sh (webuiArtifactScan): fetch+verify the signed index, write
artifacts_available.json ATOMICALLY (build in temp → jq-validate → one write;
keep the prior file on any failure — never emits broken JSON). Annotates each
artifact with applied (a per-id record exists) + applicable (target installed).
- artifactApplyAuto + `libreportal artifact apply-auto`: enqueue apply tasks for
the eligible signed hotfixes — only when the index is VERIFIED-signed, only
auto==true + in the severity policy + applicable + not already applied. Each
apply is its own task (visible in the log + History), never applied inline.
- `updater check` now also refreshes the index (webuiArtifactScan) and runs
artifactApplyAuto — one front door, no second phone-home.
Unit-tested 13/13: policy filtering (security-breakage / off / all), auto:false
exclusion, already-applied skip, non-installed-app skip, unsigned-index fail-closed,
and the scan transform's signed/applied/applicable fields.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
checkSuccess silently reported '✓ Success' for failed commands, which is how
the de-sudo write gaps (throttle stamp, passwords, updater) hid. Rework it:
- Capture the real exit code up front; success path unchanged.
- On failure, ALWAYS append to a greppable $logs_dir/error_report.log tagged
with the caller's script:line + exit code — a failure can't hide behind a
green check anymore.
- New CFG_REQUIREMENT_CONTINUE_ON_ERROR (default true): log + continue so one
failure doesn't abort the run and we surface EVERY issue in a single pass.
Flip it off later for strict abort/prompt (the prior behaviour, preserved).
Documents the 'local VAR=$(cmd); checkSuccess' footgun (local resets $?), which
the next commit fixes across the tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Adds the install-time Beginner/Advanced choice the user described, with
the linked dev-mode escape hatch and global body-class machinery that
any surface can hang advanced/dev-only DOM off.
Three-tier mental model, two flags in the data model:
Beginner default. nothing extra shown.
Advanced .lp-advanced DOM revealed; advanced wizard steps shown
Adv+Dev .lp-dev DOM also revealed; dev-only fields visible
Linking rule (enforced inside LpUi):
- enabling dev auto-enables advanced (dev w/o advanced is incoherent)
- disabling advanced auto-disables dev
Wire shape:
CFG_INSTALL_LEVEL beginner | advanced (general_basic)
CFG_DEV_MODE existing, unchanged behaviour
window.LpUi.{advanced,dev} {get(), set(), apply()}
localStorage keys lp.ui.advanced, lp.ui.dev, lp.ui.seeded
body classes lp-ui--advanced, lp-ui--dev
events lp-ui-advanced-changed, lp-ui-dev-changed
global CSS gates body:not(.lp-ui--advanced) .lp-advanced { hide }
body:not(.lp-ui--dev) .lp-dev { hide }
Setup wizard:
- New step 1 "Choose your experience" with Beginner/Advanced cards.
Beginner is preselected so race-through gets the safe default.
- Picking a level updates totalSteps live (4 for beginner, 5 for
advanced) so the progress bar reflects the choice.
- Metrics step (Prometheus + Grafana) is gated to Advanced — beginner
never sees it, never gets asked, never installs them by accident.
- Submit payload now carries install_level; setup-routes.js validates
it against the enum (beginner|advanced).
- scripts/setup/setup_apply.sh writes it to CFG_INSTALL_LEVEL via
updateConfigOption.
- On submit, LpUi.advanced.set is called immediately so the next
surface (running-tasks page) is already in the right mode — no
refresh needed.
WebUI bootstrap:
- js/utils/lp-ui.js loads first thing in index.html (before any other
bootstrap) so body.lp-ui--advanced is applied pre-paint — no FOUC
of advanced content on a fresh tab.
- On first run, seeds lp.ui.advanced from CFG_INSTALL_LEVEL.
Subsequent loads honour the user's per-browser override.
- Mirrors CFG_DEV_MODE → lp.ui.dev on the seed pass.
Dev-mode unlock:
- Existing 10-click LibrePortal-logo easter egg unchanged.
- NEW: same 10-click unlock on the Advanced toggle (in services-manager).
Reuses the countdown-toast pattern; on the 10th click delegates to
the topbar's _setDevMode so there's one canonical setter and the
config_update task path stays singular.
- TopbarComponent now exposes its instance as window.topbar so the
toggle's tap handler can reach _setDevMode.
- topbar._setDevMode also calls LpUi.dev.set(enabled) so the body
class flips immediately (no reload needed to see dev-only DOM).
Convention rolled out:
- Services tab's .service-rich panel was already gated on
body.lp-ui--advanced.
- .lp-advanced / .lp-dev are now first-class hide classes any
component can tag DOM with — see style.css globals.
Signed-off-by: librelad <librelad@digitalangels.vip>
The Features section was a grab-bag of ~27 toggles, most of which are
either category-specific (firewall, SSL, Docker network, SSH hardening)
or install-time choices that brick the box if flipped on a live
install (the WebUI / config / CLI / Docker requirements). One page
made auditing easier but flattened the risk hierarchy.
Reorganised so each toggle lives where it conceptually belongs, and
the dangerous install-time set is double-gated:
network_docker (Advanced) DOCKER_NETWORK, DOCKER_NETWORK_PRUNE,
DOCKER_SWITCHER
network_firewall (Advanced) UFW, UFWD, WHITELIST_PORT_UPDATER [new]
network_domains (field-Adv) SSLCERTS
security_ssh (Advanced) SSHKEY_DOWNLOADER, SSH_DISABLE_PASSWORDS,
BCRYPT_SAVE, GLUETUN_FOR_ALL [new]
general_terminal (Advanced) CRONTAB, CONFIGS_CHECK,
CONFIGS_AUTO_UPDATE, CONFIGS_AUTO_DELETE,
MISSING_IPS, CONTINUE_PROMPT,
SUGGEST_INSTALLS, SUGGEST_METRICS
general_install (Adv+DEV) CONFIG, COMMAND, WEBUI, WEBUI_SERVICE,
DATABASE, PASSWORDS, DOCKER_CE,
DOCKER_COMPOSE
The install-time eight are marked **ADVANCED** **DEV** — invisible
unless Developer Mode is on AND "Show Advanced Options" is expanded.
Each field's description was updated to note "Disabling on an existing
install will brick the system" / "install-time choice only" so a user
who does get to the toggle understands the gun before pulling the
trigger.
Other cleanup that fell out:
- Removed `configs/features/` directory entirely.
- Added the two new subcategories to SUBCATEGORY_ORDER in
network/.category and security/.category.
- Dropped the `category === 'features'` Danger Zone header special-case
in config-manager.js and its .danger-zone-section--header-only CSS
variant (sole user).
- Trimmed an obsolete "Edit the features config" notice in
check_requirements.sh.
Signed-off-by: librelad <librelad@digitalangels.vip>
What this delivers (Stage 1+2 of the dev-mode feature):
1. New `**DEV**` marker for config fields. Mirrors the existing
`**ADVANCED**` pattern: stays in the description string, frontend
strips it for display, presence flips a 'hide unless dev mode is on'
behaviour. Implemented in ConfigUtils.cleanDescription /
isDevField / isDevModeOn and in ConfigShared._filterDevKeys, which
the two generateFieldsForCategory* helpers now call before rendering.
2. New CFG_DEV_MODE field in configs/general/general_install. Visible
under Advanced; defaults to false. The canonical place to toggle
dev mode (the WebUI easter egg writes to it, the auto-detector
writes to it, and users can flip it directly here too).
3. Marked CFG_INSTALL_MODE and CFG_RELEASE_CHANNEL with `**DEV**`.
Normal users no longer see either field — they install Release-
Stable and that's the whole story. Devs see both with the
user-facing labels you asked for:
CFG_INSTALL_MODE Release - Stable | Git clone | Local folder
CFG_RELEASE_CHANNEL Release - Stable | Release - Bleeding Edge
(CFG_INSTALL_MODE label for the release option also renamed to match.)
4. 10-click LibrePortal-logo easter egg in topbar.js:
- Counter on any .libreportal-logo click; idle-reset after 3 s
- Toast countdown from click 6 ('4 clicks away from being a developer…')
- At 10: toggles CFG_DEV_MODE via the standard config_update task
(same path the Config form uses); shows '🛠️ Developer mode
unlocked. Reload to see the extra options.'
- Re-using the same logo when dev mode is on toggles it back off
('… away from disabling developer mode') — symmetric, no separate UI
5. Auto-detect: on every WebUI load, if CFG_INSTALL_MODE is git or
local AND CFG_DEV_MODE is off, auto-flip to on with a one-time
toast 'Developer mode auto-enabled — you're on a git install.
Click the LibrePortal logo 10× to disable.' Stops dev-install
users getting locked out of the very options they need to manage
their install. Idempotent — runs once per page load; no-op if
already on or on release.
Disable surfaces: (a) CFG_DEV_MODE in Advanced on the Config form is
the canonical toggle; (b) 10 more logo clicks. A 3rd surface (a System
page banner) is deferred — those two cover the practical cases.
Signed-off-by: librelad <librelad@digitalangels.vip>
scripts/source/fetch.sh (sourced at runtime via files_source.sh):
- lpFetchRelease [ver]: resolve channel manifest -> download tarball -> VERIFY
sha256 (refuse on mismatch/absence) -> replace the install tree ( is
code-only now; configs/logs live in the separate system tree, so no backup
dance). Host/channel from LP_RELEASE_BASE_URL/CFG_RELEASE_BASE_URL + channel.
- lpFetchSource: dispatch release|git|local.
- lpVersionGt: numeric dotted semver compare (used by the updater + badge).
init.sh initGIT is now release-aware: the bootstrap (install.sh) stages+verifies
the code and sets LP_ALREADY_FETCHED=1 (skip re-fetch); a direct release run sources
fetch.sh; a bare /root reinstall is directed to install.sh. install.sh exports
LP_ALREADY_FETCHED + LP_RELEASE_BASE_URL on hand-off. validateUnattended already
accepts release (git-url is gated on git mode).
Config: CFG_INSTALL_MODE default -> release, + CFG_RELEASE_BASE_URL / CFG_RELEASE_CHANNEL
(add-only reconcile preserves existing installs' git/local mode).
Verified: lpVersionGt across cases; lpFetchRelease downloads+verifies+extracts a
clean tree against a local server. The updater + reset/reinstall release paths are
phase D.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Containers now run unprivileged by default — a container breakout maps to a
sub-UID, not host root. Rooted remains available as a legacy opt-in. Existing
installs keep their current mode (config reconciliation is add-only); fresh
installs get rootless. The rootless path already handles unprivileged ports
(ip_unprivileged_port_start=0) and userns.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys,
Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun
VPN routing, and a web dashboard to manage it all.
Free & open forever to self-host; optional paid hosted services fund it.
See PROMISE.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>