Runs the first-run install: creates every permission name of permissions/0 that is missing,
grants "*" to the master user (auto-confirmed, no email), creates the first (master) site,
installs the chosen Chelekom components, writes the site's starter content — the theme, the
header, the footer, the site-main layout and the home page
(MishkaCmsCore.Install.StarterContent.seed!/2) — and its sign-in pages
(MishkaCmsCore.Install.StarterContent.ensure_auth!/1), compiles the new site's runtime —
exactly as a normal boot would — and flips the install flag. A name already stored is left as it
is, so re-running after seeds is safe.
THE STARTER IS NOT A CHOICE. Every page of a site, an extension's included, is drawn in the
site-main layout (MishkaCmsCore.Runtime.SiteLayout), so a site without it has no frame for
any of them; and a first install whose / answers nothing is a broken one. The installer's box
says so and cannot be unticked.
Everything runs authorize?: false because no privileged actor exists yet — which is why
this is reachable only while the CMS is uninstalled (MishkaCms.Plugs.InstallGate).
The install runs in ordered phases (@steps); the installer renders one progress row per entry,
so adding a phase here (with a matching run_step/3 clause) makes it appear in the UI
automatically.
The master user is created via the existing register_with_password strategy action (which mints
the token and hashes the password); with no tenant the user stays site_id: nil (a master), and
authorize?: false relaxes the master-creation policy. The master is confirmed in-place and the
changeset is tagged with mishka_install: true so the confirmation sender skips the email (see
MishkaCmsCore.Accounts.User.Senders.SendNewUserConfirmationEmail).
Essential settings are written through the Settings façade with the category/source declared for
each key in the registry, so each lands on the right tab of the settings page; unregistered keys
are ignored.
The site's languages (:languages, codes MishkaCmsCore.Runtime.Languages.valid?/1 accepts, the
first being the primary) are saved to the new site's own site.languages before any page is
written, so every page the install writes answers each of them. A run given none leaves the site
reading the global list. How its addresses name them (:language_urls, one of
MishkaCmsCore.Runtime.Languages.url_modes/0) is saved beside them to its own
site.language_urls; a run given none leaves the site reading the global setting.
Summary
Functions
Creates each name of permissions (the core's, permissions/0, when none are given) that is not
stored yet. Returns every one of them, stored before or now, and the ones this call created. A
stored name keeps its row and its description.
Every permission name the core install creates, with its description. An extension's own names
are not here: its provision manifest ships them, and installing it creates them
(MishkaCmsCore.Runtime.ExtensionProvision).
Runs the install phases in order, calling progress with {step_id, status, detail} as each phase
starts (:running), emits a line (:log), finishes (:done) or fails (:error). Returns
{:ok, %{user: user, site: site}} or {:error, reason} from the first failing phase. run/1 keeps
the silent behaviour for non-interactive callers (seeds, tests).
The ordered install phases as {id, label} — one progress row per entry.
Types
@type params() :: %{ :email => String.t(), :password => String.t(), :site_name => String.t(), :host => String.t(), optional(:component_selection) => map(), optional(:languages) => [String.t()], optional(:language_urls) => String.t(), optional(:settings) => %{optional(String.t()) => term()}, optional(:extensions) => [String.t()], optional(:extension_answers) => %{optional(String.t()) => map()} }
Functions
ensure_permissions(permissions \\ [{"*", "Every permission within the holder's own site. For a master that means every site. It never makes anyone a master."}, {"admin:*", "Full admin of the dashboard and the API."}, {"admin:dashboard:*", "Full dashboard admin: every dashboard area, every builder and code."}, {"admin:api:*", "Full API admin: every admin JSON:API and GraphQL area, and code."}, {"admin:dashboard:runtime:*", "Every runtime section on the dashboard, their code included, and opening every builder."}, {"admin:dashboard:runtime:site", "The Sites screen and site data: read, create, edit and archive. A site-wide action still needs a master."}, {"admin:dashboard:runtime:page", "Writing pages, their drafts and versions, their code included, and opening the page builder."}, {"admin:dashboard:runtime:layout", "Writing layouts, their drafts and versions, their code included, and opening the layout builder."}, {"admin:dashboard:runtime:component", "Writing components and their versions, their code included, and opening the component builder."}, {"admin:dashboard:runtime:error_page", "Writing error pages, their drafts and versions, their code included, and opening the error page builder."}, {"admin:dashboard:runtime:stylesheet", "Writing stylesheets, their CSS included."}, {"admin:dashboard:runtime:js_hook", "Writing JS hooks, their content included."}, {"admin:dashboard:runtime:media", "Writing media, media categories and their versions. Every dashboard admin reads them."}, {"admin:dashboard:runtime:translation", "Writing translation rows. Every dashboard admin reads them."}, {"admin:dashboard:runtime:redirect", "Writing the site's redirect rules. Every dashboard admin reads them."}, {"admin:dashboard:setting", "Writing site settings that declare no permission of their own, the extension switches and the notification toggles included."}, {"admin:dashboard:user", "The Users screen: list, read, create, edit and delete the accounts of the holder's own site."}, {"admin:dashboard:permission", "The Permissions screen: read the permission names, and grant or revoke permissions within what the holder holds."}, {"admin:dashboard:runtime:activity", "The Activity screen, activity entries and the recent-activity widget."}, {"admin:dashboard:code", "Writing pages, layouts, components, error pages, stylesheets and JS hooks, their code included, and opening every builder."}, {"admin:api:runtime:*", "Every runtime section over the admin JSON:API, their code included."}, {"admin:api:runtime:site", "Site data over the admin JSON:API."}, {"admin:api:runtime:page", "Writing pages over the admin JSON:API, their code included."}, {"admin:api:runtime:layout", "Writing layouts over the admin JSON:API, their code included."}, {"admin:api:runtime:component", "Writing components over the admin JSON:API, their code included."}, {"admin:api:runtime:error_page", "Writing error pages over the admin JSON:API, their code included."}, {"admin:api:runtime:stylesheet", "Writing stylesheets over the admin JSON:API, their CSS included."}, {"admin:api:runtime:js_hook", "Writing JS hooks over the admin JSON:API, their content included."}, {"admin:api:runtime:media", "Writing media and media categories over the admin JSON:API."}, {"admin:api:runtime:translation", "Writing translations over the admin JSON:API."}, {"admin:api:runtime:redirect", "Writing redirect rules over the admin JSON:API."}, {"admin:api:user", "Accounts over the admin JSON:API."}, {"admin:api:permission", "The permission names and the grants over the admin JSON:API."}, {"admin:api:runtime:activity", "The activity log over the admin JSON:API."}, {"admin:api:code", "Writing pages, layouts, components, error pages, stylesheets and JS hooks over the admin JSON:API, their code included."}])
@spec ensure_permissions([{String.t(), String.t()}]) :: {:ok, %{ stored: [MishkaCmsCore.Accounts.Permission.t()], created: [MishkaCmsCore.Accounts.Permission.t()] }} | {:error, term()}
Creates each name of permissions (the core's, permissions/0, when none are given) that is not
stored yet. Returns every one of them, stored before or now, and the ones this call created. A
stored name keeps its row and its description.
Every permission name the core install creates, with its description. An extension's own names
are not here: its provision manifest ships them, and installing it creates them
(MishkaCmsCore.Runtime.ExtensionProvision).
Runs the install phases in order, calling progress with {step_id, status, detail} as each phase
starts (:running), emits a line (:log), finishes (:done) or fails (:error). Returns
{:ok, %{user: user, site: site}} or {:error, reason} from the first failing phase. run/1 keeps
the silent behaviour for non-interactive callers (seeds, tests).
The ordered install phases as {id, label} — one progress row per entry.