Files
alpine-customizable/entrypoint.py

157 lines
5.8 KiB
Python
Raw Normal View History

2026-02-01 22:21:59 +03:00
#!/usr/bin/env python3
import os
import shutil
import subprocess
import sys
import tomllib
SYSCONFIG_PATH = "/etc/sysconfig.toml"
# Config sections
SECTION_GENERAL = "general"
SECTION_USERS = "users"
SECTION_GROUPS = "groups"
SECTION_CONFIGS = "configs"
# Config keys
KEY_PACKAGES = "packages"
KEY_PASSWORD = "password"
KEY_PUBKEYS = "pubkeys"
KEY_USERS_LIST = "users"
KEY_BODY = "body"
KEY_PERMISSIONS = "permissions"
KEY_OWNER = "owner"
class SystemConfigurator:
def __init__(self, path: str):
self.config_path = path
def _parse_config(self):
with open(self.config_path, "rb") as f:
self.config = tomllib.load(f)
def _exec_command(self, command: list[str], quiet=False) -> int:
return subprocess.run(command, capture_output=quiet).returncode
def _install_packages(self):
packages = self.config.get(SECTION_GENERAL, {}).get(KEY_PACKAGES, [])
if not packages:
print("No packages to install.")
return
print(f"Installing packages: {', '.join(packages)}")
command = ["apk", "add", "--no-cache"] + packages
if self._exec_command(command) != 0:
raise RuntimeError(f"Failed to install packages: {', '.join(packages)}")
print("Packages installed successfully.")
def _create_users(self):
users = self.config.get(SECTION_USERS, {})
if not users:
print("No users to create.")
return
for username, user_config in users.items():
print(f"Creating user: {username}")
if self._exec_command(["useradd", "-m", "-s", "/bin/sh", username]) != 0:
raise RuntimeError(f"Failed to create user: {username}")
if KEY_PASSWORD in user_config:
print(f"Setting password for user: {username}")
proc = subprocess.run(
["chpasswd"],
input=f"{username}:{user_config[KEY_PASSWORD]}".encode(),
capture_output=True,
)
if proc.returncode != 0:
raise RuntimeError(
f"Failed to set password for user: {username}: {proc.stderr.decode()}"
)
if KEY_PUBKEYS in user_config:
print(f"Adding SSH key for user: {username}")
ssh_dir = os.path.join("/home", username, ".ssh")
os.makedirs(ssh_dir, mode=0o700, exist_ok=True)
authorized_keys_path = os.path.join(ssh_dir, "authorized_keys")
with open(authorized_keys_path, "w") as f:
for key in user_config[KEY_PUBKEYS]:
f.write(key + "\n")
os.chmod(authorized_keys_path, 0o600)
shutil.chown(ssh_dir, user=username, group=username)
shutil.chown(authorized_keys_path, user=username, group=username)
print(f"User {username} created successfully.")
def _create_groups(self):
groups = self.config.get(SECTION_GROUPS, {})
if not groups:
print("No groups to create.")
return
for groupname, group_config in groups.items():
if self._exec_command(["getent", "group", groupname], quiet=True) == 0:
print(f"Group '{groupname}' already exists, skipping creation.")
else:
print(f"Creating group: {groupname}")
if self._exec_command(["groupadd", groupname]) != 0:
raise RuntimeError(f"Failed to create group: {groupname}")
users = group_config.get(KEY_USERS_LIST, [])
for username in users:
print(f"Adding user {username} to group {groupname}")
if self._exec_command(["gpasswd", "-a", username, groupname]) != 0:
raise RuntimeError(
f"Failed to add user {username} to group {groupname}"
)
print(f"Group '{groupname}' configured successfully.")
def _create_configs(self):
configs = self.config.get(SECTION_CONFIGS, {})
if not configs:
print("No configs to create.")
return
for file_path, config in configs.items():
print(f"Creating config file: {file_path}")
os.makedirs(os.path.dirname(file_path), exist_ok=True)
if KEY_BODY not in config:
raise RuntimeError(
f"Config file '{file_path}' is missing the mandatory '{KEY_BODY}' key."
)
with open(file_path, "w") as f:
f.write(config[KEY_BODY])
if KEY_PERMISSIONS in config:
os.chmod(file_path, int(str(config[KEY_PERMISSIONS]), 8))
if KEY_OWNER in config:
owner_val = config[KEY_OWNER]
if ":" not in str(owner_val):
raise RuntimeError(
f"Config file '{file_path}' has an invalid '{KEY_OWNER}' format. Expected 'user:group'."
)
user, group = str(owner_val).split(":", 1)
shutil.chown(file_path, user=user, group=group)
print(f"Config file {file_path} created successfully.")
def process(self) -> int:
steps = [
(self._parse_config, 1),
(self._install_packages, 2),
(self._create_users, 3),
(self._create_groups, 4),
(self._create_configs, 5),
]
for func, code in steps:
try:
func()
except Exception as e:
print(e, file=sys.stderr)
return code
return 0
if __name__ == "__main__":
configurator = SystemConfigurator(SYSCONFIG_PATH)
exit_code = configurator.process()
if exit_code != 0:
sys.exit(exit_code)
if len(sys.argv) > 1:
os.execvp(sys.argv[1], sys.argv[1:])