This commit is contained in:
Matthew Tran
2025-05-03 22:34:20 -07:00
parent 1846d973a3
commit 3e5a59d513
10 changed files with 233 additions and 189 deletions
+154 -37
View File
@@ -5,24 +5,32 @@ import json
import secrets
import subprocess
import yaml
from pathlib import Path
from update import SOURCE_DIR, IMAGES, generate
if __name__ == "__main__":
cfg = json.load(open("config/server.json"))
but = {
"variant": "fcos",
"version": "1.6.0",
}
UIDS = {
"web" : 1001,
"monero" : 1002,
"game" : 1003,
"nas" : 1004,
}
# recommend keys if needed
if "var_key" not in cfg["core"]:
print(f'cfg["core"]["var_key"] doesn\'t exist, try "{base64.b64encode(secrets.token_bytes(64)).decode("utf-8")}"')
PORTS = {
"game": [
"25565:25565",
],
}
def check_keys():
if "home_key" not in cfg["core"]:
print(f'cfg["core"]["home_key"] doesn\'t exist, try "{base64.b64encode(secrets.token_bytes(64)).decode("utf-8")}"')
exit(1)
for i, d in enumerate(cfg["drives"]):
if "key" not in d:
print(f'cfg["drives"][{i}]["key"] doesn\'t exist, try "{base64.b64encode(secrets.token_bytes(64)).decode("utf-8")}"')
exit(1)
# configure root drive
def add_root_drive():
but["storage"] = {
"disks": [
{
@@ -36,7 +44,7 @@ if __name__ == "__main__":
"resize": True,
},
{
"label": "var",
"label": "home",
"size_mib": 0,
},
],
@@ -52,10 +60,10 @@ if __name__ == "__main__":
"clevis": { "tpm2": True },
},
{
"name": "var",
"device": "/dev/disk/by-partlabel/var",
"wipe_volume": cfg["core"]["var_wipe"],
"key_file": { "inline": base64.b64decode(cfg["core"]["var_key"]) },
"name": "home",
"device": "/dev/disk/by-partlabel/home",
"wipe_volume": cfg["core"]["home_wipe"],
"key_file": { "inline": base64.b64decode(cfg["core"]["home_key"]) },
},
],
"filesystems": [
@@ -66,18 +74,18 @@ if __name__ == "__main__":
"label": "root",
},
{
"path": "/var",
"device": "/dev/mapper/var",
"path": "/var/home",
"device": "/dev/mapper/home",
"format": "xfs",
"wipe_filesystem": cfg["core"]["var_wipe"],
"wipe_filesystem": cfg["core"]["home_wipe"],
"with_mount_unit": True,
},
],
"files": [],
"directories": [],
"files": [],
}
# add additional drives
def add_more_drive():
for d in cfg["drives"]:
raid = len(d["devices"]) > 1
if raid:
@@ -105,18 +113,7 @@ if __name__ == "__main__":
"group": { "name": "core" },
})
# add SSH keys
assert(len(cfg["core"]["ssh_keys"]) > 0)
but["passwd"] = {
"users": [
{
"name": "core",
"ssh_authorized_keys": cfg["core"]["ssh_keys"],
},
],
}
# add packages
def add_packages():
# TODO update once done https://github.com/coreos/fedora-coreos-tracker/issues/681
but["systemd"] = {
"units": [
@@ -147,30 +144,150 @@ if __name__ == "__main__":
],
}
# set hostname
def add_ssh_keys():
assert(len(cfg["core"]["ssh_keys"]) > 0)
but["passwd"] = {
"users": [
{
"name": "core",
"ssh_authorized_keys": cfg["core"]["ssh_keys"],
},
],
}
def set_hostname():
but["storage"]["files"].append({
"path": "/etc/hostname",
"mode": 0o644,
"contents": { "inline": cfg["core"]["hostname"] },
})
# allow unprivileged port access
def allow_port_access():
but["storage"]["files"].append({
"path": "/etc/sysctl.d/99-unprivileged-ports.conf",
"mode": 0o644,
"contents": { "inline": "net.ipv4.ip_unprivileged_port_start=80" },
})
def add_users():
for user in UIDS:
but["passwd"]["users"].append({
"name": user,
"uid": UIDS[user],
})
but["storage"]["files"].append({
"path": f"/var/lib/systemd/linger/{user}",
"contents": { "inline": "" },
})
# TODO make server build images on first boot?
# TODO serve backup.zip to restore on first boot? only if wipe specified
def copy_source():
but["storage"]["directories"].append({
"path": SOURCE_DIR,
"user": { "name": "core" },
"group": { "name": "core" },
})
for i in (f for s in IMAGES.values() for f in s):
but["storage"]["directories"].append({
"path": str(Path(SOURCE_DIR) / i),
"user": { "name": "core" },
"group": { "name": "core" },
})
for f in Path(i).glob("*"):
but["storage"]["files"].append({
"path": str(Path(SOURCE_DIR) / f),
"contents": { "inline": open(f, "r").read() },
"user": { "name": "core" },
"group": { "name": "core" },
})
# TODO convert all to quadlets? whatever compose likes
def build_images():
but["storage"]["directories"].append({ "path": "/etc/containers/systemd/users" })
for user in IMAGES:
but["storage"]["directories"].append({ "path": f"/etc/containers/systemd/users/{UIDS[user]}" })
for img in IMAGES[user]:
but["storage"]["files"].append({
"path": f"/etc/containers/systemd/users/{UIDS[user]}/{img}.build",
"contents": { "inline": "\n".join([
"[Build]",
f"ImageTag={img}",
f"SetWorkingDirectory={SOURCE_DIR}/{img}",
])}
})
def create_pods():
for user in IMAGES:
but["storage"]["files"].append({
"path": f"/etc/containers/systemd/users/{UIDS[user]}/{user}.pod",
"contents": { "inline": "[Pod]\n" + "\n".join([f"PublishPort={p}" for p in PORTS[user]])}
})
def create_folders():
but["storage"]["directories"].append({
"path": cfg["core"]["data_dir"],
"user": { "name": "core" },
"group": { "name": "core" },
})
for user in IMAGES:
for img in IMAGES[user]:
but["storage"]["directories"].append({
"path": str(Path(cfg["core"]["data_dir"]) / img),
"user": { "name": user },
"group": { "name": user },
})
def run_containers():
for user in IMAGES:
for img in IMAGES[user]:
but["storage"]["files"].append({
"path": f"/etc/containers/systemd/users/{UIDS[user]}/{img}.container",
"contents": { "inline": "\n".join([
"[Container]",
f"ContainerName={img}",
f"Image={img}.build",
f"Pod={user}.pod",
f"Volume={str(Path(cfg["core"]["data_dir"]) / img)}:/root/data:z",
"[Install]",
"WantedBy=default.target",
])}
})
if __name__ == "__main__":
cfg = json.load(open("config/server.json"))
but = {
"variant": "fcos",
"version": "1.6.0",
}
# core setup
check_keys()
add_root_drive()
add_more_drive()
add_packages()
add_ssh_keys()
set_hostname()
allow_port_access()
# server setup
add_users()
generate(cfg)
copy_source()
build_images()
create_pods()
create_folders()
run_containers()
# TODO add rest of containers
# add core to nas group
# TODO script to backup => restore backup if desired
# TODO enable bedrock => check idle cpu
# TODO reduce disk logging?
# TODO generate ISO, else nginx if --insecure
with open("config/server.bu", "w") as f:
f.write(yaml.dump(but, sort_keys=False))
subprocess.check_output(["butane", "-p", "-s", "-o", "config/server.ign", "config/server.bu"])
print("NOTE - TPM may need to be cleared after enough provisions.")
print("WARNING - Using unencrypted connections without authentication, ensure LAN is secure!")
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
import json
import shutil
import subprocess
from pathlib import Path
SOURCE_DIR = "/var/source"
IMAGES = {
"game": [
"minecraft",
],
}
def generate(cfg):
# minecraft
shutil.copy("minecraft/server.default", "minecraft/server.properties")
with open("minecraft/server.properties", "a") as f:
f.write(f"level-name=data/{cfg["minecraft"]["world"]}")
def run(cmds):
try:
subprocess.check_output(["ssh", f"core@{cfg["core"]["hostname"]}.local", ";".join(cmds)], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
print("\033[31m", end="")
print(e.output.decode())
print("\033[0m", end="")
exit(1)
if __name__ == "__main__":
cfg = json.load(open("config/server.json"))
# generate helper files
generate(cfg)
# copy files
for f in (f for l in IMAGES.values() for f in l):
subprocess.run(["scp", "-r", f, f"core@{cfg["core"]["hostname"]}.local:{SOURCE_DIR}"], check=True)
# run builds
for user in IMAGES:
print(f"building images for {user}...")
run([f"cd {SOURCE_DIR}"] + [
f"sudo -u {user} podman build --tag {i} {SOURCE_DIR}/{i}"
for i in IMAGES[user]
])
# restart pods
for user in IMAGES:
print(f"restarting pod for {user}...")
run([
f"cd {SOURCE_DIR}",
f"sudo systemctl --machine={user}@.host --user restart {user}-pod " + " ".join(IMAGES[user]),
])