aboutsummaryrefslogtreecommitdiff
path: root/post/framework-server.typ
diff options
context:
space:
mode:
Diffstat (limited to 'post/framework-server.typ')
-rw-r--r--post/framework-server.typ558
1 files changed, 558 insertions, 0 deletions
diff --git a/post/framework-server.typ b/post/framework-server.typ
new file mode 100644
index 0000000..4e9d5b6
--- /dev/null
+++ b/post/framework-server.typ
@@ -0,0 +1,558 @@
+#import "/global/common.typ": *
+
+#let doc = [
+How I re-purposed my Framework as a standalone server for my home-lab.
+
+#quote(block: true)[
+ This is not sponsored by Framework ... though I wish it were :)
+]
+
+= Primary Motivation
+
+I \#feelsbad for abusing my lowly NAS -
+
+#img("framework-server/feelsbadman.png", alt: "feelsbadman", width: "150px")
+
+Its not it's fault that its owner expected it to run Collabora Online while
+running all his other services without instantly crashing...
+
+#fig("framework-server/error.jpg", alt: "meme of error page")
+
+Did I learn anything? Basically a NAS should just do NAS things like worry
+about storage. Compute should be handled by something else.
+
+#quote(block: true)[Lesson learned.]
+
+= Secondary Motivation
+
+Battery life is an issue with this gen.
+
+For my personal laptop I'd want something that has "all day" battery like the
+newer gen Intels or Ryzens.
+
+My plan is to snag one of those when prices become more attractive and retrofit
+it into my now hollow laptop chassis.
+
+Isn't this the Framework
+#extlink("https://frame.work/ca/en/about#what-we-re-fixing")[mission]?
+
+#quote(block: true)[
+ "Even better, what we've done to enable repair also opens up upgradability
+ and customization. This lets you get exactly the product you need and
+ extends usable lifetime too."
+]
+
+= Idea
+
+Re-purpose my 99%-of-the-time idle personal Framework laptop which is already
+overkill for the remaining 1%-of-the-time tasks, to be a hypervisor server.
+
+Setup my home-lab services there, and provide data storage for those services
+over NFS from my NAS.
+
+I'll setup a dedicated "workstation" VM for those 1%-of-the-time tasks.
+
+#fig("framework-server/physical.png", alt: "physical architecture image")
+
+= Prior to starting!
+
+To avoid making the same mistakes I made, do the following *while the mainboard
+is still in the laptop chassis!*
+
+#fig("framework-server/mistakes.jpg", alt: "mistakes meme")
+
++ *Update your BIOS firmware to at least 3.17.* It is *impossible* to do so
+ once the battery is un-plugged from the board on older firmwares. I learned
+ this the hard-way. This update will provide the critical options below for
+ this process to be feasible.
++ In your BIOS settings, enable options for optimizing CPU performance over
+ battery life.
++ In your BIOS settings, enable "Standalone operation".
++ In your BIOS settings, under "Boot" enable the option to power-on on
+ power-attach.
+ #fig("framework-server/bios.jpg", alt: "bios image")
++ Install Proxmox VE. Since this will require a screen, and keyboard input, you
+ might as well do it now. See the #link(<hypervisor>)[hypervisor] section
+ below.
+
+#html.hr()
+
+= Build
+
+== Server
+
+I'm using my original
+#extlink("https://frame.work/ca/en/products/mainboard-11th-gen-intel-core?v=FRANFG000B")[Framework
+ 13] laptop.
+
+#fig("framework-server/case.jpg", alt: "case image")
+
+Its an i7-1165G7 - yes a "mobile" CPU. Am I asking for trouble? We'll see. Its
+more than powerful enough for me (for now).
+
+Alternatively you could build/use another server.
+
+If you are also using the Framework 13, then you'll want to make sure that you
+are running at least *BIOS 3.17*. See the #link(<issues>)[issues] section below
+about my troubles.
+
+== Case
+
+As you can see above, I opted for the
+#extlink("https://frame.work/ca/en/products/cooler-master-mainboard-case")[Coolermaster
+ Case] because honestly it looks cool.
+
+Alternatively, you could self-print the
+#extlink("https://github.com/FrameworkComputer/Framework-Laptop-13")[community]
+3D case or get someone else to print it.
+
+== Network Connectivity
+
+I opted for the 2.5G
+#extlink("https://frame.work/ca/en/products/ethernet-expansion-card")[ethernet
+ expansion card] from Framework.
+
+#fig("framework-server/eth.jpg", alt: "framework eth adapter image")
+
+I suppose one could also use a WiFi card, but it makes more sense to go wired
+in a 24/7 "on" configuration.
+
+Alternatively, most thunderbolt-ethernet dongles should do the job. You'd want
+to check that there are Linux drivers for it.
+
+Since I host a few publicly accessible services (this blog for example), I also
+need a public static IP under which an internet user can access these services.
+
+So I opt for a cheap VPS from
+#extlink("https://www.racknerd.com/kvm-vps")[Racknerd]. These are about a \$1/mo
+for a public IPv4 IP, and a decent amount of network bandwidth.
+
+#fig("framework-server/vps.png", alt: "racknerd vps pricing image")
+
+== Storage
+
+=== for VMs
+
+I used the 1TB NVME drive I had on my Framework.
+
+=== for Data
+
+_You should have a storage solution (NAS) in place already!_
+
+#fig("framework-server/nas.jpg", alt: "nas image")
+
+Setting this up is beyond the scope of this post. You can check out my DIY ZFS
+backed NAS setup
+#extlink("https://gitlab.com/kdam0/vps#nas-equipment")[here]. I've had no
+(storage related) issues with my NAS since its inception over a year ago.
+
+Then when I need to allocate space for a new service I do:
+
+```bash
+zfs create <pool>/<dataset>
+zfs set compression=on/off
+zfs set sharenfs="..." sharesmb=".."
+```
+
+Then when the time comes, configure the NFS mount on the client VM that
+requires access to this data store:
+
+```bash
+mount -t nfs hostname-of-your-nas:/mnt/pool/dataset /mnt/data
+```
+
+There are also many options for pre-made NAS appliances such as Synology.
+
+== Power
+
+Highly recommend a UPS such as
+#extlink("https://www.amazon.ca/APC-BE600M1-Back-Uninterrupted-Electronics-Computers/dp/B01FWAZEIU/ref=sr_1_6?crid=28ZH7JP9IYXPU&keywords=ups&qid=1690928556&sprefix=ups%2Caps%2C172&sr=8-6")[this].
+
+#fig("framework-server/ups.jpg", alt: "ups image")
+
+Even if you don't care about uptime, *you should care about data integrity*
+issues resulting from abrupt power-loss.
+
+A UPS should buy you enough time to power things down gracefully.
+
+= Hypervisor <hypervisor>
+
+I opted for
+#extlink("https://www.proxmox.com/en/proxmox-virtual-environment/overview")[Proxmox
+ VE] since v8 was just released, I've been itching to try it out. Now felt
+like the right time.
+
+There is also a Terraform
+#extlink("https://registry.terraform.io/providers/Telmate/proxmox/latest/docs")[provider]
+allowing provisioning of VMs, and LXC Containers through code. Neat!
+
+#quote(block: true)[
+ I recommend you do this *before* taking the mainboard out of the laptop
+ chassis as you will need to provide keyboard input, as well as see whats
+ going on the screen.
+]
+
+- Install it. I just followed the
+ #extlink("https://www.proxmox.com/en/proxmox-virtual-environment/get-started")[official
+ instructions].
+
+- Under your host (mine's `pve`), under System, Network, create a bridge
+ `vmbr0` with:
+
+#fig("framework-server/net1.png", alt: "proxmox net image")
+
+This will make your Proxmox host available in your home network. *Set this to
+something else if you like.*
+
+#quote(block: true)[
+ You should also reserve the corresponding IP in your home router for this
+ host.
+]
+
+While you're here, create another bridge `vmbr1` with `VLAN Aware` checked:
+
+#fig("framework-server/net2.png", alt: "proxmox net image")
+
+#quote(block: true)[
+ We are going to create our home-lab network primarily on `vmbr1`. Think of
+ `vmbr0` as a _management_ bridge.
+]
+
+= Network
+
+I always find it best to sort out networking *before* we start building stuff.
+
+Since I'm basically starting over, I want to do it properly. A few design
+goals:
+
++ I want segregation of my home-lab services from my non-home-lab hosts (my
+ "workstation" VM for example), as well as the rest of my home network.
++ I don't want to keep track of static IP leases, everything should be DHCP,
+ and DNS.
++ I want all traffic to/from my home-lab segment to be encrypted. Non-home-lab
+ traffic does not need to be encrypted.
++ I need some way to make these services publicly accessible over the internet.
+
+The first two goals necessitate a virtual firewall/router deployment capable of
+_selectively_ routing a segment of my network over a VPN.
+
+Here is what I would like:
+
+#fig("framework-server/network.png", alt: "network architecture")
+
+== Goals: 1,2
+
+At first I went with the popular pfSense VM. For a #link(<pfsense>)[few]
+reasons, I abandoned it quickly.
+
+#html.hr()
+
+Then I decided to try out OpenWRT, and boy am I glad I did.
+
+#html.hr()
+
+I gave it two networking interfaces, each `vbmrX` we created earlier:
+
+#fig("framework-server/pfnet.png", alt: "openwrt bridge image")
+
+- `vmbr0` will be used to access our router from the home-network. It will also
+ be how our router gets access to the internet. Aka. *WAN*.
+- `vmbr1` will be used to create various subnets for our home-lab. Aka. *LAN*.
+
+#quote(block: true)[
+ You should reserve another IP in your home router using the MAC address
+ generated for `net0`. This will be how to access your home-lab router from
+ your home network.
+]
+
+I was able to setup a network with a few VLANs:
+
+#fig("framework-server/wrtnet.png", alt: "openwrt network image")
+
+#quote(block: true)[
+ This means that the next time we create a new VM or LXC container, we can
+ place it on `vmbr1`, with a particular VLAN Tag/`id` and it will start with
+ the corresponding IP for that network.
+]
+
+Few observations:
+
+- The Luci web UI is much more minimal and to the point. The terminology used
+ for things are more in-line with what I'm familiar with.
+- Soon I had the segregation I was looking for thanks to Firewall settings:
+ #fig("framework-server/fw1.png", alt: "firewall settings image")
+- DHCP, *and* DNS working as expected.
+
+== Goals: 3
+
+Then I started working on setting up my VPN (Wireguard) remote on my VPS.
+
+- I followed the script from
+ #extlink("https://github.com/angristan/wireguard-install")[here]. Worked
+ flawlessly.
+- Added a peer through the script - my OpenWRT VM.
+- Setup a domain - eg. `vpn.mydomain.com` to point to my VPS's public IP.
+- Then I enabled the "DMZ" setting (but *not* "Advanced DMZ" - which is
+ probably the _real_ DMZ) on my home ISP router settings, and added my OpenWRT
+ VM to it.
+ #fig("framework-server/dmz.png", alt: "dmz image")
+- I also setup a port-forward for the Wireguard port 51820 from my ISP router
+ settings, to point to my OpenWRT VM's internal port 51820.
+ #fig("framework-server/port1.png", alt: "port forward image")
+
+After all that, I started setting up Wireguard on OpenWRT.
+
+- The corresponding OpenWRT package is called `luci-app-wireguard`.
+- This will give you the ability to create a Wireguard interface on OpenWRT.
+ #fig("framework-server/wg.png", alt: "wg menu image")
+
+- I followed the following excellent tutorial for setting up the Wireguard
+ interface, configured to use my VPS as its peer:
+ #extlink("https://www.youtube.com/watch?v=04q41GEPvKA")[Setting up Wireguard
+ on OpenWRT]
+- Created the new interface and added the firewall settings:
+ #fig("framework-server/wrtnet2.png", alt: "openwrt wg ifrace")
+ #fig("framework-server/fw2.png", alt: "firewall wg settings image")
+
+- As is, this will mean that *only* LAB will have internet access (using the
+ Wireguard interface) - which is not what I want. I still want other subnets
+ to be routed via *WAN*.
+
+*_If only there was a way to route by VLANs..._*
+
+#quote(block: true)[
+ This is known as VPN _split-tunnelling_, or _Policy based routing_.
+]
+
+Luckily OpenWRT has a way to accomplish exactly this with a package called
+`luci-app-pbr`. This will add the PBR setting to your menu:
+
+#fig("framework-server/pbr.png", alt: "pbr setting image")
+
+- I followed the following tutorial:
+ #extlink("https://www.youtube.com/watch?v=FN2qfxNIs2g")[Setting up PBR on
+ OpenWRT]
+
+- Set up the rule for my PBR:
+ #fig("framework-server/pbr2.png", alt: "pbr rule image")
+
+- The default interface is set to `wan`, so everything else should behave as
+ normal:
+ #fig("framework-server/pbr1.png", alt: "pbr global gateways image")
+
+However, it did take many trail & error sessions (see the
+#link(<pbr-dns-leakage>)[dns leakage issues] section below) to get this working
+exactly as I wanted.
+
+*In summary*,
+
+When I create a VM/container in my _Lab_ subnet,
+
+- its external IP is that of my VPS
+- when it reaches out to the internet, it goes through the Wireguard tunnel
+ encrypted, to the VPS, and out the VPS's ISP.
+ #fig("framework-server/lab.png", alt: "lab flow image")
+
+When I create a VM/container *not* in my Lab subnet,
+
+- its external IP is the same as my home-network's public IP
+- when it reaches out to the internet, it goes through my home ISP (NAT), just
+ like all other devices on my home-network.
+ #fig("framework-server/lan.png", alt: "lan flow image")
+
+PBR provides quite granular control over this, for example, if you run a Plex
+server, it frequently needs to access the `plex.tv` domain. You can choose to
+route any source going to `plex.tv` to route via the `wan` interface instead.
+
+== Goals: 4
+
+For publicly accessible services, I need to point their domains to my VPS. I
+use sub-domains, so this is just a matter of creating `CNAME` records for these
+services.
+
+Then we setup our Wireguard on the VPS to forward all 80,443 TCP traffic to our
+Wireguard on OpenWRT. We'd need to add these lines *in addition* to your
+existing VPS `wg0` interface config:
+
+```
+PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 80 -j DNAT --to-destination <ip of your OpenWRT wg0>:80
+PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 443 -j DNAT --to-destination <ip of your OpenWRT wg0>:443
+...
+PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 80 -j DNAT --to-destination <ip of your OpenWRT wg0>:80
+PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 443 -j DNAT --to-destination <ip of your OpenWRT wg0>:443
+```
+
+Then we also need to setup port-forwards for 80,443 on OpenWRT going to our
+Reverse Proxy VM/Container.
+
+#fig("framework-server/port2.png", alt: "port forwards image")
+
+I just setup a temporary Caddy container in reverse-proxy mode to test this
+out.
+
+#fig("framework-server/rproxy.png", alt: "reverse-proxy setup image")
+
+Once this works, we can move on to building out our services :)
+
+= Home Lab Services
+
+These are _some_ of the services I run ad LXC container:
+
+- #extlink("https://nextcloud.com/athome/")[Nextcloud] - files, and image
+ storage and sharing (thanks to the excellent
+ #extlink("https://apps.nextcloud.com/apps/memories")[Memories] app). I used
+ the official #extlink("https://github.com/nextcloud/all-in-one")[AIO]
+ install.
+- #extlink("https://www.plex.tv/")[Plex] - media streaming.
+- #extlink("https://github.com/dani-garcia/vaultwarden")[Vaultwarden] - a
+ self-hosted password manager based on Bitwarden.
+- A few static websites.
+
+How I set up these services is beyond the scope of this post. You should check
+out my git repo's #extlink("https://gitlab.com/kdam0/home-lab")[readme] for
+instructions. I will just briefly summarize my process here.
+
+- I use LXC wherever possible as it has lower overhead than VMs.
+- LXC or VM hosts are provisioned by Terraform and services are configured by
+ Ansible.
+
+One thing to note is that as of now, there is no automated way to pass-through
+my iGPU to my LXC containers that would benefit from this - such as Nextcloud
+(for Memories app), Plex etc.
+
+Instead you must edit the config on Proxmox for the corresponding container,
+and add the following lines and restart the container. Edit
+`/etc/pve/lxc/<id of your container>.conf`:
+
+```
+lxc.cgroup2.devices.allow: c 226:0 rwm
+lxc.cgroup2.devices.allow: c 226:128 rwm
+lxc.cgroup2.devices.allow: c 29:0 rwm
+lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir
+lxc.mount.entry: /dev/dri/renderD128 dev/renderD128 none bind,optional,create=file
+```
+
+Then inside your LXC container do `ls -al /dev/dri/` and you should see some
+devices.
+
+#fig("framework-server/dri.png", alt: "gpu devices image")
+
+= Issues <issues>
+
+== Intermittent connectivity loss with Framework
+
+- When I initially started this project, I had already installed Proxmox, and
+ my setup my networking while on BIOS 3.16.
+- However every couple of days (sometimes hours) I was observing loss of
+ connectivity to both my Proxmox host, and my virtual router.
+- When I plugged in a monitor to see what was going on, I saw a few messages on
+ the TTY regarding `usbX disconnected...`.
+- Unplugging and re-plugging in the ethernet card did not fix it.
+- Usually a hard reset was required to bring things back to normal.
+
+I've encountered issues with other Framework peripherals in the past, so I
+reached out to the Framework support folks, and they immediately suggested a
+BIOS update.
+
+So I proceeded to give that a shot from within Proxmox using `fwupmgr`:
+
+#fig("framework-server/fail.png", alt: "bios up fail image")
+
+And to my surprise it was complaining about _battery level_? Its not even
+connected to the battery...
+
+I brought this up to the support and they confirmed my fear that it was not
+possible to update the BIOS while having the battery detached. Yikes!
+
+This was a deal-breaker for this whole project, until they confirmed that once
+I was on 3.17, it would be possible to do future updates without the battery
+being connected.
+
+- So I went ahead and pretty much attached the whole thing back into the laptop
+ chassis
+- Updated to 3.17, and applied "Standalone operation"
+ #fig("framework-server/bios2.png", alt: "bios update image")
+
+- Thankfully, all my connectivity issues have gone away now.
+- Framework support service is excellent!
+
+== Intermittent loss of internet for hosts in Lab Vlan.
+
+This one kept me up for many nights. To summarize:
+
+- in a host in the Lab network, doing an `apt update` the first time would
+ hang, mid-way through
+- running it a few times again, would eventually succeed the operation
+ #fig("framework-server/wtf.jpg", alt: "wtf meme")
+
+Could it be DNS? Could it be PBR? Could it be Wireguard?
+
+After many hours of troubleshooting, I happened to have seen errors during a
+`tcpdump` session on my VPN interface.
+
+Something about `...need to fragment...`. DOH!
+
+- Lab internet access is via VPN (due to PBR).
+- *VPN remote interface has an MTU of 1420, not 1500.*
+- Proxmox `vmbr1` set MTU to 1500 by default.
+- Therefore all hosts in Lab have a MTU mismatch trying to get to the internet.
+
+#quote(block: true)[Fixed. It was not DNS... it was MTU.]
+
+== PBR DNS leakage <pbr-dns-leakage>
+
+The whole point having my Lab traffic go through Wireguard is to prevent my
+home ISP from knowing what I am up to. *DNS queries leaking to my ISP
+completely defeats this purpose!*
+
+Although my external IP would correctly report to being my VPS one, tests like
+#extlink("https://github.com/macvk/dnsleaktest")[this] would show that DNS was
+still being sent to my home ISP.
+
+#fig("framework-server/dns.jpg", alt: "dns meme image")
+
+To fix this, I had to:
+
+- *Un-check* setting default route on the Lab interface.
+ #fig("framework-server/fix1.png", alt: "dns fix 1 image")
+
+- Explicitly set DHCP DNS settings for my Lab gateway interface to use my VPS's
+ `wg0` interface IP.
+ #fig("framework-server/fix2.png", alt: "dns fix 2 image")
+
+- Needed a static route defied for the `wg0` interface targeting the VPN
+ network via the OpenWRT local IP for `wg0` as a gateway.
+ #fig("framework-server/fix3.png", alt: "dns fix 3 image")
+
+== pfSense <pfsense>
+
+Few observations:
+
+- Segregation was working how I wanted it to.
+- A few firewall rules were required to allow access to parts of my
+ home-network - NAS.
+- DHCP, worked out of the box.
+- However, DNS did not.
+
+I'm still not sure what the issue was, but it was probably something small.
+
+More importantly, I was quite overwhelmed with all the options in the UI for
+which I had no knowledge of.
+
+Also, everything in pfSense pretty much _requires_ a UI, which for now is fine,
+but I'd like the option to move to a more automation friendly configuration in
+the future.
+
+= Summary
+
+The Framework laptop is happily fulfilling its destiny as a compute server, and
+so is my NAS as a storage server.
+
+Since switching to this setup, I've observed a dramatic improvement in
+responsiveness for all of my services.
+
+#fig("framework-server/imp.png", alt: "improvement meme image")
+
+Overall I'm quite pleased with how everything turned out once the appropriate
+BIOS was running on the Framework.
+]