Index

SetupWizard

MMO\admin\helpers · 1 symbol · generated 5 Oct 2026

class

SetupWizard

SetupWizard.php:46

php scripts/admin.php setup: the questions whose answers are a project's deployment, asked in the order deploying needs them, each one explained -- then the files those answers make, shown before they are written.

class MMO\admin\helpers\SetupWizard
Namespace
MMO\admin\helpers
It is a guide as much as a form. Every step says what it asks for, why it matters and what the tool does with it, so that whoever sets a project up for the first time ends up knowing how it deploys, not only holding a deploy.json. Run again on a project that has one, every question starts from what the files already say: ENTER all the way through changes nothing -- but what it found missing or disagreeing, and asked about first. What it writes, and how: - `deploy.json`, through [`JsonEditor`](https://mcp.mmoframework.fr/documentation/mmo.admin/php/helpers/JsonEditor.md#jsoneditor): only the lines that change. Its comments document its keys and it is reviewed like code, so it is edited, never regenerated; a new one starts from mmo.admin's template. Every key of every server and target is written, the default of the ones nobody chose included, and each one has a comment saying what it does ([`Declaration`](https://mcp.mmoframework.fr/documentation/mmo.admin/php/helpers/Declaration.md#declaration)): what a deployment does is all in plain sight. - `deploy.credentials.json`, when a server needs a secret: private (0600) from the moment it exists, and only once `.gitignore` ignores it. A secret found in deploy.json is offered a move there. - a `Host` entry of the project's `deploy.ssh.config` ([`SshConfig`](https://mcp.mmoframework.fr/documentation/mmo.admin/php/helpers/SetupWizard.md#setupwizard-sshconfig)), for a server reached through an alias it does not declare yet -- copied from `~/.ssh/config` when that has one. Private and git-ignored too: everything the project needs is in the project folder, and nothing it writes is outside it. - what `init` writes and the project lacks, the `.gitignore` and `.gitattributes` rules it misses, the pre-commit hook (`core.hooksPath`), and the values of `scripts/bootstrap.php` and `scripts/apache2.conf` the answers decide ([`PhpConfigEditor`](https://mcp.mmoframework.fr/documentation/mmo.admin/php/helpers/PhpConfigEditor.md#phpconfigeditor)). Nothing is written before the last step, which shows every change as a diff -- secrets masked -- and checks that deploy.json loads ([`Store`](https://mcp.mmoframework.fr/documentation/mmo.admin/php/helpers/Store.md#store)) before asking. Nothing on a server is changed: reading a host key is the only connection it opens, and only when asked to. Answers are read through \MMO\core\shell's prompts, so a stream given to \MMO\core\shell\setInput() answers them as a person would: that is how it is tested.
$wizard = new SetupWizard('/var/www/acme/deploy.json', templateFolder: '/path/to/mmo.admin/templates'); $wizard->run(); // every step $wizard->run('targets'); // the targets alone, then the review

Index

CANCELLEDthe code of the eRuntime saying nobody answered: the run stops, and nothing is written.
HOST_KEYSthe kind of a host key => the file holding it on the server, whose fingerprint ssh-keygen -l prints there.
NAMEwhat a server, a project or a new target may be called: what a shell and a path take as they are.
STEPSstep => what it is about, in the order deploying needs them.
appends
bootstrap
commands
committable
credentialsData
credentialsFilename
credentialsText
data
dryRun
filename
folder
git
hosts
original
renames
secrets
settings
sshConfig
template
templateFolder
templates
vhost
webspaceRoots
written
address($row)
aliasSettings($row)
apply($changes)
ask($question, $default, $validate)
askAlias($server, $row, $was)
askAuth($row, $secrets)
askFingerprint($row)
askHost($row)
askList($question, $default, $validate)
askManaged($row)
askPort($question, $default)
askRoot($server, $row, $secrets, $local)The folder every relative path of a target on this server starts from.
askVhost($template)Make the vhost template take its paths and its server name from each target: {public}, {folder} and {host} (Target::resolveTemplate) -- one template, right for every target whatever it d…
askWebspace($row, $folder, $before, $root)What a target on a shared webspace needs that the others do not.
bootstrapFilename()
branch()
changes()Every file the answers change, in the order it is safe to write them: .gitignore before the credentials file exists, so git never sees it unignored.
checkAgent()Say whether an ssh agent is running and holds a key: agent needs both, and neither is in a file to check later.
checkAttributes()
checkCredentialsFile()
checkHook()
checkIgnored()
checkKeys()Deal, before anything else, with what would stop deploy.json from loading whatever the answers: a secret in it, and a key mmo.admin does not know.
checkRef($ref)Warn about a branch this repository does not have: the first release of the target would fail on it.
closest($key, $known)
conclude()What to do next, in order, with the commands to type.
credentialsText()
debugName()
declarationText()
defaultKey()
describeServer($row)
directive($text, $pattern, $value)
editServer($name)
editTarget($name)
entity($name, $row)
expand($path)
fingerprint($value)
freeName($prefix, $taken)
hasDatabase()
hostKeyFile($type)
ignoresNeeded()The .gitignore rules the files about to be written need and nothing gives them: a private file is never written where git would take it, whichever step made it.
ignoreText($rule)
introduce($full)
isExample($section, $name)
isHost($host)
isIgnored($file)
isUrl($url)
listServers()
listTargets()
load()
manifest()
mask($text)
missingTemplates()
moveSecret($path, $key, $what)
need($answer)
objectify($data)
panelPath($path, $root)
pick($question, $options, $default)
pickName($question, $names, $default)
privateFiles()
protectedTags()
readHostKey($host, $port)Read the host key a server presents, the way the deployment's own connection will check it.
readWebspaceRoot($server, $row, $secrets)
reference($path)
relative($filename)
relativePaths($paths)
relativeTo($root, $path, $default)
removeServer($name)
removeTarget($name)
renameKey($values, $old, $new)
review($changes)Show every change: what is added, removed, run -- secrets masked.
rules($text)
run($only)Ask, explain, show, write.
saveServer($old, $new, $row, $secrets)
saveTarget($old, $new, $row)
secret($question)
setting($key)
sshConfig()
step($step)
stepFiles()
stepInstall()installTemplates and postInstall: the vhost install sets up on a server of yours -- and on no other.
stepProject()
stepScripts()
stepServers()
stepTargets()
stepWrite()
templateText($file)
urlHost($url)
userSshConfig()
validate($changes)Refuse what mmo.admin would refuse: a deploy.json that does not load, a PHP file that does not parse.
valueAt($path)
webspaceRoot($server, $row, $secrets)The folder a webspace account lands in: what pwd prints there.
withoutComments($value)
write($filename, $text, $mode)Write a file beside, then move it over: a file half written is never the one read.
yes($question, $default)
__construct($filename, $credentialsFilename, $templateFolder, $git, $sshConfig, $settings, $dryRun)

Constants4

CANCELLED

the code of the eRuntime saying nobody answered: the run stops, and nothing is written.

const CANCELLED = 499

HOST_KEYS

the kind of a host key => the file holding it on the server, whose fingerprint ssh-keygen -l prints there.

const HOST_KEYS = […3 items]

NAME

what a server, a project or a new target may be called: what a shell and a path take as they are.

const NAME = '/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/'

STEPS

step => what it is about, in the order deploying needs them.

const STEPS = […6 items]

Properties24

appends

protected array $appends = []
Type
array

bootstrap

protected array $bootstrap = []
Type
array

commands

protected array $commands = []
Type
array

committable

protected array $committable = []
Type
array

credentialsData

protected array $credentialsData = []
Type
array

credentialsFilename

protected ?string $credentialsFilename
Type
null | string

credentialsText

protected string $credentialsText = ''
Type
string

data

protected array $data = []
Type
array

dryRun

protected bool $dryRun
Type
bool

filename

protected string $filename
Type
string

folder

protected string $folder
Type
string

hosts

protected array $hosts = []
Type
array

original

protected string $original = ''
Type
string

renames

protected array $renames = []
Type
array

secrets

protected array $secrets = []
Type
array

settings

protected array $settings
Type
array

sshConfig

protected ?MMO\admin\helpers\SshConfig $sshConfig
Type
null | SshConfig

template

protected array $template = []
Type
array

templateFolder

protected ?string $templateFolder
Type
null | string

templates

protected array $templates = []
Type
array

vhost

protected ?string $vhost = null
Type
null | string

webspaceRoots

protected array $webspaceRoots = []
Type
array

written

protected bool $written = false
Type
bool

Methods96

address($row)

SetupWizard.php:1678
protected function address(array $row): array
$rowarray
returnsarraystring|null, 1: int} a server's host and port, from deploy.json or from its alias.

