- Demote transient route-replace errors (vanishing interfaces) to trace
- Tolerate errors during policy routing cleanup on drop
- Use join_all instead of try_join_all for gateway watcher jobs
- Simplify wifi interface detection to always use find_wifi_iface()
- Write wifi enabled state to db instead of interface name
Load pre-saved container images from /usr/lib/startos/migration-images
before migrating packages, removing the need for internet access during
the v1→v2 s9pk conversion. Add a periodic progress logger so the user
can see which package is being migrated.
Bundle start9/compat, start9/utils, and tonistiigi/binfmt container
images into the OS image so the v1→v2 s9pk migration can run without
internet access.
The pipe-wrap binary guarantees FDs are always pipes (not sockets),
making the chown safe. The chown is still needed because anonymous
pipes have mode 0600 — without it, non-root users cannot re-open
/dev/stderr via /proc/self/fd/2.
Move BackupProgress { complete: true } into the same db.mutate() as the
DesiredStatus revert in the backup transition. Previously these were
separate mutations—the status would revert to Running before progress
showed complete, causing a visible gap in the UI.
After setuid, the kernel clears the dumpable flag, making /proc/self/
entries owned by root. This broke open("/dev/stderr") for non-root
users inside subcontainers. The previous fix (chowning /proc/self/fd/*)
was dangerous because it chowned whatever file the FD pointed to (could
be the journal socket).
The proper fix is prctl(PR_SET_DUMPABLE, 1) after setuid, which restores
/proc/self/ ownership to the current uid.
Additionally, adds a `pipe-wrap` subcommand that wraps a child process
with piped stdout/stderr, relaying to the original FDs. This ensures all
descendants inherit pipes (which support re-opening via /proc/self/fd/N)
even when the outermost FDs are journal sockets. container-runtime.service
now uses this wrapper.
With pipe-wrap guaranteeing pipe-based FDs, the exec and launch non-TTY
paths no longer need their own pipe+relay threads, eliminating the bug
where exec would hang when a child daemonized (e.g. pg_ctl start).
- Pre-create and chown dump file for postgres user before pg_dump
- Chown volume mountpoint to postgres before initdb on restore
- Add --no-privileges to pg_restore to skip GRANT/REVOKE for missing roles
Implemented pipe FD handoff from exec to launch via Unix socket +
SCM_RIGHTS for grandchild log capture. Superseded by the simpler
PR_SET_DUMPABLE approach which eliminates the need for pipes entirely.
e2fsck returns 1 when errors are corrected and 2 when corrections
require a reboot. These are expected during ext4→btrfs conversion.
Only exit codes >= 4 indicate actual failure. Previously, .invoke()
treated any non-zero exit as an error, causing the conversion to
fail after successful filesystem repairs.
Two issues fixed:
1. Process group cascade: exec-command processes inherited the
container runtime's process group. When an entrypoint script
did kill(0, SIGTERM) during shutdown, it signaled ALL processes
in the group — including other subcontainers' launch wrappers,
causing their PID namespaces to collapse. Fixed by calling
setsid() in exec-command's pre_exec to isolate each service
in its own process group.
2. Unordered daemon termination: removeChild("main") fired
onLeaveContext callbacks for all Daemon.of() instances
simultaneously, bypassing Daemons.term()'s reverse-dependency
ordering. Fixed by having Daemons.build() mark individual
daemons as managed (suppressing their onLeaveContext) and
registering a single onLeaveContext that calls the ordered
Daemons.term(). The term() method is deduplicated so
system.stop() and onLeaveContext share the same shutdown.
The regex used `$` (end-of-string anchor) instead of no anchor,
so it never matched the percentage in rsync output. Every line,
including empty ones, was logged instead of parsed.
- Track the restart loop as an awaitable { abort, done } handle
- Remove shouldBeRunning flag — signal.aborted serves the same purpose
- Remove exiting field — term() awaits command termination inline
- Guard start() on loop existence to prevent concurrent restart loops
- Make backoff sleep abortable so term() returns immediately
- Suppress error logging during intentional termination
- Loop clears its own handle in finally block for natural exit (oneshot)
- Refactor HealthDaemon to use a tracked session (AbortController + awaitable
promise) instead of fire-and-forget health check loops, preventing health
checks from running after a service is stopped
- Stop health checks before terminating daemon to avoid false crash reports
during intentional shutdown
- Guard onExit callbacks with AbortSignal to prevent stale session callbacks
- Add logErrorOnce utility to deduplicate repeated error logging
- Fix SystemForEmbassy.stop() to capture clean promise before deleting ref
- Treat SIGTERM (signal 15) as successful exit in subcontainer sync
- Fix asError to return original Error instead of wrapping in new Error
- Remove unused ExtendedVersion import from Backups.ts
- Fix restoreBackup using backupOptions instead of restoreOptions
- Add missing await on preRestore/postRestore hooks
- Remove -c (checksum) flag that forced full reads on every run
- Add --partial to keep partially transferred files on interruption
- Add --inplace to avoid temp-file+rename metadata churn
- Add --timeout=300 to prevent hangs on stalled mounts
Adds a VPS restart button to the settings page, above logout. Shows a
spinner while the RPC completes, then a dialog telling the user to wait
1-2 minutes and refresh.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug: After running an action (e.g. bitcoin's autoconfig), update_tasks was
called with the submitted form input — which for task-triggered actions is
filtered to only the task's fields (e.g. {zmqEnabled: true}). Other services'
tasks targeting the same action were then compared against this partial via
is_partial_of, so any task wanting a field NOT in the submission (e.g.
{blocknotify: "curl..."}) would incorrectly become active, even though the
full config still satisfied it.
This caused a cycling bug: running LND's autoconfig (zmqEnabled) would
activate Datum's task (blocknotify), and vice versa, despite the merge
correctly preserving both values in the config.
Fix: After running an action, fetch the full current config via
get_action_input (same as create_task and recheck_tasks already do) and
compare tasks against that.
The one-liner fix would have been to add a get_action_input call in the
RunAction handler. Instead, we extracted eval_action_tasks on
ServiceActorSeed — a single method that both RunAction and recheck_tasks
now call — because the duplication between these two sites is exactly how
this bug happened: recheck_tasks fetched the full config, RunAction didn't,
and they silently diverged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug: Setting a task input property to undefined (e.g. { prune: undefined })
to express "this key should be deleted" resulted in no task being created.
JSON.stringify strips undefined values, so { prune: undefined } serialized
as {}, and is_partial_of({}, any_config) always returns true — meaning
input-not-matches saw a "match" and never activated the task.
Fix (two parts):
- SDK: coerce undefined to null in task input values before serialization,
so they survive JSON.stringify and reach the Rust backend
- Rust: treat null in a partial as matching a missing key in the full
config, so tasks correctly deactivate when the key is already absent
Assumption: null and undefined/absent are semantically equivalent for
StartOS config values. Input specs produce concrete values (strings,
numbers, booleans, objects, arrays) — null never appears as a meaningful
distinct-from-absent value in real-world configs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Guard z.union() against empty arrays in dynamicSelect/dynamicMultiselect
by falling back to z.string() (fixes zod v4 _zod TypeError)
- Add smtpShape: typed zod schema for store file models, replacing
smtpInputSpec.validator which caused cross-zod-instance errors
- Bump version to 0.4.0-beta.62
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>