Ctrl K
ComunidadGuías…

projects/start-os/CHANGELOG.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 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 <name>` prints a token once, and requests sent with `Authorization: Bearer <token>` 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 <GATEWAY>` 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 `<defunct>`, 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://<name>:<port>`; `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 `<hostname>.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 <id> --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 <id> <config>` (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/<server-id>` 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 `<form>` 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