aliasSettings($row)

SetupWizard.php:1671
protected function aliasSettings(array $row): array
$rowarray
returnsarraythe deploy.ssh.config settings of a server's alias: the ones about to be added, or the file's.

askAuth($row, $secrets)

SetupWizard.php:623
protected function askAuth(array &$row, array $secrets): array
$rowarraythe server's declaration.
$secretsarraywhat the credentials file holds for it.
returnsarraywhat it will hold.

askRoot($server, $row, $secrets, $local)

SetupWizard.php:785

The folder every relative path of a target on this server starts from.

protected function askRoot(string $server, array &$row, array $secrets, bool $local): voidOn a webspace the host decides it, and prints it nowhere a person can copy it reliably (IONOS has moved it between contracts): it is read where it is true, with `pwd` ([`webspaceRoot`](https://mcp.mmoframework.fr/documentation/mmo.admin/php/helpers/SetupWizard.md#setupwizard-webspaceroot)), and offered.
$serverstring
$rowarraythe server's declaration, which gets its root.
$secretsarraywhat the credentials file will hold for it: how to get in, to read it.
$localboolit is this very machine: a root is optional.
returnsvoid

askVhost($template)

SetupWizard.php:1202

Make the vhost template take its paths and its server name from each target: {public}, {folder} and {host} (Target::resolveTemplate) -- one template, right for every target whatever it declares -- and ask where the certificate is.

protected function askVhost(string $template): void
$templatestringthe local template, as installTemplates names it.
returnsvoid

askWebspace($row, $folder, $before, $root)

SetupWizard.php:988

What a target on a shared webspace needs that the others do not.

protected function askWebspace( array &$row, string $folder, MMO\admin\entities\Target $before, ?string $root ): void
$rowarraythe target's declaration.
$folderstringits folder, absolute.
$beforeTargetwhat it was before this edit.
$rootnull | stringits server's root: how a panel names a folder.
returnsvoid

changes()

SetupWizard.php:1288

Every file the answers change, in the order it is safe to write them: .gitignore before the credentials file exists, so git never sees it unignored.

protected function changes(): array
returnsarrayarray{label: string, old: string, new: string, mode?: int, secret?: bool, template?: bool}> by filename.

checkAgent()

SetupWizard.php:667

Say whether an ssh agent is running and holds a key: agent needs both, and neither is in a file to check later.

protected function checkAgent(): void
returnsvoid

checkKeys()

SetupWizard.php:248

Deal, before anything else, with what would stop deploy.json from loading whatever the answers: a secret in it, and a key mmo.admin does not know.

protected function checkKeys(): void
returnsvoid

checkRef($ref)

SetupWizard.php:1091

Warn about a branch this repository does not have: the first release of the target would fail on it.

protected function checkRef(string $ref): void
$refstring
returnsvoid

conclude()

SetupWizard.php:1503

What to do next, in order, with the commands to type.

protected function conclude(): void
returnsvoid

credentialsText()

SetupWizard.php:1393
protected function credentialsText(): ?string
returnsnull | stringthe credentials file once the answers are in it, null when there is none and none is needed.

declarationText()

SetupWizard.php:1378
protected function declarationText(): string
returnsstringdeploy.json once the answers are in it: its own text, edited -- every key of every server and target written, and each one with a comment saying what it does (Declaration).

describeServer($row)

SetupWizard.php:524
protected function describeServer(array $row): string
$rowarray
returnsstringhow a server is reached, how it lets you in, and whether its host key is pinned.

directive($text, $pattern, $value)

SetupWizard.php:1237
protected function directive(string $text, string $pattern, string $value): string
$textstring
$patternstring
$valuestring
returnsstring$text with the value of the directive $pattern matches -- its first group kept -- replaced by $value.

entity($name, $row)

SetupWizard.php:1586
protected function entity(string $name, ?array $row = null): MMO\admin\entities\Target
$namestring
$row = nullnull | arrayits declaration (null: the one it has).
returnsTargetthe target as the store would load it: its own values, on its server.

fingerprint($value)

SetupWizard.php:769
protected function fingerprint(string $value): ?string
$valuestringa fingerprint as a tool prints it: MD5:aa:bb..., AABB..., with or without colons.
returnsnull | stringlowercase hex pairs joined by colons -- what \MMO\core\server\SSH compares -- or null when it is not 32 (MD5) or 40 (SHA1) hex digits. A base64 fingerprint (SHA256:...) is not one the ssh2 extension can compute.

freeName($prefix, $taken)

SetupWizard.php:1726
protected function freeName(string $prefix, array $taken): string
$prefixstring
$takenarray
returnsstringthe first of {prefix}1, {prefix}2... that is not taken.

hostKeyFile($type)

SetupWizard.php:756
protected function hostKeyFile(string $type): string
$typestring
returnsstringwhere the server keeps the public key of that kind.

ignoresNeeded()

SetupWizard.php:1352

The .gitignore rules the files about to be written need and nothing gives them: a private file is never written where git would take it, whichever step made it.

protected function ignoresNeeded(): array
returnsarray

introduce($full)

SetupWizard.php:221
protected function introduce(bool $full = true): void
$full = trueboolevery step follows: say what they are about. One step alone says only what it has to.
returnsvoid

isExample($section, $name)

SetupWizard.php:1638
protected function isExample(string $section, string $name): bool
$sectionstring
$namestring
returnsboolwhether a server or a target is still the template's example, untouched.

isIgnored($file)

SetupWizard.php:375
protected function isIgnored(string $file): bool
$filestring
returnsboolwhether git would leave $file, relative to the project, out of a commit.

mask($text)

SetupWizard.php:1457
protected function mask(string $text): string
$textstring
returnsstring$text with the value of every secret-shaped key replaced by ****.

missingTemplates()

SetupWizard.php:327
protected function missingTemplates(): array
returnsarrayfile relative to the project => template, for every template file the project lacks.

moveSecret($path, $key, $what)

SetupWizard.php:289
protected function moveSecret(array $path, string $key, string $what): void
$patharraywhere the secret is: a server's or a target's row.
$keystring
$whatstringhow to name the row.
returnsvoid

need($answer)

SetupWizard.php:1533
protected function need(mixed $answer): mixed
$answermixedwhat a prompt returned.
returnsmixed$answer, once it is known not to be null.
throwsnobody answered: the input ended, or an invalid answer came from something that is not a terminal.

objectify($data)

SetupWizard.php:1597
protected function objectify(array $data): array
$dataarray
returnsarray$data with an empty object written {} where deploy.json has objects: [] is an empty list.

panelPath($path, $root)

SetupWizard.php:1086
protected function panelPath(string $path, ?string $root): string
$pathstring
$rootnull | string
returnsstring$path as a webspace panel names it: relative to the server's root, when it is under it.

pickName($question, $names, $default)

SetupWizard.php:1560
protected function pickName(string $question, array $names, string|int|null $default = null): string
$questionstring
$namesarraykeys of the declaration -- PHP makes a name like 1 an int.
$default = nullstring | int | null
returnsstringone of $names -- the only one, without asking.

privateFiles()

SetupWizard.php:362
protected function privateFiles(): array
returnsarrayarray{0: string, 1: string}> file relative to the project => [the .gitignore rule ignoring it, why it must be]: what the project keeps that cannot be shared. Kept in the project, private, and out of git -- never in a folder of the user's.

readHostKey($host, $port)

SetupWizard.php:727

Read the host key a server presents, the way the deployment's own connection will check it.

protected function readHostKey(string $host, int $port): ?array
$hoststring
$portint
returnsnull | arraystring, sha1: string, md5: string}|null null when it could not be read, and why is printed.

readWebspaceRoot($server, $row, $secrets)

SetupWizard.php:1057
protected function readWebspaceRoot(string $server, array $row, array $secrets): ?string
$serverstring
$rowarray
$secretsarray
returnsnull | stringwhat pwd prints on it, null when that is not an absolute path.
throwswhen it cannot be reached.

relativePaths($paths)

SetupWizard.php:1766
protected function relativePaths(array $paths): ?string
$pathsarray
returnsnull | stringwhy these paths are not paths of a release, or null.

relativeTo($root, $path, $default)

SetupWizard.php:1078
protected function relativeTo(?string $root, ?string $path, string $default): string
$rootnull | stringthe server's root.
$pathnull | stringa folder as deploy.json writes it (null: none yet).
$defaultstringwhat to propose when there is none.
returnsstring$path as it is best written: relative to the root when it is under it, its last folder under the root when it is outside it -- another account's path, a placeholder -- and as it is otherwise.

renameKey($values, $old, $new)

SetupWizard.php:1611
protected function renameKey(array $values, string $old, string $new): array
$valuesarray
$oldstring
$newstring
returnsarray$values with the key $old named $new, where it was.

review($changes)

SetupWizard.php:1439

Show every change: what is added, removed, run -- secrets masked.

protected function review(array $changes): void
$changesarray
returnsvoid

rules($text)

SetupWizard.php:413
protected function rules(string $text): array
$textstring
returnsarraythe rules of a .gitattributes text, spaces normalized.

run($only)

SetupWizard.php:158

Ask, explain, show, write.

function run(?string $only = null): bool
$only = nullnull | stringone step of STEPS alone -- followed by the review, which is where writing happens.
returnsboolwhether anything was written.
throwswhen $only names no step, or deploy.json is not JSON at all.

setting($key)

SetupWizard.php:1655
protected function setting(string $key): array
$keystring
returnsarraya list setting of ProductionController, as it is configured.

stepInstall()

SetupWizard.php:1151

installTemplates and postInstall: the vhost install sets up on a server of yours -- and on no other.

protected function stepInstall(): void
returnsvoid

userSshConfig()

SetupWizard.php:1666
protected function userSshConfig(): ?MMO\admin\helpers\SshConfig
returnsnull | SshConfigthe user's own ~/.ssh/config: only ever read, for an alias to copy into the project.

validate($changes)

SetupWizard.php:1420

Refuse what mmo.admin would refuse: a deploy.json that does not load, a PHP file that does not parse.

protected function validate(array $changes): ?string
$changesarraychanges
returnsnull | stringwhy, or null when everything is fine.

valueAt($path)

SetupWizard.php:1618
protected function valueAt(array $path): mixed
$patharray
returnsmixedthe value of $data at $path, null when there is none.

webspaceRoot($server, $row, $secrets)

SetupWizard.php:1037

The folder a webspace account lands in: what pwd prints there.

protected function webspaceRoot(string $server, array $row, array $secrets): ?stringRead over the same ssh connection a deployment opens, with the answers given so far -- the alias about to be written, the password about to be stored. Read once per server; nothing else is run.
$serverstring
$rowarraythe server's declaration, as answered so far.
$secretsarraywhat the credentials file will hold for it.
returnsnull | stringnull when it could not be read, and why is printed: the root is then asked plainly.

withoutComments($value)

SetupWizard.php:1774
protected function withoutComments(mixed $value): mixed
$valuemixed
returnsmixed$value without the keys starting with //, at any depth: comments, as Store reads them.

write($filename, $text, $mode)

SetupWizard.php:1485

Write a file beside, then move it over: a file half written is never the one read.

protected function write(string $filename, string $text, ?int $mode): void
$filenamestring
$textstring
$modenull | intapplied BEFORE anything is written in it: a secret never sits in a file others can read. null: the mode it has, or the default for a new file.
returnsvoid

__construct($filename, $credentialsFilename, $templateFolder, $git, $sshConfig, $settings, $dryRun)

SetupWizard.php:131
function __construct( string $filename, ?string $credentialsFilename = null, ?string $templateFolder = null, ?MMO\git\helpers\Repository $git = null, ?MMO\admin\helpers\SshConfig $sshConfig = null, array $settings = [], bool $dryRun = false )
$filenamestringthe project's deploy.json, which may not exist yet.
$credentialsFilename = nullnull | stringnull: deploy.credentials.json beside it.
$templateFolder = nullnull | stringmmo.admin's templates (null: this library's).
$git = nullnull | Repositorythe project's repository (null: the folder of deploy.json, if it is one).
$sshConfig = nullnull | SshConfignull: the project's deploy.ssh.config, beside deploy.json.
$settings = []arraywhat ProductionController is configured with: protectedTags, buildCommands, include, installTemplates, postInstall, repository (whether one is set), bootstrap (the file configuring it).
$dryRun = falseboolask everything, show every change, write nothing.