# Noust 3.3 changelog

Changes since Noust 3.2.1. Upgrade notes are in [UPGRADING-3.3.md](/docs/releases/upgrading-3.3/); the
databases area is described in [databases.md](/docs/guides/databases/).

3.3 shows the databases a server really has. Until 3.2 the Databases area only saw engines
installed on the host, so a database in a Docker container (most of them, on the servers this
release was measured against) did not exist for Noust; an engine that refused Noust's account was
shown as an engine with nothing in it; and an application was linked to a database only if Noust
had written its connection string. None of that changes anything on the server by itself: what is
new is what Noust sees and says, and the actions it offers.

## Databases in containers (item 67)

- **Instances.** A database server is now either the host's engine (as before) or a container
  Noust finds on its own: the official images of PostgreSQL, MySQL, MariaDB, Redis, Valkey and
  MongoDB and their common derivatives (PostGIS, TimescaleDB, pgvector, Bitnami). Each one is
  named by a key that stays the same when the container is recreated:
  `postgresql@<project>.<service>` for a Compose service, `postgresql@<container>` otherwise.
- **The same code as the host.** Listing databases, users and tables, sizes, the data browser,
  read-only queries, dumps, restores, backup policies and links all work on an instance: the
  engine's own client runs inside the container through `docker exec`. The container's
  passwords are read inside the container and never reach the host's process list; the ones
  Noust holds itself travel in the environment of the `docker` process.
- **Whose it is.** A container's databases belong to the application its Compose project is
  (the project label against the application's project or name), so a stack's
  Postgres and Redis show as its application's.
- **What differs.** Start, stop and restart act on the container. Installing, uninstalling and
  configuring do not exist for an instance: the image decides the engine, and Noust says so.
  When only the application's own account is available (no root password in the container's
  environment), the instance is marked as limited access.
- Every `--engine` option of `noust db` takes an instance key, and `noust db engines` lists the
  containers after the host's engines.

## An engine that does not let Noust in says so (item 68)

- A listing that failed used to come back empty: a MySQL that asked root for a password showed as
  a server with no databases and no users, and its tracked databases as gone. Now the engine's
  own answer is shown, verbatim, with the fix above it, and the tracked databases of an engine
  that could not be read are marked "not verified" instead of missing.
- For MySQL/MariaDB and Redis the fix is in the console: store the account Noust signs in with
  (`PUT /api/databases/engines/{engine}/credentials`, sudo mode, audited as
  `db.credentials.set`). The engine is asked first, and nothing is saved if it refuses.
  `noust db config` goes through the same check, and `--password` alone asks for the password
  without echo, so it never reaches the shell history or the process list.
- PostgreSQL and MongoDB do not sign in with a stored password; their message says what Noust
  needs instead (peer authentication for `postgres`, the administrator Noust created).

## Links that already exist (item 69)

- Noust reads each application's environment and finds the databases it names: connection URLs
  (`DATABASE_URL`, `REDIS_URL`, Prisma, Laravel, JDBC, driver suffixes) and the `DB_*` and
  `REDIS_*` sets. A reference resolves to one engine: by port on this machine (a container's
  published port included), by service name inside the application's Compose project, never by
  guessing between two. Nothing of the application runs, no password is kept or shown, and a
  `.env` that leads outside the application's tree is not read.
- The databases list shows those applications as **detected** beside the database. "Record the
  link" makes one a link without touching the application (`POST .../links/detected`, audited as
  `db.link.record`), one at a time and only when the operator asks: an application's `.env` is
  written by whoever deploys it, so what it names is a claim. A database another application owns
  is refused, an existing link is never overwritten, and the recorded link carries no account, so
  a later password rotation never writes the new password into an application that merely named
  the account. `noust db adopt` tracks databases and records no links.

## Counts (item 70)

MySQL counts tables in its listing; Redis reports keys rather than tables; a count the listing
does not know is shown as unknown, never as 0.

## Engines: versions and settings (item 71)

