`Keymap=`, `--keymap=`,
`Timezone=`, `--timezone=`,
`Hostname=`, `--hostname=`,
-`RootPassword=`, `--root-password=`,
-`RootPasswordHashed=`, `--root-password-hashed=`,
-`RootPasswordFile=`, `--root-password-file=`,
`RootShell=`, `--root-shell=`
: These settings correspond to the identically named systemd-firstboot options. See the systemd firstboot
[manpage](https://www.freedesktop.org/software/systemd/man/systemd-firstboot.html) for more information.
+ Additionally, where applicable, the corresponding systemd credentials for these settings are written to
+ `/usr/lib/credstore`, so that they apply even if only `/usr` is shipped in the image.
+
+`RootPassword=`, `--root-password=`,
+
+: Set the system root password. If this option is not used, but a `mkosi.rootpw` file is found in the local
+ directory, the password is automatically read from it. If the password starts with `hashed:`, it is treated
+ as an already hashed root password. The root password is also stored in `/usr/lib/credstore` under the
+ appropriate systemd credential so that it applies even if only `/usr` is shipped in the image.
### [Validation] Section
script. After running the build script, the contents of this directory are installed into the final image.
This is useful to inspect the results of the install step of the build.
-* The **`mkosi.rootpw`** file can be used to provide the password or
- hashed password (if `--password-is-hashed` is set) for the root user
- of the image. The password may optionally be followed by a newline
- character which is implicitly removed. The file must have an access
- mode of 0600 or less. If this file does not exist, the
- distribution's default root password is set (which usually means
- access to the root user is blocked).
+* The **`mkosi.rootpw`** file can be used to provide the password for the root user of the image. If the
+ password is prefixed with `hashed:` it is treated as an already hashed root password. The password may
+ optionally be followed by a newline character which is implicitly removed. The file must have an access
+ mode of 0600 or less. If this file does not exist, the distribution's default root password is set (which
+ usually means access to the root user is blocked).
* The **`mkosi.passphrase`** file provides the passphrase to use when
LUKS encryption is selected. It should contain the passphrase
# Default values are assigned via the parser so we go via the argument parser to construct
# the config for the initrd.
with complete_step("Building initrd"):
+ password, hashed = state.config.root_password or (None, False)
+ if password:
+ rootpwopt = f"hashed:{password}" if hashed else password
+ else:
+ rootpwopt = None
+
args, presets = MkosiConfigParser().parse([
"--directory", "",
"--distribution", str(state.config.distribution),
*(["--keymap", state.config.keymap] if state.config.keymap else []),
*(["--timezone", state.config.timezone] if state.config.timezone else []),
*(["--hostname", state.config.hostname] if state.config.hostname else []),
- *(["--root-password", state.config.root_password] if state.config.root_password else []),
- *(["--root-password-hashed", state.config.root_password_hashed] if state.config.root_password_hashed else []),
- *(["--root-password-file", os.fspath(state.config.root_password_file)] if state.config.root_password_file else []),
+ *(["--root-password", rootpwopt] if rootpwopt else []),
*(["-f"] * state.args.force),
"build",
])
Keymap: {none_to_default(config.keymap)}
Timezone: {none_to_default(config.timezone)}
Hostname: {none_to_default(config.hostname)}
- Root Password: {("(set)" if config.root_password or config.root_password_hashed or config.root_password_file else "(default)")}
+ Root Password: {("(set)" if config.root_password else "(default)")}
Root Shell: {none_to_default(config.root_shell)}
Autologin: {yes_no(config.autologin)}
def run_firstboot(state: MkosiState) -> None:
+ password, hashed = state.config.root_password or (None, False)
+ pwopt = "--root-password-hashed" if hashed else "--root-password"
+ pwcred = "passwd.hashed-password.root" if hashed else "passwd.plaintext-password.root"
+
settings = (
- ("--locale", state.config.locale),
- ("--locale-messages", state.config.locale_messages),
- ("--keymap", state.config.keymap),
- ("--timezone", state.config.timezone),
- ("--hostname", state.config.hostname),
- ("--root-password", state.config.root_password),
- ("--root-password-hashed", state.config.root_password_hashed),
- ("--root-password-file", state.config.root_password_file),
- ("--root-shell", state.config.root_shell),
+ ("--locale", "firstboot.locale", state.config.locale),
+ ("--locale-messages", "firstboot.locale-messages", state.config.locale_messages),
+ ("--keymap", "firstboot.keymap", state.config.keymap),
+ ("--timezone", "firstboot.timezone", state.config.timezone),
+ ("--hostname", None, state.config.hostname),
+ (pwopt, pwcred, password),
+ ("--root-shell", "passwd.shell.root", state.config.root_shell),
)
options = []
+ creds = []
- for setting, value in settings:
+ for option, cred, value in settings:
if not value:
continue
- options += [setting, value]
+ options += [option, value]
+
+ if cred:
+ creds += [(cred, value)]
- if not options:
+ if not options and not creds:
return
with complete_step("Applying first boot settings"):
run(["systemd-firstboot", "--root", state.root, "--force", *options])
+ # Initrds generally don't ship with only /usr so there's not much point in putting the credentials in
+ # /usr/lib/credstore.
+ if state.config.output_format != OutputFormat.cpio or not state.config.make_initrd:
+ (state.root / "usr/lib/credstore").mkdir(mode=0o755, exist_ok=True)
+
+ for cred, value in creds:
+ (state.root / "usr/lib/credstore" / cred).write_text(value)
+
+ if "password" in cred:
+ (state.root / "usr/lib/credstore" / cred).chmod(0o600)
+
def run_selinux_relabel(state: MkosiState) -> None:
selinux = state.root / "etc/selinux/config"
if absolute:
path = path.absolute()
- if secret:
+ if secret and path.exists():
mode = path.stat().st_mode & 0o777
if mode & 0o007:
die(textwrap.dedent(f"""\
return Path(value).exists()
+def config_parse_root_password(dest: str, value: Optional[str], namespace: argparse.Namespace) -> Optional[tuple[str, bool]]:
+ if dest in namespace:
+ return getattr(namespace, dest) # type: ignore
+
+ if not value:
+ return None
+
+ value = value.strip()
+ hashed = value.startswith("hashed:")
+ value = value.removeprefix("hashed:")
+
+ return (value, hashed)
+
+
@dataclasses.dataclass(frozen=True)
class MkosiConfigSetting:
dest: str
default: Any = None
default_factory: Optional[ConfigDefaultCallback] = None
paths: tuple[str, ...] = tuple()
+ path_read_text: bool = False
+ path_secret: bool = False
def __post_init__(self) -> None:
if not self.name:
keymap: Optional[str]
timezone: Optional[str]
hostname: Optional[str]
- root_password: Optional[str]
- root_password_hashed: Optional[str]
- root_password_file: Optional[Path]
+ root_password: Optional[tuple[str, bool]]
root_shell: Optional[str]
# QEMU-specific options
MkosiConfigSetting(
dest="root_password",
section="Content",
- parse=config_parse_string,
- ),
- MkosiConfigSetting(
- dest="root_password_hashed",
- section="Content",
- parse=config_parse_string,
- ),
- MkosiConfigSetting(
- dest="root_password_file",
- section="Content",
- parse=config_make_path_parser(secret=True),
+ parse=config_parse_root_password,
paths=("mkosi.rootpw",),
+ path_read_text=True,
+ path_secret=True,
),
MkosiConfigSetting(
dest="root_shell",
for s in self.SETTINGS:
for f in s.paths:
- if Path(f).exists():
- setattr(namespace, s.dest, s.parse(s.dest, f, namespace))
+ p = parse_path(f, secret=s.path_secret, required=False, absolute=False, expanduser=False, expandvars=False)
+ if p.exists():
+ setattr(namespace, s.dest,
+ s.parse(s.dest, p.read_text() if s.path_read_text else f, namespace))
return True
metavar="PASSWORD",
action=action,
)
- group.add_argument(
- "--root-password-hashed",
- help="Set the system root password (hashed)",
- metavar="PASSWORD-HASHED",
- action=action,
- )
- group.add_argument(
- "--root-password-file",
- help="Set the system root password (file)",
- metavar="PATH",
- action=action,
- )
group.add_argument(
"--root-shell",
help="Set the system root shell",
if "firstboot.locale" not in creds:
creds["firstboot.locale"] = "C.UTF-8"
- if "firstboot.hostname" not in creds:
- creds["firstboot.hostname"] = args.output
-
if args.ssh and "ssh.authorized_keys.root" not in creds and "SSH_AUTH_SOCK" in os.environ:
key = run(
["ssh-add", "-L"],