migrate to coreos

This commit is contained in:
Matthew Tran 2025-04-19 02:24:10 -07:00
parent 12039fb862
commit 8bd3def755
42 changed files with 752 additions and 579 deletions

32
.gitignore vendored
View File

@ -1,34 +1,26 @@
.DS_Store .DS_Store
__pycache__ __pycache__
compose.override.yml *.bak
*.zip
# config
config/server.json
config/*.bu
config/*.ign
config/*.iso
# website # website
website/gitea
website/certbot
website/sendgrid.key website/sendgrid.key
# monerod
monerod/.bitmonero
# p2pool
p2pool/cache
# minecraft # minecraft
minecraft/world* minecraft/server.properties
# minecraft_bedrock # minecraft_bedrock
minecraft_bedrock/worlds* minecraft_bedrock/server.properties
# terraria # terraria
terraria/worlds terraria/*.txt
terraria/mods
terraria/config.txt
terraria/password.txt
# nas # nas
nas/*.json nas/Dockerfile
nas/smb.conf nas/smb.conf
nas/users.sh
# backup
data.zip

View File

@ -7,49 +7,31 @@ Services deployed on [matthewtran.com](https://matthewtran.com).
- monerod - monerod
- p2pool (`xmrig -o matthewtran.com`) - p2pool (`xmrig -o matthewtran.com`)
- minecraft - minecraft
- ~~minecraft bedrock~~ - minecraft bedrock
- terraria - terraria
- nas (`<server>/<name>` on LAN) - nas (`<server>/<name>` on LAN)
- wireguard - wireguard
## setup ## 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. 1. Create `config/server.json` and run `config/provision.py`.
- You may need to manually enable IPv6 on the network connection. Use `Automatic` not `Automatic, DHCP only`. 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.
- Add an SSH key if you need remote access, setup will disable password authentication. 3. To configure the OpenWrt router, run `/opt/router.py --provision <interface>` on the server. Then reboot the router and server.
- Clone this repo and `cd` into it. 4. Add the following DNS entries at the registrar.
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 <interface>`
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 `"<name>":"<directory>"` for the SMB share.
- Create `nas/users.json` which contains a list of `"<user>":"<password>"` 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 <drive> <mount>`
7. Optionally, run `scripts/setup_peer.py <name>` for each WireGuard client.
8. Optionally, add the following DNS entries at the registrar.
| hosts | type | data | | hosts | type | data |
| ----------------------- | ------ | ------------------------ | | ----------------------- | ------ | ------------------------ |
| `@`, `git`, `wg`, `www` | `A` | `<public IPv4>` | | `@`, `git`, `wg`, `www` | `A` | `<public IPv4>` |
| `@`, `git`, `www` | `AAAA` | `<delegated prefix>::69` | | `@`, `git`, `www` | `AAAA` | `<delegated prefix>::69` |
| `wg` | `AAAA` | `<delegated prefix>::1` | | `wg` | `AAAA` | `<delegated prefix>::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. - 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.
btrfs device stats <mount>
btrfs scrub start -B <mount>
```
## 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 <name>.zip` to back up critical files.
- Run `config/backup.py --restore <name>.zip` to restore those files.
- Run `sudo mdadm -D /dev/md/<name>` on the server to check RAID status.

View File

@ -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

67
config/backup.py Executable file
View File

@ -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)

View File

@ -3,7 +3,7 @@
import subprocess import subprocess
import sys import sys
from ipaddress import ip_address, ip_network 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(): def ips():
try: try:

398
config/provision.py Executable file
View File

@ -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([
"<?xml version=\"1.0\" standalone='no'?>",
"<!DOCTYPE service-group SYSTEM \"avahi-service.dtd\">",
"<service-group>",
f" <name replace-wildcards=\"yes\">%h - {mnt}</name>",
" <service>",
" <type>_smb._tcp</type>",
" <port>445</port>",
" </service>",
" <service>",
" <type>_adisk._tcp</type>",
f" <txt-record>dk0=adVN={mnt},adVF=0x82</txt-record>",
" <txt-record>sys=waMa=0,adVF=0x100</txt-record>",
" </service>",
"</service-group>",
])}
})
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/<boot drive> --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()

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse
import hashlib import hashlib
import subprocess import subprocess
import sys
DP_LEN = 64 # xfinity delegated prefix length DP_LEN = 64 # xfinity delegated prefix length
WRT_ULA = "fd16:8f4d:f516::" # OpenWrt random WRT_ULA = "fd16:8f4d:f516::" # OpenWrt random
@ -30,7 +30,19 @@ def key():
return (pub.decode("utf-8"), priv.decode("utf-8")) return (pub.decode("utf-8"), priv.decode("utf-8"))
if __name__ == "__main__": 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 # basic setup
run([ run([
@ -43,8 +55,7 @@ if __name__ == "__main__":
# static IP # static IP
run([ run([
"uci add dhcp host", "uci add dhcp host",
"uci set dhcp.@host[-1].name='matt-ryzen'", f"uci set dhcp.@host[-1].mac='{mac(args.provision)}'",
f"uci set dhcp.@host[-1].mac='{mac(ETH)}'",
f"uci set dhcp.@host[-1].ip='{IPV4}'", f"uci set dhcp.@host[-1].ip='{IPV4}'",
f"uci set dhcp.@host[-1].duid='{duid()}'", f"uci set dhcp.@host[-1].duid='{duid()}'",
f"uci set dhcp.@host[-1].hostid='{IPV6}'", f"uci set dhcp.@host[-1].hostid='{IPV6}'",

44
config/server.example Normal file
View File

@ -0,0 +1,44 @@
{
"core": {
"hostname": "<server name>",
"ssh_keys": [
"ssh-ed25519 AAAA..."
],
"stash_key": "<LUKS key>",
"stash_wipe": false,
"data_dir": "/var/home/core/matthewtrancom_data"
},
"drives": [
{
"devices": ["/dev/sda"],
"key": "<LUKS key>",
"name": "stuff",
"wipe": false
}
],
"website": {
"sendgrid_key": "<SendGrid API key from https://app.sendgrid.com/settings/api_keys>"
},
"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"
}
}
}

100
config/update.py Executable file
View File

@ -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]),
])

1
gitea/Dockerfile Normal file
View File

@ -0,0 +1 @@
FROM docker.io/gitea/gitea:1.23.7

View File

@ -1 +0,0 @@
worlds/

View File

@ -3,18 +3,18 @@ FROM ubuntu:24.04
RUN apt-get update && apt-get -y upgrade RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y wget openjdk-21-jre RUN apt-get install -y wget openjdk-21-jre
RUN groupadd -g 2002 me && useradd -u 2002 -g 2002 -m me WORKDIR /root
USER me
WORKDIR /home/me
# from https://github.com/itzg/rcon-cli # 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 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) # from https://www.minecraft.net/en-us/download/server (currently 1.21.5)
RUN wget https://piston-data.mojang.com/v1/objects/4707d00eb834b446575d89a61a11b5d548d8c001/server.jar RUN wget https://piston-data.mojang.com/v1/objects/e6ec2f64e6080b9b5d9b471b291c33cc7f509733/server.jar
COPY --chown=me:me eula.txt ./ COPY entry.sh ./
COPY --chown=me:me entry.sh ./ COPY eula.txt ./
COPY --chown=me:me server.properties ./ COPY ops.json ./
COPY --chown=me:me ops.json ./ COPY server.properties ./
CMD ["/bin/bash", "/root/entry.sh"]

View File

@ -4,7 +4,7 @@ cleanup() {
./rcon-cli --password password stop ./rcon-cli --password password stop
} }
trap 'cleanup' TERM trap 'cleanup' SIGTERM SIGINT
java -Xmx1024M -Xms1024M -jar server.jar nogui & java -Xmx1024M -Xms1024M -jar server.jar nogui &
wait $! # wait for SIGTERM wait $! # wait for SIGTERM

View File

@ -20,7 +20,6 @@ hardcore=true
hide-online-players=false hide-online-players=false
initial-disabled-packs= initial-disabled-packs=
initial-enabled-packs=vanilla initial-enabled-packs=vanilla
level-name=worlds/main
level-seed= level-seed=
level-type=minecraft\:normal level-type=minecraft\:normal
max-chained-neighbor-updates=1000000 max-chained-neighbor-updates=1000000

View File

@ -1 +0,0 @@
worlds/

View File

@ -3,14 +3,15 @@ FROM ubuntu:24.04
RUN apt-get update && apt-get -y upgrade RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y wget unzip curl tmux RUN apt-get install -y wget unzip curl tmux
RUN groupadd -g 2002 me && useradd -u 2002 -g 2002 -m me WORKDIR /root
USER me
WORKDIR /home/me
# from https://www.minecraft.net/en-us/download/server/bedrock (currently 1.21.61.01) # 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.61.01.zip 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 RUN unzip server.zip && rm server.zip
COPY --chown=me:me entry.sh ./ COPY entry.sh ./
COPY --chown=me:me server.properties ./ COPY permissions.json ./
COPY --chown=me:me permissions.json ./ COPY server.properties ./
RUN ln -s /data /root/worlds
CMD ["/bin/bash", "/root/entry.sh"]

View File

@ -4,9 +4,8 @@ cleanup() {
tmux send-keys stop Enter tmux send-keys stop Enter
} }
trap 'cleanup' TERM trap 'cleanup' SIGTERM SIGINT
rm log
mkfifo log mkfifo log
tmux new -d 'LD_LIBRARY_PATH=. ./bedrock_server > log' tmux new -d 'LD_LIBRARY_PATH=. ./bedrock_server > log'
cat log & cat log &

View File

@ -13,7 +13,6 @@ view-distance=32
tick-distance=4 tick-distance=4
player-idle-timeout=0 player-idle-timeout=0
max-threads=4 max-threads=4
level-name=test
level-seed= level-seed=
default-player-permission-level=visitor default-player-permission-level=visitor
texturepack-required=false texturepack-required=false

View File

@ -1 +0,0 @@
.bitmonero

View File

@ -3,12 +3,12 @@ FROM ubuntu:24.04
RUN apt-get update && apt-get -y upgrade RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y wget bzip2 RUN apt-get install -y wget bzip2
RUN groupadd -g 2001 me && useradd -u 2001 -g 2001 -m me WORKDIR /root
USER me
WORKDIR /home/me
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 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"]

View File

@ -3,6 +3,7 @@
# check bitmonero.log for log # check bitmonero.log for log
monero/monerod \ monero/monerod \
--prune-blockchain \ --prune-blockchain \
--data-dir /data \
--rpc-bind-port 18089 \ --rpc-bind-port 18089 \
--rpc-restricted-bind-ip 0.0.0.0 \ --rpc-restricted-bind-ip 0.0.0.0 \
--rpc-restricted-bind-port 18081 \ --rpc-restricted-bind-port 18081 \
@ -17,6 +18,6 @@ monero/monerod \
cleanup() { cleanup() {
monero/monerod exit --rpc-bind-port 18089 monero/monerod exit --rpc-bind-port 18089
} }
trap 'cleanup' TERM trap 'cleanup' SIGTERM SIGINT
tail -f /dev/null & tail -f /dev/null &
wait $! wait $!

View File

@ -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

15
nas/Dockerfile.template Normal file
View File

@ -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

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
smbd -s smb.conf -l=/home/me/samba/log smbd -s smb.conf
trap 'echo "stopping smbd..."' TERM trap 'echo "stopping smbd..."' SIGTERM SIGINT
tail -f /dev/null & tail -f /dev/null &
wait $! wait $!

View File

@ -1,6 +1,6 @@
[global] [global]
workgroup = WORKGROUP workgroup = WORKGROUP
smb ports = 8445 smb ports = 445
load printers = no load printers = no
disable spoolss = yes disable spoolss = yes
@ -22,19 +22,12 @@ client smb3 signing algorithms = AES-128-GMAC AES-128-CMAC HMAC-SHA256
client signing = required client signing = required
client ipc 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 browseable = yes
writable = yes writable = yes
create mask = 0660 create mask = 0660
directory mask = 0770 directory mask = 0770
force user = me force user = root
force group = me force group = root
vfs objects = fruit streams_xattr vfs objects = fruit streams_xattr
fruit:metadata = stream fruit:metadata = stream

View File

@ -1 +0,0 @@
cache

View File

@ -3,13 +3,13 @@ FROM ubuntu:24.04
RUN apt-get update && apt-get -y upgrade RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y wget RUN apt-get install -y wget
RUN groupadd -g 2001 me && useradd -u 2001 -g 2001 -m me WORKDIR /root
USER me
WORKDIR /home/me
# currently v4.4 # currently v4.5
RUN wget https://github.com/SChernykh/p2pool/releases/download/v4.4/p2pool-v4.4-linux-x64.tar.gz -O p2pool.tar.gz 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 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"]

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
cd cache cd /data
exec ~/p2pool \ exec ~/p2pool \
--mini \ --mini \
--host monerod \ --host 127.0.0.1 \
--wallet 42j7pyNRf8WE96D1xb6pjPWCwaDaYYevwZSPpELbTJjnXiKp7Lhtahbhb5Gc3p2BVxgMB3FEGNPUcbST1oZds6nBERA4jrQ --wallet 42j7pyNRf8WE96D1xb6pjPWCwaDaYYevwZSPpELbTJjnXiKp7Lhtahbhb5Gc3p2BVxgMB3FEGNPUcbST1oZds6nBERA4jrQ

View File

@ -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())

View File

@ -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")

View File

@ -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 [
"<?xml version=\"1.0\" standalone='no'?>",
"<!DOCTYPE service-group SYSTEM \"avahi-service.dtd\">",
"<service-group>",
f" <name replace-wildcards=\"yes\">%h - {m}</name>",
" <service>",
" <type>_smb._tcp</type>",
" <port>445</port>",
" </service>",
" <service>",
" <type>_adisk._tcp</type>",
f" <txt-record>dk0=adVN={m},adVF=0x82</txt-record>",
" <txt-record>sys=waMa=0,adVF=0x100</txt-record>",
" </service>",
"</service-group>",
])
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)

View File

@ -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")

View File

@ -1 +0,0 @@
worlds/

View File

@ -3,21 +3,21 @@ FROM ubuntu:24.04
RUN apt-get update && apt-get -y upgrade RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y wget unzip python3 iproute2 dotnet-runtime-8.0 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 WORKDIR /root
USER me
WORKDIR /home/me
# from https://github.com/tModLoader/tModLoader/releases (currently v2025.02.3.2) # from https://github.com/tModLoader/tModLoader/releases (currently v2025.03.3.1)
RUN wget https://github.com/tModLoader/tModLoader/releases/download/v2025.02.3.2/tModLoader.zip 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 unzip tModLoader.zip -d server && rm tModLoader.zip
RUN chmod +x server/start-tModLoaderServer.sh RUN chmod +x server/start-tModLoaderServer.sh
RUN mkdir server/tModLoader-Logs && touch server/tModLoader-Logs/server.log RUN mkdir server/tModLoader-Logs && touch server/tModLoader-Logs/server.log
RUN echo "" > server/LaunchUtils/InstallDotNet.sh RUN echo "" > server/LaunchUtils/InstallDotNet.sh
COPY --chown=me:me entry.py ./
COPY --chown=me:me config.default ./config.txt COPY config.txt ./
COPY --chown=me:me password.default ./password.txt COPY entry.py ./
COPY --chown=me:me config.tx[t] password.tx[t] ./ 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/ # 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 # Then modify/create mods/enabled.json and add the desired mods to enable

View File

@ -1,15 +1,12 @@
# world file
world=/home/me/worlds/master.wld
# default options if no world # default options if no world
autocreate=3
worldname=poopy worldname=poopy
difficulty=2
# server options # server options
motd=poopy motd=poopy
worldpath=/home/me/worlds worldpath=/data/worlds
secure=1 secure=1
# tmodloader options # tmodloader options
modpath=/home/me/mods modpath=/data/mods
# generated options

View File

@ -27,13 +27,13 @@ class Runner:
logging.info(f"attempted connection from {addr}, starting server...") logging.info(f"attempted connection from {addr}, starting server...")
# start server # start server
with open("/home/me/password.txt", "r") as f: with open("/root/password.txt", "r") as f:
password = f.read() password = f.read()
self.server = subprocess.Popen([ self.server = subprocess.Popen([
"/bin/bash", "/bin/bash",
"/home/me/server/start-tModLoaderServer.sh", "/root/server/start-tModLoaderServer.sh",
"-nosteam", "-nosteam",
"-config", "/home/me/config.txt", "-config", "/root/config.txt",
"-pass", f"{password}", "-pass", f"{password}",
], stdin=subprocess.PIPE, start_new_session=True) ], stdin=subprocess.PIPE, start_new_session=True)
while not self.started(): while not self.started():

View File

@ -1 +0,0 @@
password

View File

@ -1,2 +0,0 @@
gitea
certbot

View File

@ -8,12 +8,11 @@ RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y nginx certbot python3-pip RUN apt-get install -y nginx certbot python3-pip
RUN pip3 install sendgrid --break-system-packages RUN pip3 install sendgrid --break-system-packages
RUN groupadd -g 2000 me && useradd -u 2000 -g 2000 -m me WORKDIR /root
USER me
WORKDIR /home/me
RUN mkdir nginx certbot
# TODO make the website code not terrible ;-; # TODO make the website code not terrible ;-;
COPY --chown=me:me html ./html COPY html /var/www/html
COPY --chown=me:me sendgrid.ke[y] ip_update.py ./ COPY sendgrid.key ip.py ./
COPY --chown=me:me server.conf entry.sh ./ COPY server.conf entry.sh ./
CMD ["/bin/bash", "/root/entry.sh"]

View File

@ -2,10 +2,9 @@
# get certs if needed # get certs if needed
certbot certonly --standalone \ certbot certonly --standalone \
--http-01-port 8080 \ --config-dir /data \
--config-dir ~/certbot \ --work-dir /data/work \
--work-dir ~/certbot/work \ --logs-dir /data/logs \
--logs-dir ~/certbot/logs \
--non-interactive --agree-tos -m matthewlamtran@berkeley.edu \ --non-interactive --agree-tos -m matthewlamtran@berkeley.edu \
-d matthewtran.com \ -d matthewtran.com \
-d www.matthewtran.com \ -d www.matthewtran.com \
@ -14,16 +13,16 @@ certbot certonly --standalone \
# background process to renew certs and check ip changes # background process to renew certs and check ip changes
update() { update() {
certbot renew --quiet \ certbot renew --quiet \
--config-dir ~/certbot \ --config-dir /data \
--work-dir ~/certbot/work \ --work-dir /data/work \
--logs-dir ~/certbot/logs --logs-dir /data/logs
sleep 86400 sleep 86400
} }
update & update &
./ip_update.py & python3 ip.py &
# run server # run server
nginx -c ~/server.conf nginx -c ~/server.conf
trap 'echo "stopping website..."' TERM trap 'echo "stopping website..."' SIGTERM SIGINT
tail -f /dev/null & tail -f /dev/null &
wait $! wait $!

View File

@ -1,6 +1,5 @@
# adapted from /etc/nginx/nginx.conf # adapted from /etc/nginx/nginx.conf
worker_processes auto; worker_processes auto;
pid /home/me/nginx/site.pid;
error_log /dev/stderr; error_log /dev/stderr;
events { events {
@ -15,42 +14,37 @@ http {
ssl_protocols TLSv1.2 TLSv1.3; ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
ssl_certificate /home/me/certbot/live/matthewtran.com/fullchain.pem; ssl_certificate /data/live/matthewtran.com/fullchain.pem;
ssl_certificate_key /home/me/certbot/live/matthewtran.com/privkey.pem; ssl_certificate_key /data/live/matthewtran.com/privkey.pem;
include /etc/nginx/mime.types; include /etc/nginx/mime.types;
default_type application/octet-stream; default_type application/octet-stream;
access_log /dev/stdout; 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;
# SSL redirect # SSL redirect
server { server {
listen 8080 default_server; listen 80 default_server;
listen [::]:8080 default_server; listen [::]:80 default_server;
server_name _; server_name _;
return 301 https://$host$request_uri; return 301 https://$host$request_uri;
} }
# default # default
server { server {
listen 8443 ssl default_server; listen 443 ssl default_server;
listen [::]:8443 ssl default_server; listen [::]:443 ssl default_server;
server_name _; server_name _;
return 404; return 404;
} }
# website # website
server { server {
listen 8443 ssl; listen 443 ssl;
listen [::]:8443 ssl; listen [::]:443 ssl;
server_name matthewtran.com www.matthewtran.com; server_name matthewtran.com www.matthewtran.com;
root /home/me/html; root /var/www/html;
index index.html; index index.html;
location / { location / {
try_files $uri $uri/ =404; try_files $uri $uri/ =404;
@ -59,13 +53,13 @@ http {
# gitea # gitea
server { server {
listen 8443 ssl; listen 443 ssl;
listen [::]:8443 ssl; listen [::]:443 ssl;
server_name git.matthewtran.com; server_name git.matthewtran.com;
location / { location / {
client_max_body_size 512M; 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 Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;