- **Choose what to install.** PostgreSQL 14 to 18 (from the PostgreSQL project's repository, or
  the distribution's when it is the one asked for), MariaDB 10.11, 11.4 or 11.8 (from MariaDB's
  repository) or the distribution's, MySQL from the distribution, Redis or Valkey, MongoDB 7.0
  or 8.0, as far as each upstream publishes for the server's release. Every upstream signing key
  is pinned by fingerprint and checked before apt trusts it, MongoDB's included. MySQL and MariaDB,
  and Redis and Valkey, cannot be installed beside each other, a check the install itself makes.
  Only the pinned primary key is written to apt's keyring (exported from a throwaway keyring, so
  a file that also carries another key or a secret key is refused); a leftover source for another
  release (`mongodb-org-7.0.list` under an 8.0 install) is refused with the file to remove; a
  source Noust added is removed again when `apt-get update` then fails; `apt-get install` runs
  with `--no-remove`; and the version that ends up running is compared with the one asked for.
  Installing needs sudo mode in the API, as removing does. `GET /api/databases/engines/catalog`
  and `noust db catalog` say what this server can install; `noust db install postgresql --version 17`.
- **Settings.** A closed set per engine (connections, memory, listen addresses, slow-query
  logging, time zone...) with the current value, a recommendation for this server's memory and
  cores, and whether it needs a restart: `noust db settings <engine> [KEY=VALUE ...]`, the
  console's Settings page, `GET/PUT /api/databases/engines/{engine}/settings` (sudo mode, audited
  as `db.settings.change`). Values go into Noust's own include file, never the distribution's;
  each change is checked with the engine's own tool, applied (reload, restart or at runtime, as
  the setting needs), and put back with the engine's journal in the error if the engine does not
  come back. Opening an engine beyond the loopback asks for confirmation, and the `ens-medium`
  profile refuses it; removing a listen setting counts as opening it unless what the engine falls
  back to is known to be the loopback, and the live value is read back after the change. A Redis
  `maxmemory` below what it already uses, `appendonly no` and `save off` ask for confirmation too
  (`confirm` in the API, which answers `confirmation_required` with every warning;
  `confirm_exposure` is still honoured), memory settings are capped at 90% of the RAM, and Redis
  writes a snapshot before any restart Noust makes. Changes to one engine are serialised, a
  symlinked configuration file or directory is refused, every PostgreSQL step targets the same
  cluster (several online clusters are refused), a slow start that is still loading is waited
  for, and the previous files are put back whatever interrupts the change.

## Exposure without false alarms (item 72)

- A port Docker publishes that the `DOCKER-USER` chain refuses on the public interface is shown
  as closed by the firewall, not exposed; an IPv6 publication on a server without an IPv6 route
  likewise. The database exposure check reads the chain with the same code as the server's
  security check.
- An image that only mentions a database in its name (`zabbix/zabbix-server-mysql`) is no longer
  taken for one: there is one reader of images, which compares the repository name. A container
  of an image that reader does not know (a registry other than Docker Hub, an image id,
  `supabase/postgres`) is still reported when it publishes a database's own port.
- A rule in `DOCKER-USER` that jumps to another chain before the drop leaves the port exposed:
  where the packet goes is not proven closed.

## Compose stacks running outside their unit (item 73)

A Compose application whose unit is stopped while its containers run (started by hand with
`docker compose up -d`) used to show as stopped and raise "the unit is not running" while it
served. It is now its own state, "Running outside Noust" (amber), in `noust list`, the console,
the overview and the fleet summary; the monitor says once (`app.outside_unit`) that a reboot would
not bring it back. `noust app reclaim <domain>` (and the button on the application's page,
`POST /api/apps/{domain}/reclaim`, sudo mode, audited as `apps.reclaim`) enables and starts the
unit after the same `docker compose up --dry-run` rehearsal adoption uses; if that rehearsal would
recreate a container it refuses unless `--accept-recreate` is given.

## Fixes

- A Redis container counts as installed even when the host has no `redis-cli`.
- A container's MongoDB archive is restored into the database asked for: the archive says which
  database it holds and `mongorestore` renames it (`--nsFrom`/`--nsTo`), so restoring "as a new
  database" or testing a dump never touches the original. Container archives pass verification.
- A container's log masks the passwords in it (generated root passwords, the container's secret
  variables, passwords in logged statements) and needs `databases.manage`.
