diff --git a/.gitignore b/.gitignore index c28a8b8..c631eb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,26 @@ .DS_Store __pycache__ -compose.override.yml +*.bak +*.zip + +# config +config/server.json +config/*.bu +config/*.ign +config/*.iso # website -website/gitea -website/certbot website/sendgrid.key -# monerod -monerod/.bitmonero - -# p2pool -p2pool/cache - # minecraft -minecraft/world* +minecraft/server.properties # minecraft_bedrock -minecraft_bedrock/worlds* +minecraft_bedrock/server.properties # terraria -terraria/worlds -terraria/mods -terraria/config.txt -terraria/password.txt +terraria/*.txt # nas -nas/*.json +nas/Dockerfile nas/smb.conf -nas/users.sh - -# backup -data.zip diff --git a/README.md b/README.md index ce2c95a..f52ecd2 100644 --- a/README.md +++ b/README.md @@ -7,49 +7,31 @@ Services deployed on [matthewtran.com](https://matthewtran.com). - monerod - p2pool (`xmrig -o matthewtran.com`) - minecraft -- ~~minecraft bedrock~~ +- minecraft bedrock - terraria - nas (`/` on LAN) - wireguard ## setup -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 -Run `scripts/backup.py` and save the resultant `data.zip` somewhere. Also run the following commands for BTRFS maintenance. I should probably automate this. -``` -btrfs device stats -btrfs scrub start -B -``` +- For quick iteration, run `config/update.py`. This copies over sources, rebuilds images, and restarts containers. +- 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. -## security +## maintenance -To protect against vulnerabilities, all services run as non-root users inside containers that are on separate networks by function and have all capabilities dropped. These non-root users have a UID that doesn't exist on the host and a GID that maps to their function. Hopefully, even in the event of a full container compromise and root escalation, there is little damage an attacker can do. The main security hole left is containers accessing the LAN and host, AppArmor might help with this. +- Run `config/backup.py .zip` to back up critical files. +- Run `config/backup.py --restore .zip` to restore those files. +- Run `sudo mdadm -D /dev/md/` on the server to check RAID status. diff --git a/compose.yml b/compose.yml deleted file mode 100644 index 5a58a0e..0000000 --- a/compose.yml +++ /dev/null @@ -1,134 +0,0 @@ -networks: - web: - enable_ipv6: true - ipam: - config: - - subnet: "172.20.0.0/16" - - subnet: "fd3a:138e:8fd0:0020::/64" - monero: - enable_ipv6: true - ipam: - config: - - subnet: "172.21.0.0/16" - - subnet: "fd3a:138e:8fd0:0021::/64" - game: - enable_ipv6: true - ipam: - config: - - subnet: "172.22.0.0/16" - - subnet: "fd3a:138e:8fd0:0022::/64" - nas: - enable_ipv6: true - ipam: - config: - - subnet: "172.23.0.0/16" - - subnet: "fd3a:138e:8fd0:0023::/64" -services: - website: - restart: always - build: website/. - entrypoint: ["/bin/sh", "/home/me/entry.sh"] - ports: - - "80:8080" - - "443:8443" - networks: - - web - volumes: - - ./website/certbot:/home/me/certbot - cap_drop: - - ALL - gitea: - restart: always - image: gitea/gitea:latest-rootless - user: "2000:2000" - ports: - - "2222:2222" - networks: - - web - volumes: - - ./website/gitea/data:/var/lib/gitea - - ./website/gitea/config:/etc/gitea - - /etc/timezone:/etc/timezone:ro - - /etc/localtime:/etc/localtime:ro - cap_drop: - - ALL - monerod: - restart: always - build: monerod/. - entrypoint: ["/bin/sh", "/home/me/entry.sh"] - stdin_open: true - tty: true - ports: - - "18080:18080" - - "18081:18081" - networks: - - monero - volumes: - - ./monerod/.bitmonero:/home/me/.bitmonero - cap_drop: - - ALL - p2pool: - stop_grace_period: 1m # TODO reduce m_shutdownCountdown to reduce this - restart: always - build: p2pool/. - entrypoint: ["/bin/sh", "/home/me/entry.sh"] - stdin_open: true - tty: true - ports: - - "3333:3333" - - "37888:37888" - - "37889:37889" - networks: - - monero - volumes: - - ./p2pool/cache:/home/me/cache - cap_drop: - - ALL - minecraft: - restart: always - build: minecraft/. - entrypoint: ["/bin/sh", "/home/me/entry.sh"] - ports: - - "25565:25565" - networks: - - game - volumes: - - ./minecraft/worlds:/home/me/worlds - cap_drop: - - ALL - # minecraft_bedrock: - # restart: always - # build: minecraft_bedrock/. - # entrypoint: ["/bin/sh", "/home/me/entry.sh"] - # ports: - # - "19132:19132/udp" - # - "19133:19133/udp" - # networks: - # - game - # volumes: - # - ./minecraft_bedrock/worlds:/home/me/worlds - # cap_drop: - # - ALL - terraria: - restart: always - build: terraria/. - entrypoint: ["/usr/bin/python3", "/home/me/entry.py"] - ports: - - "7777:7777" - networks: - - game - volumes: - - ./terraria/worlds:/home/me/worlds - - ./terraria/mods:/home/me/mods - cap_drop: - - ALL - nas: - restart: always - build: nas/. - entrypoint: ["/bin/sh", "/home/me/entry.sh"] - ports: - - "445:8445" - networks: - - nas - cap_drop: - - ALL diff --git a/config/backup.py b/config/backup.py new file mode 100755 index 0000000..0b8d5de --- /dev/null +++ b/config/backup.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +import argparse +import json +import subprocess + +BACKUPS = { + "web": [ + "gitea", + ], + "game": [ + "minecraft", + "minecraft_bedrock", + "terraria", + ], +} + +def run(cmds): + subprocess.run(["ssh", remote, ";".join(cmds)], check=True) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--restore", action="store_true", help="restore zip instead of saving to it") + parser.add_argument("file", type=str) + args = parser.parse_args() + + cfg = json.load(open("config/server.json")) + remote = f"core@{cfg["core"]["hostname"]}.local" + if args.restore: + # stop needed containers + run([ + f"sudo systemctl --machine={user}@.host --user stop " + " ".join(BACKUPS[user]) + for user in BACKUPS + ]) + + # restore backup + subprocess.run(["scp", args.file, f"{remote}:{cfg["core"]["data_dir"]}/data.zip"], check=True) + run([ + f"cd {cfg["core"]["data_dir"]}", + "sudo rm -rf " + " ".join([ + " ".join([f"{user}/{img}" for img in BACKUPS[user]]) + for user in BACKUPS + ]), + "unzip data.zip", + "rm data.zip", + ]) + + # fix permissions + run([f"cd {cfg["core"]["data_dir"]}"] + [ + f"sudo chown -R {user}:{user} " + " ".join([f"{user}/{img}" for img in BACKUPS[user]]) + for user in BACKUPS + ]) + + # restart needed containers + run([ + f"sudo systemctl --machine={user}@.host --user restart {user}-pod " + " ".join(BACKUPS[user]) + for user in BACKUPS + ]) + else: + run([ + f"cd {cfg["core"]["data_dir"]}", + f"sudo zip -FS -r /var/mnt/stash/data.zip " + " ".join([ + " ".join([f"{user}/{img}" for img in BACKUPS[user]]) + for user in BACKUPS + ]), + ]) + subprocess.run(["scp", f"{remote}:/var/mnt/stash/data.zip", args.file], check=True) 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 new file mode 100755 index 0000000..0d04223 --- /dev/null +++ b/config/provision.py @@ -0,0 +1,398 @@ +#!/usr/bin/env python3 + +import base64 +import json +import http.server +import os +import secrets +import socket +import socketserver +import subprocess +import yaml +from pathlib import Path +from update import SOURCE_DIR, IMAGES, generate + +UIDS = { + "web" : 1001, + "monero" : 1002, + "game" : 1003, + "nas" : 1004, +} + +PORTS = { + "web": [ + "80:80", # website + "443:443", + "2222:22", # gitea + ], + "monero": [ + "18080:18080", # monerod + "18081:18081", + "3333:3333", # p2pool + "37888:37888", + "37889:37889", + ], + "game": [ + "25565:25565", # minecraft + "19132:19132/udp", # minecraft_bedrock + "19133:19133/udp", + "7777:7777", # terraria + ], + "nas": [ + "445:445", # nas + ], +} + +def check_keys(): + 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: + print(f'cfg["drives"][{i}]["key"] doesn\'t exist, try "{base64.b64encode(secrets.token_bytes(64)).decode("utf-8")}"') + exit(1) + +def add_root_drive(): + but["storage"] = { + "disks": [ + { + "device": "/dev/disk/by-id/coreos-boot-disk", + "wipe_table": False, + "partitions": [ + { + "number": 4, + "label": "root", + "size_mib": 65536, + "resize": True, + }, + { + "label": "stash", + "size_mib": 0, + }, + ], + }, + ], + "raid": [], + "luks": [ + { + "name": "root", + "label": "luks-root", + "device": "/dev/disk/by-partlabel/root", + "wipe_volume": True, + "clevis": { "tpm2": True }, + }, + { + "name": "stash", + "device": "/dev/disk/by-partlabel/stash", + "wipe_volume": cfg["core"]["stash_wipe"], + "key_file": { "inline": base64.b64decode(cfg["core"]["stash_key"]) }, + }, + ], + "filesystems": [ + { + "device": "/dev/mapper/root", + "format": "xfs", + "wipe_filesystem": True, + "label": "root", + }, + { + "path": "/var/mnt/stash", + "device": "/dev/mapper/stash", + "format": "ext4", + "wipe_filesystem": cfg["core"]["stash_wipe"], + "with_mount_unit": True, + }, + ], + "directories": [ + { + "path": f"/var/mnt/stash", + "user": { "name": "core" }, + "group": { "name": "core" }, + }, + ], + "files": [], + } + +def add_more_drive(): + for d in cfg["drives"]: + raid = len(d["devices"]) > 1 + if raid: + but["storage"]["raid"].append({ + "name": d["name"], + "level": "raid1", + "devices": d["devices"], + }) + but["storage"]["luks"].append({ + "name": d["name"], + "device": f"/dev/md/{d["name"]}" if raid else d["devices"][0], + "wipe_volume": d["wipe"], + "key_file": { "inline": base64.b64decode(d["key"]) }, + }) + but["storage"]["filesystems"].append({ + "path": f"/var/mnt/{d["name"]}", + "device": f"/dev/mapper/{d["name"]}", + "format": "ext4", + "wipe_filesystem": d["wipe"], + "with_mount_unit": True, + }) + but["storage"]["directories"].append({ + "path": f"/var/mnt/{d["name"]}", + "user": { "name": "core" }, + "group": { "name": "core" }, + }) + +def add_packages(): + # TODO update once done https://github.com/coreos/fedora-coreos-tracker/issues/681 + but["systemd"] = { + "units": [ + { + "name": "rpm-ostree-install.service", + "enabled": True, + "contents": "\n".join([ + "[Unit]", + "Description=Install packages", + "Wants=network-online.target", + "After=network-online.target", + "Before=zincati.service", + "ConditionPathExists=!/etc/rpm/%N.stamp", + "[Service]", + "Type=oneshot", + "RemainAfterExit=yes", + f"ExecStart=/usr/bin/usermod -a -G {",".join(UIDS.keys())} core", + "ExecStart=/usr/bin/rpm-ostree install -y --allow-inactive " + " ".join([ + "avahi", + "htop", + "python3", + "vim", + "zip", + ]), + "ExecStart=/bin/touch /etc/rpm/%N.stamp", + "ExecStart=/bin/systemctl --no-block reboot", + "[Install]", + "WantedBy=multi-user.target", + ]), + }, + ], + } + +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"] }, + }) + +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], + "ssh_authorized_keys": cfg["core"]["ssh_keys"], + }) + but["storage"]["files"].append({ + "path": f"/var/lib/systemd/linger/{user}", + "contents": { "inline": "" }, + }) + +def copy_source(): + but["storage"]["directories"].append({ + "path": SOURCE_DIR, + "user": { "name": "core" }, + "group": { "name": "core" }, + }) + for user in IMAGES: + for img in IMAGES[user]: + but["storage"]["directories"].append({ + "path": str(Path(SOURCE_DIR) / img), + "mode": 0o770, + "user": { "name": user }, + "group": { "name": user }, + }) + for f in Path(img).glob("**/*"): + if f.is_dir(): + but["storage"]["directories"].append({ + "path": str(Path(SOURCE_DIR) / f), + "user": { "name": user }, + "group": { "name": user }, + }) + else: + but["storage"]["files"].append({ + "path": str(Path(SOURCE_DIR) / f), + "contents": { "inline": open(f, "rb").read() }, + "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" }) + 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": "\n".join([ + "[Pod]", + *[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: + but["storage"]["directories"].append({ + "path": str(Path(cfg["core"]["data_dir"]) / user), + "mode": 0o770, + "user": { "name": user }, + "group": { "name": user }, + }) + for img in IMAGES[user]: + but["storage"]["directories"].append({ + "path": str(Path(cfg["core"]["data_dir"]) / user / img), + "user": { "name": user }, + "group": { "name": user }, + }) + for mnt in cfg["nas"]["mounts"]: + but["storage"]["directories"].append({ + "path": str(Path(cfg["nas"]["mounts"][mnt]) / "share"), + "mode": 0o770, + "user": { "name": "nas" }, + "group": { "name": "nas" }, + }) + +def run_containers(): + for user in IMAGES: + for img in IMAGES[user]: + env = [] + if img == "gitea": + env.extend([ + "Environment=GITEA__server__SSH_PORT=2222", + "Environment=GITEA__service__DISABLE_REGISTRATION=true", + "Environment=GITEA__openid__ENABLE_OPENID_SIGNIN=false", + "Environment=GITEA__openid__ENABLE_OPENID_SIGNUP=false", + ]) + + vols = [f"Volume={str(Path(cfg["core"]["data_dir"]) / user / img)}:/data:z"] + if user == "nas": + vols.extend([ + f"Volume={str(Path(cfg["nas"]["mounts"][mnt]) / "share")}:/mnt/{mnt}:z" + for mnt in cfg["nas"]["mounts"] + ]) + + 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", + *env, + *vols, + "[Install]", + "WantedBy=default.target", + ])} + }) + +def advertise_services(): + but["storage"]["directories"].extend([ + { "path": "/etc/avahi" }, + { "path": "/etc/avahi/services" }, + ]) + for mnt in cfg["nas"]["mounts"]: + but["storage"]["files"].append({ + "path": f"/etc/avahi/services/nas-{mnt}.service", + "contents": { "inline": "\n".join([ + "", + "", + "", + f" %h - {mnt}", + " ", + " _smb._tcp", + " 445", + " ", + " ", + " _adisk._tcp", + f" dk0=adVN={mnt},adVF=0x82", + " sys=waMa=0,adVF=0x100", + " ", + "", + ])} + }) + +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() + advertise_services() + + # 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"]) + + # 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.example b/config/server.example new file mode 100644 index 0000000..f067725 --- /dev/null +++ b/config/server.example @@ -0,0 +1,44 @@ +{ + "core": { + "hostname": "", + "ssh_keys": [ + "ssh-ed25519 AAAA..." + ], + "stash_key": "", + "stash_wipe": false, + "data_dir": "/var/home/core/matthewtrancom_data" + }, + "drives": [ + { + "devices": ["/dev/sda"], + "key": "", + "name": "stuff", + "wipe": false + } + ], + "website": { + "sendgrid_key": "" + }, + "minecraft": { + "world": "main" + }, + "minecraft_bedrock": { + "world": "main" + }, + "terraria": { + "password": "password", + "world": "main", + "autogen": { + "size": 3, + "difficulty": 2 + } + }, + "nas": { + "users": { + "matt": "password" + }, + "mounts": { + "stuff": "/var/mnt/stuff" + } + } +} \ No newline at end of file diff --git a/config/update.py b/config/update.py new file mode 100755 index 0000000..cf83a99 --- /dev/null +++ b/config/update.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 + +import json +import shutil +import subprocess + +SOURCE_DIR = "/var/source" + +IMAGES = { + "web": [ + "website", + "gitea", + ], + "monero": [ + "monerod", + "p2pool", + ], + "game": [ + "minecraft", + "minecraft_bedrock", + "terraria", + ], + "nas": [ + "nas", + ], +} + +def generate(cfg): + # website + with open("website/sendgrid.key", "w") as f: + f.write(cfg["website"]["sendgrid_key"]) + + # 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"]}\n") + + # minecraft_bedrock + shutil.copy("minecraft_bedrock/server.default", "minecraft_bedrock/server.properties") + with open("minecraft_bedrock/server.properties", "a") as f: + f.write(f"level-name={cfg["minecraft_bedrock"]["world"]}\n") + + # terraria + shutil.copy("terraria/config.default", "terraria/config.txt") + with open("terraria/config.txt", "a") as f: + f.write(f"world=/data/worlds/{cfg["terraria"]["world"]}.wld\n") + f.write(f"autocreate={cfg["terraria"]["autogen"]["size"]}\n") # 1=small, 2=medium, 3=large + f.write(f"difficulty={cfg["terraria"]["autogen"]["difficulty"]}\n") # 0=normal, 1=expert, 2=master, 3=journey + with open("terraria/password.txt", "w") as f: + f.write(cfg["terraria"]["password"]) + + # nas + shutil.copy("nas/Dockerfile.template", "nas/Dockerfile") + shutil.copy("nas/smb.conf.template", "nas/smb.conf") + with open("nas/Dockerfile", "a") as f: + for user in cfg["nas"]["users"]: + p = cfg["nas"]["users"][user] + f.write(f"RUN useradd -M -s /bin/false {user}\n") + f.write(f"RUN echo \"{p}\\n{p}\\n\" | pdbedit -s smb.conf -a {user}\n") + with open("nas/smb.conf", "a") as f: + for mnt in cfg["nas"]["mounts"]: + f.write(f"[{mnt}]\n") + f.write(f"path = /mnt/{mnt}\n\n") + +def run(cmds, user="core"): + try: + subprocess.check_output(["ssh", f"{user}@{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 user in IMAGES: + for img in IMAGES[user]: + subprocess.run(["scp", "-r", img, f"{user}@{cfg["core"]["hostname"]}.local:{SOURCE_DIR}"], check=True) + run([f"chmod 770 {SOURCE_DIR}/{img}"], user=user) + + # 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]), + ]) diff --git a/gitea/Dockerfile b/gitea/Dockerfile new file mode 100644 index 0000000..0a0d43b --- /dev/null +++ b/gitea/Dockerfile @@ -0,0 +1 @@ +FROM docker.io/gitea/gitea:1.23.7 diff --git a/minecraft/.dockerignore b/minecraft/.dockerignore deleted file mode 100644 index 6a2fdbd..0000000 --- a/minecraft/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -worlds/ diff --git a/minecraft/Dockerfile b/minecraft/Dockerfile index f9605dd..d761d0f 100644 --- a/minecraft/Dockerfile +++ b/minecraft/Dockerfile @@ -3,18 +3,18 @@ FROM ubuntu:24.04 RUN apt-get update && apt-get -y upgrade RUN apt-get install -y wget openjdk-21-jre -RUN groupadd -g 2002 me && useradd -u 2002 -g 2002 -m me -USER me -WORKDIR /home/me +WORKDIR /root # from https://github.com/itzg/rcon-cli -RUN wget -O rcon-cli.tar.gz https://github.com/itzg/rcon-cli/releases/download/1.6.9/rcon-cli_1.6.9_linux_amd64.tar.gz +RUN wget -O rcon-cli.tar.gz https://github.com/itzg/rcon-cli/releases/download/1.7.0/rcon-cli_1.7.0_linux_amd64.tar.gz RUN tar xvf rcon-cli.tar.gz && rm rcon-cli.tar.gz -# from https://www.minecraft.net/en-us/download/server (currently 1.21.4) -RUN wget https://piston-data.mojang.com/v1/objects/4707d00eb834b446575d89a61a11b5d548d8c001/server.jar +# from https://www.minecraft.net/en-us/download/server (currently 1.21.5) +RUN wget https://piston-data.mojang.com/v1/objects/e6ec2f64e6080b9b5d9b471b291c33cc7f509733/server.jar -COPY --chown=me:me eula.txt ./ -COPY --chown=me:me entry.sh ./ -COPY --chown=me:me server.properties ./ -COPY --chown=me:me ops.json ./ +COPY entry.sh ./ +COPY eula.txt ./ +COPY ops.json ./ +COPY server.properties ./ + +CMD ["/bin/bash", "/root/entry.sh"] diff --git a/minecraft/entry.sh b/minecraft/entry.sh index 14eeb2d..fab019d 100644 --- a/minecraft/entry.sh +++ b/minecraft/entry.sh @@ -4,7 +4,7 @@ cleanup() { ./rcon-cli --password password stop } -trap 'cleanup' TERM +trap 'cleanup' SIGTERM SIGINT java -Xmx1024M -Xms1024M -jar server.jar nogui & wait $! # wait for SIGTERM diff --git a/minecraft/server.properties b/minecraft/server.default similarity index 98% rename from minecraft/server.properties rename to minecraft/server.default index 99bb9e6..569c3cc 100644 --- a/minecraft/server.properties +++ b/minecraft/server.default @@ -20,7 +20,6 @@ hardcore=true hide-online-players=false initial-disabled-packs= initial-enabled-packs=vanilla -level-name=worlds/main level-seed= level-type=minecraft\:normal max-chained-neighbor-updates=1000000 diff --git a/minecraft_bedrock/.dockerignore b/minecraft_bedrock/.dockerignore deleted file mode 100644 index 6a2fdbd..0000000 --- a/minecraft_bedrock/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -worlds/ diff --git a/minecraft_bedrock/Dockerfile b/minecraft_bedrock/Dockerfile index 824c1a8..4d280b1 100644 --- a/minecraft_bedrock/Dockerfile +++ b/minecraft_bedrock/Dockerfile @@ -3,14 +3,15 @@ FROM ubuntu:24.04 RUN apt-get update && apt-get -y upgrade RUN apt-get install -y wget unzip curl tmux -RUN groupadd -g 2002 me && useradd -u 2002 -g 2002 -m me -USER me -WORKDIR /home/me +WORKDIR /root -# from https://www.minecraft.net/en-us/download/server/bedrock (currently 1.21.61.01) -RUN wget -O server.zip --user-agent "Mozilla/5.0" https://www.minecraft.net/bedrockdedicatedserver/bin-linux/bedrock-server-1.21.61.01.zip +# from https://www.minecraft.net/en-us/download/server/bedrock (currently 1.21.73.01) +RUN wget -O server.zip --user-agent "Mozilla/5.0" https://www.minecraft.net/bedrockdedicatedserver/bin-linux/bedrock-server-1.21.73.01.zip RUN unzip server.zip && rm server.zip -COPY --chown=me:me entry.sh ./ -COPY --chown=me:me server.properties ./ -COPY --chown=me:me permissions.json ./ +COPY entry.sh ./ +COPY permissions.json ./ +COPY server.properties ./ + +RUN ln -s /data /root/worlds +CMD ["/bin/bash", "/root/entry.sh"] diff --git a/minecraft_bedrock/entry.sh b/minecraft_bedrock/entry.sh index 9a86145..e6ba9ae 100644 --- a/minecraft_bedrock/entry.sh +++ b/minecraft_bedrock/entry.sh @@ -4,9 +4,8 @@ cleanup() { tmux send-keys stop Enter } -trap 'cleanup' TERM +trap 'cleanup' SIGTERM SIGINT -rm log mkfifo log tmux new -d 'LD_LIBRARY_PATH=. ./bedrock_server > log' cat log & diff --git a/minecraft_bedrock/server.properties b/minecraft_bedrock/server.default similarity index 98% rename from minecraft_bedrock/server.properties rename to minecraft_bedrock/server.default index 4d1ba3b..a5b05f7 100644 --- a/minecraft_bedrock/server.properties +++ b/minecraft_bedrock/server.default @@ -13,7 +13,6 @@ view-distance=32 tick-distance=4 player-idle-timeout=0 max-threads=4 -level-name=test level-seed= default-player-permission-level=visitor texturepack-required=false diff --git a/monerod/.dockerignore b/monerod/.dockerignore deleted file mode 100644 index 64677db..0000000 --- a/monerod/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -.bitmonero diff --git a/monerod/Dockerfile b/monerod/Dockerfile index 2cc768f..b17a8ad 100644 --- a/monerod/Dockerfile +++ b/monerod/Dockerfile @@ -3,12 +3,12 @@ FROM ubuntu:24.04 RUN apt-get update && apt-get -y upgrade RUN apt-get install -y wget bzip2 -RUN groupadd -g 2001 me && useradd -u 2001 -g 2001 -m me -USER me -WORKDIR /home/me +WORKDIR /root -RUN wget https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.3.4.tar.bz2 -O monerod.tar.bz2 +RUN wget https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.4.0.tar.bz2 -O monerod.tar.bz2 RUN tar xvf monerod.tar.bz2 && rm monerod.tar.bz2 -RUN mv monero-x86_64-linux-gnu-v0.18.3.4 monero +RUN mv monero-x86_64-linux-gnu-v0.18.4.0 monero -COPY --chown=me:me entry.sh ./ +COPY entry.sh ./ + +CMD ["/bin/bash", "/root/entry.sh"] diff --git a/monerod/entry.sh b/monerod/entry.sh index be29ffc..99b6bbe 100644 --- a/monerod/entry.sh +++ b/monerod/entry.sh @@ -3,6 +3,7 @@ # check bitmonero.log for log monero/monerod \ --prune-blockchain \ + --data-dir /data \ --rpc-bind-port 18089 \ --rpc-restricted-bind-ip 0.0.0.0 \ --rpc-restricted-bind-port 18081 \ @@ -17,6 +18,6 @@ monero/monerod \ cleanup() { monero/monerod exit --rpc-bind-port 18089 } -trap 'cleanup' TERM +trap 'cleanup' SIGTERM SIGINT tail -f /dev/null & wait $! diff --git a/nas/Dockerfile b/nas/Dockerfile deleted file mode 100644 index 46ab3da..0000000 --- a/nas/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM ubuntu:24.04 - -ENV TZ=America/Los_Angeles -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN apt-get update && apt-get -y upgrade -RUN apt-get install -y samba - -# create required files and user -RUN groupadd -g 2003 me && useradd -u 2003 -g 2003 -m me -USER me -WORKDIR /home/me -RUN mkdir share samba samba/log samba/lock samba/state samba/cache samba/pid samba/private samba/ncalrpc -COPY --chown=me:me smb.conf entry.sh ./ - -# create additional users -USER root -COPY users.sh ./ -RUN /bin/sh users.sh && rm users.sh -USER me diff --git a/nas/Dockerfile.template b/nas/Dockerfile.template new file mode 100644 index 0000000..04f8bfb --- /dev/null +++ b/nas/Dockerfile.template @@ -0,0 +1,15 @@ +FROM ubuntu:24.04 + +ENV TZ=America/Los_Angeles +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN apt-get update && apt-get -y upgrade +RUN apt-get install -y samba + +WORKDIR /root + +COPY smb.conf entry.sh ./ + +CMD ["/bin/bash", "/root/entry.sh"] + +# create users diff --git a/nas/entry.sh b/nas/entry.sh index 35f285c..4311ace 100644 --- a/nas/entry.sh +++ b/nas/entry.sh @@ -1,6 +1,6 @@ #!/bin/sh -smbd -s smb.conf -l=/home/me/samba/log -trap 'echo "stopping smbd..."' TERM +smbd -s smb.conf +trap 'echo "stopping smbd..."' SIGTERM SIGINT tail -f /dev/null & wait $! diff --git a/nas/base.conf b/nas/smb.conf.template similarity index 77% rename from nas/base.conf rename to nas/smb.conf.template index bbe17c1..c496f9d 100644 --- a/nas/base.conf +++ b/nas/smb.conf.template @@ -1,6 +1,6 @@ [global] workgroup = WORKGROUP -smb ports = 8445 +smb ports = 445 load printers = no disable spoolss = yes @@ -22,19 +22,12 @@ client smb3 signing algorithms = AES-128-GMAC AES-128-CMAC HMAC-SHA256 client signing = required client ipc signing = required -lock directory = /home/me/samba/lock -state directory = /home/me/samba/state -cache directory = /home/me/samba/cache -pid directory = /home/me/samba/pid -private dir = /home/me/samba/private -ncalrpc dir = /home/me/samba/ncalrpc - browseable = yes writable = yes create mask = 0660 directory mask = 0770 -force user = me -force group = me +force user = root +force group = root vfs objects = fruit streams_xattr fruit:metadata = stream diff --git a/p2pool/.dockerignore b/p2pool/.dockerignore deleted file mode 100644 index 06cf653..0000000 --- a/p2pool/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -cache diff --git a/p2pool/Dockerfile b/p2pool/Dockerfile index 6a8dd43..99bcf02 100644 --- a/p2pool/Dockerfile +++ b/p2pool/Dockerfile @@ -3,13 +3,13 @@ FROM ubuntu:24.04 RUN apt-get update && apt-get -y upgrade RUN apt-get install -y wget -RUN groupadd -g 2001 me && useradd -u 2001 -g 2001 -m me -USER me -WORKDIR /home/me +WORKDIR /root -# currently v4.4 -RUN wget https://github.com/SChernykh/p2pool/releases/download/v4.4/p2pool-v4.4-linux-x64.tar.gz -O p2pool.tar.gz +# currently v4.5 +RUN wget https://github.com/SChernykh/p2pool/releases/download/v4.5/p2pool-v4.5-linux-x64.tar.gz -O p2pool.tar.gz RUN tar xvf p2pool.tar.gz && rm p2pool.tar.gz -RUN mv p2pool-v4.4-linux-x64/p2pool ./p2pool +RUN mv p2pool-v4.5-linux-x64/p2pool ./p2pool -COPY --chown=me:me entry.sh ./ +COPY entry.sh ./ + +CMD ["/bin/bash", "/root/entry.sh"] diff --git a/p2pool/entry.sh b/p2pool/entry.sh index 5466ba8..0068b76 100644 --- a/p2pool/entry.sh +++ b/p2pool/entry.sh @@ -1,7 +1,7 @@ #!/bin/sh -cd cache +cd /data exec ~/p2pool \ --mini \ - --host monerod \ + --host 127.0.0.1 \ --wallet 42j7pyNRf8WE96D1xb6pjPWCwaDaYYevwZSPpELbTJjnXiKp7Lhtahbhb5Gc3p2BVxgMB3FEGNPUcbST1oZds6nBERA4jrQ diff --git a/scripts/backup.py b/scripts/backup.py deleted file mode 100755 index b0caf3c..0000000 --- a/scripts/backup.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/sudo /usr/bin/python3 - -import os -import shutil -import subprocess - -if __name__ == "__main__": - out = "data.zip" - subprocess.run(["zip", "-FS", "-r", out, - "minecraft/worlds", - "minecraft_bedrock/worlds", - "terraria/worlds", - "terraria/mods", - "website/gitea", - ], check=True) - shutil.chown(out, os.getlogin(), os.getlogin()) diff --git a/scripts/setup_drive.py b/scripts/setup_drive.py deleted file mode 100755 index 5eba7b9..0000000 --- a/scripts/setup_drive.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/sudo /usr/bin/python3 - -import os -import shutil -import subprocess -import sys -from pathlib import Path - -KEY_DIR = Path("/opt/luks") - -def run(cmd): - subprocess.run(cmd.split(), check=True) - -if __name__ == "__main__": - drive = sys.argv[1] - mount = Path(sys.argv[2]) - key = KEY_DIR / f"{drive}.key" - assert(Path(f"/dev/{drive}").exists()) - assert(not key.exists()) - - # create directories and key - KEY_DIR.mkdir(exist_ok=True) - mount.mkdir(exist_ok=True) - run(f"dd if=/dev/random bs=32 count=1 of={key}") - key.chmod(0o400) - - # format and mount drive - run(f"cryptsetup luksFormat --key-file={key} /dev/{drive}") - run(f"cryptsetup luksOpen --key-file={key} /dev/{drive} {drive}_luks") - run(f"mkfs.btrfs /dev/mapper/{drive}_luks") - run(f"mount /dev/mapper/{drive}_luks {mount}") - shutil.chown(mount, os.getlogin(), "nas") - mount.chmod(0o770) - - # TODO modify /etc/crypttab instead once Ubuntu fixed - with open("/opt/luks.sh", "a") as f: - f.write(f"systemd-cryptsetup attach {drive}_luks /dev/{drive} {key} luks\n") - f.write(f"mount /dev/mapper/{drive}_luks {mount}\n") diff --git a/scripts/setup_repo.py b/scripts/setup_repo.py deleted file mode 100755 index 2e22b2d..0000000 --- a/scripts/setup_repo.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/sudo /usr/bin/python3 - -import json -import os -import shutil -import subprocess -import yaml -from pathlib import Path - -if __name__ == "__main__": - override = {} - - # create folders so containers have access - PATHS = { - "web": [ - "website/certbot", - "website/gitea/config", - "website/gitea/data", - ], - "monero": [ - "monerod/.bitmonero", - "p2pool/cache", - ], - "game": [ - "minecraft/worlds", - "minecraft_bedrock/worlds", - "terraria/worlds", - "terraria/mods", - ] - } - for group in PATHS: - for p in PATHS[group]: - Path(p).mkdir(parents=True, exist_ok=True) - Path(p).chmod(0o775) - shutil.chown(p, user=os.getlogin(), group=group) - - # add users to nas - file = Path("nas/users.json") - script = Path("nas/users.sh") - with script.open("w") as f: - if file.exists(): - users = json.load(file.open()) - for id, user in enumerate(users): - id = 3000 + id - f.writelines(s + "\n" for s in [ - f"groupadd -g {id} {user}", - f"useradd -M -s /bin/false -u {id} -g {id} {user}", - f"su - me -c 'echo \"{users[user]}\\n{users[user]}\\n\" | pdbedit -s smb.conf -a {user}'", - ]) - shutil.chown(script, user=os.getlogin(), group=os.getlogin()) - - # add volumes to nas - file = Path("nas/mounts.json") - serv = Path("/etc/avahi/services") - conf = Path("nas/smb.conf") - shutil.copyfile("nas/base.conf", conf) - shutil.chown(conf, user=os.getlogin(), group=os.getlogin()) - for f in serv.glob("nas-*.service"): - f.unlink() - if file.exists(): - mounts = json.load(file.open()) - with open("nas/smb.conf", "a") as f: - for m in mounts: - f.write(f"[{m}]\n") - f.write(f"path = /home/me/share/{m}\n") - f.write("\n") - override.setdefault("services", {})["nas"] = {"volumes": [f"{mounts[m]}:/home/me/share/{m}" for m in mounts]} - for m in mounts: - with (serv / f"nas-{m}.service").open("w") as f: - f.writelines(s + "\n" for s in [ - "", - "", - "", - f" %h - {m}", - " ", - " _smb._tcp", - " 445", - " ", - " ", - " _adisk._tcp", - f" dk0=adVN={m},adVF=0x82", - " sys=waMa=0,adVF=0x100", - " ", - "", - ]) - subprocess.run(["systemctl", "restart", "avahi-daemon"], check=True) - - # generate compose override - file = Path("compose.override.yml") - if override: - with file.open("w") as f: - yaml.dump(override, f) - shutil.chown(file, user=os.getlogin(), group=os.getlogin()) - else: - file.unlink(True) diff --git a/scripts/setup_server.py b/scripts/setup_server.py deleted file mode 100755 index 45fc11d..0000000 --- a/scripts/setup_server.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/sudo /usr/bin/python3 - -import json -import os -import subprocess -from pathlib import Path - -def run(cmd, capture=False): - if capture: - return subprocess.check_output(cmd.split()) - else: - subprocess.run(cmd.split(), check=True) - -if __name__ == "__main__": - # install dependencies and configure - run("apt update") - run("apt upgrade -y") - run("apt install -y avahi-daemon btrfs-progs openssh-server python-is-python3 python3-pip wireguard zip") - with open("/etc/sysctl.conf", "a+") as f: # enable huge pages for local mining - f.seek(0) - if "vm.nr_hugepages=1280\n" not in f.readlines(): - f.write("vm.nr_hugepages=1280\n") - file = Path("/etc/ssh/sshd_config.d/restrict.conf") # only allow public key login - if not file.exists(): - with file.open("w") as f: - f.write("PasswordAuthentication no\n") - - # install docker and configure - run("snap install docker") - run("addgroup --system docker") - run(f"adduser {os.getlogin()} docker") - with open("/var/snap/docker/current/config/daemon.json", "r+") as f: - cfg = json.load(f) - cfg["ipv6"] = True - cfg["fixed-cidr-v6"] = "fd3a:138e:8fd0:0000::/64" # Docker ULA - f.seek(0) - json.dump(cfg, f, indent=4) - run("systemctl restart snap.docker.dockerd.service") - - try: - run("addgroup --gid 2000 web") - run("addgroup --gid 2001 monero") - run("addgroup --gid 2002 game") - run("addgroup --gid 2003 nas") - run(f"adduser {os.getlogin()} web") - run(f"adduser {os.getlogin()} monero") - run(f"adduser {os.getlogin()} game") - run(f"adduser {os.getlogin()} nas") - except: - pass - - # restrict network access from containers - file = Path("/etc/systemd/system/docker-restrict.service") - if not file.exists(): - with file.open("w") as f: - f.writelines(s + "\n" for s in [ - "[Unit]", - "Description=Restrict Docker network access", - "Before=network.target", - "After=network-pre.target", - "", - "[Service]", - "Type=oneshot", - "ExecStart=/opt/docker-restrict.sh", - "RemainAfterExit=yes", - "", - "[Install]", - "WantedBy=multi-user.target", - ]) - file = Path("/opt/docker-restrict.sh") - if not file.exists(): - with file.open("w") as f: - f.writelines(s + "\n" for s in [ - "#!/bin/sh", - "iptables -N DOCKER-USER || true", - "iptables -I DOCKER-USER -d 10.0.0.0/8 -j DROP", # xfinity gateway - "iptables -I DOCKER-USER -p tcp --dport 22 -j DROP", # SSH - "ip6tables -N DOCKER-USER || true", - "ip6tables -I DOCKER-USER -p tcp --dport 22 -j DROP", # SSH - ]) - file.chmod(0o755) - run("systemctl enable docker-restrict.service") - - # TODO modify /etc/crypttab instead once Ubuntu fixed - file = Path("/etc/systemd/system/luks.service") - if not file.exists(): - with file.open("w") as f: - f.writelines(s + "\n" for s in [ - "[Unit]", - "Description=Mount more LUKS drives", - "After=local-fs.target", - "Requires=local-fs.target", - "", - "[Service]", - "Type=oneshot", - "ExecStart=/opt/luks.sh", - "RemainAfterExit=yes", - "", - "[Install]", - "WantedBy=multi-user.target", - ]) - file = Path("/opt/luks.sh") - if not file.exists(): - with file.open("w") as f: - f.write("#!/bin/sh\n") - file.chmod(0o755) - run("systemctl enable luks.service") diff --git a/terraria/.dockerignore b/terraria/.dockerignore deleted file mode 100644 index 6a2fdbd..0000000 --- a/terraria/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -worlds/ diff --git a/terraria/Dockerfile b/terraria/Dockerfile index e0a2119..1d32d05 100644 --- a/terraria/Dockerfile +++ b/terraria/Dockerfile @@ -3,21 +3,21 @@ FROM ubuntu:24.04 RUN apt-get update && apt-get -y upgrade RUN apt-get install -y wget unzip python3 iproute2 dotnet-runtime-8.0 -RUN groupadd -g 2002 me && useradd -u 2002 -g 2002 -m me -USER me -WORKDIR /home/me +WORKDIR /root -# from https://github.com/tModLoader/tModLoader/releases (currently v2025.02.3.2) -RUN wget https://github.com/tModLoader/tModLoader/releases/download/v2025.02.3.2/tModLoader.zip +# from https://github.com/tModLoader/tModLoader/releases (currently v2025.03.3.1) +RUN wget https://github.com/tModLoader/tModLoader/releases/download/v2025.03.3.1/tModLoader.zip RUN unzip tModLoader.zip -d server && rm tModLoader.zip RUN chmod +x server/start-tModLoaderServer.sh RUN mkdir server/tModLoader-Logs && touch server/tModLoader-Logs/server.log RUN echo "" > server/LaunchUtils/InstallDotNet.sh -COPY --chown=me:me entry.py ./ -COPY --chown=me:me config.default ./config.txt -COPY --chown=me:me password.default ./password.txt -COPY --chown=me:me config.tx[t] password.tx[t] ./ + +COPY config.txt ./ +COPY entry.py ./ +COPY password.txt ./ + +CMD ["/usr/bin/python3", "/root/entry.py"] # To add mods, install them on the client and copy over the .tmod files to mods/ # Then modify/create mods/enabled.json and add the desired mods to enable diff --git a/terraria/config.default b/terraria/config.default index 8f656aa..15ac03d 100644 --- a/terraria/config.default +++ b/terraria/config.default @@ -1,15 +1,12 @@ -# world file -world=/home/me/worlds/master.wld - # default options if no world -autocreate=3 worldname=poopy -difficulty=2 # server options motd=poopy -worldpath=/home/me/worlds +worldpath=/data/worlds secure=1 # tmodloader options -modpath=/home/me/mods +modpath=/data/mods + +# generated options diff --git a/terraria/entry.py b/terraria/entry.py index 99c31bd..1ad3f81 100644 --- a/terraria/entry.py +++ b/terraria/entry.py @@ -27,13 +27,13 @@ class Runner: logging.info(f"attempted connection from {addr}, starting server...") # start server - with open("/home/me/password.txt", "r") as f: + with open("/root/password.txt", "r") as f: password = f.read() self.server = subprocess.Popen([ "/bin/bash", - "/home/me/server/start-tModLoaderServer.sh", + "/root/server/start-tModLoaderServer.sh", "-nosteam", - "-config", "/home/me/config.txt", + "-config", "/root/config.txt", "-pass", f"{password}", ], stdin=subprocess.PIPE, start_new_session=True) while not self.started(): diff --git a/terraria/password.default b/terraria/password.default deleted file mode 100644 index 7aa311a..0000000 --- a/terraria/password.default +++ /dev/null @@ -1 +0,0 @@ -password \ No newline at end of file diff --git a/website/.dockerignore b/website/.dockerignore deleted file mode 100644 index f55a997..0000000 --- a/website/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -gitea -certbot diff --git a/website/Dockerfile b/website/Dockerfile index c0a2350..c31a568 100644 --- a/website/Dockerfile +++ b/website/Dockerfile @@ -8,12 +8,11 @@ RUN apt-get update && apt-get -y upgrade RUN apt-get install -y nginx certbot python3-pip RUN pip3 install sendgrid --break-system-packages -RUN groupadd -g 2000 me && useradd -u 2000 -g 2000 -m me -USER me -WORKDIR /home/me -RUN mkdir nginx certbot +WORKDIR /root # TODO make the website code not terrible ;-; -COPY --chown=me:me html ./html -COPY --chown=me:me sendgrid.ke[y] ip_update.py ./ -COPY --chown=me:me server.conf entry.sh ./ +COPY html /var/www/html +COPY sendgrid.key ip.py ./ +COPY server.conf entry.sh ./ + +CMD ["/bin/bash", "/root/entry.sh"] diff --git a/website/entry.sh b/website/entry.sh index 0adbcb7..2b9cbf4 100644 --- a/website/entry.sh +++ b/website/entry.sh @@ -2,10 +2,9 @@ # get certs if needed certbot certonly --standalone \ - --http-01-port 8080 \ - --config-dir ~/certbot \ - --work-dir ~/certbot/work \ - --logs-dir ~/certbot/logs \ + --config-dir /data \ + --work-dir /data/work \ + --logs-dir /data/logs \ --non-interactive --agree-tos -m matthewlamtran@berkeley.edu \ -d matthewtran.com \ -d www.matthewtran.com \ @@ -14,16 +13,16 @@ certbot certonly --standalone \ # background process to renew certs and check ip changes update() { certbot renew --quiet \ - --config-dir ~/certbot \ - --work-dir ~/certbot/work \ - --logs-dir ~/certbot/logs + --config-dir /data \ + --work-dir /data/work \ + --logs-dir /data/logs sleep 86400 } update & -./ip_update.py & +python3 ip.py & # run server nginx -c ~/server.conf -trap 'echo "stopping website..."' TERM +trap 'echo "stopping website..."' SIGTERM SIGINT tail -f /dev/null & wait $! diff --git a/website/ip_update.py b/website/ip.py similarity index 100% rename from website/ip_update.py rename to website/ip.py diff --git a/website/server.conf b/website/server.conf index 1b0a292..0c280b7 100644 --- a/website/server.conf +++ b/website/server.conf @@ -1,6 +1,5 @@ # adapted from /etc/nginx/nginx.conf worker_processes auto; -pid /home/me/nginx/site.pid; error_log /dev/stderr; events { @@ -15,42 +14,37 @@ http { ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; - ssl_certificate /home/me/certbot/live/matthewtran.com/fullchain.pem; - ssl_certificate_key /home/me/certbot/live/matthewtran.com/privkey.pem; + ssl_certificate /data/live/matthewtran.com/fullchain.pem; + ssl_certificate_key /data/live/matthewtran.com/privkey.pem; include /etc/nginx/mime.types; default_type application/octet-stream; - access_log /dev/stdout; - client_body_temp_path /home/me/nginx/body; - proxy_temp_path /home/me/nginx/proxy; - fastcgi_temp_path /home/me/nginx/fastcgi; - uwsgi_temp_path /home/me/nginx/uwsgi; - scgi_temp_path /home/me/nginx/scgi; + access_log /dev/stdout; # SSL redirect server { - listen 8080 default_server; - listen [::]:8080 default_server; + listen 80 default_server; + listen [::]:80 default_server; server_name _; return 301 https://$host$request_uri; } # default server { - listen 8443 ssl default_server; - listen [::]:8443 ssl default_server; + listen 443 ssl default_server; + listen [::]:443 ssl default_server; server_name _; return 404; } # website server { - listen 8443 ssl; - listen [::]:8443 ssl; + listen 443 ssl; + listen [::]:443 ssl; server_name matthewtran.com www.matthewtran.com; - root /home/me/html; + root /var/www/html; index index.html; location / { try_files $uri $uri/ =404; @@ -59,13 +53,13 @@ http { # gitea server { - listen 8443 ssl; - listen [::]:8443 ssl; + listen 443 ssl; + listen [::]:443 ssl; server_name git.matthewtran.com; location / { client_max_body_size 512M; - proxy_pass http://gitea:3000; + proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;