Difference between revisions of "User:CounterPillow/Quartz64 Minecraft Server Guide"

From PINE64
Jump to navigation Jump to search
 
(7 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{warning|'''This article is a work-in-progress and incomplete, if you came here from a search engine, turn back.'''}}
{{warning|'''This article is a work-in-progress and incomplete, if you came here from a search engine, turn back.'''}}
{{hint|'''For prospective editors:''' This guide was authored by [[User:CounterPillow]]. Please read the lengthy comment at the top of the article in the edit window before trying to contribute to it in any way.}}
<!--FOR EDITORS PLEASE READ AND RESPECT
This guide makes a lot of deliberately chosen recommendations, and generally does not suggest "alternatives" at every step. This is on purpose. The author weighed the options against each other, and chose one they felt was the best fit for the situation to streamline the guide, or of the best educational value. This helps avoid needlessly bloating an already lengthy guide, and reduces potential for user confusion or decision paralysis. For this reason, the guide will remain in CounterPillow's user namespace.


This guide details how to build and set up a small Minecraft (Java Edition) server on a [[Quartz64]] Model B single board computer. The guide is intended to cover the basics in detail so that inexperienced users can follow it.
Please respect that this is the work of an individual and you should not tamper with their author voice. In particular:
- Do not suggest alternative ways of doing a thing, e.g. alternative disk formatting tools, alternative OS images, alternative firewall management systems, the blight upon humanity that is Docker, and so on.
- Do not alter the recommendations made, especially not in the Security Advice and Maintaining Your Server sections.
- Do not reorder sections or steps, the order they're done in is usually deliberately chosen to introduce new concepts as few at a time as possible.
What you may do is contribute fixes such as typo/grammar fixes, or illustrative diagrams for explained concepts or hardware configurations. If you think you have something to contribute beyond that, please discuss it with CounterPillow first.
 
TL;DR: Don't shit up my guide. I put a lot of time into it, and it's opinionated for a reason.
-->
This guide details how to build and set up a small Minecraft (Java Edition) server on a [[Quartz64]] Model B single board computer. The guide is intended to cover the basics in detail so that inexperienced users can follow it. You require no preexisting Linux knowledge whatsoever.


{{Info|If you have a 12+ year old child interested in computers and have some experience yourself, this could be a great guide to work through with them.}}
{{Info|If you have a 12+ year old child interested in computers and have some experience yourself, this could be a great guide to work through with them.}}
Line 338: Line 349:


