diff --git a/stages/org.osbuild.kickstart b/stages/org.osbuild.kickstart index 6f8bec6b..b63847e0 100755 --- a/stages/org.osbuild.kickstart +++ b/stages/org.osbuild.kickstart @@ -13,6 +13,8 @@ commands are supported here. import sys import os +from typing import Dict, List + import osbuild.api @@ -52,11 +54,135 @@ SCHEMA = """ "type": "string" } } + }, + "groups": { + "type": "object", + "additionalProperties": false, + "description": "Keys are group names, values are objects with group info", + "patternProperties": { + "^[A-Za-z0-9_][A-Za-z0-9_-]{0,31}$": { + "type": "object", + "properties": { + "gid": { + "type": "number", + "description": "GID for this group" + } + } + } + } + }, + "users": { + "additionalProperties": false, + "type": "object", + "description": "Keys are usernames, values are objects giving user info.", + "patternProperties": { + "^[A-Za-z0-9_][A-Za-z0-9_-]{0,31}$": { + "type": "object", + "properties": { + "uid": { + "description": "User UID", + "type": "number" + }, + "gid": { + "description": "User GID", + "type": "number" + }, + "groups": { + "description": "Array of group names for this user", + "type": "array", + "items": { + "type": "string" + } + }, + "description": { + "description": "User account description (or full name)", + "type": "string" + }, + "home": { + "description": "Path to user's home directory", + "type": "string" + }, + "shell": { + "description": "User's login shell", + "type": "string" + }, + "password": { + "description": "User's encrypted password, as returned by crypt(3)", + "type": "string" + }, + "key": { + "description": "SSH Public Key to add to ~/.ssh/authorized_keys", + "type": "string" + } + } + } + } } } """ +def make_groups(groups: Dict) -> List[str]: + # group --name NAME [--gid GID] + + res = [] + + for name, opts in groups.items(): + gid = opts.get("gid") + + arguments = [f"group --name {name}"] + if gid: + arguments += ["--gid", str(gid)] + + res.append(" ".join(arguments)) + + return res + + +def make_users(users: Dict) -> List[str]: + # user [--homedir HOMEDIR] [--iscrypted] --name NAME [--password PASSWORD] + # [--shell SHELL] [--uid INT] [--lock] [--plaintext] [--gecos GECOS] + # [--gid INT] [--groups GROUPS] + + res = [] + + for name, opts in users.items(): + + arguments = [f"user --name {name}"] + + password = opts.get("password") + if password is not None: + arguments += ["--password", password or '""'] + + shell = opts.get("shell") + if shell: + arguments += ["--shell", shell] + + uid = opts.get("uid") + if uid is not None: + arguments += ["--uid", str(uid)] + + gid = opts.get("gid") + if gid is not None: + arguments += ["--gid", str(gid)] + + groups = opts.get("groups") + if groups: + arguments += ["--groups", ",".join(groups)] + + home = opts.get("home") + if home: + arguments += ["--homedir", home] + + res.append(" ".join(arguments)) + + key = opts.get("key") + if key: + res.append(f"sshkey --username {name} {key}") + + return res + + def main(tree, options): path = options["path"].lstrip("/") ostree = options.get("ostree") @@ -83,6 +209,9 @@ def main(tree, options): url = liveimg["url"] config += [f"liveimg --url {url}"] + config += make_groups(options.get("groups", {})) + config += make_users(options.get("users", {})) + target = os.path.join(tree, path) base = os.path.dirname(target) os.makedirs(base, exist_ok=True)