diff --git a/.gitignore b/.gitignore index e6289d8..55af0fd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__ config/server.json config/*.bu config/*.ign +config/*.iso # website website/sendgrid.key diff --git a/README.md b/README.md index c3bc2e1..2981749 100644 --- a/README.md +++ b/README.md @@ -14,47 +14,34 @@ Services deployed on [matthewtran.com](https://matthewtran.com). ## setup -1. Designate one computer as the configuration server. Create `config/server.json` which contains the configuration for the server to be provisioned. Reference `config/server.default` for fields. Run the following. - - `config/provision.py` -2. Create a [Fedora CoreOS](https://fedoraproject.org/coreos/download?stream=stable) installation media and boot it on the server to be provisioned. Run the following on it and reboot. - - `sudo coreos-installer install /dev/ --ignition-url http:///server.ign --insecure-ignition` - -## update - -quick dev => scp dockerfiles => rebuild locally -final dev => reprovision + wipe home so images rebuilds -logs => sudo -u game podman logs container - - -1. Install [Ubuntu Desktop 24.04.1 LTS](https://ubuntu.com/download/desktop) with TPM-backed FDE. Server currently has a [bug](https://bugs.launchpad.net/ubuntu/+source/cryptsetup/+bug/1980018) that makes TPM-backed FDE hard. - - You may need to manually enable IPv6 on the network connection. Use `Automatic` not `Automatic, DHCP only`. - - Add an SSH key if you need remote access, setup will disable password authentication. - - Clone this repo and `cd` into it. -2. Set up the server. - - `scripts/setup_server.py` -3. Set up the OpenWrt 24.10 router. Copy SSH keys first to make it easier. Use a strong root password. - - `scripts/setup_router.py ` -4. Reboot the router and server. -5. Configure, build, and start services. - - Create `website/sendgrid.key` with a [SendGrid API key](https://app.sendgrid.com/settings/api_keys). - - Create `terraria/config.txt` and `terraria/password.txt` if needed. - - Create `nas/mounts.json` which contains a list of `"":""` for the SMB share. - - Create `nas/users.json` which contains a list of `"":""` for the SMB share. - - `scripts/setup_repo.py` - - Restore backups if needed. Make sure to set correct ownership. For example, `chown -R 2000:2000 website/gitea`. - - `docker compose build` - - `docker compose up -d` -6. Optionally, add additional drives. This script formats the drive as LUKS/BTRFS with the key file stored in `/opt/luks` and auto-mounts on boot. Make sure to backup the key file elsewhere. - - `scripts/setup_drive.py ` -7. Optionally, run `scripts/setup_peer.py ` for each WireGuard client. -8. Optionally, add the following DNS entries at the registrar. +1. Create `config/server.json` and run `config/provision.py`. +2. On the server to be provisioned, boot a [Fedora CoreOS installation media](https://fedoraproject.org/coreos/download?stream=stable) and run the install command. +3. To configure the OpenWrt router, run `/opt/router.py --provision ` on the server. Then reboot the router and server. +4. Add the following DNS entries at the registrar. | hosts | type | data | | ----------------------- | ------ | ------------------------ | | `@`, `git`, `wg`, `www` | `A` | `` | | `@`, `git`, `www` | `AAAA` | `::69` | | `wg` | `AAAA` | `::1` | +5. Optionally, run `config/peer.py` for each WireGuard client. -## backup +## development + +1. For quick iteration, run `config/update.py`. This copies over sources, rebuilds images, and restarts containers. +2. After development, it's best to reprovision (see above) with `wipe=false` for drives you want to keep. Then run `/opt/router.py` on the server and reboot. + + + + + + + + + +## maintenance + +logs => sudo -u game podman logs container +TODO mdadm status? backup? Run `scripts/backup.py` and save the resultant `data.zip` somewhere. Also run the following commands for BTRFS maintenance. I should probably automate this. ``` diff --git a/scripts/backup.py b/config/backup.py similarity index 65% rename from scripts/backup.py rename to config/backup.py index b0caf3c..ba18982 100755 --- a/scripts/backup.py +++ b/config/backup.py @@ -1,4 +1,4 @@ -#!/usr/bin/sudo /usr/bin/python3 +#!/usr/bin/env python3 import os import shutil @@ -14,3 +14,10 @@ if __name__ == "__main__": "website/gitea", ], check=True) shutil.chown(out, os.getlogin(), os.getlogin()) + +# TODO backup and scp +# TODO restore + # fix permissions + # no wipe folders we didn't save + # may need to chown 777 for gitea +# TODO router wipe + peer.py diff --git a/scripts/setup_peer.py b/config/peer.py similarity index 97% rename from scripts/setup_peer.py rename to config/peer.py index d96c982..6bbab9c 100755 --- a/scripts/setup_peer.py +++ b/config/peer.py @@ -3,7 +3,7 @@ import subprocess import sys from ipaddress import ip_address, ip_network -from setup_router import WG_IPV4, WG_IPV6, run, key +from router import WG_IPV4, WG_IPV6, run, key def ips(): try: diff --git a/config/provision.py b/config/provision.py index 0b824f2..40c7391 100755 --- a/config/provision.py +++ b/config/provision.py @@ -2,7 +2,11 @@ import base64 import json +import http.server +import os import secrets +import socket +import socketserver import subprocess import yaml from pathlib import Path @@ -40,8 +44,8 @@ PORTS = { } 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")}"') + if "stash_key" not in cfg["core"]: + print(f'cfg["core"]["stash_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: @@ -58,11 +62,11 @@ def add_root_drive(): { "number": 4, "label": "root", - "size_mib": 16384, + "size_mib": 65536, "resize": True, }, { - "label": "home", + "label": "stash", "size_mib": 0, }, ], @@ -78,10 +82,10 @@ def add_root_drive(): "clevis": { "tpm2": True }, }, { - "name": "home", - "device": "/dev/disk/by-partlabel/home", - "wipe_volume": cfg["core"]["home_wipe"], - "key_file": { "inline": base64.b64decode(cfg["core"]["home_key"]) }, + "name": "stash", + "device": "/dev/disk/by-partlabel/stash", + "wipe_volume": cfg["core"]["stash_wipe"], + "key_file": { "inline": base64.b64decode(cfg["core"]["stash_key"]) }, }, ], "filesystems": [ @@ -92,14 +96,20 @@ def add_root_drive(): "label": "root", }, { - "path": "/var/home", - "device": "/dev/mapper/home", - "format": "xfs", - "wipe_filesystem": cfg["core"]["home_wipe"], + "path": "/var/mnt/stash", + "device": "/dev/mapper/stash", + "format": "ext4", + "wipe_filesystem": cfg["core"]["stash_wipe"], "with_mount_unit": True, }, ], - "directories": [], + "directories": [ + { + "path": f"/var/mnt/stash", + "user": { "name": "core" }, + "group": { "name": "core" }, + }, + ], "files": [], } @@ -152,6 +162,7 @@ def add_packages(): "ExecStart=/usr/bin/rpm-ostree install -y --allow-inactive " + " ".join([ "avahi", "htop", + "python3", "vim", ]), "ExecStart=/bin/touch /etc/rpm/%N.stamp", @@ -228,6 +239,11 @@ def copy_source(): "user": { "name": user }, "group": { "name": user }, }) + but["storage"]["files"].append({ + "path": "/var/opt/router.py", + "mode": 0o755, + "contents": { "inline": open("config/router.py", "rb").read() }, + }) def build_images(): but["storage"]["directories"].append({ "path": "/etc/containers/systemd/users" }) @@ -365,17 +381,17 @@ if __name__ == "__main__": run_containers() advertise_services() - - # TODO update router scripts bc DUID => make fixed?? - # TODO script to backup => restore backup if desired => fix permissions - # may need to chown 777 for gitea restore - # TODO generate ISO, else nginx if --insecure - # TODO full wipe test (wipefs all) => check folder permissions secure - - + # generate ignition file 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.") + # host ignition file + ip = socket.gethostbyname(socket.gethostname()) print("WARNING - Using unencrypted connections without authentication, ensure LAN is secure!") + print("NOTE - TPM may need to be cleared after enough provisions.") + print(f"NOTE - Run \"sudo coreos-installer install /dev/ --ignition-url http://{ip}/server.ign --insecure-ignition\"") + print("NOTE - Starting HTTP server, ctrl-c to exit...") + os.chdir("config") + with socketserver.TCPServer(("", 80), http.server.SimpleHTTPRequestHandler) as httpd: + httpd.serve_forever() diff --git a/scripts/setup_router.py b/config/router.py similarity index 88% rename from scripts/setup_router.py rename to config/router.py index 633c71e..d0fb066 100755 --- a/scripts/setup_router.py +++ b/config/router.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 +import argparse import hashlib import subprocess -import sys DP_LEN = 64 # xfinity delegated prefix length WRT_ULA = "fd16:8f4d:f516::" # OpenWrt random @@ -30,7 +30,19 @@ def key(): return (pub.decode("utf-8"), priv.decode("utf-8")) if __name__ == "__main__": - ETH = sys.argv[1] # e.g. enp5s0 + parser = argparse.ArgumentParser() + parser.add_argument("--provision", type=str, help="network interface to set static IP for, e.g. enp5s0") + args = parser.parse_args() + + # update static IP + if args.provision is None: + run([ + f"uci set dhcp.@host[0].duid='{duid()}'", + "uci commit dhcp", + "service dnsmasq restart", + "service odhcpd restart", + ]) + exit(0) # basic setup run([ @@ -43,8 +55,7 @@ if __name__ == "__main__": # static IP run([ "uci add dhcp host", - "uci set dhcp.@host[-1].name='matt-ryzen'", - f"uci set dhcp.@host[-1].mac='{mac(ETH)}'", + f"uci set dhcp.@host[-1].mac='{mac(args.provision)}'", f"uci set dhcp.@host[-1].ip='{IPV4}'", f"uci set dhcp.@host[-1].duid='{duid()}'", f"uci set dhcp.@host[-1].hostid='{IPV6}'", diff --git a/config/server.default b/config/server.example similarity index 93% rename from config/server.default rename to config/server.example index def418c..f067725 100644 --- a/config/server.default +++ b/config/server.example @@ -4,8 +4,8 @@ "ssh_keys": [ "ssh-ed25519 AAAA..." ], - "home_key": "", - "home_wipe": false, + "stash_key": "", + "stash_wipe": false, "data_dir": "/var/home/core/matthewtrancom_data" }, "drives": [ diff --git a/website/entry.sh b/website/entry.sh index 834267e..2b9cbf4 100644 --- a/website/entry.sh +++ b/website/entry.sh @@ -19,7 +19,7 @@ update() { sleep 86400 } update & -./ip.py & +python3 ip.py & # run server nginx -c ~/server.conf