A more advanced option is <code>vim</code>. You can run a lengthy interactive guide to using vim with the command <code>vimtutor</code>. Learning vim is a whole can of worms in of itself, so it's best to stick to nano if you're unsure.
A more advanced option is <code>vim</code>. You can run a lengthy interactive guide to using vim with the command <code>vimtutor</code>. Learning vim is a whole can of worms in of itself, so it's best to stick to nano if you're unsure.
=== Permissions ===
On Unix-like systems like Linux, files and directories are owned by one user and one group. Additionally, there are read/write/execute permissions applying for each file or directory for either the user, the group or everyone else. A user may be part of multiple groups at the same time.
We can view these permissions with <code>ls -al</code> in the first column. You'll see something like:
-rwxrw-r--
This would indicate a file (<code>d</code> not being present as the first letter, instead being <code>-</code>) that is
* '''r'''eadable by the user that owns it, '''w'''ritable by the user that owns it, e'''x'''ecutable by the user that owns it
* readable by anyone in the group that owns it, writable by anyone in the group that owns it, '''not''' executable by anyone in the group that owns it (unless, in this case, that specific user owns it)
* readable by everyone else, but neither writable nor executable by them.
You can ignore the next column (the number) in the output. The two columns that come after that are the user and the group that own the file/directory respectively.
{{hint|'''Further Explanation:''' "Executable" on a directory doesn't quite do what one might think it does: it means one is able to <code>cd</code> into the directory and read what files/directories it contains.}}
We can change the owner of a file with the <code>chown</code> command:
sudo chown <var>newuser</var>:<var>newgroup</var> <var>foo</var>
This would change the user that owns <var>foo</var> to <var>newuser</var>, and the group that owns the <var>foo</var> to <var>newgroup</var>. By adding the <code>-R</code> argument, we can also make this operation apply recursively to every file and directory contained within a targeted directory.
Similarly, we can modify the permission flags for the '''u'''ser, '''g'''roup and '''o'''ther users with the <code>chmod</code> command. This is best illustrated with a few examples:
* <code>chmod o+w <var>foo</var></code>: give everybody write permissions to <var>foo</var>.
* <code>chmod g=rw <var>foo</var></code>: set group permissions to read and write <var>foo</var>, but remove the group execute flag if present.
* <code>chmod u-x <var>foo</var></code>: remove execute permissions for the user that owns <var>foo</var>, but leave read/write flags as-is.
* <code>chmod ug=rw <var>foo</var></code>: set user and group permissions for <var>foo</var> to read/write but not execute.
You can once again combine this with the <code>-R</code> argument to make the operation recursive.
{{hint|'''Hint:''' Only the superuser can change the permissions of files or directories not owned by them.}}
{{hint|'''Further Explanation:''' Consult <code>man chown</code> and <code>man chmod</code> for further details. You can close the manual reader by pressing '''q'''.}}
== Setting Up The Network ==
=== Configuring A Fixed LAN IP Address ===
By default, the Plebian images use NetworkManager to automatically configure the ethernet interface in such a way that it asks your router to assign it an IP address inside your local network with a protocol called DHCP. What happens is that the Quartz64 asks the router for an IP address, and the router hands it one from a pool of addresses for a limited span of time, a so-called "lease". Dynamically configuring the IP address like this is great for quickly getting started, especially when in combination with mDNS, as everything just works automatically out of the box.
However, a bit later on in this guide, we'll want the router to know exactly what IP address our Quartz64 Model B is going to respond to even across power cycles of either device. That's why at this point, we'll set it up to have a fixed local network address, with '''one''' of two methods.
==== Method A: Using Reserved DHCP Leases ====
With this method, we'll simply configure your router to always hand the same IP address out over DHCP for the hardware address (MAC) of your Quartz64 Model B.
Many routers will have a web interface through which you can configure them. To find your router's IP address, type <code>ip route</code> into your SSH session with the Quartz64 Model B and hit enter to run the command. It should output something like:
default via 192.168.0.1 dev end0 proto dhcp src 192.168.0.58 metric 100
192.168.0.0/24 dev end0 proto kernel scope link src 192.168.0.58 metric 100
In this case, we can see that our router is <code>192.168.0.1</code>, as the ''default via'' route goes through it. Yours may be different.
You can now type this router IP address into your web browser, and with any luck, you'll be greeted with a web login prompt. If you've never set a password on your router's management interface, check the bottom or sides of your device for a sticker that lists a default password.
The precise management interface differs from router to router, so this guide can't give precise instructions. However, once logged in, there is usually a section labelled "DHCP". In it, you may be able to configure reserved IP addresses for a specific MAC address. To find the MAC address of your Quartz64 Model B, run "ip link" on it, which will give you output like this:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: end0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 76:36:c7:c4:57:e2 brd ff:ff:ff:ff:ff:ff
What we want here is the first colon separated address in the second link (end0), in this case <code>76:36:c7:c4:57:e2</code>. Your router should let you enter this MAC address somewhere to reserve a specific address from the DHCP pool for it.
==== Method B: Using A Fixed IP ====
If Method A isn't available to you, this method should always work.
Instead of using DHCP to request a lease for an IP address, our Quartz64 Model B can also simply assume it can communicate using a fixed IP address.
'''Important:''' Our fixed IP address should be ''outside'' the range of IP addresses our router hands out through DHCP, as otherwise collisions may occur! In the router web interface, look for a DHCP start address field and DHCP pool size field. An IP address either smaller than the start address (though above 0 and not colliding with the router's address) or larger than the start address plus the pool size (though below 255) will work; you may have to reduce the size of the DHCP pool if it's all maxed out though.
For example, with a DHCP pool that starts at 192.168.0.40 and has a size of 215, we could use a fixed IP address like 192.168.0.10 without accidentally colliding with one the router hands out.
Run the command
sudo nmtui
which should open up an interactive connection editor. Use your cursor keys to highlight the entry "Wired connection 1", and hit the right arrow key to navigate towards the "Edit" option, and hit enter.
Navigate to "IPv4 CONFIGURATION" and hit enter on "&lt;Automatic&gt;", then select "&lt;Manual&gt;". Navigate to the right over the "&lt;Show&gt;", and hit enter again. You should now be greeted by a whole host of options:
# In "Addresses:", hit "&lt;Add...&gt;" and enter your chosen fixed IPv4 address followed by <code>/32</code>, for example <code>192.168.0.21/32</code>.
# In "Gateway", enter your router's IPv4 address.
# In DNS Servers, enter on "&lt;Add...&gt;" and add <code>1.1.1.1</code>. Repeat the same process, but with <code>1.0.0.1</code>. This will add Cloudflare's DNS servers, as we'll no longer be receiving the router's DNS servers with manual configuration.
# Check the box next to "Ignore automatically obtained DNS parameters" by navigating over it and hitting the space bar. This makes sure that if our router tries to give us some DNS servers anyway, we don't use them.
# Check "Require IPv4 addressing for this connection" in the same way.
# Just to be consistent, go to "IPv6 CONFIGURATION", but this time leave it on Automatic. Hit "&lt;Show...&gt;" to expand it.
# Add the following two Cloudflare DNS servers: <code>2606:4700:4700::1111</code> and <code>2606:4700:4700::1001</code>.
# Check "Ignore automatically obtained DNS parameters".
# Navigate down to "&lt;OK&gt;" and hit enter
# Hit escape twice to quit nmtui
The easiest way to make sure your new network connection settings are used is to reboot your board with
sudo reboot
After logging back in, the command
ip addr
should show your chosen IP under end0 after "inet".
=== Configuring A Firewall ===
Even in a home network that is behind a router's NAT (more on that later), it's good practice to have your individual devices protected by firewalls as well. A firewall can work in many ways, but the simplest and most common method of operation is that it drops all incoming connection attempts originating from globally routed IPs and only allows those on network ports the administrator has explicitly allowed. This prevents locally running services that accidentally listen to incoming connections from all IPs from being accessible to any surprise guests.
While the Linux kernel includes all the logic to filter network packets, we still need an application to set up and manage these filter rules persistently. There are a multitude of such applications that can do this by building their own concept on top of Linux's packet filtering infrastructure, but the one we will be using in this guide is firewalld. firewalld is the default service for managing firewall rules in RedHat Enterprise Linux, Fedora and openSUSE, and is also available to be used on many other Linux distributions, such as Debian (the one we're using).
firewalld has a concept of "zones", which are logical constructs that either allow certain services through or not, can either be applied to specific interfaces or source IPs, and can pass network traffic between each other with policies. In our case, NetworkManager has assigned the ''end0'' interface the pre-defined ''public'' zone already. Our plan here is to use the pre-defined ''home'' zone for any traffic originating from Local Area Network IP addresses, and let ''public'' handle all the rest.
We first need to install firewalld through Debian's package manager, apt. The first command refreshes our index of packages, the second installs firewalld:
sudo apt update
sudo apt install firewalld
{{note|'''Warning:''' Debian automatically starts firewalld and configures it to auto-start on boot. It happens to be configured in a way that breaks our mDNS (.local domain) resolution. After installing firewalld, immediately work through the next few steps without closing your SSH session as you'll otherwise have to type the board's IP address manually to get back in.}}
We should now tell firewalld about what IP prefixes we consider to be our local network:
sudo firewall-cmd --permanent --new-ipset=lanv4 --type=hash:net
sudo firewall-cmd --permanent --ipset=lanv4 --add-entry=10.0.0.0/8
sudo firewall-cmd --permanent --ipset=lanv4 --add-entry=172.16.0.0/12
sudo firewall-cmd --permanent --ipset=lanv4 --add-entry=192.168.0.0/16
sudo firewall-cmd --permanent --new-ipset=lanv6 --option=family=inet6 --type=hash:net
sudo firewall-cmd --permanent --ipset=lanv6 --add-entry=fe80::/64
{{hint|'''Further Explanation:''' We've used [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation CIDR notation] here to denote entire ranges of IP addresses. These specific ranges are specified by the IETF and IANA to be used for private networks, such as our local area network (LAN).}}
Now that we have a ''lanv4'' and ''lanv6'' ipset, we can tell firewalld that any traffic originating from these IPs should be considered part of our ''home'' zone:
sudo firewall-cmd --permanent --zone=home --add-source=ipset:lanv4
sudo firewall-cmd --permanent --zone=home --add-source=ipset:lanv6
We should now reload those new permanent settings, so that our mDNS announcements work again:
sudo firewall-cmd --reload
Next, we'll want to remove the ''ssh'' service from the ''public'' zone, as Debian added it there by default:
sudo firewall-cmd --permanent --zone=public --remove-service=ssh
sudo firewall-cmd --reload
Finally, we want to add a service definition for Minecraft, which uses port 25565 with the TCP protocol, and allow it for our ''home'' and ''public'' zone:
sudo firewall-cmd --permanent --new-service=minecraft
sudo firewall-cmd --permanent --service=minecraft --add-port=25565/tcp
sudo firewall-cmd --permanent --zone=home --add-service=minecraft
sudo firewall-cmd --permanent --zone=public --add-service=minecraft
sudo firewall-cmd --reload
{{hint|'''Further Explanation:''' You can find a (somewhat lacking) documentation for firewalld on [https://firewalld.org/documentation/ the official firewalld website]. A better resource is, as always, <code>man firewall-cmd</code>.}}
{{hint|'''For Expert Users:''' Experienced users might at this point run <code>sudo iptables -L</code> to look at the actual filtering rules that firewalld has added, and they would be shocked to see some completely empty chains with an ACCEPT policy. Fear not, the firewall is working, but the iptables utility isn't. Modern kernels all use nftables, which confuses the legacy iptables specific tools when something like firewalld makes full use of nftables. Use <code>sudo nft list table inet firewalld</code> to get a listing of the filtering rules.}}
== Formatting The NVMe Drive ==
{{note|'''TODO:''' Explain how to wipefs + fdisk + format the NVMe drive, make a mount point and put it in fstab.}}
== Setting Up The Minecraft Java Edition Server ==
{{note|'''TODO:'''
# Add a minecraft system user with a minecraft group that has write permissions to the minecraft directory on NVMe, add minecraft to supplemental groups for user.
# Install Java, download PaperMC to the Minecraft directory, symlink it to paper.jar, run it once, accept the EULA.
# Edit server.properties, enable rcon with a password, enable whitelist, edit motd and set difficulty.
# Add the systemd unit to /etc/systemd/system/, set it to auto-start, then start it.
# Add player to whitelist and ops through rcon.
# Connect to the server for the first time!}}
=== Getting An RCon Client ===
Minecraft's server has a control protocol called "rcon". We'll need this protocol to gracefully shut down the Minecraft server, and add ourselves to ops later on. For this, we'll need a Minecraft RCon client. [https://github.com/Tiiffi/mcrcon Tiiffi/mcrcon] is a good one, but we need to compile and install it first.
To start off, let's install some required tools to compile it. In your Quartz64 Model B's SSH session, type:
sudo apt update
sudo apt install build-essential
Next we'll download the source code for mcrcon:
wget --content-disposition https://github.com/Tiiffi/mcrcon/archive/refs/tags/v0.7.2.tar.gz
This should have downloaded us an ''mcrcon-0.7.2.tar.gz'' archive file. Let's verify that what we've downloaded matches what this guide expects:
sha256sum mcrcon-0.7.2.tar.gz
This should output the following; compare the first string of letter and numbers to yours, they must match:
1743b25a2d031b774e805f4011cb7d92010cb866e3b892f5dfc5b42080973270  mcrcon-0.7.2.tar.gz
Now let's unpack the ''.tar.gz'' archive and change into the directory it contains:
tar -xf mcrcon-0.7.2.tar.gz
cd mcrcon-0.7.2/
{{hint|'''Explanation:''' <code>tar</code> is the standard utility for creating, modifying, inspecting and unpacking tar archives, commonly referred to as "tarballs". The <code>-x</code> argument states that we want to e'''x'''tract, and the <code>-f</code> argument specifies that the next argument is the filename of the tarball we wish to operate on. <code>tar</code> will automatically infer the compression of the tarball, in this case gzip, and decompress it as it is unpacking.}}
In this directory (check with <code>ls -l</code>!) there should be some files, including a ''Makefile''. This allows us to now compile the source code and install the resulting binary:
make
sudo make install
{{hint|'''Explanation:''' A Makefile instructs the <code>make</code> command on how to build a project that uses it. In this case, it also contains a ''install'' target, which copies the resulting binary into ''/usr/local/bin'' and its manual page into the local prefix for manual pages. Since only the superuser can write to these directories, we need to run the install target with <code>sudo</code>.}}
If everything went well, we should now have the <code>mcrcon</code> command available, as well as its manual page with <code>man mcrcon</code>.
{{hint|'''For The Future:''' If you ever need to recompile <code>mcrcon</code>, for example, after a major version distribution upgrade years down the line that gets rid of ''libc.so.6'', you can run:
sudo make uninstall
make clean
make
sudo make install
}}
== Making The Server Accessible From Outside Your Home Network ==
{{note|'''TODO:'''
# Explain what a dynamic IP is, set up ddclient with DuckDNS, have a systemd timer for that.
# Explain what port forwarding is, why it's needed, and why this depends on your router. Warn not to forward port 22/tcp, only port 25565/tcp.}}
== Maintaining Your Server ==
{{note|'''TODO:'''
# Explain how to start, stop, restart the Minecraft server with systemctl.
# Explain how to access server logs with journalctl.
# Explain how to keep PaperMC updated (including changing the symlink).
# Explain apt update and apt upgrade. Recommend unattended-upgrades.
# Explain Debian major releases and to keep an eye out for them.
# Recommend htop to see how CPU and RAM usage is looking.
# Recommend some backup solutions; rsync to flash drive, short mention of borg/borgmatic over the network.
# Explain how to transfer files to/from server: sftp, rsync, winscp, etc.}}
== Security Advice ==
This section will give some general security advice on running your own server, and elaborates on some decisions this guide makes.
=== SSH Public Key Authentication ===
If you absolutely insist on opening up the SSH port to the public internet, you should use public key authentication and disable password authentication for SSH altogether.
This way, public key cryptography is used to authenticate, which is computationally extremely hard to break in this case. The password you encrypt your private key with never travels over the network.
To generate an SSH public/private key pair, run
ssh-keygen
on '''your client machine''', i.e. not the Quartz64 Model B.
Then copy the newly generated SSH public key over to the Quartz64 Model B with
ssh-copy-id <var>youruser</var>@<var>yourhost</var>.local
Try logging in with ssh now. It should prompt you for your key's password, rather than the user account's password.
If it works, you should then finally disable password authentication by editing ''/etc/ssh/sshd_config'' as the superuser and setting:
PasswordAuthentication no
Then, restart the ''sshd.service'' with <code>sudo systemctl restart sshd.service</code>.
Note that from this point on, if you want to log in from a different machine, you'll need to either copy your public/private keypair over or generate a new one there and edit ''~/.ssh/authorized_keys'' manually, or run <code>ssh-copy-id</code> with the <code>-i</code> argument pointing to that public key.
=== On Minecraft Server Modifications And Plugins ===
Please note that Minecraft server modifications or plugins run with the same access as your Minecraft server. While we tried our best in this guide to restrict the Minecraft server's access to the rest of the system, it can obviously still do bad things to our Minecraft world, our computational resources and our network. That's why, whenever you install a server-side modification or plugin, you should ask yourself these questions:
# Do you trust the website/service you are downloading the mod/plugin from to not be malicious?
# Do you trust the website/service you are downloading the mod/plugin from to have the latest version uploaded by the mod/plugin's author rather than an untrustworthy third party?
# Do you trust the mod/plugin author to not be malicious?
# Do you trust the mod/plugin author to have good security practices, as to not get their account compromised or development computer infected with malware?
# Do you trust the mod/plugin to not open up security vulnerabilities in your server?
Chances are the answers to some of these questions will be "no". In that case you have some leg work to do: research the site and users, double-check you're on the right domain, read the code for anything fishy, and if you're ever unsure, ask for a second opinion from somebody knowledgeable on the topics.
This is not unwarranted paranoia: Minecraft server modifications [https://github.com/dogboy21/serializationisbad have introduced security vulnerabilities before]. Well trusted people's accounts [https://github.com/fractureiser-investigation/fractureiser have been hijacked before, or their uploads unknowingly infected with malware]. And the way this happened? They themselves were downloading modifications from malicious users.
Please do note that all these issues generally do not apply to Minecraft Data Packs. While they are more limited than modifications or plugins that hook into the actual code, these limitations are good in the context of security.
=== Do Not Engage In Security By Obscurity ===
You may be tempted to not enable a whitelist because you think nobody is going to find your Minecraft server anyway. This is false. People and bots are constantly scanning the entire internet for open services. This includes Minecraft servers. If you do not have a whitelist, you will sooner or later get uninvited visitors.
Similarly, if you expose any service other than the Minecraft server to the internet through your router, expect that to be instantly discovered as well. Automated exploit scanners will then likely be hitting it regularly.
This sounds scary, but it's really not: if your system is up-to-date and follows best security practices, you generally won't have much to fear. It just means that you can't expose any insecurely configured service to the internet and expect nobody to find it.
=== Can't Be Bothered To Maintain It? Turn It Off ===
One of the biggest possible dangers to your home network's security is unwillingness to take responsibility. If you no longer want to maintain the server, turn the board off, unplug it, and delete the port forwarding rule in your router's web interface.
The majority of security breaches don't happen because somebody tries very hard to breach one target, but because a lot of people try very little to breach many targets. Chances are they will find a system somewhere that nobody feels responsible for anymore, and which hasn't been updated in 5 years, and runs a service of a version with a known vulnerability. Avoid being that system: if your Quartz64 Model B begins operating as part of a botnet due to your indifference, the rest of the internet will suffer.
=== Why Reboots Are Sometimes Recommended ===
Occasionally, your Linux kernel package (''linux-image-arm64'') will update. This is great! The kernel is the most basic part of the operating system, and responsible for enforcing fundamental privilege separation. Unfortunately, live-patching the running kernel (i.e. updating it in memory without requiring a reboot) is something that only enterprise-grade commercial Linux distributions like RedHat Enterprise Linux or SUSE Linux do. Since we're running Debian here and Debian doesn't have this feature, the only way to run the newly updated kernel is to reboot the system.
You can run <code>cat /var/run/reboot-required</code> to see if your Quartz64 Model B requires a reboot. It's worth noting that the Linux kernel development community does not distinguish regular bugs from security bugs, as many a "regular" bug could be a security bug in the right circumstances. However, Debian itself [https://www.debian.org/security/#DSAS publishes security advisories] when a Linux kernel update they ship fixes known potential security issues.
Whether or not you want to reboot for a specific kernel update is up to you. People run servers with hundreds of days of uptime (check with the command <code>uptime</code>) just fine; just because a potential security issue was fixed in the Linux kernel doesn't mean it'd be part of a realistic attack scenario on a specific system. In our case, the attack scenario we're most worried about is that somebody manages to breach the PaperMC Minecraft server, and then gains arbitrary code execution in its context, from which they will then likely attempt to attack the kernel to gain additional privileges. A second, much less likely attack scenario is that the Linux kernel would have a vulnerability in its network stack that would allow an attacker to remotely exploit it. Such vulnerabilities are exceedingly rare in Linux.
=== Why Not Docker: On Software Supply Chains And Understanding Your Stack ===
For the majority of this guide, self-declared DevOps EpicPoggers Awesomeninjas have probably been foaming at their mouth jumping at the opportunity to exclaim that one should just use Docker. This section serves as a soapbox to talk about the problems with that suggestion.
While a Docker container may be, well, contained, it pulls in an entire stacking doll of other images it is built upon. This means an entire operating system to support the application being run, minus a kernel.
This is great if you are the person writing the image manifests, know what your dependency requirements are, and track the entire software supply chain to ensure you always get the right versions for your stack, with all the latest vulnerabilities patched.
However, this is not how the vast majority of people use Docker. The overwhelming majority of Docker users simply pull some random person's image, run it, and thus make these assumptions:
# That the author of the image is not malicious. Malicious images [https://sysdig.com/blog/analysis-of-supply-chain-attacks-through-public-docker-images/ are widespread on Docker Hub].
# That the author of the image has good security practices in order to avoid publishing malicious images. We know account compromises will happen as Docker Hub seemingly does not require Two-Factor Authentication.
# That the author will update the images when critical security fixes in any of the things they depend on are fixed. A [https://dl.acm.org/doi/10.1145/3029806.3029832 2017 research paper] found that many images are hundreds of days old.
# That any of the things that image depends on, recursively, will also meet these three points.
For a single service/single node scenario, Docker complicates the software supply chain massively. Instead of getting most of our system from a source we already trust (Debian), we're now getting a whole second system from a myriad of sources opaquely compiled into one image.
Additionally, Docker complicates the following things:
* Knowing what version a specific runtime dependency is, and that all copies of this dependency on the entire system are patched against the latest vulnerability.
* Updating the container image and spinning up a new container based on it; this is not handled through your system's package manager.
* Persistent data; it's stored in Docker volumes, which may or may not be attached to containers, and you need to actively try to have them stored somewhere other than the default location.
* Service management; Docker reinvents service management in its own, arguably worse way.
* Networking; things are on a virtual network with Docker, which complicates firewall rules and other network configurations.
* Logging; Docker reinvents this too.
By not using a Docker container, but rather a locked down systemd service, we get the following benefits:
* All system software updates, including the Java Runtime, are handled by Debian, irrespective of the version of the Minecraft server we run.
* All of our services use the same system dependencies, and we can answer the question of "has <var>xyz</var> been patched?" by just looking at <code>apt</code>.
* We still get reasonably strong process isolation thanks to the various security sandboxing options our systemd service uses, despite not using Linux namespaces (containers).
* Our persistent data is where we expect it to be, owned by the user and group we expect it to be owned by.
* Our network is not burdened with virtual interfaces and forwarding rules.
* Our Minecraft server is tracked and dependency ordered by systemd's service management. It's automatically started on boot, and gracefully stopped on shutdown.
* Our Minecraft server's logs will show up in the system journal, and we can filter through them or correlate them with other sources just as with any other service reporting to the journal.
Container images built from manifests start to make sense when you are running complex sets of services scaled horizontally across a massive homogeneous cluster, where you want to rescale as needed and deploy new versions of your entire stack as immutable images that you can roll back easily. They also make some amount of sense in a case where you only have access to a very old userland (e.g. a stable enterprise Linux release) but need to run software requiring a myriad of dependencies that are either not packaged or not of a recent enough version in your distribution.


== Troubleshooting ==
== Troubleshooting ==
Line 376: Line 728:
  screen /dev/ttyUSB<var>n</var> 1500000 cs8 -cstop
  screen /dev/ttyUSB<var>n</var> 1500000 cs8 -cstop


You can quit screen by hitting Ctrl+a, then hit backspace (<code>\</code>) and confirm with y.
You can quit screen by hitting Ctrl+a, then hit backslash (<code>\</code>) and confirm with y.


For minicom, (replacing <var>n</var> with the number of your serial adapter) use:
For minicom, (replacing <var>n</var> with the number of your serial adapter) use:
Line 404: Line 756:
  screen /dev/tty.usbserial-<var>foo</var> 1500000 cs8 -cstop
  screen /dev/tty.usbserial-<var>foo</var> 1500000 cs8 -cstop


You can quit screen by hitting Ctrl+a, then hit backspace (<code>\</code>) and confirm with y.
You can quit screen by hitting Ctrl+a, then hit backslash (<code>\</code>) and confirm with y.


==== Using The Serial Console ====
==== Using The Serial Console ====

Latest revision as of 17:33, 15 September 2023

Warning: This article is a work-in-progress and incomplete, if you came here from a search engine, turn back.
For prospective editors: This guide was authored by User:CounterPillow. Please read the lengthy comment at the top of the article in the edit window before trying to contribute to it in any way.

This guide details how to build and set up a small Minecraft (Java Edition) server on a Quartz64 Model B single board computer. The guide is intended to cover the basics in detail so that inexperienced users can follow it. You require no preexisting Linux knowledge whatsoever.

Note: If you have a 12+ year old child interested in computers and have some experience yourself, this could be a great guide to work through with them.

Introduction

Motivation

You may ask yourself why one would want to host a Minecraft server themselves instead of renting one. That is a valid question, and one big consideration is running cost. Buying the hardware and running the software yourself from your own internet access means that you're only paying a one-time cost (ignoring the minuscule power draw), whereas renting a server would usually incur a monthly fee. Since it's your server, you also have complete control over the software you run. Lastly, it's a great and fun way to learn about Linux system administration.

As for disadvantages, the server will only be as available as your home internet connection and electricity supply, and you yourself are responsible for keeping your system safe and up-to-date. The server is also not very high performance. It's best for a small group of friends, and not suitable for a public server that is likely to come under DDoS attacks.

In the below table, you can see a comparison between paying Microsoft for a realms server and self-hosting it according to this guide.

Minecraft Realms Self-Hosted
Setup Cost USD 0 USD ~130
Monthly Cost USD 7.99 USD 0
Modifications No Yes
Players 10 Uncapped
Availability Always Depends on you
Security Provided by Microsoft Your responsibility
Performance High Low
Full Shell Access No Yes
Teaches You Linux No Yes

Ignoring all points other than cost and assuming a USD 0.30/kWh rate for power with an average power draw of 3W for the board, the Quartz64 Model B as it is set up in this guide will have paid for itself in less than 18 months.

Game Plan

Let's get a high-level overview of what will be set up in this guide.

The goal is to have the base operating system (Debian Bookworm, using the Plebian images) installed on the microSD card. The Minecraft server itself will read and write its data from an NVMe M.2 SSD. The Minecraft server we'll use is PaperMC. We'll run it as a locked down systemd service. To make the server available to others on the internet, we'll use ddclient with DuckDNS to get ourselves a domain name for our (likely) dynamic IP, and forward the ports on the router.

Shopping List

Total cost: Around USD 130 excluding shipping.

From Pine Store

Note: When choosing "Courier Shipping" on Pine Store, you may unfortunately be charged additional import duties once the package arrives. How high these are depends on your jurisdiction, the order value and the courier Pine Store chooses.

From Elsewhere

  • A good 32GB microSD card for the operating system — USD ~15
    • SanDisk Max Endurance is a decent brand, Samsung EVO is fine too.
    • Even if the brand looks correct, buy from a reputable marketplace and seller: fakes are common! Double-check who's selling it to you on e.g. Amazon.
    • Avoid no-name cards, like the ones from Pine Store, or cards that aren't intended for longevity but maximum speed.
  • A TLC flash 500GB or higher capacity NVMe M.2 drive — USD ~35
    • Look at the SSD spreadsheet and pick a cheap one with TLC, and either DRAM or HMB
    • PCIe 3/4 doesn't matter, it'll run at one lane of PCIe 2 anyway
    • Decent usually cheap options: Kioxia Exceria, Patriot P300, Lexar NM620, WD Blue SN570
    • Avoid: Kingston NV1/NV2 (no cache, meaningfully slow here), anything with QLC flash (could be meaningfully slow here), anything SATA/AHCI (won't work), anything Aliexpress (fraud).
    • Shop around for deals and used drives! Just look up the manufacturer and model in the spreadsheet to verify that it's both TLC and has either DRAM or HMB cache. Bandwidth will be severely limited by the Quartz64's one lane of PCIe 2 here so paying a lot is not worth it.

Things You Hopefully Already Have

  • A microSD card reader (your laptop may have one built-in already), USB3 microSD card readers are cheap and useful.
  • An ethernet Cat5e or Cat6 or Cat6a cable (though technically you could run it on Wi-Fi)

Getting Started

Unbox your Quartz64 Model B. Carefully attach the U.2 wireless antenna (the little grey wire with the sticker on the end) to the antenna connector that can be found near the Wi-Fi chipset (the shiny metal square). If you do not need Wi-Fi, you can skip attaching the antenna. Now is a good time to stick on the tiny aluminium heatsink; remove the protective film on its bottom and stick it onto the black chip in the centre labelled "Rockchip".

Take the baseplate from your Acrylic Open Enclosure, the small screws, and the brass nuts. Insert the screws from below the base plate (top side is the one with the acrylic standoffs) through the standoffs. Place your Quartz64 Model B board on the standoffs such that the screw threads poke through its mounting holes, oriented such that the board ends up in the middle of the base plate; the standoffs are not centred on purpose, so orientation matters. Now, secure the board by fastening the four brass nuts onto the exposed screw threads.

Next, get the large screws and metal posts, as well as the top acrylic plate. Insert the large screws through the bigger outside holes of the base plate, and fasten the metal posts on the other side. Lay the top plate on the four metal posts, and fasten it down with the remaining four large screws.

Connect the board with an ethernet cable to your network, but do not hook up power yet.

Stick your microSD card into your SD card reader, then follow Plebian's official flashing instructions.

Once it's flashed successfully, stick the microSD with the black bottom facing upwards into the underside of the board, below the metallic Wi-Fi chipset. This can be fiddly to get in with the board mounted to the acrylic base plate. If you're unsure of where exactly the microSD connector on the Quartz64 Model B is, peer through the transparent bottom of the acrylic base plate.

Next, unbox the M.2 NVMe SSD, flip it around and stick it in right below where you mounted the SD card; it will awkwardly protrude from the side of the case, and the spring loading mechanism of the connector will push it against the acrylic bottom plate. As sketchy as this is, it should work, so long as you don't violently bump the protruding SSD.

First Contact

After you hook up the power supply to the barrel connector (important: not the audio jack, that would be bad), your board's LEDs should light up. After a couple dozen seconds, one of the LEDs should begin rhythmically blinking in a "heartbeat" like fashion. If the heartbeat pattern does not appear even after a minute or so, try hitting the little white switch closest to the antenna mount to reset the board. If it still doesn't appear after another minute, go to #Connecting UART.

Once you see the heartbeat pattern, you may now connect to the board over the network, using SSH.

Note: If you see a heartbeat pattern but the board never gets a DHCP lease, please follow the steps in #Is My Ethernet Broken?.

Using SSH on Windows

Recent versions of Windows 10 and 11 have SSH as well as mDNS support built-in. Click on the start menu, search for the Command Prompt and open it.

Into the command prompt, type:

ssh pleb@plebian-quartz64b.local

if everything went well, it should prompt you with a host key question. Type "yes" to accept and remember the host key for future use. If the Command Prompt says ssh is not a recognised command, look at #Installing OpenSSH On Windows.

ssh will now ask you for a password for the user "pleb". Type in "pleb" as the password. You will then be prompted to change your password; change it to something secure, then log back in (with the same ssh command again, hit the up arrow to get the previous command from your history) with your new password.

Congratulations, you are now logged into your Quartz64 Model B.

Using SSH on Linux or BSD

Make sure the OpenSSH client is installed; how depends on your distribution. If your system is set up with an mDNS resolver such as Avahi or systemd-resolved with mDNS resolution enabled, you can open a terminal and type:

ssh pleb@plebian-quartz64b.local

if everything went well, it should prompt you with a host key question. Type "yes" to accept and remember the host key for future use.

ssh will now ask you for a password for the user "pleb". Type in "pleb" as the password. You will then be prompted to change your password; change it to something secure, then log back in (with the same ssh command again, hit the up arrow to get the previous command from your history) with your new password.

Congratulations, you are now logged into your Quartz64 Model B.

Using SSH on macOS

Open a terminal by clicking on the Launchpad icon in the Dock, typing Terminal in the search field and then clicking on Terminal.

Into the newly opened terminal, type:

ssh pleb@plebian-quartz64b.local

if everything went well, it should prompt you with a host key question. Type "yes" to accept and remember the host key for future use.

ssh will now ask you for a password for the user "pleb". Type in "pleb" as the password. You will then be prompted to change your password; change it to something secure, then log back in (with the same ssh command again, hit the up arrow to get the previous command from your history) with your new password.

Congratulations, you are now logged into your Quartz64 Model B.

First Steps

TODO: Validate this.

Changing The Hostname

First up, you may want to change the hostname the board uses. We can do this quite easily; in your ssh session with the board, type

sudo hostnamectl hostname yourhost

to change the hostname to yourhost. You will be prompted for your password.

Explanation: What we just did was run the hostnamectl command as the superuser with sudo. The superuser, or sometimes called the root user, is the ultimate administrator account on Unix-like systems such as Linux: it has access to everything, and should therefore be used with appropriate caution. By prefixing a command with sudo like this, we ask for it to be run as the superuser. The user "pleb" is allowed to use sudo in this way because it is in the necessary supplementary group.

After changing our hostname, a reboot is usually a good idea; type

sudo reboot

to reboot the board. To connect to it after doing this, you'll have to use the new hostname with the .local top-level domain, for example

ssh pleb@mcserver.local

if you set your hostname to "mcserver". You will be prompted to accept the host key again, only this time, it should tell you that it already knows the host by a different name.

Changing The Username

Being called a pleb isn't very nice, so we'll change this as well. In your ssh session, type:

sudo usermod -l yourname -md /home/yourname pleb

to update your username and home folder to yourname. Don't try to use spaces in your name and don't start your new username with a number. For example, if we wanted to rename the user pleb to greg, we would type:

sudo usermod -l greg -md /home/greg pleb
Explanation: The command usermod here is run with several arguments, each separated by space. The first argument, -l, signals that the next argument will be the new user name. What follows next in our example is "greg", which is the new username we chose.

Next is the argument -md. This is actually two arguments in one, and could be written instead as -m -d in usermod's case, though this depends on the command. -m tells the command to move our old home to our new home, and -d tells it that the next argument is the path of the new home directory. In this case, we chose "/home/greg/". This is the usual expected location for a home directory for a user called "greg", though it's not a strict requirement.

Finally, the last argument is a positional argument, which usermod expects to be the current username you wish to operate on.

Then, we should also rename the user group:

sudo groupmod -n yourname pleb
Explanation: Each user also has a "login group" they belong to, usually named the same as their username (though not necessarily so). To keep it consistent with your new username, we're renaming it with the groupmod command.

Finally, log out (by either typing logout or hitting Ctrl+d, and start a new ssh session with:

ssh yourname@yourhost.local

Your prompt should now show you your new name, and the command id -gn (no sudo!) should show you that your group has been renamed as well.

Getting Our Feet Wet

If you are already familiar with basic Linux/Unix usage from the command line, you can skip this section. However, to ensure nobody is left behind, this will be a quick crash course into how to do very basic things.

Where Are We?

There is a concept of a "current working directory". Any command we run assumes relative paths are relative to this working directory. We can show the current working directory with:

pwd

This should currently show your home directory.

Creating Directories

Using the mkdir command, we can create a new directory.

mkdir foo

would create a new directory called foo in our current working directory. That's because the argument we gave it is interpreted as a path relative to the current working directory, as it does not begin with a forward slash (/).

Changing The Current Working Directory

With the cd command, we can change our current working directory:

cd foo

we should now be in the directory foo; we can verify this by running pwd again:

pwd

This should output /home/yourname/foo.

If we want to ascend the directory hierarchy by one level, we can change to the path ".." to do so; ".." has a special meaning to be the parent directory:

cd ..

If you are ever in a hurry, you can run cd without any arguments to return to your home directory.

Further Explanation: Another special directory name is .: this refers to the current directory. For example, cd . would do nothing.

cd foo/./bar would be equivalent to cd foo/bar, and cd foo/../bar would be equivalent to cd bar (into foo, up one level, into bar).

Another path component with special meaning is ~. Our shell expands this to our user's home folder. cd ~ would be equivalent to cd with no arguments.

We can also give cd an absolute path. Absolute paths start with a forward slash, and the highest level directory is simply /, the root level directory. We can change your working directory to / with cd /, but be aware that we're not allowed to write any files or make any directories here.

Listing Directories

If we want to find out what's in our current directory, we can use the ls command:

ls

We can also give it a path as an argument to list the contents of that path instead:

ls /usr

This would list the contents of the system usr directory.

To get a more detailed list, including who owns the files and what their access rights are, we can use the -l argument to ls:

ls -l

By default, ls does not list files or directories starting with a dot (.). We can give it the -a argument to list those as well, including the special . (the folder itself being listed) and .. (its parent folder) directories:

ls -al
Further Explanation: Note that we once again were able to combine the two single-letter short options into one argument. ls -al is equivalent to ls -a -l or ls -l -a.

Creating (Empty) Files

To create empty files, we can use the touch command:

touch foo

would create a new empty file named foo in our current working directory.

Further Explanation: What touch actually does is update a file's last-modified time, and create the file if it doesn't already exist. Running touch on an already existing time would only update its last-modified time, and not alter the file contents in any way.

Deleting Things

To delete an empty directory, we can use the rmdir command:

rmdir foo

would remove the foo directory, but only if it's empty.

To remove a file, we can use the rm command:

touch foo
ls
rm foo
ls

This would create a file called foo, list the current working directory to show you that it's there, then remove the file, and then list the current working directory again.

rm can also remove things recursively, in this case it's able to remove directories. Be aware though that there is no undo, if you rm something near and dear to you, you better have had backups. In the following example, we'll create a directory, then create a file in it, then delete the directory recursively by passing rm the -r argument:

mkdir foo
touch foo/bar
ls foo
rm -r foo
ls

More Useful Commands

A short list of handy commands to know:

  • cp: Copy a file (or, when recursive, a whole directory)
    • Example: cp foo bar copies file "foo" to "bar".
    • Example: cp -R foo bar recursively copies the directory "foo" and its contents to "bar".
  • mv: Move/Rename a file or directory
    • Example: mv foo bar moves a file or directory "foo" to its new name "bar".
  • man: Show manual page for a command.
    • Example: man ls shows the manual page for the ls command.
    • Use this whenever you're not sure about a command's invocation. It'll be more accurate to your situation than what an internet search will tell you, and a lot less annoying to use.
  • wget: Download a file from the internet
Useful Reminder: You can tab-complete a lot of things! For example, if you don't want to type out a whole directory name, hit tab once to complete it as far as possible, and hit tab twice to show the remaining options with how far you've completed it so far. Try typing ls /u and hitting tab, and you should have it complete to ls /usr/.

Editing Text Files

For the rest of the guide, we'll be spending significant amounts of time editing text files. That's why it's good to familiarise yourself with a terminal text editor. The simplest to use of these is nano.

nano foo.txt

opens up nano, writing to the file foo.txt. We can save to the file with Ctrl+o, and quit nano with Ctrl+x.

A more advanced option is vim. You can run a lengthy interactive guide to using vim with the command vimtutor. Learning vim is a whole can of worms in of itself, so it's best to stick to nano if you're unsure.

Permissions

On Unix-like systems like Linux, files and directories are owned by one user and one group. Additionally, there are read/write/execute permissions applying for each file or directory for either the user, the group or everyone else. A user may be part of multiple groups at the same time.

We can view these permissions with ls -al in the first column. You'll see something like:

-rwxrw-r--

This would indicate a file (d not being present as the first letter, instead being -) that is

  • readable by the user that owns it, writable by the user that owns it, executable by the user that owns it
  • readable by anyone in the group that owns it, writable by anyone in the group that owns it, not executable by anyone in the group that owns it (unless, in this case, that specific user owns it)
  • readable by everyone else, but neither writable nor executable by them.

You can ignore the next column (the number) in the output. The two columns that come after that are the user and the group that own the file/directory respectively.

Further Explanation: "Executable" on a directory doesn't quite do what one might think it does: it means one is able to cd into the directory and read what files/directories it contains.

We can change the owner of a file with the chown command:

sudo chown newuser:newgroup foo

This would change the user that owns foo to newuser, and the group that owns the foo to newgroup. By adding the -R argument, we can also make this operation apply recursively to every file and directory contained within a targeted directory.

Similarly, we can modify the permission flags for the user, group and other users with the chmod command. This is best illustrated with a few examples:

  • chmod o+w foo: give everybody write permissions to foo.
  • chmod g=rw foo: set group permissions to read and write foo, but remove the group execute flag if present.
  • chmod u-x foo: remove execute permissions for the user that owns foo, but leave read/write flags as-is.
  • chmod ug=rw foo: set user and group permissions for foo to read/write but not execute.

You can once again combine this with the -R argument to make the operation recursive.

Hint: Only the superuser can change the permissions of files or directories not owned by them.
Further Explanation: Consult man chown and man chmod for further details. You can close the manual reader by pressing q.

Setting Up The Network

Configuring A Fixed LAN IP Address

By default, the Plebian images use NetworkManager to automatically configure the ethernet interface in such a way that it asks your router to assign it an IP address inside your local network with a protocol called DHCP. What happens is that the Quartz64 asks the router for an IP address, and the router hands it one from a pool of addresses for a limited span of time, a so-called "lease". Dynamically configuring the IP address like this is great for quickly getting started, especially when in combination with mDNS, as everything just works automatically out of the box.

However, a bit later on in this guide, we'll want the router to know exactly what IP address our Quartz64 Model B is going to respond to even across power cycles of either device. That's why at this point, we'll set it up to have a fixed local network address, with one of two methods.

Method A: Using Reserved DHCP Leases

With this method, we'll simply configure your router to always hand the same IP address out over DHCP for the hardware address (MAC) of your Quartz64 Model B.

Many routers will have a web interface through which you can configure them. To find your router's IP address, type ip route into your SSH session with the Quartz64 Model B and hit enter to run the command. It should output something like:

default via 192.168.0.1 dev end0 proto dhcp src 192.168.0.58 metric 100
192.168.0.0/24 dev end0 proto kernel scope link src 192.168.0.58 metric 100

In this case, we can see that our router is 192.168.0.1, as the default via route goes through it. Yours may be different.

You can now type this router IP address into your web browser, and with any luck, you'll be greeted with a web login prompt. If you've never set a password on your router's management interface, check the bottom or sides of your device for a sticker that lists a default password.

The precise management interface differs from router to router, so this guide can't give precise instructions. However, once logged in, there is usually a section labelled "DHCP". In it, you may be able to configure reserved IP addresses for a specific MAC address. To find the MAC address of your Quartz64 Model B, run "ip link" on it, which will give you output like this:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: end0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 76:36:c7:c4:57:e2 brd ff:ff:ff:ff:ff:ff

What we want here is the first colon separated address in the second link (end0), in this case 76:36:c7:c4:57:e2. Your router should let you enter this MAC address somewhere to reserve a specific address from the DHCP pool for it.

Method B: Using A Fixed IP

If Method A isn't available to you, this method should always work.

Instead of using DHCP to request a lease for an IP address, our Quartz64 Model B can also simply assume it can communicate using a fixed IP address.

Important: Our fixed IP address should be outside the range of IP addresses our router hands out through DHCP, as otherwise collisions may occur! In the router web interface, look for a DHCP start address field and DHCP pool size field. An IP address either smaller than the start address (though above 0 and not colliding with the router's address) or larger than the start address plus the pool size (though below 255) will work; you may have to reduce the size of the DHCP pool if it's all maxed out though.

For example, with a DHCP pool that starts at 192.168.0.40 and has a size of 215, we could use a fixed IP address like 192.168.0.10 without accidentally colliding with one the router hands out.

Run the command

sudo nmtui

which should open up an interactive connection editor. Use your cursor keys to highlight the entry "Wired connection 1", and hit the right arrow key to navigate towards the "Edit" option, and hit enter.

Navigate to "IPv4 CONFIGURATION" and hit enter on "<Automatic>", then select "<Manual>". Navigate to the right over the "<Show>", and hit enter again. You should now be greeted by a whole host of options:

  1. In "Addresses:", hit "<Add...>" and enter your chosen fixed IPv4 address followed by /32, for example 192.168.0.21/32.
  2. In "Gateway", enter your router's IPv4 address.
  3. In DNS Servers, enter on "<Add...>" and add 1.1.1.1. Repeat the same process, but with 1.0.0.1. This will add Cloudflare's DNS servers, as we'll no longer be receiving the router's DNS servers with manual configuration.
  4. Check the box next to "Ignore automatically obtained DNS parameters" by navigating over it and hitting the space bar. This makes sure that if our router tries to give us some DNS servers anyway, we don't use them.
  5. Check "Require IPv4 addressing for this connection" in the same way.
  6. Just to be consistent, go to "IPv6 CONFIGURATION", but this time leave it on Automatic. Hit "<Show...>" to expand it.
  7. Add the following two Cloudflare DNS servers: 2606:4700:4700::1111 and 2606:4700:4700::1001.
  8. Check "Ignore automatically obtained DNS parameters".
  9. Navigate down to "<OK>" and hit enter
  10. Hit escape twice to quit nmtui

The easiest way to make sure your new network connection settings are used is to reboot your board with

sudo reboot

After logging back in, the command

ip addr

should show your chosen IP under end0 after "inet".

Configuring A Firewall

Even in a home network that is behind a router's NAT (more on that later), it's good practice to have your individual devices protected by firewalls as well. A firewall can work in many ways, but the simplest and most common method of operation is that it drops all incoming connection attempts originating from globally routed IPs and only allows those on network ports the administrator has explicitly allowed. This prevents locally running services that accidentally listen to incoming connections from all IPs from being accessible to any surprise guests.

While the Linux kernel includes all the logic to filter network packets, we still need an application to set up and manage these filter rules persistently. There are a multitude of such applications that can do this by building their own concept on top of Linux's packet filtering infrastructure, but the one we will be using in this guide is firewalld. firewalld is the default service for managing firewall rules in RedHat Enterprise Linux, Fedora and openSUSE, and is also available to be used on many other Linux distributions, such as Debian (the one we're using).

firewalld has a concept of "zones", which are logical constructs that either allow certain services through or not, can either be applied to specific interfaces or source IPs, and can pass network traffic between each other with policies. In our case, NetworkManager has assigned the end0 interface the pre-defined public zone already. Our plan here is to use the pre-defined home zone for any traffic originating from Local Area Network IP addresses, and let public handle all the rest.

We first need to install firewalld through Debian's package manager, apt. The first command refreshes our index of packages, the second installs firewalld:

sudo apt update
sudo apt install firewalld
Warning: Debian automatically starts firewalld and configures it to auto-start on boot. It happens to be configured in a way that breaks our mDNS (.local domain) resolution. After installing firewalld, immediately work through the next few steps without closing your SSH session as you'll otherwise have to type the board's IP address manually to get back in.

We should now tell firewalld about what IP prefixes we consider to be our local network:

sudo firewall-cmd --permanent --new-ipset=lanv4 --type=hash:net
sudo firewall-cmd --permanent --ipset=lanv4 --add-entry=10.0.0.0/8
sudo firewall-cmd --permanent --ipset=lanv4 --add-entry=172.16.0.0/12
sudo firewall-cmd --permanent --ipset=lanv4 --add-entry=192.168.0.0/16
sudo firewall-cmd --permanent --new-ipset=lanv6 --option=family=inet6 --type=hash:net
sudo firewall-cmd --permanent --ipset=lanv6 --add-entry=fe80::/64
Further Explanation: We've used CIDR notation here to denote entire ranges of IP addresses. These specific ranges are specified by the IETF and IANA to be used for private networks, such as our local area network (LAN).

Now that we have a lanv4 and lanv6 ipset, we can tell firewalld that any traffic originating from these IPs should be considered part of our home zone:

sudo firewall-cmd --permanent --zone=home --add-source=ipset:lanv4
sudo firewall-cmd --permanent --zone=home --add-source=ipset:lanv6

We should now reload those new permanent settings, so that our mDNS announcements work again:

sudo firewall-cmd --reload

Next, we'll want to remove the ssh service from the public zone, as Debian added it there by default:

sudo firewall-cmd --permanent --zone=public --remove-service=ssh
sudo firewall-cmd --reload

Finally, we want to add a service definition for Minecraft, which uses port 25565 with the TCP protocol, and allow it for our home and public zone:

sudo firewall-cmd --permanent --new-service=minecraft
sudo firewall-cmd --permanent --service=minecraft --add-port=25565/tcp
sudo firewall-cmd --permanent --zone=home --add-service=minecraft
sudo firewall-cmd --permanent --zone=public --add-service=minecraft
sudo firewall-cmd --reload
Further Explanation: You can find a (somewhat lacking) documentation for firewalld on the official firewalld website. A better resource is, as always, man firewall-cmd.
For Expert Users: Experienced users might at this point run sudo iptables -L to look at the actual filtering rules that firewalld has added, and they would be shocked to see some completely empty chains with an ACCEPT policy. Fear not, the firewall is working, but the iptables utility isn't. Modern kernels all use nftables, which confuses the legacy iptables specific tools when something like firewalld makes full use of nftables. Use sudo nft list table inet firewalld to get a listing of the filtering rules.

Formatting The NVMe Drive

TODO: Explain how to wipefs + fdisk + format the NVMe drive, make a mount point and put it in fstab.

Setting Up The Minecraft Java Edition Server

TODO:
  1. Add a minecraft system user with a minecraft group that has write permissions to the minecraft directory on NVMe, add minecraft to supplemental groups for user.
  2. Install Java, download PaperMC to the Minecraft directory, symlink it to paper.jar, run it once, accept the EULA.
  3. Edit server.properties, enable rcon with a password, enable whitelist, edit motd and set difficulty.
  4. Add the systemd unit to /etc/systemd/system/, set it to auto-start, then start it.
  5. Add player to whitelist and ops through rcon.
  6. Connect to the server for the first time!

Getting An RCon Client

Minecraft's server has a control protocol called "rcon". We'll need this protocol to gracefully shut down the Minecraft server, and add ourselves to ops later on. For this, we'll need a Minecraft RCon client. Tiiffi/mcrcon is a good one, but we need to compile and install it first.

To start off, let's install some required tools to compile it. In your Quartz64 Model B's SSH session, type:

sudo apt update
sudo apt install build-essential

Next we'll download the source code for mcrcon:

wget --content-disposition https://github.com/Tiiffi/mcrcon/archive/refs/tags/v0.7.2.tar.gz

This should have downloaded us an mcrcon-0.7.2.tar.gz archive file. Let's verify that what we've downloaded matches what this guide expects:

sha256sum mcrcon-0.7.2.tar.gz

This should output the following; compare the first string of letter and numbers to yours, they must match:

1743b25a2d031b774e805f4011cb7d92010cb866e3b892f5dfc5b42080973270  mcrcon-0.7.2.tar.gz

Now let's unpack the .tar.gz archive and change into the directory it contains:

tar -xf mcrcon-0.7.2.tar.gz
cd mcrcon-0.7.2/
Explanation: tar is the standard utility for creating, modifying, inspecting and unpacking tar archives, commonly referred to as "tarballs". The -x argument states that we want to extract, and the -f argument specifies that the next argument is the filename of the tarball we wish to operate on. tar will automatically infer the compression of the tarball, in this case gzip, and decompress it as it is unpacking.

In this directory (check with ls -l!) there should be some files, including a Makefile. This allows us to now compile the source code and install the resulting binary:

make
sudo make install
Explanation: A Makefile instructs the make command on how to build a project that uses it. In this case, it also contains a install target, which copies the resulting binary into /usr/local/bin and its manual page into the local prefix for manual pages. Since only the superuser can write to these directories, we need to run the install target with sudo.

If everything went well, we should now have the mcrcon command available, as well as its manual page with man mcrcon.

For The Future: If you ever need to recompile mcrcon, for example, after a major version distribution upgrade years down the line that gets rid of libc.so.6, you can run:
sudo make uninstall
make clean
make
sudo make install

Making The Server Accessible From Outside Your Home Network

TODO:
  1. Explain what a dynamic IP is, set up ddclient with DuckDNS, have a systemd timer for that.
  2. Explain what port forwarding is, why it's needed, and why this depends on your router. Warn not to forward port 22/tcp, only port 25565/tcp.

Maintaining Your Server

TODO:
  1. Explain how to start, stop, restart the Minecraft server with systemctl.
  2. Explain how to access server logs with journalctl.
  3. Explain how to keep PaperMC updated (including changing the symlink).
  4. Explain apt update and apt upgrade. Recommend unattended-upgrades.
  5. Explain Debian major releases and to keep an eye out for them.
  6. Recommend htop to see how CPU and RAM usage is looking.
  7. Recommend some backup solutions; rsync to flash drive, short mention of borg/borgmatic over the network.
  8. Explain how to transfer files to/from server: sftp, rsync, winscp, etc.

Security Advice

This section will give some general security advice on running your own server, and elaborates on some decisions this guide makes.

SSH Public Key Authentication

If you absolutely insist on opening up the SSH port to the public internet, you should use public key authentication and disable password authentication for SSH altogether.

This way, public key cryptography is used to authenticate, which is computationally extremely hard to break in this case. The password you encrypt your private key with never travels over the network.

To generate an SSH public/private key pair, run

ssh-keygen

on your client machine, i.e. not the Quartz64 Model B.

Then copy the newly generated SSH public key over to the Quartz64 Model B with

ssh-copy-id youruser@yourhost.local

Try logging in with ssh now. It should prompt you for your key's password, rather than the user account's password.

If it works, you should then finally disable password authentication by editing /etc/ssh/sshd_config as the superuser and setting:

PasswordAuthentication no

Then, restart the sshd.service with sudo systemctl restart sshd.service.

Note that from this point on, if you want to log in from a different machine, you'll need to either copy your public/private keypair over or generate a new one there and edit ~/.ssh/authorized_keys manually, or run ssh-copy-id with the -i argument pointing to that public key.

On Minecraft Server Modifications And Plugins

Please note that Minecraft server modifications or plugins run with the same access as your Minecraft server. While we tried our best in this guide to restrict the Minecraft server's access to the rest of the system, it can obviously still do bad things to our Minecraft world, our computational resources and our network. That's why, whenever you install a server-side modification or plugin, you should ask yourself these questions:

  1. Do you trust the website/service you are downloading the mod/plugin from to not be malicious?
  2. Do you trust the website/service you are downloading the mod/plugin from to have the latest version uploaded by the mod/plugin's author rather than an untrustworthy third party?
  3. Do you trust the mod/plugin author to not be malicious?
  4. Do you trust the mod/plugin author to have good security practices, as to not get their account compromised or development computer infected with malware?
  5. Do you trust the mod/plugin to not open up security vulnerabilities in your server?

Chances are the answers to some of these questions will be "no". In that case you have some leg work to do: research the site and users, double-check you're on the right domain, read the code for anything fishy, and if you're ever unsure, ask for a second opinion from somebody knowledgeable on the topics.

This is not unwarranted paranoia: Minecraft server modifications have introduced security vulnerabilities before. Well trusted people's accounts have been hijacked before, or their uploads unknowingly infected with malware. And the way this happened? They themselves were downloading modifications from malicious users.

Please do note that all these issues generally do not apply to Minecraft Data Packs. While they are more limited than modifications or plugins that hook into the actual code, these limitations are good in the context of security.

Do Not Engage In Security By Obscurity

You may be tempted to not enable a whitelist because you think nobody is going to find your Minecraft server anyway. This is false. People and bots are constantly scanning the entire internet for open services. This includes Minecraft servers. If you do not have a whitelist, you will sooner or later get uninvited visitors.

Similarly, if you expose any service other than the Minecraft server to the internet through your router, expect that to be instantly discovered as well. Automated exploit scanners will then likely be hitting it regularly.

This sounds scary, but it's really not: if your system is up-to-date and follows best security practices, you generally won't have much to fear. It just means that you can't expose any insecurely configured service to the internet and expect nobody to find it.

Can't Be Bothered To Maintain It? Turn It Off

One of the biggest possible dangers to your home network's security is unwillingness to take responsibility. If you no longer want to maintain the server, turn the board off, unplug it, and delete the port forwarding rule in your router's web interface.

The majority of security breaches don't happen because somebody tries very hard to breach one target, but because a lot of people try very little to breach many targets. Chances are they will find a system somewhere that nobody feels responsible for anymore, and which hasn't been updated in 5 years, and runs a service of a version with a known vulnerability. Avoid being that system: if your Quartz64 Model B begins operating as part of a botnet due to your indifference, the rest of the internet will suffer.

Why Reboots Are Sometimes Recommended

Occasionally, your Linux kernel package (linux-image-arm64) will update. This is great! The kernel is the most basic part of the operating system, and responsible for enforcing fundamental privilege separation. Unfortunately, live-patching the running kernel (i.e. updating it in memory without requiring a reboot) is something that only enterprise-grade commercial Linux distributions like RedHat Enterprise Linux or SUSE Linux do. Since we're running Debian here and Debian doesn't have this feature, the only way to run the newly updated kernel is to reboot the system.

You can run cat /var/run/reboot-required to see if your Quartz64 Model B requires a reboot. It's worth noting that the Linux kernel development community does not distinguish regular bugs from security bugs, as many a "regular" bug could be a security bug in the right circumstances. However, Debian itself publishes security advisories when a Linux kernel update they ship fixes known potential security issues.

Whether or not you want to reboot for a specific kernel update is up to you. People run servers with hundreds of days of uptime (check with the command uptime) just fine; just because a potential security issue was fixed in the Linux kernel doesn't mean it'd be part of a realistic attack scenario on a specific system. In our case, the attack scenario we're most worried about is that somebody manages to breach the PaperMC Minecraft server, and then gains arbitrary code execution in its context, from which they will then likely attempt to attack the kernel to gain additional privileges. A second, much less likely attack scenario is that the Linux kernel would have a vulnerability in its network stack that would allow an attacker to remotely exploit it. Such vulnerabilities are exceedingly rare in Linux.

Why Not Docker: On Software Supply Chains And Understanding Your Stack

For the majority of this guide, self-declared DevOps EpicPoggers Awesomeninjas have probably been foaming at their mouth jumping at the opportunity to exclaim that one should just use Docker. This section serves as a soapbox to talk about the problems with that suggestion.

While a Docker container may be, well, contained, it pulls in an entire stacking doll of other images it is built upon. This means an entire operating system to support the application being run, minus a kernel.

This is great if you are the person writing the image manifests, know what your dependency requirements are, and track the entire software supply chain to ensure you always get the right versions for your stack, with all the latest vulnerabilities patched.

However, this is not how the vast majority of people use Docker. The overwhelming majority of Docker users simply pull some random person's image, run it, and thus make these assumptions:

  1. That the author of the image is not malicious. Malicious images are widespread on Docker Hub.
  2. That the author of the image has good security practices in order to avoid publishing malicious images. We know account compromises will happen as Docker Hub seemingly does not require Two-Factor Authentication.
  3. That the author will update the images when critical security fixes in any of the things they depend on are fixed. A 2017 research paper found that many images are hundreds of days old.
  4. That any of the things that image depends on, recursively, will also meet these three points.

For a single service/single node scenario, Docker complicates the software supply chain massively. Instead of getting most of our system from a source we already trust (Debian), we're now getting a whole second system from a myriad of sources opaquely compiled into one image.

Additionally, Docker complicates the following things:

  • Knowing what version a specific runtime dependency is, and that all copies of this dependency on the entire system are patched against the latest vulnerability.
  • Updating the container image and spinning up a new container based on it; this is not handled through your system's package manager.
  • Persistent data; it's stored in Docker volumes, which may or may not be attached to containers, and you need to actively try to have them stored somewhere other than the default location.
  • Service management; Docker reinvents service management in its own, arguably worse way.
  • Networking; things are on a virtual network with Docker, which complicates firewall rules and other network configurations.
  • Logging; Docker reinvents this too.

By not using a Docker container, but rather a locked down systemd service, we get the following benefits:

  • All system software updates, including the Java Runtime, are handled by Debian, irrespective of the version of the Minecraft server we run.
  • All of our services use the same system dependencies, and we can answer the question of "has xyz been patched?" by just looking at apt.
  • We still get reasonably strong process isolation thanks to the various security sandboxing options our systemd service uses, despite not using Linux namespaces (containers).
  • Our persistent data is where we expect it to be, owned by the user and group we expect it to be owned by.
  • Our network is not burdened with virtual interfaces and forwarding rules.
  • Our Minecraft server is tracked and dependency ordered by systemd's service management. It's automatically started on boot, and gracefully stopped on shutdown.
  • Our Minecraft server's logs will show up in the system journal, and we can filter through them or correlate them with other sources just as with any other service reporting to the journal.

Container images built from manifests start to make sense when you are running complex sets of services scaled horizontally across a massive homogeneous cluster, where you want to rescale as needed and deploy new versions of your entire stack as immutable images that you can roll back easily. They also make some amount of sense in a case where you only have access to a very old userland (e.g. a stable enterprise Linux release) but need to run software requiring a myriad of dependencies that are either not packaged or not of a recent enough version in your distribution.

Troubleshooting

This section contains various subsections for helping you troubleshoot problems you may have while following this guide.

Connecting UART

To hook up UART serial debug, connect your Woodpecker, ideally with a decent USB extension cord, to your computer's USB port. Make sure the yellow jumper is set to 3.3V.

Connect the GND pin of the Woodpecker to a GND pin of your Quartz64 Model B, which are coloured as black pins. A good one to use is pin number 6 (the one after the red pin 2 and pin 4), as it's right next to the other UART pins we'll need.

Next, hook up the Woodpecker's RXD pin to the Quartz64 Model B's pin number 8, which is next to (lengthwise down the row of pins) the aforementioned pin number 6, coloured in green.

Finally, hook up the Woodpecker's TXD pin to the QUartz64 Model B's pin number 10, which is down yet another pin in the same direction, also coloured in green.

TODO: Add a diagram showing the connections here.

Accessing The Serial Console On Windows

TODO: Add a guide on how to install, configure and use PuTTY

Accessing The Serial Console On Linux

TODO: Validate the serial settings here.
Warning: Some poorly written software, like gpsd or brltty, will randomly and happily claim and mess with any USB-to-serial adapters on the system, especially when you least want them to. Use sudo lsof /dev/ttyUSBn to determine whether this is the case for you if you run into any trouble with the following steps.

First, list all the available USB-to-Serial adapters on your system:

ls /dev/ttyUSB*

If you only have one plugged in, this should probably only return /dev/ttyUSB0.

Next, you have some choice of serial terminal to use: screen (likely already installed), minicom and picocom.

For screen, (replacing n with the number of your serial adapter) use:

screen /dev/ttyUSBn 1500000 cs8 -cstop

You can quit screen by hitting Ctrl+a, then hit backslash (\) and confirm with y.

For minicom, (replacing n with the number of your serial adapter) use:

minicom -D /dev/ttyUSBn -b 1500000 -8

You can quit minicom by hitting Ctrl+a, then hit q.

For picocom, (replacing n with the number of your serial adapter) use:

picocom /dev/ttyUSBn -b 1500000

You can quit picocom by hitting Ctrl+a, then hit Ctrl+q.

Accessing The Serial Console On macOS

TODO: Validate the serial settings here, and validate the device name.

Open up a terminal, then list all the available serial devices on your system:

ls /dev/tty.*

This will likely return something starting with /dev/tty.usbserial-.

Now we can open this serial port with screen as follows, replacing foo with the adapter name from the previous step:

screen /dev/tty.usbserial-foo 1500000 cs8 -cstop

You can quit screen by hitting Ctrl+a, then hit backslash (\) and confirm with y.

Using The Serial Console

If you just hooked it up to a running system, you're unlikely to see any output. You will only be able to see messages from the point on when you've opened up your serial communication program.

If your board is doing the heartbeat pattern (i.e., it booted, but you're unable to log in over the network) you can hit Enter to (hopefully) make it show you a login prompt. If the login prompt is your first time communicating with the board, use username pleb and password pleb. From then on, the serial terminal should work like any normal shell you'd have on the system, modulo some possible weirdness with special escape sequences making things a little more prone to drawing in broken ways.

If your board is not doing the heartbeat pattern, hit the reset button (small white button on side of board, the one closer towards the antenna mount) and you should be getting serial output from a fresh boot.

If you still don't get any output, make sure that you flashed the microSD card correctly, and make sure the TXD and RXD lines aren't the wrong way around. If you still don't get any output after that, your Quartz64 Model B may have defective level shifting transistors on the serial lines, and you should open a ticket with Pine Store immediately and ask for a replacement or refund.

Installing OpenSSH On Windows

On Windows 11, open the start menu, go to Settings > Apps > Optional features. If "OpenSSH Client" doesn't show up in your installed features, click on "View features" in "Add an optional feature" and search for "OpenSSH Client" and check it, then hit "Next" and "Install".

Is My Ethernet Broken?

Pine Store has sold a number of boards where the ethernet PHY chip was seemingly broken. If your board exhibits the heartbeat LED pattern but never shows up on the network, either hook up a monitor and a keyboard or use your Woodpecker to hook up to serial (see #Connecting UART) and try to log in.

After logging in, type:

sudo dmesg | grep rk_gmac-dwmac

If the output contains any lines like

rk_gmac-dwmac fe010000.ethernet end0: __stmmac_open: Cannot attach to PHY (error: -22)

then your hardware is defective, and you should open a ticket with Pine Store immediately and ask for a replacement or refund.