- One `.env` that is not UTF-8 no longer breaks the databases list; a `.env` is opened once,
  without following links, and at most 1 MiB of it is read.
- Changing only the password of an engine's stored account is tested with the stored user, as it
  is saved.

## 3.3.1

- **Sudo mode lasts while you work.** Confirming it's you opens sudo mode for 15 minutes, and
  every action that needs it extends that again, up to 2 hours from the confirmation and never
  past the session. An account with a second factor confirms with one: a code from its
  authenticator, a backup code or a passkey; the password is not asked again. The windows and the
  password are settings (`auth.sudo.idle_minutes`, `auth.sudo.max_minutes`,
  `auth.sudo.require_password`); the `ens-medium` profile keeps the password and at most 10 and 30
  minutes. The confirmation dialog says how long it lasts.
- **A pending SSH or firewall change says whether it is proven.** The banner watches for the new
  SSH login that proves the way in still works, names it when it arrives (who, from where, when)
  and only then offers Keep; before, Keep answered "No new SSH login since the change". Undoing no
  longer shows that error left over from an earlier Keep. The window before a change undoes
  itself is 5 minutes instead of 2. `noust server security pending` shows the same.
- **Refusing root logins works for Ubuntu and Debian administrators** (issue #14). A sudoers rule
  separated by a tab, which is how Debian and Ubuntu ship `%sudo`, was not read, so a member of
  `sudo` was "an account that cannot use sudo" and the fix was refused. Sudoers is now read as
  sudo reads it (aliases, host and runas lists, negation, `Defaults` scopes, includes); a rule
  that does not run every command as root no longer counts as a way to root.
- **Install hints name this server's package manager** (issue #12): `apt` on Debian and Ubuntu,
  `dnf` on Fedora and RHEL, `zypper` on openSUSE, all three only when Noust cannot tell.
- **Every "how to fix" names a command that exists** (issue #13). The failed-units finding pointed
  at `noust server units --failed`; it now opens the services list filtered to the failed units
  (`noust service list --all --state failed`, `GET /api/services?state=failed`). A test checks
  every command and route Noust suggests; it found and fixed ten more.
- **`noust web start` without root says so** (issue #11) instead of ending in a traceback.
- **The engines page no longer contradicts itself.** The server's own engines are now listed under
  "On this server" when engines in containers are listed below them, and a server engine that is
  not installed but runs in a container says so beside its state ("Not installed", "1 in a
  container"). `GET /api/databases/engines` reports each engine's `engine_type`, so a client
  never splits an instance key.
- **A container's missing password or authorization is only a warning when something can reach
  it.** A Redis or MongoDB in a container is judged by what Docker publishes of its port: on its
  Docker networks only it is the image's default and is not reported; published on the server's
  loopback it names the address and says any process on the server can reach it; published on any
  other address it says anything that reaches that port can. MongoDB containers are now asked
  whether they enforce authorization, and their fix is the compose file's
  (`MONGO_INITDB_ROOT_USERNAME` and `MONGO_INITDB_ROOT_PASSWORD`, which take effect on an empty
  data volume) rather than `/etc/mongod.conf`. The servers' own Redis and MongoDB keep their
  warnings.
- **Every page of the console shows that it is loading.** The Databases list drew its header over
  a blank area until the list, the backup policies and the open ports had all answered; it now
  keeps its filters and a table of placeholder rows from the first frame, and says nothing about
  the databases (no "No databases yet", no count) before it knows them. The rows wait at most a second
  for the notice above them: the open-port check can take seconds and no longer holds them back. The same gap is closed on
  a service's page, the unit file, the table of tables, the connection tab, the logs (a pane still
  waiting for its first lines draws the shape of lines and is marked busy), the servers count of
  Settings > Central, and on the figures, cards and sections that were placeholders a screen
  reader could not tell from content. A route that keeps the console waiting more than a second
  (the session, a page's code) now shows a skeleton in the page's place instead of leaving the
  previous page, or nothing, on screen. A test opens every route of the console against a server
  that never answers and refuses a page that shows nothing, a placeholder a screen reader cannot
  hear, or an empty state, so a new page cannot ship without its loading state.
