Skip to content

Add a Provision startup mode for non-interactive first-time setup - #17902

Open
cramt wants to merge 1 commit into
jellyfin:masterfrom
cramt:provision-startup-mode
Open

Add a Provision startup mode for non-interactive first-time setup#17902
cramt wants to merge 1 commit into
jellyfin:masterfrom
cramt:provision-startup-mode

Conversation

@cramt

@cramt cramt commented Sep 9, 2026

Copy link
Copy Markdown

Changes

Adds a Provision value to StartupMode plus --provision-file <path>. The server reads a JSON manifest, applies the same operations StartupController performs (server name and metadata locale, the initial administrator, remote access, and optionally libraries), marks the wizard complete, and exits without ever starting the HTTP listener. This lets a fresh server come up fully configured on a host that is deployed from configuration, instead of stopping at the wizard until someone opens a browser — and it avoids the window in which the unauthenticated /Startup/* routes can be used to claim the administrator account, which is what the existing third-party tooling relies on.

Answering the three questions from #17880 with the shape I found easiest to defend, all of which are cheap to change if you'd rather go the other way:

  • A file, not flags, so credentials stay out of ps and shell history and the operator controls the permissions.
  • A no-op when already provisioned, so configuration management can rerun it on every rebuild.
  • JSON, read through JsonDefaults so enums spell the same as in the API models, then loosened for a file a human maintains: comments, trailing commas, and either casing of property names are accepted.

Every member except the administrator is optional, and an omitted member leaves the server default alone rather than resetting it to empty. Failures are ordered so nothing is left half-provisioned: the manifest is read and validated before any of the migration and service startup work, library paths are checked before anything is written, and a failure exits non-zero rather than only logging.

Two things worth a reviewer's attention:

  • The issue proposed warning and continuing on a library path that doesn't exist. LibraryManager.AddVirtualFolder throws on those, so that isn't available without changing it — provisioning now checks every path up front and fails the run instead, which also keeps a not-yet-mounted media directory from leaving a half-configured server behind.
  • SetupServer still starts in this mode, so a provisioning run holds port 8096 for its duration, exactly as MigrateSystem does today. It only serves the startup splash, so no wizard API is exposed, but it will collide with an instance that is already running. Skipping the setup server for one-shot modes would change MigrateSystem too, so I left it alone rather than decide that here.

Server-only; no changes to clients, the web UI, or any API contract. Covered by 22 new tests in tests/Jellyfin.Server.Tests/Provisioning/, and verified by hand against a real server: provisioning a fresh data directory, authenticating as the provisioned administrator over HTTP afterwards, confirming the libraries and configuration landed, and confirming a second run is a no-op.

Code assistance

Claude Code (Opus) drafted the implementation and the tests from the design in #17880, and ran the end-to-end verification described above. It found the AddVirtualFolder path-validation behaviour that the issue's proposed warn-and-continue conflicted with, and the port 8096 observation. I reviewed the result before opening this.

Issues

Fixes #17880

A fresh server stops at the startup wizard and needs a browser before it
will serve anything, which makes it the one service in an otherwise fully
declarative deployment that cannot come up on its own. The workaround is
to script the /Startup/* endpoints, which are reachable unauthenticated
while IsStartupWizardCompleted is false, so every automated deployment has
a window where any caller could claim the administrator account.

`--mode Provision --provision-file <path>` reads a JSON manifest, performs
the same operations the startup wizard performs, marks the wizard complete
and exits without ever starting the HTTP listener.

The manifest is a file rather than flags so credentials stay out of ps and
shell history. Every member except the administrator is optional, and an
omitted member leaves the server default alone rather than resetting it.

Running against a server that is already set up is a no-op, so
configuration management can rerun it on every rebuild. The manifest is
read before any of the migration and service startup work, and library
paths are checked before anything is written, so a bad manifest fails
fast and leaves nothing half-provisioned.

Fixes jellyfin#17880
@sonarqubecloud

sonarqubecloud Bot commented Sep 9, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a non-interactive provisioning mode for first-time setup

1 participant