# Changelog All notable changes to the StartOS OS product are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and StartOS uses an [extended version](https://docs.start9.com) of semantic versioning. This file tracks notable changes since the move to the monorepo, and is what each [GitHub release](https://github.com/Start9Labs/start-technologies/releases) links to for the detail behind its highlights. ## [0.4.2] ### Added - Service actions can now let you upload and download files, with progress shown during uploads. ### Fixed - Display apostrophes and other special characters correctly in action-form list headings. - Correct the maximum text-length error message. - Submitting an action's input no longer reloads the page. - Pressing Enter in SMTP settings saves changes instead of cancelling them; in the test-email field, it sends the email instead of reloading the page. - Long dropdown lists keep their background all the way down, so the page behind no longer shows through the options. - Show server-provided plaintext HTTP error details and preserve meaningful timeout and cancellation errors. - The installer no longer offers the USB drive it was booted from as the OS drive or the data drive. - Automatic firewall configuration (PCP) now opens IPv6 ports on home routers, so a public address published over IPv6 is reachable without a manual firewall rule. - Numeric forms preserve zero defaults and honor bounds set to zero. - Improved access to services through public domains over IPv6. - Service updates keep your public domains off if you switched them off. - For services that StartOS forwards directly, public domains and the public IP addresses they use now turn on and off together. - Preserve saved Ethernet and Wi-Fi settings across server restarts. - Improved wired network reliability on Server Pure. - Sideloaded packages appear in Installed services with upload progress before the upload finishes. - Show registry contact email addresses alongside GPG identities in Trusted Keys. - Record virtual-host client/service connection failures at trace level instead of flooding normal logs. Use `start-cli server set-log-level trace` for temporary `start-core`/`start-os` trace output, or choose `error`, `warn`, `info`, or `debug`. The startup log filter is restored when the daemon restarts. - Reject source-preserving proxy connections when the client's address cannot be preserved, instead of substituting the host's address. Services on unproxied TLS bindings need an IPv6 listener to accept IPv6 clients; TLS rejection does not trigger automatic IPv4 fallback. ## [0.4.1.2] ### Changed - Diagnostic updates honor your saved StartOS-update signing keys without needing the data drive. Accepting an untrusted signer requires an authenticated CLI running locally on the server. - Removed preseeded Alpha build keys from new installations and during migration; connected registries can offer those keys again for explicit trust. - Removed the Podman dependency from StartOS; legacy service conversion uses bundled system image archives offline. - Preserved images used only by legacy health checks or dependency configuration during conversion. - Reworked signing-key management makes it easier to see who signed packages and StartOS updates, inspect key details, and control trust separately for each in **System → Trusted Keys**. Manage registries and choose your StartOS update source under **System → Registries**. Keys listed by connected registries show their links and cannot be deleted while still listed. ### Fixed - iPhones and iPads can install the Root CA of a server set up on StartOS 0.4.0 or 0.4.0.1. Its fingerprint changes on update, and devices that already trust it keep trusting the server. - Fixed migration from StartOS 0.3 and sideloading legacy service packages that use system images, including most services' backup procedures. ## [0.4.1.1] ### Fixed - Servers running StartOS 0.3.5 can update to this release. ## [0.4.1] ### Added - Added opt-in background conversion of existing backups to the authenticated filesystem format, with weighted overall progress, all phases listed upfront, explicit resumption after interruption, and success or failure notifications naming the target. Creating a backup offers to update this server’s older format or skip conversion; restoring marks each old-format server backup with an explanatory warning tag. Unconverted backups remain usable for backups and restores. - Added named read-only snapshots to the `startos-backup-fs` utility, with explicit creation, listing, deletion, and recovery mounts. Snapshots share unchanged data and retain earlier versions as the current backup changes; retention is managed manually. - Added bearer tokens for API clients that cannot sign requests: `start-cli auth token create --name ` prints a token once, and requests sent with `Authorization: Bearer ` authenticate as a session with that name, which the session list shows and can revoke. Tokens idle for 30 days are revoked like other sessions. - Added an MCP server at `/mcp`: AI agents and other MCP clients can call the StartOS API as tools over the streamable HTTP transport, authenticating with a bearer token from `start-cli auth token create`. - Added `package.exec` to the API, also offered as an MCP tool: it runs a command in a service's subcontainer to completion and returns its output and exit code, with optional standard input and a timeout. - Manage named trusted keyrings in a responsive System table with row edit/delete menus and independent package/OS trust switches. New keyrings trust packages only by default. Import Ed25519 public keys in PEM or armored GPG format, honoring revocations present in GPG exports and retaining full certificates with names and email addresses. Built-in keyrings stay first, with read-only names and no Delete option; either trust switch can still be changed. - Rename SSH Keys to SSH Access. - Show trusted package signers, block untrusted Marketplace installs, and require explicit confirmation before sideloading untrusted software. - Require trusted signers for CLI package installs and OS updates by default; `--trust` explicitly overrides signer trust while preserving signature and integrity checks. - Enable push from the Notifications settings modal to receive StartOS notifications while the UI is closed, including through browser-provided self-hosted UnifiedPush endpoints. - **Format a backup drive from StartOS.** `System > Create Backup` offers "Format a drive", which erases a connected drive and leaves it with a single Btrfs partition ready for backups. The drive holding StartOS or its data is refused. Also available as `start-cli disk format`. - Included `lspci` and `lsusb` for PCI and USB hardware diagnostics. - Compatible security updates announced by your selected OS registry appear in Notifications and use browser push when enabled. Their Software Update link opens System → General. - Beta updates can replace an earlier build with the same OS version. ### Changed - **The session API separates signed-in devices from bearer tokens.** `auth.session.list` returns sessions as `{ pubkeys, tokens }`, and `auth.session.kill` takes `pubkeys` and `tokens` in place of `ids`. - Store structurally typed keys without PEM armor while preserving existing sign-ins and trusted update signers during upgrades. - A service's Dependencies card shows the version of each dependency you have installed. A dependency at an incompatible version opens the Marketplace on a compatible one. - Starting and loading health checks can show a clock while waiting for an external condition. All health statuses display nonempty service-provided messages as-is; empty messages use the status label. - Proxy plaintext service connections through StartOS when HTTP proxying is enabled. Applications see StartOS as the TCP peer; client IPs are available through `X-Forwarded-For` when forwarded-header generation is enabled. - Preserve client source IP addresses for raw TLS service connections that do not require HTTP proxying. ### Fixed - Prevented stale backup data beyond a file's saved length from reappearing when the file grows after an interrupted write. - Allow retrying an interrupted first backup when no encryption key was saved, reclaiming its unusable encrypted data through the target trash. - Prevent concurrent first opens from publishing different encryption keys for the same backup store. - Reclaim unused data-drive chunks with an initial background Btrfs balance after upgrading and weekly maintenance thereafter. - Warn when data-drive metadata allocation space runs low, before services encounter write failures despite reported free space. - Network-folder backups and recovery accept dotted DNS hostnames. Bare computer names remain NetBIOS-only, and lookup errors identify the resolver used. - Accept subcontainer environment overrides through numbered `JSON_ENV_*` variables, including multiline values and variable removal. - Preserve multiline image environment defaults in `package attach`. - Respect explicit removal of `HOME` without restoring its default. - Show release highlights and their full-changelog link in the server's language before and after an update, with English fallback when a translation is unavailable. - Point the welcome notification's full-changelog link at the published changelog. - The server log records when no gateway grants a port mapping, with each gateway's reason, once per mapping until it is granted or its request changes. - Fixed dependency links leaving the Marketplace stuck loading. - Backups to network shares are much faster, especially over high-latency links such as remote sites reached through StartTunnel. Backup data is now written in large batches with many requests in flight, instead of waiting for a network round trip on each small write. - Updating from StartOS 0.3.5 carries each `.onion` address over on the ports it served there, including for services that finish migrating after Tor. An interface that had no Tor address on 0.3.5 gets none. - Commands run through `package attach` that read stdin to EOF finish after their input ends instead of hanging. - Fixed interface updates getting stuck after a StartOS update. The Refresh Needed dialog shows interface sync progress, offers Refresh after syncing completes, and provides recovery actions if syncing fails or stalls. - Fixed adding public domains through port-range interfaces in the web UI and CLI. - Enabled port ranges retain ownership of their configured domains when checking for duplicates. - Removing a gateway also clears the address choices services made on it. - Gateways selected for outbound traffic by the server or a service must be deselected before removal, preventing removal from cutting off outbound connectivity. - `start-container subcontainer exec` reports a command killed by a signal with the shell exit code (128 plus the signal number) instead of exiting 0 for SIGTERM or 1 for other signals. - Adding a tunnel reports an error after 30 seconds if its network interface remains unavailable, instead of waiting indefinitely. - The UI offers recovery actions when its cached copy is corrupted. - The UI picks up its own update sooner after a StartOS update. ### Security - Strengthened backup encryption authentication and password-protected key wrapping, and removed backup encryption keys from process arguments and recovery errors. Authenticated current references reject substitutions of individual stored records; rollback to a genuine older complete backup view remains possible. Existing backups remain usable for backups and restores until explicit conversion. Conversion rewrites encrypted data, resumes after interruption, and prevents older StartOS releases from reading the converted target. - Enforce HTTP authentication on both plaintext and TLS service connections when an HTTP proxy authentication policy is configured. - Websites you visit can no longer use your browser to control a server that is being set up or is in diagnostic mode. During setup and diagnostics, open the server at its `.local` address or IP address. Thanks to dritzps for reporting this. - Update node-forge to 1.4.2. - Updated service-container system packages to include available Debian security fixes, including the Brotli decoder fix. ## [0.4.0.4] ### Fixed - Action menus near the right edge of the screen size to fit their items. - Service interfaces on automatically assigned ports no longer occasionally fail to accept connections because StartOS was using the port for an outbound connection. - On the server itself, `start-cli auth login` and `auth logout` explain that no login is needed there, instead of failing with an unclear error. - Restore the Gateways list to connected gateways, hiding stale bridge entries. - Menus and dropdowns open when the browser or OS has reduced motion turned on. - Revoking a sign-in immediately closes its idle streams. - The Start menu and the More menus in Backups and Certificate Authorities close after you choose an item. - The serial console login prompt starts only on servers that have a serial port. - Fixed services whose container image has no `/etc`, such as one built `FROM scratch`, failing to start. - Opening System Settings from the Start menu or the System tab while already in System shows General Settings. - A gateway's WAN IP is shown while another gateway is selected for outbound traffic. ### Security - Link-Local Multicast Name Resolution (LLMNR, port 5355) is disabled. - The RPC port mapper (port 111) is disabled. NFS shares mount over NFSv4, or over NFSv3 with `-o nolock`. ## [0.4.0.3] ### Added - Bash tab completion for `start-cli` is enabled automatically in new shells. - **Crash diagnostics survive subsequent boots.** Available kernel crash records are preserved on the data drive. `gather-debug-info` collects them together with kernel logs, the previous boot's journal, watchdog status and NVMe power-state information. - Diagnostic mode has an **Update server** button that downloads the latest StartOS release, or reinstalls the current one, to apply on restart. - Forward Email is a provider choice under **System > SMTP**, and the providers are listed alphabetically. - **Servers with a watchdog restart after a system hang.** StartOS keeps the hardware watchdog armed with a one-minute timeout where a supported device is available. - Added in-memory database support to patch-db for ephemeral application state. ### Changed - **The marketplace and service pages load package icons as they are shown**, instead of with every registry listing and database update, so they open faster. Icons show a loading placeholder and are cached across page reloads. - **Legacy v1 package conversion creates SquashFS images natively**, preserving container filesystem ownership and permissions. - **NVIDIA images include driver 580.178.04**, with Linux 7.2 compatibility. - **StartOS publishes a private domain's record to a router over TCP when it has no WireGuard key to sign the update with.** A router that accepts DNS UPDATE only over UDP no longer receives these records. - **Static DNS servers that point back at the server itself are refused**, such as `0.0.0.0`, `::`, or a loopback address. ### Removed - **The `tunnel.*` RPC passthrough is removed.** It forwarded StartTunnel API calls signed with the server's key. ### Fixed - Certificate acquisition trusts custom certificate authorities installed in the host trust store. - Certificate failure notifications include the authority's explanation, including Let's Encrypt's unpause link for paused domains. - Bash tab completion works for nested `start-cli` subcommands and their options. - **Large HTTP responses arrive complete through service HTTPS addresses when the backend closes the connection.** - **Required tasks preserve a service's desired running status.** Services resume automatically when the last blocking task clears, unless explicitly stopped. CLI `package start` records running intent and warns while critical tasks block execution. - Private domains keep resolving over the server's IPv6 address when the network changes its IPv6 prefix. - Keep expanded form fields correctly spaced after focusing inputs. - Respect configured form-list limits when adding entries. - Fixed service effects losing supplied event IDs, including the ID shared by opening and submitting an action input form. Supplied IDs take precedence over the caller's event ID. - Services start on servers set to a language other than English, including those running PostgreSQL. - **The Software Update badge clears after an OS update, and the update dialog opens only when a newer release is available.** - **A package installs reliably while it is still downloading or uploading.** - Services record the time of their most recent successful backup. - **`start-cli package install` reports a package missing from the registry it asked**, naming that registry, instead of prompting to choose from an empty list of flavors. - Restoring a service that is still installed is refused with an error; uninstall it first. - Stop a service daemon's entire process tree when its image-provided init exceeds the shutdown timeout. - **Services keep their internal IPv6 bridge address enabled.** Address switches affect access from other networks while service-to-service access stays available. - **Setup accepts backslash-separated network backup paths** when verifying or restoring a CIFS share. - Failed service updates no longer restore stale data from an earlier update when a new rollback snapshot cannot be created. - A command that cannot be started in a service subcontainer, such as through `start-cli package attach`, now reports the command and working directory it tried. - **Services that read an optional dependency's manifest can initialize when that dependency is not installed.** ### Security - Enforce action access permissions when services request another service’s action form, preventing unauthorized reads of form fields and prefilled values. - **Update dependencies with security fixes**, including TLS certificate verification and handshake handling, HTTP/2 flow control, and XML parsing. - Keep diagnostic mode off public HTTPS domains after a server error. - Keep service and system DNS forwarding on their selected outbound gateways. Edit gateway names and DNS together, and show resolver addresses with automatic/public-fallback indicators. - Control outbound gateway selection and independent OS/service DNS overrides on dedicated Outbound Traffic tabs; services using gateway DNS use the gateway's settings rather than the OS override. - Resolve configured WireGuard endpoint names through the physical connection for host-only tunnel bootstrap; service DNS retains its outbound gateway. - Keep setup and early diagnostic host DNS available through the automatic physical gateway and retain stored DNS policy after it loads even if initialization fails. - Keep private DNS answers aligned with their gateway when an interface address moves. - Preserve existing system-wide static DNS settings as overrides on known gateways during upgrade. - Block UI-initiated OS updates unless their registry metadata lists a trusted signer. The trust list is stored on the server, initially includes drbonez, matthill, and Start9, and supports labeled CLI management. - Require a valid signature from a built-in trusted signer for diagnostic OS updates. - Isolate login rate limits by server address, ingress gateway, and public or private access. Log the client IP, gateway, and address on the first blocked attempt per throttle window. - Reject signed requests using another gateway's address or a private-only address over public access. - Reject remote requests using unconfigured loopback signing addresses. ## [0.4.0.2] ### Security - **Update dependencies with security fixes**, including Angular, DOMPurify, networking libraries, and the service runtime. Replace the unmaintained YAML library while preserving configuration formats. ### Fixed - **Concurrent service commands and `package attach` calls run without intermittent filesystem errors.** - **Large packages sideload successfully in the UI over slow connections.** A failed upload shows an error message. - **Restart requests during service startup take effect after startup completes.** - **Starting a StartOS update from the UI installs the release whose notes it shows.** It previously installed whatever version the registry listed last. - **Forwarded IPv4 UDP ports retain their outbound mapping.** UDP traffic a service starts from a forwarded port leaves through that forward's gateway with its external address and port, keeping peer-to-peer services such as HyperDHT directly reachable under load. - **Required service dependencies appear from the package manifest during initialization.** StartOS shows their base version and health requirements even if the service has not reported runtime dependencies; active runtime requirements can tighten the base, including for optional dependencies. While a service is not using an optional dependency, its tasks for that dependency are hidden and do not prevent it from starting. - **Freshly generated Root CAs carry an Authority Key Identifier conforming to RFC 5280 and the CA/Browser Forum Baseline Requirements.** Existing servers retain their trusted Root CA when updated. - **Timed-out or aborted service commands stop running inside their subcontainer.** StartOS kills the command when its exec wrapper dies, including during health checks. - **Services start from images without `/etc/passwd` or `/etc/group`.** - **A service command set to run as a user or group its image does not define fails instead of running as root.** - **Upgrades from 0.3.5.1 succeed on nearly full data drives.** StartOS skips the optional filesystem optimization when a drive lacks room for it. - **Services log at the level their package sets.** A service process no longer inherits the container runtime's environment, including its `RUST_LOG`, when its package passes no environment variables. Every service process receives the server's language as `LANG` unless its package sets one. - **IPv4 public domains work on servers with IPv6 disabled.** StartOS sets up the IPv4 reply path for source-preserving TLS routing on those servers. - **An update's loading indicator stays on the service being updated** when you switch registries in the Updates tab. - **A service installed again after a failed install is reachable without restarting the server.** ### Added - **Administrators can add a custom CA root to the host trust store with `start-cli server trust-ca`.** The root takes effect immediately and persists across reboots and OS updates. Services keep the trust store their package provides. - **Packages can show a localized pre-download alert before an update.** A matching installed version prompts for confirmation in the Marketplace and Updates tab before StartOS fetches the package. The alert supports Markdown formatting. - **UEFI installations carry a standard fallback bootloader.** StartOS remains bootable if firmware loses its saved boot entry while retaining the normal entry and StartOS's automatic priority for a plugged-in installer. - **Typing a service's domain without `https://` opens its web interface over HTTPS.** This works on each network where the domain is assigned. Server IP addresses and domains assigned to the StartOS UI retain their existing behavior. The server's own `.local` name remains reserved for StartOS. See [Private Domains](https://docs.start9.com/start-os/private-domains.html). - **Open UI can honor a service's configured address.** Services that depend on one canonical origin can now direct Open UI to that address while it remains enabled and compatible with the current browser session. - **Private-domain routes on a shared port survive a network path that blocks PCP.** When you bind a private domain through a Start9 gateway, StartOS asks the gateway to route the hostname by TLS SNI — previously only over PCP, so a network that filters UDP 5351 between your server and the gateway kept ordinary port forwards working while the domain silently stopped being routed. StartOS now falls back to asking over UPnP (a Start9 vendor action the gateway advertises), so the route comes up either way; PCP remains preferred when it gets through. - **WireGuard configs can declare inbound gateway support.** A config containing `# inbound: yes` is classified as an inbound/outbound gateway when imported. Existing StartTunnel configs remain recognizable by their StartTunnel header; configs without either marker are classified as outbound-only. - **A registry's description appears at the top of the marketplace** while that registry is selected. - **`start-cli server epp` shows and sets the CPU's energy/performance preference**, persisted across reboots the way the governor is. On Librem Mini v2 systems without a saved preference, StartOS applies `balance_power` when available. Without a saved preference, all other systems retain their current value. - **A service can permanently retire a network host or a port it no longer uses, and the port numbers it held become available again.** A service that reorganizes its interfaces across an update — renaming a host, dropping a port — could previously only switch the old one off, which keeps its port number reserved for as long as the service is installed. Retiring removes it outright and releases its port forwards, proxy entries, local DNS records and any port mapping StartOS asked your router for. A domain you had assigned to a retired host is removed with it, so check the service's release notes and assign it to one of the service's current interfaces. - **Services that run virtual machines can use the CPU's virtualization support.** Packages for emulators, CI runners, and development tools can request the hardware interface (`/dev/kvm`) for QEMU, Firecracker, and similar workloads. StartOS grants it only to packages that request it and only on servers whose processors provide it. - **A gateway can be marked secure, so services' plaintext addresses are offered over it.** `start-cli net gateway set-secure ` records that you trust the network on the other side of a gateway; `unset-secure` hands the decision back to StartOS, which trusts only the loopback and container-bridge gateways. `net gateway list` shows the current setting. This is one switch for the whole server: every installed service's non-SSL addresses — the server's LAN IP addresses, its `.local` name and its private domains — are offered on that network at once and enabled immediately. An address unlocked this way reaches devices on that gateway's own network and, over IPv4, on any private network routed to it; it is never opened to the public internet. Mark a gateway secure only when you control every device on those networks: anything on them can read and alter traffic to a plaintext address, including the passwords typed into it. See [Gateways](https://docs.start9.com/start-os/gateways.html). - **An action result that hands you a link can be opened in a new tab.** Where a service returns a URL — an authorization link, an admin panel — the result shows an open-in-new-tab button beside it. - **Installed services can be shown as a grid of tiles.** A toggle at the top of the page switches between the grid and the list, and your choice follows you to any browser pointed at this server. Narrow windows and phones always show the grid. - **A notification welcomes you to a new version after your server restarts.** It names the version you landed on and carries that release's highlights and important follow-up instructions. Pre-update warnings appear before updating. - **An action can return a multi-line value** — a diagnostic report, a generated config file, an exported key block. It appears as a read-only monospace box that keeps its line breaks, and, where the service asks for it, can be copied, shown as a QR code, or saved to a file. ### Changed - **A service's `.local` address has its own switch, and its LAN IP addresses follow it.** A LAN IP you switch yourself keeps that setting. Every other one, including an address your server is assigned later, is on while `.local` is on and off while it is off. On an interface served without TLS, switching on a LAN IP switches `.local` on, and switching `.local` off switches its LAN IPs off. There, a `.local` address left on beside a gateway's IP addresses, all of them off, shows a warning. - **ZRAM compressed swap is now off by default, and updating turns it off on your server.** With it on, services under heavy memory load could take the RAM StartOS reserves for itself and leave the server unreachable. A server that leaned on ZRAM to fit its services has less memory to work with after the update. `start-cli server experimental zram --enable` turns it back on. - **Your server's name is now its `.local` address, without the `.local` on the end.** A server previously carried two names: a display label shown in the browser tab, and the `.local` address derived from it by lowercasing and hyphenating — so the tab could say "My Cool Server" while every address you actually typed said `my-cool-server.local`. There is now one name. The `Server Name` field under `System > General Settings` edits the `.local` address directly, and accepts lowercase letters, numbers and hyphens — up to 32 of them, not starting or ending with a hyphen. On update, an existing name outside those rules is repaired to fit; every other address stays unchanged. The browser tab now shows that address instead of the old display label. **Two `start-cli` commands change with it**, so an unattended-install or provisioning script needs updating: `setup execute` no longer takes `--name`, and `server set-hostname` now takes one required hostname where it used to take an optional name and hostname. Setting it moves the `.local` address, where passing only a name used to change the label alone. - **Installing the StartOS UI as an app names it after your server.** Adding it to a phone's home screen or installing it from a desktop browser labels it with the server name in place of `StartOS`. An app installed before this update, or before a rename, may keep the name it was installed with — remove it and install it again to pick up the current one. - **Switching registries no longer opens a warning dialog.** A banner atop the marketplace carries the caveat while a registry Start9 does not operate is selected. - **The NVIDIA images now use NVIDIA's open kernel modules, which support GeForce RTX 20-series, Quadro RTX and newer.** This is what makes current cards work at all — an RTX 50-series, an RTX PRO 6000 or an NVIDIA GB10 can only be driven by these modules. The trade is at the other end of the range: **a GeForce GTX 900-series or 10-series card, a Titan X or Xp, or a Tesla M40, P40, P100 or V100 will no longer be driven by the NVIDIA images.** If you rely on one of those, stay on 0.4.0.1 or use the Standard image, whose open-source `nouveau` driver still provides display output without GPU compute. ### Fixed - **You can sign in to the StartOS UI, and log in with `start-cli`, at one of your server's IPv6 addresses**, such as `https://[2001:db8::1]`. - **A port forward takes effect for peers that were already sending to it.** A peer that sent to a port while it had no forward, such as after a gateway's public address changed or during a service update, kept reaching the server instead of the service, and the service's own connections to that peer could leave from a different port. Services on raw UDP ports were the most affected. - **You can sign in to the StartOS UI at any loopback address**, such as `http://127.1.1.19:8989` through a Holesail tunnel. - **Switching off a service's LAN IP address closes it.** The address kept answering for as long as the service's `.local` address was on. On an interface served over TLS, `.local` and the service's domains still answer there by name. - **A service keeps its `.local` address while your server has no LAN address.** The address left the service's list whenever the network dropped, and a service that checks the URL you chose for it could stop and ask for it again. - **Services the 0.3.5.1 update renames keep their onion addresses.** Nostr, Ghost, Synapse, Monero and Fedimint addresses carry over under the services' new package ids. A server that already updated recovers them on its next start. - **Services start once StartOS has detected the network, and an interface that loses its connection drops its addresses right away.** A service reading its own addresses as it starts sees the server's LAN addresses. Startup waits up to 30 seconds for NetworkManager to finish connecting. - **The Raspberry Pi 4 image includes the Broadcom firmware needed for its built-in WiFi interface.** - **Raspberry Pi images use all space allocated to the StartOS filesystem.** First boot expands the filesystem to fill its partition before setup begins. - **Fallback service-container cleanup finishes after an unresponsive runtime.** StartOS bounds the fallback shutdown waits so it can release the container's network routes and continue teardown. - **A service keeps reacting to changes after several land at once.** A burst of changes to a value a service watches, such as its addresses, a dependency's status, or its outbound gateway, could permanently stop StartOS from notifying it. Its generated files, certificates, and registrations then stayed stale until the container was rebuilt. - **Services keep resolving domain names when the network provides no separate DNS server.** StartOS uses its built-in Cloudflare fallback instead of leaving service containers without a working resolver. - **A backup made on one CPU architecture restores on another.** StartOS runs backed-up service images under emulation. Reinstalling or updating a service lets StartOS select its marketplace package for the server architecture. - **Requested restarts and shutdowns complete when concurrent service teardown has already removed a mountpoint.** - **A service migrated from 0.3.5.1 keeps its data when an install or update fails.** A failed update rolls back to the data it started with, which previously took a reboot after the upgrade, and a failed install leaves existing data in place. - **Nextcloud (Legacy) and other migrated 0.3.5.1 services with a package-managed certificate start when the server has a public IP or uses StartTunnel.** - **Service interfaces show addresses only for gateways that accept inbound connections.** Commercial VPNs remain available for system-wide and per-service outbound routing. - **A service's plain (non-SSL) port accepts connections from other private networks routed to the server.** From a second VLAN, a wired/wireless split or a routed IoT network, a service's web interface opened but a plain port such as a mining pool's stratum port refused the connection, so the service looked down from that network. A private IPv4 address now admits every private (RFC 1918) source on its plain ports, as it already did on its web interfaces. - **StartOS keeps using the selected drive when Linux enumerates disks in a different order.** Fresh installs and updated servers also keep mounting their OS partitions when device names change. - **The port-forwarding test reports a port as open to the Internet only where it is reachable from the Internet.** Where StartOS's port-forward request was granted by a router that sits behind another router, the test could pass a port that nothing outside could reach, and two otherwise identical setups could disagree depending on which forwarding protocol the router spoke. StartOS now measures the port from the Internet in that case and reports what it finds. - **Port forwards left behind by an earlier run of the server are cleared at startup.** After a crash, or a restart during which the server's address changed, a stale forward could keep sending a port to an old address until the next reboot. - **Client connections through StartOS's TLS-terminating reverse proxy now fail within 15 seconds if StartOS cannot connect to the service or complete a required TLS handshake with it.** - **A port mapping StartOS opens on a UPnP router closes on its own within an hour of the server going away.** It was requested as a permanent mapping, so it stayed on the router after the server was powered off or moved to another network. A router that grants only permanent mappings still gets one. - **Transfers preserve the source filesystem format.** StartOS mounts source filesystems read-only while copying persistent data, repairing ext4 only when needed to mount it. This leaves the source drive available as a fallback. - **Transfers preserve file permissions.** Files copied from the previous drive lost their modes, which left a WireGuard gateway such as StartTunnel disconnected after a transfer until its profile was made private again by hand. - **An app that remembers your server's certificate sees the same certificate across every route to that name.** Wallets and other apps that pin the first certificate they are shown — Sparrow and the Electrum clients most visibly — raised a man-in-the-middle warning when the same name resolved through a different address or the server's public IP changed. StartOS now reuses one certificate per name until renewal. This changes how one address behaves, on a server reached through a NAT router: typing its public IP address while on the same network as the server now produces a certificate warning, because the router rewrites such a connection to the LAN address and the server cannot see which of the two you asked for. Reach it from inside your own network by its `.local` name, by a domain you have assigned to it, or by its LAN IP address. A server that holds its public address directly, or that is reached over StartTunnel, is unaffected. See [Public IP](https://docs.start9.com/start-os/public-ip.html). - **`--format json` on `start-cli server governor` and `start-cli ssh list` returns the result** it was asked for. - **A downgrade to a version that cannot take over the service's data is refused before anything is downloaded or stopped**, with an explanation of what to do instead. - **A service that fails to install, update, restore or uninstall says so right away.** The notification naming what went wrong was held back until StartOS had finished cleaning up after the attempt, which can take several minutes. It now arrives as soon as the operation fails, while that cleanup is still running. - **`start-cli package install --sideload` reports long service installation errors in its progress output.** Very long messages are shortened safely to fit the progress stream. - **Restoring from a backup, or transferring to a new drive, keeps your server's name.** Both flows renamed the server to `start9`, so the restored server answered at `start9.local` rather than the address it had before — and two servers restored on the same network collided on that one name. - **Apps that hold a connection open — desktop sync clients, API pollers — stop dropping in and out.** Nextcloud Desktop and clients like it showed a recurring "Network error" that cleared itself a few seconds later. A service routinely closes a connection once it has sat idle for a few seconds, and a client is built to notice that and open a fresh one. StartOS's reverse proxy kept the client's half of the pair open after the service had closed its own, so the client went on holding a connection it had every reason to believe was good, and the next request it sent over that connection was cut off with no reply. The proxy now closes the client's half as soon as the service closes its own, which is the signal the client is waiting for. - Trim whitespace on form inputs. - **Marketplace search matches anywhere in a package's name, ID or description, and ranks results by how closely they match.** Searching `cloud` finds Nextcloud. - **Restoring a service from a backup is many times faster.** A restore reads the service's whole package out of the backup, and the encrypted backup filesystem stores file contents as sealed 1 MiB blocks that it fetches and decodes whole. It kept none of them, so each 8 KiB read along the package fetched and decoded the enclosing block over again — dragging roughly 72 GB across a network share to deliver a 480 MB package, and turning a restore that should take a couple of minutes into 42. A block is now decoded once for the run of reads inside it, which for reading a file start to finish is once per block. - **A service that reads back a region of a file it has only partly written no longer fails its backup.** Inside a backup or restore, a service's own procedure works against the encrypted backup filesystem directly, and a program that writes at one offset and then reads a little further on — a database extending a file, an image being assembled — asked for bytes that the pending write had not reached. That read failed with an I/O error, and closing the file afterwards left the whole backup filesystem unresponsive, so the operation hung rather than finishing. Such a read now returns the zeros it should. - **File operations within a backup handle end-of-file and partial failures correctly.** Reads past the end return no bytes, large copies use bounded memory, and a copy interrupted by an error reports the bytes it completed. A failed write during a storage-layout change preserves a readable inode, its length, and every successfully written prefix byte. - **Helper processes a service starts are cleared away once they finish.** A service that shells out to other programs — a media downloader calling `yt-dlp` and `ffmpeg`, an agent running tool subprocesses — orphans a helper whenever the program that started it exits first. Those finished helpers stayed listed inside the service's container as ``, each still holding a process slot, for as long as the service ran — so a service that starts many of them built them up without limit, and only a restart cleared them. The container's first process now collects them as they finish. - **A Let's Encrypt certificate is issued even when the authority takes longer than a second to check your domain.** StartOS asked Let's Encrypt to run the check and then, one second later, asked it to run the check again — which Let's Encrypt refuses, because the one it was already running had not finished. The whole request failed there, and the retry a minute later failed the same way, so a domain could sit without a certificate indefinitely. Let's Encrypt validates from several vantage points around the world and routinely takes longer than a second, so this affected almost every domain. StartOS now waits for the answer instead of asking again. - **A Let's Encrypt domain works on an interface served on a port other than `443`** — an Electrum server on `50002`, a TURN server on `5349`. Let's Encrypt proves you control a name by connecting to it on port `443` whatever port the service behind it uses, and nothing on your server answered there for that name: the certificate was never issued, so the address served your server's Root CA instead and clients that validate against public authorities could not connect at all. StartOS now answers the challenge on port `443` for every domain it holds an authority for, and claims that port automatically wherever the domain is public — over StartTunnel, and as an IPv6 firewall pinhole on a gateway that opens one. On a router, forward `443` to your server as you would for a standard domain — the domain's own port is not enough on its own, and Port Forwards lists the `443` rule alongside it. The checks that run when you add or enable such a domain cover port `443` as well, so a domain that would refuse every connection is reported before you go looking for it, with its own row in the setup modal. Such a domain is served on its own port only, so reach it as `https://:`; `443` carries the certificate authority's checks and nothing else. - **A service that presents its own TLS certificate is shown as presenting it.** StartOS does not terminate such a connection, so the certificate the client checks is the service's — but a domain on that interface reported whichever certificate authority had been chosen for it. Those addresses now report `Self signed`. - **Services that run their own containers or a VPN can reach the devices they were granted.** A service opting into `userspaceFilesystems` or `virtualNetworking` is handed `/dev/fuse` and `/dev/net/tun` inside its container. Those nodes are now created with the same permissions the host gives them, so a service running as a non-root user can open them. Previously the permissions were left to whatever StartOS's own file-creation mask produced, and only the container's boot-time device pass — which races with the grant — widened them; on the losing side of that race a CI runner's jobs failed to start with `Failed to open() /dev/net/tun: Permission denied`. - **Reinstalling with "Preserve" copies your server's configuration before it rewrites the drive, so the configuration survives.** The copy used to be taken afterwards, by which point there was nothing left to read — so the server came back with its configuration reset, and said nothing about it. Among the things lost was the key your server uses to sign its add-on drivers, which on a machine with Secure Boot left hardware such as an NVIDIA GPU unavailable afterwards. A reinstall that cannot read the configuration it was asked to keep now stops and says so, rather than continuing and discarding it. - **On a server with Secure Boot enabled, hardware that needs an add-on driver — an NVIDIA GPU, most commonly — works after setup.** Secure Boot only loads such a driver once you approve the key your server signs it with, and approving that key is protected by your master password. Your server only ever asked the firmware to trust the key while starting up, which on a new server happens before you have set a password, so the request was never made and you were never shown the prompt — leaving the driver unable to load, with nothing to say why. Your server now asks as soon as you set your password, so the prompt appears on the next restart. See [Initial Setup](https://docs.start9.com/start-os/initial-setup.html). - **The 64-bit ARM NVIDIA image boots on NVIDIA GB10 hardware again, such as the DGX Spark, GPU workloads like Ollama and vLLM run on it, and the image is considerably smaller.** Four separate faults were involved, two of which each stopped the machine on the last line the bootloader printed — so fixing either one alone changed nothing. These images drive the GPU with NVIDIA's own driver and turn nouveau off, yet were still built with graphics firmware they cannot use — nouveau's, for every chip it supports, along with an older driver series' — and 152 MB of it sat inside the initramfs that the bootloader must read in full before the kernel starts. Separately, a Tegra fabric driver that recent kernels build in claims one of the GB10's internal buses and reads a protected register on it, stopping the machine during hardware detection before it can display anything. Third, the image built NVIDIA's driver in the flavour that does not support this generation of GPU, so the driver found the card but could never bring it up. Last, the GPU came up and reported itself correctly while every CUDA program still failed at startup, because NVIDIA's driver declines a GPU whose address-translation support does not match what the kernel offers, and this kernel is built without the support that an integrated GPU like the GB10 advertises. The images now carry only the graphics firmware they can use, switch that driver off, build the kernel modules NVIDIA supports here — which is what NVIDIA's own operating system does on the same hardware — and accept the GPU rather than turning it away. - **Your server answers to its own addresses and no others.** A name that resolved to your server but was never configured on it — a domain you pointed at its LAN IP, or its `.local` name typed without the `.local` — was served your dashboard, along with a certificate for that name signed by your server's Root CA. Logging in was never possible under those names, so the page could not be used for anything, but it should not have been reachable. Your server now serves its `.local` address, the domains you have assigned to it, the addresses a service such as Tor has added for it, and direct connections to its IP address. - **Image upgrades verify their checksum again.** `upgrade` compared the image's blake3 hash only when it was given a second positional argument, which no caller passed — so the comparison never ran and a corrupt but still mountable image would be installed without complaint. It now verifies whenever `CHECKSUM` is set. - **Large QR codes render, and a value too long for any QR code says so rather than opening an empty dialog.** Copy that value instead of scanning it. - **Removing a domain from a service leaves its network settings otherwise untouched.** Naming a network host the service does not have — a stale id, or a typo — added that host to the service as an empty entry, which then stayed in its network settings with nothing to remove it. - **A service whose startup routine throws now reports the failure in its own logs, and StartOS names the failure for what it is.** The container runtime handed the exception back to StartOS over its socket without also printing it, so a service that failed to start went quiet in `Logs` at the moment it needed to speak, while restarting every ten seconds. The exception now appears in the service's own log next to the procedure that raised it, and StartOS labels a failure that came from the runtime `Service Runtime Error` rather than `Unknown Error`. - **The StartOS UI is served over plain HTTP on port 80.** Servers set up before 0.4.0.1 gave the interface a high-numbered port instead, and nothing answered on it — so a service that reached the StartOS API over the container bridge, and any address StartOS reported for its own plaintext interface, pointed somewhere dead. Existing servers move to port 80 on update, and the high port goes back to the pool for services to use. - **The login banner reports system status for every user, not just the first one to log in.** It staged its database snapshot at a fixed path in `/tmp`, which `pam_motd` created as root at login — so any subsequent non-root run could not write it and the banner fell back to `Services: Unknown`, `WAN: N/A` and `NTP: Unknown`. It now uses a private temporary file and removes it on every exit path. - **A failed or cancelled service update can no longer lose that service's data.** Rolling an update back replaces the service's data with the copy taken before the update started. That replacement used to delete the current data before putting the copy in place, so an interruption in between — a restart, a power loss, or a second failure — could leave the service with neither, and the next update attempt would discard the surviving copy as stale. The rollback now moves the current data aside and only drops it once the copy is fully in place, so an interruption at any point leaves a state StartOS can finish on the next boot, and a rollback that cannot be completed is reported instead of passing silently. A failed first-time install over data that was already there no longer deletes that data either. - **The copy taken before an update is now made with the service stopped**, so it can no longer capture a database mid-write. - **A service's outbound gateway takes precedence over the system-wide default.** You can keep one gateway pinned under **System > Gateways > Outbound Traffic** while sending selected services through another gateway with **Set Outbound Gateway**. A service given its own gateway while a system-wide gateway was pinned has been following the system-wide one, and switches to its own when you update. Changing or clearing the service's selection also drops its established outbound connections, so new connections use the newly selected gateway. - **A service reached over IPv6 through a tunnel now answers.** StartOS sends a reply back out the interface its connection arrived on by restoring a connection mark, but the kernel routes the reply that _opens_ a connection before that mark is restored. On a server whose gateway carries no IPv6 of its own, that reply fell to the gateway's routing table — which drops IPv6 to keep it from leaking out the wrong interface — and was discarded before it was ever sent, so an inbound IPv6 connection to a tunnel-delegated address hung until it timed out. A reply from an interface's own global IPv6 address now leaves by that interface. IPv4, and traffic forwarded to a service container, were unaffected. - **A tunnel's IPv6 address now loads from a device on the server's own network.** A phone or computer sharing the server's network connected to an IPv6 address delegated through a tunnel — directly, or through a public domain pointing at it — and then hung until the request timed out, while devices everywhere else loaded it normally. The connection itself appeared to succeed, so a domain that also had an IPv4 address never fell back to it. IPv4 was unaffected. - **Notification selection checkboxes no longer cover text on phones.** When notification selection is active, each checkbox replaces its notification icon while preserving the title's spacing. - **A service that uses UDP is reachable from the Internet on a public IP address.** StartOS already passed UDP through to the service, but the mapping it asked your gateway for over PCP, NAT-PMP or UPnP covered TCP only, and a router maps each protocol separately — so a VPN, a video-call relay or a game server needed a UDP rule added to the router by hand. StartOS now asks for both protocols: on a public IP address, over IPv4 and IPv6 alike, and on a published port range. It withdraws both when the address is disabled or deleted. - **An interface a service update moves to a different internal port is listed once, not twice.** StartOS keeps a service's former port binding dormant so its addresses and external port survive if the service returns to it — but the interface record exported from that binding lingered too, so after an update like Jitsi's (Web UI moved from port 80 to 8000) the service showed the same interface twice. Exporting an interface now removes the record of its previous export, wherever it lived; a lingering duplicate clears the next time its service initializes — at the reboot this update performs. - **A port a service asks StartOS to keep off insecure networks stays off them, even when the service also publishes a port range.** A range and a single port could both claim one container port, and a range carries its own exposure rules — so the range's rule could reach the port on gateways the port's own settings excluded. StartOS now rejects the overlapping claim and names both ports, so the service reports the conflict instead of serving it. - **An interface whose service encrypts its own end now loads when the binding pins the protocols it offers.** Setting `alpn` changed how StartOS dialled the service as well as which protocols it put forward, so the service received plaintext on a port expecting TLS. The setting now narrows only the protocols on offer — the service chooses among the ones the client also asked for, and both ends of the connection carry the one it chose. - **A service that speaks HTTP/2 to StartOS now loads in a browser.** StartOS opens a fresh TLS connection to a service that encrypts its own end, and offers it the client's own list of application protocols. Where the service chose HTTP/2 from that list, every request on that connection ended in a connection error. The client's own connection had been offered nothing and settled on HTTP/1.1, so StartOS wrote HTTP/1 requests onto a connection the service was reading as HTTP/2. StartOS now offers the client exactly the protocol the service chose, so both halves of the connection carry the same one. For HTTP/2, StartOS advertises WebSocket extended CONNECT when the service includes support in its opening settings. If the service connection ends, StartOS sends GOAWAY so the browser follows HTTP/2's orderly shutdown. - **Dependency releases satisfy one complete version-range branch.** A release may use an installed or aliased version, but one version must satisfy every term in a conjunction. An exclusion rules out its branch when a declared version satisfies the complete excluded range. Dependency warnings, update checks and marketplace filtering now agree on that evaluation. - **Versions of different flavors sort in a stable order.** Comparing across flavors produced no answer, so a list mixing Bitcoin Core with Bitcoin Knots kept whatever order it arrived in. - **Marketplace, service-package and OS-update downloads fall back to IPv4 within a fraction of a second when a host's IPv6 does not answer.** StartOS tries a host's IPv6 and IPv4 addresses a quarter of a second apart and uses the first connection that succeeds. - **The OS log stays focused on actionable errors on a network whose router advertises a route with more than one next hop.** - **A service that mounts a dependency's files read-write fails to start when that dependency is not installed**, naming the missing volume. - **The Refresh Needed dialog offers a Refresh button in browser tabs.** Select it to open the updated interface. - **A TLS passthrough added with `start-cli net vhost add-passthrough` answers on every public IPv6 address of its gateway.** It answered on the addresses the gateway held at the moment the passthrough was registered — at boot, often not all of them — and refused the rest with a TLS `unrecognized name` error while IPv4 kept working. - **A TLS passthrough whose hostname was entered with capital letters receives its traffic.** One added as `Cloud.Example.com` was listed while every connection to it was dropped. Hostnames match in any case, and a passthrough saved that way starts working once the update is installed. ### Security - **A selected outbound gateway acts as a kill switch if it disconnects.** StartOS rejects the system-wide or per-service traffic assigned to that gateway until it reconnects, protecting the server's ISP address from fallback traffic. - **Service mount paths are validated and confined to their intended directories.** - **An address you assigned to a public certificate authority serves that authority's certificate and nothing else.** When StartOS could not obtain the certificate, the address fell back to one signed by your server's Root CA while still listing Let's Encrypt as its authority — so the fallback was invisible from the server itself, whose own trust store contains that Root CA. Your Root CA chain is the same on every address your server exposes, which makes it a value that links them all to one server, handed to anyone who connects. Such an address now refuses the connection until its certificate is available, and tells you so: a notification names the domain and why issuance failed, once per domain until a certificate lands. A certificate already issued keeps being served through its last 30 days while renewal is retried, so a renewal that begins failing does not take the address down, and a domain you also reach on your local network keeps answering there with your server's own certificate. - **Outbound IPv6 uses an address assigned to the selected gateway.** Traffic through a gateway that has an IPv6 router but no IPv6 address of its own fails immediately. - **A service's outbound gateway carries its IPv6 as well as its IPv4.** A service sent through its own gateway with **Set Outbound Gateway** kept using the system-wide default for IPv6, so those connections left under a different address than the one you chose. When the service's gateway can't carry IPv6, the service's IPv6 is dropped. - **A service's outbound gateway applies from the moment the service starts.** A service that had just started or restarted used the system-wide default until a gateway next changed. ## [0.4.0.1] ### Changed - **A service that serves its own TLS certificate now reports its external port as an SSL port, and StartOS serves it like one.** Every interface whose external port speaks TLS — whether StartOS terminates it or the service presents its own certificate — now carries that port in `assignedSslPort`, and `assignedPort` means a plaintext port. A self-TLS port is now answered by the StartOS SNI router, which pipes the raw TLS stream to the service with the client's address preserved, instead of a kernel port-forward — so every TLS-carrying port behaves uniformly, and a self-TLS service's domains are advertised on its preferred port (e.g. 443) exactly as when StartOS terminates TLS. This also means such a port accepts TLS connections only, and no longer relays UDP. The port number itself is unchanged, so existing addresses, bookmarks and router port-forwards keep working. Packages resolve a dependency's address with `sdk.host.getBridgeAddress`, which is correct under either arrangement; see [Service-to-Service Networking](https://docs.start9.com/packaging/service-to-service.html). - **The StartOS web interface holds ports 80 and 443.** StartOS runs as root, so its own interface is the one binding that may claim the privileged range, and it now does so through the same port allocator every service uses. HTTPS was already served on 443; the plaintext address — offered only over loopback and the service bridge — moves from a random high port to 80. ### Fixed - **Updating from 0.3.5.1 starts the Tor service it installs.** Your existing onion addresses come across with it and answer as soon as the update finishes, with nothing to start by hand. - **A service that fails to convert while migrating from 0.3.5.1 reports the reason.** The v1→v2 package conversion raises a notification against that service carrying the error that stopped it, in the same form as an install failure — alongside the summary notification listing every service to re-install. - **A service that is renamed during migration is recorded as migrated.** Ghost, Synapse, Monero, Nostr and Fedimint are installed under new ids on 0.4.0 (`ghost-legacy`, `synapse-legacy`, `monerod-legacy`, `nostr-rs-relay` and `fedimint-guardian`), and the migration looks each one up under the id it was installed as. These services complete their migration without appearing among the failures, and the failure list names services by ids that exist in the marketplace. - **The over-the-air update to 0.4.0 boots on the Server Pure.** The Server Pure's PureBoot firmware reads the boot configuration itself rather than running GRUB, and it takes the kernel and initramfs paths literally. The update now writes those paths in the plain form PureBoot expects, so the server boots into 0.4.0 on the restart that applies the update. - **A Server Pure applies its PureBoot firmware update.** StartOS installs the firmware image at the path it reads it from, so a Server Pure on an older PureBoot release updates its firmware on the next start. - **Installing onto a pre-installed Raspberry Pi keeps the data pool you pick.** Selecting the data pool by partition path now preserves that choice through installation. ## [0.4.0] The stable release of StartOS 0.4.0, ending the 0.4.0 early-access program. No 0.4.0-beta.10 was ever cut — the changes that had accumulated under that heading ship here instead — so this entry covers every change since 0.4.0-beta.9, the last released beta. ### Added - **Automatic `.local` resolution over WireGuard gateways.** StartOS now injects a DNS record for its own `.local` name into every connected WireGuard gateway (e.g. StartTunnel) — and only those gateways — via RFC 2136. Clients on the tunnel can't resolve `.local` by mDNS (Android and others exclude VPN connections from mDNS), so without this a `.local` lookup goes to the tunnel's resolver and fails; now the tunnel answers it, and the name resolves from anywhere the tunnel reaches. Once a gateway's resolver accepts the record, `.local` is listed among that gateway's addresses in the UI, like on the LAN. The gateway's own DNS-injection policy still applies — a gateway with injection disabled refuses the record, is not listed, and `.local` there falls back to a manual DNS record — and plain LAN gateways are left untouched, since mDNS already works on the LAN. A gateway whose resolver refuses or times out is backed off on the same trust-window cadence as port-mapping probes, not retried every few minutes. - **IPv6 GUA exposure control.** On a service interface, an IPv6 global-unicast address (GUA) keeps the usual on/off toggle and adds a **Local / Public** dropdown in the access column. **Local** (the default) keeps it reachable on the local network only — traffic from outside the subnet is rejected; **Public** exposes it to the Internet and attempts an automatic gateway pinhole (PCP). The choice is carried by the address's `public` flag, so services selecting addresses for P2P see the correct reachability. A GUA on a StartOS-terminated SSL port is served by the host's own TLS listener; a port StartOS does not terminate is forwarded (DNAT) directly to the service container. IPv6 ULAs and IPv4 are unchanged. - **DualStack public domains.** A public (clearnet) domain is now reachable over both IPv4 and IPv6 whenever its gateway has an IPv6 global-unicast address (GUA). StartOS advertises an `AAAA` target (the GUA) alongside the `A` target, opens an inbound IPv6 firewall pinhole for the domain's port (via PCP — v6 is NAT-free, so there is nothing to forward), and serves the domain on the GUA through its existing SNI-routed TLS listener. The bare GUA itself is not exposed — only traffic matching the domain by SNI is accepted — so an SSL domain needs no separate GUA WAN opt-in (a plaintext domain, which has no SNI to filter on, exposes the GUA the same way it exposes the WAN IPv4). The add-domain DNS check and the domain setup modal now verify and display both the `A`/IPv4 and `AAAA`/IPv6 records and reachability; IPv6 reachability is its own `net.gateway.check-port-v6` endpoint (separate from the IPv4 `net.gateway.check-port`) so each family is probed independently. - **`--force` on service start.** `start-cli package start --force` (and the `package.start` RPC `force` flag) starts a service even when it has an unresolved critical task. - **Automatic gateway configuration (#3306).** A StartOS server now opens its own public ports and publishes its private-domain DNS by talking to its gateway — a home router or a StartTunnel — instead of leaving it as a manual step. - **Automatic port forwarding.** When a public address needs a port open, StartOS opens it by speaking a port-control protocol to the gateway: PCP (RFC 6887) → NAT-PMP → UPnP IGD, in that order. Mappings are reference-counted and withdrawn when the address is disabled or deleted; each active mapping is renewed at about half its gateway-granted lease, well before it would expire, so a still-wanted forward is never dropped, and letting a mapping lapse (no further renewal) is itself a teardown path once an exposure goes away. A single shared `PortMapController` also answers reachability, so `check_port` skips the remote echo probe when an automatic mapping is already active and reports the gateway-assigned external IP directly. Port mapping is scoped to the gateway each exposure actually routes through (the interface's own subnet gateway), so the box never probes an unrelated LAN router. A port's forwards and mappings are driven only by the addresses advertised at that exact port — exposing an SSL address never opens its plaintext sibling. - **Private-domain DNS injection (RFC 2136).** When a private domain is enabled on a gateway, StartOS pushes an `A` record (domain → this host's IP on that subnet) to the gateway's DNS server via DNS UPDATE so LAN devices that don't use StartOS's resolver can still resolve it, and withdraws it on disable/delete. `check_dns` now verifies a private domain by resolving the specific FQDN against the LAN's DNS server(s) and confirming it returns one of this server's LAN addresses. - **PCP HOSTNAME extension (SNI demux).** SSL/TLS services can share a single external port (443) across many hostnames: the gateway demultiplexes inbound TLS by SNI. StartOS emits PCP HOSTNAME mappings for public-domain vhosts (PCP-only — NAT-PMP/UPnP can't demux), gated by a PCP ANNOUNCE capability probe so the option is only sent to gateways that understand it. The protocol is documented as an Internet-Draft (`rfcs/draft-start9-pcp-hostname`). - **PCP PORT_SET (RFC 7753).** A contiguous port range maps in a single PCP MAP instead of per-port sweeps. - **Per-gateway capability tracking, sharding, and backoff.** Each gateway's PCP/NAT-PMP/UPnP support (and the PCP HOSTNAME extension) is recorded on the gateway in the database — fed by periodic watcher probes and by the outcome of every mapping attempt — so a gateway that refuses a protocol stops being asked instead of paying a timeout per attempt (a yes is trusted for an hour, a no re-probed after five minutes). Mapping work is sharded per gateway interface, so one gateway's slow or absent answers never delay another gateway's mappings, and a mapping that keeps failing backs off exponentially (15 seconds, doubling to a 16-minute cap) rather than retrying on a fixed interval. - **In-place WireGuard gateway updates.** `start-cli net tunnel update ` (and a per-gateway **Update config** UI action) re-issues a WireGuard config onto the existing interface without churning the gateway identity, so forwards and public/private domains keyed to it survive the swap — primarily to add a `DNS =` line to an existing config. The update path uses NetworkManager `Update2` + `Device.Reapply`, so updating the gateway carrying the request no longer drops its own transport. Because NetworkManager (≤ 1.52) strips WireGuard peer preshared-keys on `Reapply` even when they are passed to it explicitly, StartOS re-applies each peer's PSK straight to the kernel device (over stdin, never argv) after the reapply, so the tunnel keeps its shared secret. - **Best-effort HTTP→HTTPS redirect (IPv6).** When a service is publicly exposed on 443 over an IPv6 GUA, StartOS asks the gateway for an `80→443` redirect pinhole so plain `http://` auto-redirects to `https`. Over IPv4 no such map is requested — the upstream gateway (e.g. StartTunnel, which serves a port-80 redirect by default) handles it. - **Insecure exposures never reach the WAN.** A port is opened to the public internet (an IPv4 WAN forward, an IPv6 GUA pinhole/forward, or an upstream port-map) only when the exposure is itself secure — TLS on the wire or a self-securing protocol. A plaintext exposure can still reach the LAN over a gateway explicitly marked secure, but the WAN is treated as never secure regardless of the gateway's setting. - **Private domains on StartTunnel gateways (ba5396f49).** Now that a StartTunnel proxies DNS per subnet, a private domain resolves for tunnel clients (their DNS forwards to this server, which answers with the gateway's tunnel address), so the UI no longer restricts the public/private domain picker to router gateways — all gateways can use it. The "DNS Server Config" guidance is now gateway-aware: for a tunnel gateway it instructs you to point the StartTunnel subnet's DNS at this server. The private-domain and clearnet setup dialogs also surface the automatic alternatives (enable DNS Injection for the device; automatic UPnP/NAT-PMP/PCP port forwarding) instead of describing only the manual paths. - **`MultiHost.bindPortRange` backend (#3270).** Host support for reserving a contiguous TCP+UDP port range (2–500 ports) in one call, stored as a single `RangeBindInfo` record under `Host.binding_ranges` and installed as one nftables rule per chain (via `PortForward.count`). Intended for real-time / WebRTC servers (coturn, RTP, SIP). Backed by the new `bindRange` effect. - **Package init progress reporting (#3323).** A service can stream progress during the install/update finalization phase via the new `setInitProgress` effect; the host nests it inside the install's finalization phase using the standard `FullProgress` wire format, and `setupInit` auto-reports one step per composed init handler. - **Phased backup progress (#3250).** `serverInfo.statusInfo.backupProgress` now uses the standard `FullProgress` shape (matching update/install progress). `NamedProgress.progress` is generalized to `PhaseProgress` so a phase can carry sub-phases, and the new `setBackupProgress` effect lets a service container stream its own backup sub-progress. - **Backup format v2 (#3289).** Backups are written to a new `StartOSBackupsV2` directory, and the backup report now includes per-package duration. The backup targets list shows the free space available on every drive and network folder so the user can confirm a backup will fit before starting it. When this server's pre-v2 `StartOSBackups/` backup is present on the selected target, the UI additionally warns before backup, then confirms the format change (#3324, `backup.target.legacy-info` RPC). Once migrated, StartOS also helps remove the now-obsolete V1 data: after a backup completes, if the target still holds this server's V1 backup, a warning notification reminds the user it is no longer needed; and the backup-create page shows a **Delete old backup** action for any target holding this server's V1 backup — whether or not a new (V2) backup exists yet — removing only this server's old backup; other servers' backups and `StartOSBackupsV2` are untouched (`backup.target.delete-legacy` RPC). Deletion returns immediately — the backup is atomically moved to a hidden trash folder on the target — and its space is reclaimed by a background sweep that posts a notification when it finishes (unlinking a large backup can take hours on some filesystems); a backup started while reclamation is pending finishes it first, shown as a `Reclaiming Space` phase in the backup's progress. Deleting requires a confirmation, plus an extra confirmation when this server has no current (V2) backup on the target (so the user can't unknowingly delete their only backup). Legacy detection and deletion are scoped to the current server's ID, so a target shared by several servers no longer flags or removes another server's backup. - **Direct cross-package action runs via `access` (#3267).** Action metadata gains an `access` field (`'public' | 'dependent' | 'user'`, default `'user'`) controlling who may invoke an action directly through `effects.action.run`. Public/dependent actions give dependents a direct path instead of only creating a task; direct runs still honor the action's `visibility` and `allowedStatuses`. - **`input-not-matches` tasks accept multiple values (#3310).** `TaskInput` splits into `accept` (a list of acceptable partial inputs) and `set` (the value to prefill when none match); the cross-package critical-conflict guard fires only when the input conflicts with every `accept` entry. The host still accepts the legacy `{ value }` shape over the effects socket for s9pks built on the pre-2.0 SDK. - **Service interfaces tab.** Service interfaces are promoted to a dedicated sidebar tab; the dashboard interfaces card and per-interface detail route are removed, sidebar nav labels are decoupled from route paths, and the tasks table is redesigned (action-first, service rendered as an icon). - **Unified marketplace + brochure.** The in-OS marketplace and the public brochure now share `@start9labs/marketplace` components behind an `AbstractMarketplaceService` (the OS persists to patch-db, the brochure to localStorage), so both ship the identical detail/preview UI. The brochure app is ported into the workspace and auto-deployed to `marketplace.start9.com` on master. The registry-selection modal is replaced by an inline registry-select dropdown (switch/add/delete inline), and custom registries can now be added by bare domain (https default; http for `.onion`) (#3349). Empty categories are hidden (snapping back to "all" on a registry switch that empties the selection), category icons are refreshed for the current set, and a "Package a service" link sits beneath the sidebar categories. - **Nested idmapped mounts (#3248).** StartOS gains syscall-based mount primitives (`open_tree`/`move_mount`/`fsopen`/`mount_setattr`) and a `start-container mount` path, wiring up the SDK's `idmap` field on volume/asset/dependency/backup mounts end-to-end. See [`../start-sdk/CHANGELOG.md`](../start-sdk/CHANGELOG.md) for the SDK-facing surface. - **Raspberry Pi image hardening (#3249).** Vendor kernel bumped to 6.18.33+rpt with apt pins so `/boot` stays vendor-only, `earlycon` for first-boot diagnostics, a loop-safe self-diagnosing `init_resize`, and data-drive-only setup for pre-installed devices. - **Graceful shutdown on external power events (#3319).** Two systemd pre-shutdown barrier units (`startos-shutdown.service` / `startos-restart.service`) call `start-cli server shutdown/restart` and wait for graceful container teardown, so externally-initiated shutdowns (UPS / `qm` / ACPI) tear services down cleanly. The shutdown/restart RPC gains an opt-in `wait` param. - **iOS root-CA install via configuration profile (#3240).** A new endpoint serves an unsigned Apple Configuration Profile (`PayloadType com.apple.security.root`); iOS/iPadOS download links are UA-sniffed and routed to `.mobileconfig`, fixing the broken `.crt` install flow on iOS 26.5 Safari. - **`diagnose-hang` capture script (#3236).** Captures startd runtime state entirely via `/proc` and basic tools (per-thread kernel stacks, fds/sockets, journal tail, dmesg, disk health, lxc status) when startd is unresponsive and `start-cli` can't help. - **`lo` and `lxcbr0` treated as secure networks (#3297)** for insecure (plain-HTTP) traffic, since loopback and the container bridge never leave the host; an explicit secure setting still overrides the intrinsic default. - **In-place 0.3.5.1 → 0.4.0 update path.** Updating the OS from 0.3.5.1 no longer requires syncing a whole root filesystem file-by-file — the step that grew flakier the more files a device had. StartOS 0.3.5.1's existing over-the-air updater instead receives a compact migration payload (the 0.4.0 base image plus a boot-time rewire) and the 0.4.0 initramfs converts the on-disk layout to the 0.4.0 format on first boot. The data partition is preserved and the existing package/database migration runs afterward as before. ### Changed - **Web UI and CLI authentication moved from session cookies to per-device signing keys.** Logging in now enrolls an Ed25519 public key with the server, and every API request is signed with the matching key instead of carrying a session cookie. Enrolled keys are tracked and managed the way sessions were: System → Active Sessions shows each key's user agent and last-active time and can revoke it, and keys idle for 30 days are removed. All existing sessions are signed out on upgrade — sign in again on each device. Resolves #3511. The device key is a non-extractable WebCrypto key held in IndexedDB: page scripts can sign with it while the page is open, but can never read the key material out. Logging in requires a browser with Ed25519 WebCrypto support — any evergreen browser (Safari 17, Firefox 130, Chrome/Edge 137, or newer). HTTP cookies are gone from the API entirely: the server no longer sets or reads any cookie, so cookies planted by services co-hosted on other ports of the same hostname can no longer collide with StartOS auth. `start-cli` run on the server itself now presents the local authcookie as an `Authorization: Bearer` header instead of a `Cookie`, and its `--cookie-path` flag and on-disk cookie cache are removed. Each request signature is bound to a server identity — a hostname, domain, or IP address the server recognizes as itself — so a signature captured on one server can't be replayed to another. Those identities are the server's hostnames, its public and private domains, `localhost`, and its own interface addresses (including loopback and the public IP, for clients reaching it through a port forward). A signature that matches none of them is rejected with an error that says so, instead of the previous opaque "no valid signature context available to verify". - **Changing the master password no longer asks for the current password.** **System → Change Password** in the UI and `start-cli auth reset-password` now prompt only for the new password — an authenticated session (an enrolled device key, or the local authcookie for `start-cli` on the server itself) is required and sufficient. This also gives "forgot password" a recovery path short of reinstalling: any device that is still signed in can set a new password. Existing backups remain encrypted with the password in effect when they were created. - **Stable, predictable IPv6 address (EUI-64).** NetworkManager is set to derive each interface's IPv6 address from its MAC (modified EUI-64) with RFC 4941 privacy extensions off. StartOS applies this to existing network connections on every boot, not just newly-created ones, so upgraded servers pick it up too. The server therefore keeps one stable global-unicast address (GUA) across reboots instead of the default rotating stable-privacy address, giving the GUA-based clearnet and public-domain features a predictable address to advertise (`AAAA`) and pinhole. - **External ports 9050 and 9051 are no longer restricted (#3407).** The port allocator reserved 9050/9051 for the 0.3.x host Tor daemon, which no longer exists. Freeing 9050 lets the tor service bind its SOCKS proxy with `preferredExternalPort: 9050` (without exporting an interface), giving every service a stable, always-valid service-to-service address for Tor SOCKS on the internal bridge — `10.0.3.1:9050` — with no reactive watch on the tor package and therefore no dependent restarts when tor is installed, updated, or removed. 9051 (the old control port) is freed as well; 0.4.x tor uses a Unix control socket, so nothing binds it host-side. - **The StartOS admin UI is now addressed like a regular service interface (#3387).** At the SDK/effects layer the server's own host is identified by the reserved package id `start-os`, host id `admin`, and interface id `admin-ui` (renamed from `startos-ui`) — no more `null`/`STARTOS` sentinels. `host_for`, the host RPC APIs, and the `getHostInfo` / `getServicePortForward` / `getServiceInterface` / `listServiceInterfaces` effects all resolve `start-os` to the server host, `start-os.startos` resolves like any package hostname, and the UI passes `start-os` wherever a package id is expected. Installing a package with the id `start-os` is rejected. The migration re-points the tor package's persisted hidden-service identity for the admin UI (`STARTOS`/`startos-ui` → `start-os`/`admin`) — including the onion-address import handoff written into tor's volume before tor is installed — preserving the server's existing `.onion` address across the identity change. - **SDK:** `PluginHostnameInfo.packageId` is required in the type — url plugins (e.g. tor) should export the StartOS UI's urls as `start-os`/`admin` instead of `packageId: null`. For backwards compatibility during the beta.10 transition, the host still accepts the legacy `packageId: null` (or an absent field) over the effects socket and maps it to `start-os`. - **OS Logs and Kernel Logs moved into System settings.** The top-level Logs tab is removed; OS Logs and Kernel Logs are now entries at the bottom of the System menu. - **Migrated `startos-backup-fs` into the monorepo** as the `start-os/backup-fs` workspace member (from the former `Start9Labs/start-fs` repo); it is no longer built as an external `cargo install --git` dependency. - **Monorepo reorganization.** `start-os` is now the monorepo for all Start9 products. The OS product moved into its own `start-os/` directory as a thin wrapper: the `startbox` and `start-container` entry points live in `src/bin/`, the admin UI and setup wizard in `web/`, and the container runtime in `container-runtime/`. Backend logic moved from the old `core/` crate to the shared `start-core` crate (`shared-libs/crates/start-core`); shared Angular libraries moved to `shared-libs/ts-modules`; the SDK to `start-sdk`; and the `patch-db` submodule to `shared-libs/crates/patch-db`. Builds now run against the root Cargo and Angular workspaces (`cargo build -p start-os`, web from `shared-libs/ts-modules`). - **Firewall migrated from iptables to native nftables (5b9cf7313).** Every StartOS-managed rule now lives in a single `table ip startos` with stable comment tags for handle-based, idempotent teardown (per-forward DNAT/hairpin/masquerade, the FORWARD `policy drop`, lxcbr0 container-egress accept, and the mangle policy-routing marks). lxc-net and wg-quick keep their own iptables-nft rules in separate tables. The `nftables` package is added to dependencies. - **Manifest capability flags split (#3271, #3275).** The misleading `nestedRuntime` flag is replaced by two independent capabilities: `userspaceFilesystems` (mounts `/dev/fuse` for fuse-overlayfs storage) and `virtualNetworking` (mounts `/dev/net/tun` for VPN / WireGuard / tun workloads). Both are device grants only — the service LXC already retains `CAP_NET_ADMIN` within its user namespace via the standard `userns.conf` include, so no capability machinery is needed (an earlier `lxc.cap.drop` snippet that was wrongly framed as "re-granting `CAP_NET_ADMIN`" — and actually dropped five caps that were otherwise kept — was removed). Hard rename — packages using `nestedRuntime` must republish. - **Service-container memory isolation (#3304).** Every service container is placed in a `services.slice` opted into systemd-oomd PSI monitoring, capped at total RAM minus a fixed 1 GiB host reservation; `system.slice`/`user.slice` get `MemoryMin` floors. A burst of concurrent installs can no longer overcommit RAM and wedge the host. - **Container-runtime RPC/action logging is gated behind a dev build (#3325)** — production builds no longer log full RPC inputs/responses (which can contain action secrets) to service logs. - **Web platform upgraded to Angular 22, TypeScript 6, and Taiga UI 5.11**, with a unified, version-pinned Prettier config enforced in CI. - **SDK 2.0.0** ships alongside this release (see [`../start-sdk/CHANGELOG.md`](../start-sdk/CHANGELOG.md)); StartOS 0.4.0-beta.10 is its minimum host version. - Backup progress is surfaced as a dialog with a percentage rather than a notification. - **Dialogs and alerts no longer steal focus when they open.** `tuiAutoFocus` is gone from every surface that used it — the shared prompt dialog, the refresh alert, the OS-update dialog, the marketplace package drawer, and the setup wizard's password page. On mobile, autofocusing raised the keyboard the instant the sheet appeared, on top of the dialog's own buttons. - **Docs prepared for general availability.** The _Update to StartOS 0.4.0_ guide now documents the over-the-air update as the standard (and only documented) path — the USB-install walkthrough is removed — and the early-access warnings are gone. _Installing StartOS_ gains Raspberry Pi microSD flashing instructions (**Raspberry Pi 4 only**), which the update guide points Raspberry Pi users to, since a Raspberry Pi cannot update in place. - **0.3.5.1 update prompt.** The release notes served with the 0.3.5.1 → 0.4.0 migration OTA payload now carry the migration guide's full pre-update checklist instead of a one-line summary. - **Release welcome.** The post-update welcome notification and its "What's new" highlights land with 0.4.0, so servers updating from any 0.4.0 beta see them on arrival at the stable release. - **Service install backups are now constant-time btrfs snapshots.** The rollback backup taken before every service install/update used to be a file-by-file reflink copy whose cost grows with the volume's extent count — on a large, aged database volume (e.g. a 263 GB Monero LMDB fragmented into 47 million extents) it pinned the update at ~80% for 19 minutes with no feedback. Package volume roots are now btrfs subvolumes, so the backup is an atomic `btrfs subvolume snapshot`: milliseconds regardless of size or fragmentation, and still zero data copied. Existing volumes are converted once, at the first boot after upgrading, under a new "Optimizing storage" boot phase with byte-accurate progress — expect that one boot to take longer on boxes with large service volumes. A volume root that still isn't a subvolume afterwards (e.g. on a non-btrfs dev data dir) simply skips the backup, as non-btrfs systems always have. ### Fixed - **`start-cli --version` on StartOS reports the CLI version.** On the server, `start-cli` is a symlink to the `startbox` multi-call binary, which advertised the OS platform version for every applet — so `start-cli --version` printed the OS version instead of the independently-versioned start-cli version. It now reports the start-cli version, matching the standalone `start-cli` binary. - **Backups wait for the service to fully stop before its data is copied.** A package's data is now captured only after its service has completely stopped, and the package isn't reported finished until it has left the backing-up state. Previously the backup could begin as soon as the package's `.s9pk` image finished serializing — often before the graceful shutdown had completed — so a slow-to-stop service could still be writing while its files were read, risking a torn or inconsistent snapshot of databases and other stateful data. - **Migrating a 0.3.5.1 package that lacks instructions no longer fails.** Converting a legacy (Embassy) package to the new s9pk format now carries over its instructions when present and falls back to a placeholder when absent, so a package with no `instructions.md` can no longer break the 0.3.5.1 → 0.4.0 upgrade. Previously the converted package omitted the file entirely. - **Migrated 0.3.5.1 packages keep their asset file permissions.** Converting a legacy (Embassy) package to the new s9pk format now preserves the mode bits of files in the package's assets, so an executable asset (a script or bundled binary) stays executable in the migrated package. Previously the assets were unpacked without their permissions and re-packed at the default mode, dropping the executable bit and breaking any asset the service ran directly. - **URL-plugin services no longer accumulate duplicate exported addresses.** `export_url` now dedupes a binding's `available` set by the same address identity `clear_urls` retains on (ignoring the row-action fields), so re-exporting a URL whose `remove_action`/`overflow_actions` changed updates the existing entry instead of inserting a second, `Ord`-distinct copy that `clear_urls` would also keep. - **Each package's backup progress stays open until its image finishes writing.** A package's backup phase now completes only once the whole package backup — including streaming its `.s9pk` image to the backup target — is done, so the progress list keeps that package at 100% and still working until it truly finishes, then advances to the next one. Previously the phase was marked complete the moment the service's data procedure returned, while the image (often the larger part) was still writing to the target — which read as the package finishing early, with a visible pause before the next one began. Older packages built against start-sdk ≤ 2.0.6 still self-report completion and see the early "complete" until rebuilt against start-sdk ≥ 2.0.7. - **The overall backup progress bar advances with each package's progress.** Every package now contributes equal weight (100) to the overall bar, with the OS-data step a small tail (10), so the top-level percentage climbs as each package's data copies rather than jumping only when a package finishes. Intra-package movement comes from the package's own reported progress, which packages built against start-sdk ≥ 2.0.7 report continuously. - **Starting a backup returns as soon as the job is queued.** The "create backup" request now validates the backup password against the target and then hands the rest to the background job, so the initial call returns promptly. Previously it also opened the target's encrypted store on the request thread — which, for a large backup or a slow drive, could read for a minute or more before returning — so a slow target looked like the request itself had timed out. Opening the store, and any failure doing so, now happens in the background and is reported through the usual backup progress and notifications. - **Backup progress shows an "Initializing" phase while the target is prepared.** Backup progress now displays a labeled "Initializing" phase while StartOS mounts the target and opens its encrypted store, instead of a bare 0% with no phases; the per-package and OS-data phases appear once that work finishes. - **Mixed-case domains now match the browser.** Domain names are lowercased when added or removed (UI and CLI alike), so a domain entered with capital letters can no longer end up unreachable against the browser's lowercased address bar. - **Login rate limiter no longer degrades logins to one per 20 seconds (#3512).** The password-login throttle used a single process-wide counter that only ever incremented and never reset, so after three logins since boot the entire box was capped at one login per 20 seconds across all clients (UI, CLI, API) for the rest of uptime. The counter now resets once 20 seconds pass without a further accepted login attempt — and a rejected attempt no longer advances the window — so the limit is a genuine three-attempts-per-20-seconds window rather than a permanent cap. - **Large package installs no longer stall or freeze the box.** Downloading and unpacking a big s9pk streamed through the page cache with no writeback pacing, so on a fragmented copy-on-write btrfs filesystem the temporary and installed archives scattered across thousands of extents and their accumulated dirty pages flushed in one enormous final `fdatasync` — a stall lasting seconds to minutes whose victim varied by package size and each box's on-disk layout. Package transfers now reserve their space up front (`fallocate`), write to `nodatacow` files so they stay contiguous on btrfs, and pace writeback (`sync_file_range`) so only a bounded amount is ever left unsynced — keeping memory use flat and the final flush cheap. - **DNS forwarder no longer wedges box-wide after an upstream blip (#3473).** After a WAN, tunnel, or DHCP event degraded the currently-configured upstream resolvers, container DNS could go dark for every service on the box — external lookups failing with `Temporary failure in name resolution` — until the upstreams recovered or the box was rebooted. The forwarder held a read lock on its upstream catalog across each upstream query (up to 30s) while the task installing new upstreams gave up after 10s and retried, starving the very update that would have replaced the dead upstreams. The resolver now snapshots the catalog and releases the lock before forwarding, and installs new upstreams by atomic swap — no lock wait, no timeout, no retry — so a pending upstream change always applies immediately. Forward queries also use a 5-second per-attempt upstream timeout (matching what container clients wait) rather than 30 seconds, and names in the private `.startos`/`.embassy` zones that no running service claims are answered authoritatively (`NXDOMAIN`) instead of being forwarded to — and leaked at — upstream resolvers. - **Enabling a public IPv4 address on an SSL service interface now opens the gateway port automatically.** Only a public _domain_ on an SSL-terminated port used to trigger automatic port forwarding (PCP/NAT-PMP/UPnP); turning on the bare public IPv4 left the port closed until it was forwarded by hand. StartOS now requests the pinhole for the SSL port the same way it already did for public domains and IPv6 GUAs, so a public IPv4 on an `addSsl` interface (the StartOS UI included) is reachable from the Internet without a manual forward. - **Installer: "Preserve" selections that cannot keep your data are now refused instead of silently erasing the data drive.** During a USB install, choosing **Preserve** for a drive whose StartOS data pool lives on a _partition_ of the drive (the 0.3.x single-drive layout) while installing the OS to a _different_ drive fell through to creating a fresh, empty pool on the data drive — permanently destroying the data the user asked to keep, with no error. The installer now validates the preserve selection before writing anything and fails with an actionable error instead: a drive whose pool lives on a partition must be selected for **both** the OS drive and the data drive, and a drive whose pool spans the entire disk must be paired with a different OS drive. The setup wizard applies the same rules up front: when the selected drives cannot keep the data, the **StartOS Data Detected** dialog says why and disables **Preserve**, so an unpreservable selection is caught with guidance to fix it rather than surfacing as an error once the install is already under way. - **IPv6 services exposed through a tunnel are now reachable, and outbound IPv6 no longer leaks around a gateway.** StartOS now applies the full IPv4 policy-routing layer to IPv6, including CONNMARK reply-routing: a reply to an inbound IPv6 connection that arrived over a tunnel — whether terminated on the host or DNAT'd to a service container — is pinned back out the interface it arrived on, so exposing a service over a StartTunnel's delegated IPv6 actually works. Previously those replies had no route back and were blackholed, so inbound IPv6 over a tunnel was dead. Outbound, the server's IPv6 default is now chosen by route metric exactly like IPv4, and leak prevention is per-gateway: an outbound gateway that is explicitly selected but can't carry IPv6 drops the server's IPv6 via a blackhole in that gateway's own routing table — so your real address never leaks out the ISP link — without blackholing the reply traffic that keeps inbound tunnel services working. - **Updating a WireGuard gateway's config no longer drops its preshared key.** The in-place update path (`net tunnel update` / the **Update config** UI action, NetworkManager `Update2` + `Reapply`) persisted the interface private key but silently dropped each peer's preshared key, so a re-issued PSK-using tunnel failed its handshake and went dead (taking tunnel-routed DNS down with it). The peer secret is now flagged system-owned so the update persists it, and the settings (peer secrets inline) are passed to `Reapply` explicitly — an empty-dict `Reapply` still stripped the PSK from the _running_ device even with the profile persisted correctly, hanging all traffic through the tunnel (e.g. forwarded ports) until the next reboot. - **Dev builds bricked by an empty persisted host id (#3387).** Builds between #3366 and #3387 persisted the server host's then-sentinel id (the empty string) in the admin UI interface's `addressInfo.hostId`, which strict deserialization rejects — every boot failed into the diagnostic UI. The server host now has a real id (`admin`) and the beta.10 migration rewrites the empty value. - **Critical-task start gate is now enforced backend-side.** Starting a service with an unresolved critical task was previously blocked only in the web UI; the CLI and RPC bypassed it. `package.start` now rejects such a start unless `--force` is passed. - **Split DNS for dual public/private domains (#3263).** A domain configured as both private (e.g. on Ethernet) and public (on a StartTunnel) is now served as private DNS to LAN clients, gated per-gateway in the resolver, instead of falling through to the upstream forwarder and hairpin-routing to the public VPS IP. - **DNS `[::]:53` wildcard listener (#3346).** DNS listeners bind with `SO_REUSEPORT` so the dual-stack catch-all coexists with the per-address sockets; previously the catch-all's TCP bind failed with `EADDRINUSE` and was silently dropped. - **Host address list renders instead of panicking (#3345)** — `start-cli` no longer hits `todo!()` displaying the server host address table. - **Web server connections run in parallel with HTTP/2 adaptive window (#3328)**, fixing head-of-line stalls under load. - **Logger writes moved off worker threads (#3259).** File/stderr log writes no longer hold a mutex across a blocking `stderr` write, which could park every tokio worker (and stall ports 80/443) if journald backpressured. - **`create_task` self-deadlock during `setupInit` (#3273)** — a service calling `createOwnTask` with an `input-not-matches` trigger from its init handler no longer wedges in `updating`. - **Replayed task state is preserved when the target service is unavailable (#3309)** — services running before shutdown no longer stay stopped after boot when a critical-severity task replays against a still-initializing dependency. - **Stale mountpoints are reconciled before remount (#3314).** After a `SIGKILL` left kernel mounts in place, a same-boot restart now lazily unmounts the stale target instead of failing every service load with "already mounted". - **`TMP_MOUNTS` self-deadlock on nested idmapped mounts** is avoided (5aee392a2). - **Backups to a physical drive no longer intermittently fail with `could not load backup` (`NotFound`).** A temporary mount's teardown unmounted by path, so a stale detached unmount (e.g. from a `Drop`) could tear a drive down _after_ its shared `TMP_MOUNTS` slot had already been reused by a fresh mount at the same hashed path — and a concurrent operation loading `mount.backup-fs` off that drive then hit `ENOENT`. Teardown now runs under the per-mountpoint slot lock and unmounts only while the slot has no live guard, so it can't unmount a mount another operation just established. Surfaced by the periodic backup-target listing (backup page open) racing backup-fs loads on exFAT and BTRFS targets (#3498). - **Unmountable partitions are skipped when listing backup targets (#3237)** instead of aborting the listing. - **Bind-mount source directories are auto-created via recursive canonicalize** (d8ae7f199), and mount propagation is corrected (27322b4a9). - **`/media/startos` ownership on migrated installs (#3311, #3312).** A migration repairs the stale `root:root` overlay entry so migrated nodes match fresh installs (`root:startos`), letting the `start9` SSH user browse package data without sudo. - **CA fingerprint hex bytes are zero-padded**, with a `0.4.0-beta.10` repair migration for affected installs. - **`.onion` (Tor) and `.local` (mDNS) are routed correctly through the host SOCKS proxy** (b5dec33cf), and reaching a `.onion` registry without Tor now reports a clear error ("the Tor service is not installed / not running") instead of a generic connection failure. - **`NetService` is torn down synchronously on container destroy (#3285)**, and the nftables/policy-routing reconcile is made atomic, idempotent, and lock-free (daeee4f12, cebd6d703, 3ee50b0c3). - **A service with no active bindings no longer wedges shutdown/restart indefinitely (#3350).** After #3285 moved network teardown into the awaited container-destroy path, a no-op `clear_bindings` emitted no patch-db revision, so the convergence wait blocked forever — stalling reboots for a service accessed only over Tor. The teardown now skips the wait when nothing changed. - **Host `reboot`/`poweroff` now tears service containers down cleanly.** On an OS-level power-cycle, systemd stopped `lxc.service` (whose `ExecStop` is Debian's `lxc-containers stop`) and `lxc-monitord` _concurrently_ with the graceful-teardown hooks, killing each container before `startd`'s `Exit` RPC could stop it — a burst of `lxc-stop … is not running` / `Connection refused (os error 111)` and a stalled reboot. The teardown hooks (`startos-restart.service` / `startos-shutdown.service`) now order `After=lxc.service lxc-monitord.service`, so the LXC container-stop infrastructure stays up until the graceful teardown has finished. `Shutdown::execute` also skips its own `reboot`/`poweroff` when systemd is already performing the transition (and exits so systemd's stop of `startd.service` completes promptly). A power-cycle now tears containers down like `start-cli server restart`. - **ACME issuance restored.** `async-acme` is re-pinned to keep its HTTP backend (#3342), and `ring` is installed as the process-default rustls provider so ACME cert acquisition no longer panics on the dual ring/aws-lc-rs build. - **Union variant memory is isolated per row (#3337)**, disabled options render correctly in select dropdowns (#3229), and the per-row Wi-Fi overflow menu no longer disappears (#3243). - **The redundant "Plugin:" prefix is dropped from interface plugin labels (#3349)** — the plugin address group already sits within a plugin section. - **Login and CA-wizard UX cleanup (e86dcc242).** The login button uses an inline loading state that holds until navigation completes (replacing the global overlay loader) and is correctly centered; the CA wizard gets a solid card background, drops the redundant "Bookmark this page" step, and moves the "repeat on every device" caveat into a notification. - **`fedimintd` → `fedimint-guardian`** package-ID rename is handled across all four `0.3.5.1`→`0.4.0` migration paths (2a806d8b8). - **Allow non-ASCII characters in WiFi SSIDs (#3365).** Adding, connecting to, or removing a WiFi network whose SSID contains non-ASCII characters (e.g. an accented letter or a typographic apostrophe) no longer fails with "SSID may not have special characters". SSIDs are passed to NetworkManager as-is, and SSIDs containing a colon are now parsed correctly when listing connections. The WiFi passphrase still requires ASCII, as mandated by WPA. - **Updates-tab progress circle vanishing during install finalization.** A package update marked the overall install progress complete right after the old version was uninstalled — before the new version's finalization/init progress (added with package init progress reporting, #3323) had run — so the Updates-tab loading circle snapped to full and then disappeared partway through "installing/finalizing". Overall progress now completes only once the package is fully installed, so the circle keeps tracking live finalization progress until the update finishes. - **The UI no longer freezes after you enter your master password.** Creating a backup, restoring a backup, and changing your password each verified the password in the browser, via a _synchronous_ WASM Argon2id call on the main thread. At the parameters StartOS hashes with (64 MiB, t=3) that blocks the tab for seconds on a desktop and 20–25 seconds on a phone, where it reads as a hung browser; creating a backup onto a target whose existing backup used a different password ran it three times over. All three flows now let the server verify — which it already did on every one of these calls, natively, in milliseconds — and the argon2 WASM module is gone from the UI and setup-wizard entirely. A target whose existing backup was encrypted under a different password now comes back as the new `BackupPasswordMismatch` error kind, so the UI still knows to prompt for that original password rather than reporting the master password as wrong. - **Backup password prompts no longer autocapitalize, and no longer look like a login to your password manager.** The shared prompt dialog sets `autocapitalize="off"`, so a mobile keyboard stops capitalizing the first character of a password you type into it, and it now submits through its own buttons (or Enter) instead of a `
` submit — a browser password manager saw that submit and offered to save your backup encryption password as a saved credential. The setup wizard's **Unlock Backup** prompt gets the same `autocapitalize` treatment. - **A cancelled or failed package update now leaves the previous version running.** When an update is interrupted, StartOS restores the service's data to its pre-update state before restarting the old version, so the service comes back on the version it had. Previously the old version was started against the partially-migrated data and failed its downgrade migration with a "cannot migrate" error, leaving the service stuck until its container was rebuilt. - **EFI system partitions are no longer listed as backup drives.** A GPT drive formatted on macOS or Windows (or a former boot drive) carries a small hidden EFI System Partition alongside its data partition, so the backup drive list showed the same physical drive twice — the second entry an unusable ~200 MB partition displayed as "0 GB". Partitions whose partition type marks them as an EFI System Partition are now skipped everywhere drives are listed (backup targets, setup-wizard drive lists, `start-cli disk list`), and a sub-gigabyte partition that does remain shows its capacity in MB instead of rounding down to "0 GB". - **A transient failure querying clock-sync status no longer aborts boot or strands the clock-sync warning.** StartOS asks systemd (`timedatectl`) whether NTP has synchronized — once per second during the boot-time sync wait, then every 30 seconds in the background until the first sync lands. Both callers treated a failed query as fatal: during init it aborted boot into Diagnostic Mode, and in the background poller it silently killed the polling task, leaving the "Clock sync failure" warning up for the rest of the boot even after time synchronized. A failed query (e.g. a D-Bus activation timeout under boot load) is now treated as "not synchronized yet" — logged and retried on the existing cadence. - **Install backups no longer silently fail for services using `nodatacow`.** btrfs refuses reflink clones between `nodatacow` (`chattr +C`) and normal files, so any service that marks its data directory `+C` — Bitcoin Core does — never actually got an install backup; the failure was only a log line. Subvolume snapshots are immune entirely, and the one-time boot conversion mirrors each file's `+C` flag before cloning it. Boot now also repairs interrupted backup restores (completing the rename so the backup's data comes back as the live volume) and sweeps backups orphaned by a package's removal; a backup left beside a healthy installed package is cleaned up on that package's next update. ### Removed - **`/proxy` HTTP route.** The authenticated reverse-proxy endpoint was unused (registry data flows over RPC and package assets are served from local archives), so it has been removed. - **Package `alerts` manifest field (BREAKING, #3333).** Packages can no longer define install / update / uninstall / restore / start / stop confirmation messages. StartOS stops reading and showing them; existing installs and old s9pks are unaffected (the field is ignored on load). Built-in confirmations for destructive actions are unchanged. - **`nestedRuntime` manifest flag** — replaced by `userspaceFilesystems` / `virtualNetworking` (see _Changed_), with no compatibility alias. ### Security - **The web UIs ship a strict Content-Security-Policy.** Every response from the UI origin — the main UI, setup wizard, and diagnostic/init pages — now carries a CSP restricting scripts and network connections to the server's own origin (no framing, no plugin content), plus `X-Content-Type-Options: nosniff`. A script injection that slips into the page can no longer pull in outside code or exfiltrate to a foreign host. - **TSIG-authenticated DNS UPDATE (#3306).** RFC 2136 injections are authenticated with TSIG (RFC 8945, HMAC-SHA256) keyed off a per-device key derived (HKDF-SHA256) from that device's WireGuard PSK, closing a forgery vector where any co-located service emitting from the server's tunnel IP could inject DNS. - **Packages are blocked from port-mapping the gateway (#3306).** Only startd may send UPnP/NAT-PMP/PCP upstream; a dedicated nftables guard table drops these protocols when forwarded from any interface (LXC), so a service can't open ports on the upstream gateway. - **Packages are blocked from talking DNS straight to the gateway.** The same guard table now drops DNS (udp/tcp 53) forwarded off the container bridge, so a service can't query a gateway or public resolver directly — its DNS goes through the OS resolver at `10.0.3.1:53`, same as every other lookup. - **Dependency/advisory cleanup:** resolved Dependabot alerts across core, web, and container-runtime (#3301); migrated to hickory 0.26 to clear DNS RUSTSEC advisories (#3302); resolved forked-dep RUSTSEC advisories in tokio-tar and async-acme (#3303). - **Password hashes and backup key material are no longer sent to the frontend.** `serverInfo.passwordHash` — the master password's argon2 hash — was replicated into the public database, so every authenticated client held offline-cracking material for the master password. Separately, the backup metadata returned by `getBackupTargets`, `setup.cifs.verify`, and the disk listing carried each backup's `passwordHash` **and** its `wrappedKey`: the backup's encryption key sealed under that password, so a client could crack the hash offline and then unwrap the actual backup key. Both existed only to let the browser verify passwords locally, which it no longer does (see _Fixed_). `ServerInfo` drops the field entirely — the real hash stays in the private database — and the on-disk backup metadata is split from its public view, so the API returns only hostname, version, and timestamp. Existing servers are cleaned up automatically: startup deserializes the database through the typed model, which drops the removed key. ## [0.4.0-beta.9] and earlier See the [GitHub releases page](https://github.com/Start9Labs/start-technologies/releases) for the full 0.4.0 beta and alpha history and all prior releases. [0.4.0]: https://github.com/Start9Labs/start-technologies/releases/tag/start-os/v0.4.0 [0.4.0-beta.9]: https://github.com/Start9Labs/start-technologies/releases/tag/v0.4.0-beta.9