This commit is contained in:
Matthew Tran
2025-05-04 22:36:28 -07:00
parent e774f3ebdc
commit 1788061642
8 changed files with 88 additions and 66 deletions
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env 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())
# TODO backup and scp
# TODO restore
# fix permissions
# no wipe folders we didn't save
# may need to chown 777 for gitea
# TODO router wipe + peer.py
Executable
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
import subprocess
import sys
from ipaddress import ip_address, ip_network
from router import WG_IPV4, WG_IPV6, run, key
def ips():
try:
ret = subprocess.check_output(["ssh", "root@OpenWrt.lan", "uci get network.@wireguard_wg0[-1].allowed_ips"]).decode("utf-8")
ipv4, ipv6 = ret.split()
ipv4, ipv6 = ipv4.split("/")[0], ipv6.split("/")[0]
except subprocess.CalledProcessError:
ipv4, ipv6 = WG_IPV4, WG_IPV6
net4, net6 = ip_network(ipv4 + "/24", strict=False), ip_network(ipv6 + "/64", strict=False)
ipv4, ipv6 = ip_address(ipv4), ip_address(ipv6)
if (ipv4 + 1) in net4 and (ipv6 + 1) in net6:
return str(ipv4 + 1), str(ipv6 + 1)
raise Exception("no ips left")
if __name__ == "__main__":
name = sys.argv[1]
ipv4, ipv6 = ips()
pub, priv = key()
run([
"uci add network wireguard_wg0",
f"uci set network.@wireguard_wg0[-1].description='{name}'",
f"uci set network.@wireguard_wg0[-1].public_key='{pub}'",
f"uci set network.@wireguard_wg0[-1].private_key='{priv}'",
f"uci add_list network.@wireguard_wg0[-1].allowed_ips='{ipv4}/32'",
f"uci add_list network.@wireguard_wg0[-1].allowed_ips='{ipv6}/128'",
"uci set network.@wireguard_wg0[-1].endpoint_host='wg.matthewtran.com'",
"uci set network.@wireguard_wg0[-1].endpoint_port='51820'",
"uci set network.@wireguard_wg0[-1].persistent_keepalive='25'",
"uci commit network",
"ifdown wg0",
"ifup wg0",
])
print("Go to the following to generate configuration, double check endpoint:")
print(f"Network > Interfaces > wg0 (Edit) > Peers > {name} (Edit) > Generate configuration...")
+38 -22
View File
@@ -2,7 +2,11 @@
import base64
import json
import http.server
import os
import secrets
import socket
import socketserver
import subprocess
import yaml
from pathlib import Path
@@ -40,8 +44,8 @@ PORTS = {
}
def check_keys():
if "home_key" not in cfg["core"]:
print(f'cfg["core"]["home_key"] doesn\'t exist, try "{base64.b64encode(secrets.token_bytes(64)).decode("utf-8")}"')
if "stash_key" not in cfg["core"]:
print(f'cfg["core"]["stash_key"] doesn\'t exist, try "{base64.b64encode(secrets.token_bytes(64)).decode("utf-8")}"')
exit(1)
for i, d in enumerate(cfg["drives"]):
if "key" not in d:
@@ -58,11 +62,11 @@ def add_root_drive():
{
"number": 4,
"label": "root",
"size_mib": 16384,
"size_mib": 65536,
"resize": True,
},
{
"label": "home",
"label": "stash",
"size_mib": 0,
},
],
@@ -78,10 +82,10 @@ def add_root_drive():
"clevis": { "tpm2": True },
},
{
"name": "home",
"device": "/dev/disk/by-partlabel/home",
"wipe_volume": cfg["core"]["home_wipe"],
"key_file": { "inline": base64.b64decode(cfg["core"]["home_key"]) },
"name": "stash",
"device": "/dev/disk/by-partlabel/stash",
"wipe_volume": cfg["core"]["stash_wipe"],
"key_file": { "inline": base64.b64decode(cfg["core"]["stash_key"]) },
},
],
"filesystems": [
@@ -92,14 +96,20 @@ def add_root_drive():
"label": "root",
},
{
"path": "/var/home",
"device": "/dev/mapper/home",
"format": "xfs",
"wipe_filesystem": cfg["core"]["home_wipe"],
"path": "/var/mnt/stash",
"device": "/dev/mapper/stash",
"format": "ext4",
"wipe_filesystem": cfg["core"]["stash_wipe"],
"with_mount_unit": True,
},
],
"directories": [],
"directories": [
{
"path": f"/var/mnt/stash",
"user": { "name": "core" },
"group": { "name": "core" },
},
],
"files": [],
}
@@ -152,6 +162,7 @@ def add_packages():
"ExecStart=/usr/bin/rpm-ostree install -y --allow-inactive " + " ".join([
"avahi",
"htop",
"python3",
"vim",
]),
"ExecStart=/bin/touch /etc/rpm/%N.stamp",
@@ -228,6 +239,11 @@ def copy_source():
"user": { "name": user },
"group": { "name": user },
})
but["storage"]["files"].append({
"path": "/var/opt/router.py",
"mode": 0o755,
"contents": { "inline": open("config/router.py", "rb").read() },
})
def build_images():
but["storage"]["directories"].append({ "path": "/etc/containers/systemd/users" })
@@ -365,17 +381,17 @@ if __name__ == "__main__":
run_containers()
advertise_services()
# TODO update router scripts bc DUID => make fixed??
# TODO script to backup => restore backup if desired => fix permissions
# may need to chown 777 for gitea restore
# TODO generate ISO, else nginx if --insecure
# TODO full wipe test (wipefs all) => check folder permissions secure
# generate ignition file
with open("config/server.bu", "w") as f:
f.write(yaml.dump(but, sort_keys=False))
subprocess.check_output(["butane", "-p", "-s", "-o", "config/server.ign", "config/server.bu"])
print("NOTE - TPM may need to be cleared after enough provisions.")
# host ignition file
ip = socket.gethostbyname(socket.gethostname())
print("WARNING - Using unencrypted connections without authentication, ensure LAN is secure!")
print("NOTE - TPM may need to be cleared after enough provisions.")
print(f"NOTE - Run \"sudo coreos-installer install /dev/<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()
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
import argparse
import hashlib
import subprocess
DP_LEN = 64 # xfinity delegated prefix length
WRT_ULA = "fd16:8f4d:f516::" # OpenWrt random
WG_ULA = "fd32:76a6:ec61:577a::" # WireGuard random
IPV4 = "192.168.1.69" # OpenWrt default
IPV6 = "69"
WG_IPV4 = "192.168.2.1" # WireGuard chosen
WG_IPV6 = WG_ULA + "1"
def run(cmds):
# ssh-keygen -t ed25519
subprocess.run(["ssh", "root@OpenWrt.lan", ";".join(cmds)], check=True)
def mac(eth):
return open(f"/sys/class/net/{eth}/address", "r").read().strip()
def duid():
# adapted from https://github.com/mss/nm-duid
id = bytes.fromhex(open("/etc/machine-id", "r").read().strip())
return "0004" + hashlib.sha256(id).digest()[:16].hex()
def key():
priv = subprocess.check_output(["wg", "genkey"]).strip()
pub = subprocess.check_output(["wg", "pubkey"], input=priv).strip()
return (pub.decode("utf-8"), priv.decode("utf-8"))
if __name__ == "__main__":
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([
f"uci set network.globals.ula_prefix='{WRT_ULA}/48'",
"uci set dropbear.main.Interface='lan'",
"uci commit network",
"uci commit dropbear",
])
# static IP
run([
"uci add dhcp host",
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}'",
"uci commit dhcp",
"service dnsmasq restart",
"service odhcpd restart",
])
# forward traffic
PORTS = {
"http" : "80",
"https" : "443",
"git" : "2222",
"monerod" : "18080-18081",
"p2pool" : "3333",
"p2pool2" : "37888-37889",
"minecraft" : "25565",
"minecraft_be": "19132-19133",
"terraria" : "7777",
}
for name in PORTS:
run([
# IPv4 port forward
"uci add firewall redirect",
f"uci set firewall.@redirect[-1].name='{name}'",
"uci set firewall.@redirect[-1].target='DNAT'",
"uci set firewall.@redirect[-1].family='IPv4'",
"uci set firewall.@redirect[-1].src='wan'",
f"uci set firewall.@redirect[-1].src_dport='{PORTS[name]}'",
"uci set firewall.@redirect[-1].dest='lan'",
f"uci set firewall.@redirect[-1].dest_ip='{IPV4}'",
f"uci set firewall.@redirect[-1].dest_port='{PORTS[name]}'",
# IPv6 traffic rules
"uci add firewall rule",
f"uci set firewall.@rule[-1].name='allow-{name}'",
"uci set firewall.@rule[-1].family='ipv6'",
"uci set firewall.@rule[-1].src='wan'",
"uci set firewall.@rule[-1].dest='lan'",
f"uci set firewall.@rule[-1].dest_ip='::{IPV6}/{DP_LEN-128}'",
f"uci set firewall.@rule[-1].dest_port='{PORTS[name]}'",
"uci set firewall.@rule[-1].target='ACCEPT'",
])
run([
"uci commit firewall",
"service firewall restart",
])
# wireguard setup
# TODO configure NAT66 to fix tunneling IPv6 traffic
pub, priv = key()
run([
# install packages
"opkg update",
"opkg install luci-proto-wireguard",
# create interface
"uci set network.wg0=interface",
"uci set network.wg0.proto='wireguard'",
f"uci set network.wg0.private_key='{priv}'",
"uci set network.wg0.listen_port='51820'",
f"uci add_list network.wg0.addresses='{WG_IPV4}/24'",
f"uci add_list network.wg0.addresses='{WG_IPV6}/64'",
"uci commit network",
# allow traffic
"uci del firewall.@zone[0].network",
"uci add_list firewall.@zone[0].network='lan'",
"uci add_list firewall.@zone[0].network='wg0'",
"uci add firewall rule",
"uci set firewall.@rule[-1].name='allow-wireguard'",
"uci add_list firewall.@rule[-1].proto='udp'",
"uci set firewall.@rule[-1].src='wan'",
"uci set firewall.@rule[-1].dest_port='51820'",
"uci set firewall.@rule[-1].target='ACCEPT'",
"uci commit firewall",
])
@@ -4,8 +4,8 @@
"ssh_keys": [
"ssh-ed25519 AAAA..."
],
"home_key": "<LUKS key>",
"home_wipe": false,
"stash_key": "<LUKS key>",
"stash_wipe": false,
"data_dir": "/var/home/core/matthewtrancom_data"
},
"drives": [