Raspberry Pi Guide

          
   .~~.   .~~.
  '. \ ' ' / .'
   .~ .~~~..~.
  : .~.'~'.~. :
 ~ (   ) (   ) ~
( : '~'.~.'~' : )
 ~ .~ (   ) ~. ~
  (  : '~' :  )
   '~ .~~~. ~'
       '~' 
        

I've gotten increasingly into small tech over the years, and in March 2026 I finally took the plunge into the world of the Raspberry Pi.

The Raspberry Pi is a Single-Board Computer (SBC) released in 2012 by the Raspberry Pi Foundation. As of writing it is the best-selling British computer, surpassing the ZX Spectrum only three years after its launch.

The Pi was originally designed to teach computer science, but has since become a metaphorical zen garden for all manner of tech projects. As it is compact in size and consumes little energy, this makes it an ideal choice for 24/7 operations such as webservers and chatrooms. Its operating system, the Raspberry Pi OS, is a Linux-based OS. As a result, Pi-work is designed with tech enthusiasts in mind and is not a tool for casual users. To use a Pi, you have to be comfortable using a terminal and with the prospect of learning some degree of Linux.

This page serves as my personal guide for current and future Pi-rojects, as well as for any of you who want to join me.

Section Menu

Pi-Rojects

Using a UPS with your Pi Running an IRC Server on your Pi

Part 1: Getting a Pi

Given the low-power nature of my services, I made the more conservative choice of the Pi 4B. I purchased this from The Pi Hut, which is the most popular UK vendor for Raspberry Pis. The delivery on Pi Hut is honestly one of the fastest I've ever had from an e-vendor, even if I had to click for Royal Mail rather than have the poor thing chucked about by Evri (formerly Hermes).

What Arrived in the Post

Raspberry Pi 4 Model B
I purchased the 2GB option, but you can buy Pis available with up to 8GB of RAM.
Official 32GB SD card (pre-loaded with 64-bit Raspberry Pi OS)
This one was pre-loaded with the Pi OS, thus saving me from having to flash it to the card manually. The latter process is not covered in this guide.
Official Raspberry Pi power supply (white UK plug)
Always use the official Raspberry Pi power supplies, using anything else can underpower your Pi.
A black USB cable
This cable came as a bonus, but was ultimately left unused during my setup. It's still good to keep around as a spare.
Official black/grey case, complete with an embossed Pi logo
This protects the Pi's circuit board from dust, static, and physical damage. You can probably put a few little stickers on it.

What Didn't Arrive

White micro-HDMI to HDMI cable
Because I didn't realise my black USB cable wasn't a micro HDMI one! This is for establishing a display on your spare desktop monitor, which you'll need for navigating the Pi setup.

Part 2: Setting the scene

Starting a Pi cannot be done from your main device. You need extra hardware, as you're (temporarily) making a miniature desktop setup. If you're low on supplies, you can unplug things from your main setup. Expect the main setup to fall asleep if it's been left on; putting a mouse or keyboard back in won't automatically wake it up.

Step 1: The scene

Spare monitor
An Acer was used for this initial setup.
Spare keyboard
A spare Dell keyboard from a late Christmas gift worked nicely here.
Spare mouse
A Razer mouse was used, as there were no spare mice on hand.

Step 2: Checking the boxes

Items

Once you have all of these, you're ready to assemble your Pi!

Part 3: Establishing your Pi

Be sure to keep a clean workspace and a non-metal platform for your Pi to sit on.

Step 1: Inserting the SD Card

  1. Slot the Pi into the bottom half of its case. Press on the flat circuit board parts of the Pi until it makes a nice snapping sound. Don't be shy, it can handle this even if it looks like it can't.
  2. The SD card slot is located on the underside of the Pi Board, on the other side of the USB ports. With your Pi in one hand and your Micro SD in the other, slot it gently into the Pi. The gold contacts have to face up towards you and the board its entering, while the label side faces underneath. If the card keeps entering at an angle, you're likely trying to insert the wrong side.
  3. Push gently until it clicks. It should not be at an angle or stick out past the edge of the bottom case.
  4. Slot the top case onto the Pi.

You've now loaded your Raspberry Pi. The SD card is crucial to doing anything with the Pi and it must always be handled carefully.

Step 2: Connecting Cables and Power

  1. Spare keyboard → any USB port on the Pi
  2. Razer mouse → any USB port on the Pi
  3. White HDMI cable:
    • SMALL micro-HDMI end → Pi (use the port CLOSEST to the power port)
    • Normal HDMI end → spare Acer monitor
  4. Make sure the spare Acer monitor is plugged in and turned on
  5. Power supply → plug into Pi's USB-C port LAST

When you plug in the power, the Pi will turn on and your spare monitor should start doing things!

Step 3: Navigating the Wizard

  • Let the Pi boot up. It should start with a black screen, then you should see a rainbow gradient and finally the Raspberry Pi welcome screen (light-mode only so don't do this in the dark).
  • You're now in the Raspberry Pi's Setup Wizard. Like other Setup Wizards, this will walk you through the installation and help you create an account for your Pi. This account is how you log into the Pi when you want to do things on it.
  • Set your language, region and timezone. My Pi automatically guessed I was in England so that wasn't an issue for me personally.
  • User Account: Username must be lowercase letters, digits, and hyphens only. Make sure it's got just enough to be useful while being comfortable to type; you'll have to enter this every time you want to access the Pi. As with any other account setup, write stuff down somewhere so you don't lose anything. Nothing appears on screen when typing password—this is normal.
  • Network: Connect to WiFi (or skip if you're using ethernet).
  • Reboot: Accept any reboot requests.

Step 4: Enabling SSH Access

Secure Shell Protocol lets you control the Pi from your main computer without needing the extra desktop setup. This is known as a "Headless" setup, and is a common practice amongst Pi users. It appears in many other services, and is a completely legitimate means of updating files, servers and more.

  1. Click the terminal icon (black monitor icon in top toolbar)
  2. Type: sudo raspi-config and press Enter to select it.
  3. It will ask for the password you set up back in the wizard. This logs you into your Pi account, and you'll have to do this every time you want to remotely access your Pi. If you don't see it while typing, this is normal and is common in terminals.
  4. A blue menu should appear. Use your keyboard's arrow keys to go to 3 Interface Options and press Enter to select it.
  5. Navigate to the option for SSH and select it. It will ask you "Would you like the SSH server to be enabled?", to which you select Yes.
  6. To finish the process, press Tab to highlight Finish and one last Enter to select.
  7. To check your SSH is working, type sudo systemctl status ssh. You should see "active (running)" in green text.
  8. Press Q to exit the menu entirely. Your SSH is now established!

Step 5: Getting Your Pi's IP Address

You're almost done with the setup, but if you stop here you won't be able to access your Pi remotely. The last thing you need is your Pi's IP Address, which you enter every time you want to access it.

  1. In the terminal, type: hostname -I
  2. Write down the number it shows. This is your Pi's IP address, and tends to differ slightly from your main computer's.
  3. You now have your Pi's IP Address!

IP Addresses can sometimes change for a variety of reasons, so if you cannot SSH into your Pi, you'll need to repeat this process.

Part 4: Safely turning off your Pi

Now that you've established your brand new Pi and written down its information, you're free to stop from here. However, you've probably noticed that your Pi lacks the conventional means of shut-down. Instead, it goes as follows:

Step 1: Proper Shutdown Procedure

  1. DO NOT, UNDER ANY CIRCUMSTANCES, pull the power cable or turn it off at the switch! This can corrupt the SD card and render it unusable!
  2. Every time you want to shut down your Pi, enter this command in the Terminal: sudo shutdown now
  3. Press Enter to execute the shutdown command. You should see a series of text in the terminal announcing the shutdown.

Step 2: Knowing when it's safe to switch off

Like other computers, the Pi does not shut down instantly. Here's how you can tell when it's safe to turn it off at the wall or remove the plug entirely.

  1. Your monitor screen turns off completely. The Acer I used showed a "No Signal" message.
  2. The LED is no longer green, leaving a red light behind. This means the Pi is powered but not fully on.

Conclusion

Congratulations, you've just set up a Raspberry Pi! Pat yourself on the back, give your pi a pat on its back and revel in the freedom of Self-Hosted Tech!

You can stop reading if this is all you needed, because from here we're going over self-hosted IRC stuff. Alternatively, you can find other guides on what to do with your brand new Pi friend!

Pi-Roject: Using a UPS with your Pi

One of the risks of running a Pi 24/7 is a sudden loss of power — whether that's a trip at the wall, a power cut, or someone accidentally kicking the cable. Without warning, this can corrupt your SD card, which as you know by now is catastrophic. The solution is a UPS HAT (Uninterruptible Power Supply Hardware Attached on Top).

This guide covers the Waveshare UPS HAT (B), which piggybacks onto your Pi via its GPIO pins and provides backup power from two 18650 lithium cells. Beyond just keeping the lights on briefly, we'll set up a background service that monitors battery level and triggers a clean, safe shutdown before the power runs out entirely.

Once everything's in place, the service runs silently in the background at all times. It checks battery level every 60 seconds, warns you when it drops below 15%, gives you 60 seconds to plug in the charger, and — if you don't — shuts the Pi down safely on its own.

What You'll Need

Waveshare UPS HAT (B)
The HAT itself. Note that the 18650 batteries are not included — you'll need to source those separately. Use official Waveshare batteries if possible, and do not mix old and new batteries or batteries from different manufacturers.
2x 18650 Lithium batteries
These slot into the HAT and provide the backup power. Handle with care — lithium batteries can be dangerous if mishandled. Mind the polarity (+ and -).
Barrel jack power adapter (5V recommended)
The HAT is powered through a barrel jack rather than USB-C. This usually comes with the HAT.
Small Phillips screwdriver
For the mounting screws that keep the HAT attached to the Pi.

Step 1: Physical Installation

The HAT connects to your Pi via pogo pins — small spring-loaded gold contacts on the HAT's surface that press against the Pi's GPIO pads. They look a bit intimidating but they're tougher than they appear, designed for repeated connection and disconnection. If a connection doesn't work first time, you just try again.

Important: Do this before your Pi is in its case. The case needs to come off entirely for the HAT to attach — learned this one the hard way.

  1. Insert the batteries first, before touching the Pi. Trying to do this after connecting everything makes the case hard to open and things get fiddly fast. Check the polarity markings on the HAT and slide both cells in flush.
  2. Remove the Pi from its case. The case fits tightly. If it won't budge, check that the SD card is fully seated (push it in until it clicks) — a partially inserted card can jam things up.
  3. Check your SD card orientation before reinserting: gold contacts face up (towards the board), label side faces down. Push gently until it clicks.
  4. Attach the HAT. Place the HAT flat on your workspace (blue side up). Lower the Pi down onto it, lining up the GPIO header with the pogo pins. Press down firmly and steadily — think "firm handshake," not "crushing grip." You'll feel the springs compress slightly. Hold it steady while you screw down the mounting screws.
  5. Power on and check. Make sure the HAT's power switch is OFF, then plug in the barrel jack. Flip the switch to ON. The red LED means the batteries are charging (normal for fresh cells). Green means fully charged. The Pi's own LEDs will light up and it'll boot as usual.

If the Pi boots normally, the pogo pins are making good contact. If the HAT isn't detected later in software, see the troubleshooting section at the bottom.

Step 2: SSH In and Enable I2C

I2C is the communication protocol that lets the Pi talk to the HAT's sensor (an INA219 chip). It's disabled by default and needs to be switched on before any monitoring software will work.

SSH into your Pi as normal, then run:

sudo raspi-config

Navigate to 3 Interface Options → I5 I2C, select Yes, and reboot when prompted.

After rebooting, verify the HAT is being detected:

sudo i2cdetect -y 1

You should see a grid with 42 appearing at row 40, column 2. That's the HAT's I2C address (0x42). If you see it, you're good to go.

While you're at it, make sure the Python smbus library is installed:

sudo apt update
sudo apt install -y python3-smbus

Step 3: (Optional) Test with Waveshare's Example Script

Waveshare provides a basic monitoring script that's useful for confirming everything's working before you set up the full service. You can skip this if you're confident, but it's a good sanity check.

sudo apt-get install p7zip
wget https://files.waveshare.com/upload/4/4a/UPS_HAT_B.7z
7zr x UPS_HAT_B.7z -r -o./
cd UPS_HAT_B
python3 INA219.py

You should see readings like:

Load Voltage: 7.776 V
Current: 1.076000 A
Power: 2.416 W
Percent: 74.0%

Load Voltage is what the Pi is receiving from the HAT (healthy range is 7.5–8.4V). Current will be positive if the batteries are charging and negative if they're actively supplying power. Percent is battery charge. Press Ctrl+C to stop. Don't run this script and the monitor service simultaneously — they both read the same sensor.

Step 4: Create the Monitor Script

The monitor script is a Python program that runs as a background service, reading the battery sensor every 60 seconds and handling the warning and shutdown logic. Create it on the Pi with:

nano ~/ups_monitor.py

Paste in the following:

#!/usr/bin/env python3
"""
Waveshare UPS HAT (B) Battery Monitor & Safe Shutdown Service
"""

import time
import struct
import smbus
import subprocess
import logging
import signal
import sys
import os
from pathlib import Path

# --- Configuration ---
I2C_BUS = 1
INA219_ADDR = 0x42

CHECK_INTERVAL = 60          # Seconds between battery checks
LOW_BATTERY_PERCENT = 15     # Trigger warning at this percentage
SHUTDOWN_DELAY = 60          # Seconds to wait before shutting down

LOG_FILE = "/var/log/ups_monitor.log"

# INA219 register addresses
REG_CONFIG = 0x00
REG_SHUNT_VOLTAGE = 0x01
REG_BUS_VOLTAGE = 0x02
REG_POWER = 0x03
REG_CURRENT = 0x04
REG_CALIBRATION = 0x05

# --- Logging Setup ---
logger = logging.getLogger("ups_monitor")
logger.setLevel(logging.DEBUG)

file_handler = logging.FileHandler(LOG_FILE)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(
    "%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
))
logger.addHandler(file_handler)

console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(logging.Formatter(
    "%(asctime)s [%(levelname)s] %(message)s", datefmt="%H:%M:%S"
))
logger.addHandler(console_handler)


class INA219:
    def __init__(self, bus_number=I2C_BUS, address=INA219_ADDR):
        self.bus = smbus.SMBus(bus_number)
        self.address = address
        self._configure()

    def _configure(self):
        self._write_register(REG_CALIBRATION, 4096)
        self._write_register(REG_CONFIG, 0x399F)

    def _write_register(self, reg, value):
        high = (value >> 8) & 0xFF
        low = value & 0xFF
        self.bus.write_i2c_block_data(self.address, reg, [high, low])

    def _read_register(self, reg):
        data = self.bus.read_i2c_block_data(self.address, reg, 2)
        value = (data[0] << 8) | data[1]
        if value >= 0x8000:
            value -= 0x10000
        return value

    def get_bus_voltage(self):
        raw = self._read_register(REG_BUS_VOLTAGE)
        return (raw >> 3) * 0.004

    def get_current_mA(self):
        self._write_register(REG_CALIBRATION, 4096)
        raw = self._read_register(REG_CURRENT)
        return raw * 0.1

    def get_battery_percentage(self):
        voltage = self.get_bus_voltage()
        max_voltage = 8.4
        min_voltage = 6.0
        if voltage >= max_voltage:
            return 100.0
        if voltage <= min_voltage:
            return 0.0
        percent = ((voltage - min_voltage) / (max_voltage - min_voltage)) * 100.0
        return round(percent, 1)


def notify_users(message):
    try:
        subprocess.run(["wall", message], timeout=5, capture_output=True)
    except Exception as e:
        logger.warning(f"Could not send wall message: {e}")
    try:
        notice_path = Path("/home/pearlnight/ups_notice.txt")
        with open(notice_path, "w") as f:
            f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} -- {message}\n")
    except Exception:
        pass


def cancel_shutdown():
    try:
        subprocess.run(["sudo", "shutdown", "-c"], timeout=5, capture_output=True)
        logger.info("Pending shutdown cancelled.")
    except Exception as e:
        logger.warning(f"Could not cancel shutdown: {e}")


def initiate_shutdown():
    logger.critical("Executing system shutdown NOW.")
    notify_users("UPS BATTERY CRITICAL -- System is shutting down NOW to protect your data.")
    try:
        subprocess.run(
            ["sudo", "shutdown", "-h", "+1",
             "UPS battery critically low -- automatic safe shutdown"],
            timeout=5, capture_output=True
        )
    except Exception as e:
        logger.error(f"Shutdown command failed: {e}")
        try:
            subprocess.run(["sudo", "poweroff"], timeout=5)
        except Exception:
            pass


running = True

def handle_signal(signum, frame):
    global running
    logger.info(f"Received signal {signum} -- stopping monitor.")
    running = False

signal.signal(signal.SIGTERM, handle_signal)
signal.signal(signal.SIGINT, handle_signal)


def main():
    global running

    logger.info("=" * 60)
    logger.info("UPS Monitor starting up")
    logger.info(f"  I2C address : 0x{INA219_ADDR:02X}")
    logger.info(f"  Check every : {CHECK_INTERVAL}s")
    logger.info(f"  Low battery : {LOW_BATTERY_PERCENT}%")
    logger.info(f"  Shutdown delay: {SHUTDOWN_DELAY}s")
    logger.info("=" * 60)

    sensor = None
    retry_count = 0
    while running and sensor is None:
        try:
            sensor = INA219()
            logger.info("INA219 sensor initialised successfully.")
        except Exception as e:
            retry_count += 1
            logger.error(f"Cannot reach INA219 (attempt {retry_count}): {e}")
            if retry_count >= 5:
                logger.critical("Failed to reach INA219 after 5 attempts. Exiting.")
                sys.exit(1)
            time.sleep(10)

    shutdown_pending = False
    shutdown_timer_start = None

    while running:
        try:
            voltage = sensor.get_bus_voltage()
            current = sensor.get_current_mA()
            percent = sensor.get_battery_percentage()

            logger.info(
                f"Battery: {percent:.1f}%  |  "
                f"Voltage: {voltage:.2f}V  |  "
                f"Current: {current:.1f}mA"
            )

            if percent >= LOW_BATTERY_PERCENT:
                if shutdown_pending:
                    logger.info(f"Power restored! Battery back to {percent:.1f}%. Cancelling shutdown.")
                    cancel_shutdown()
                    notify_users(f"Power restored -- battery at {percent:.1f}%. Shutdown cancelled.")
                    shutdown_pending = False
                    shutdown_timer_start = None
            else:
                if not shutdown_pending:
                    logger.warning(f"Battery LOW at {percent:.1f}%! Shutdown in {SHUTDOWN_DELAY} seconds.")
                    notify_users(
                        f"WARNING: UPS battery at {percent:.1f}%!\n"
                        f"System will shut down in {SHUTDOWN_DELAY} seconds to protect your data.\n"
                        "Plug in the charger to cancel."
                    )
                    shutdown_pending = True
                    shutdown_timer_start = time.time()
                else:
                    elapsed = time.time() - shutdown_timer_start
                    remaining = max(0, SHUTDOWN_DELAY - elapsed)
                    logger.warning(f"Battery still low at {percent:.1f}%. Shutdown in {remaining:.0f}s.")
                    if elapsed >= SHUTDOWN_DELAY:
                        initiate_shutdown()
                        time.sleep(120)
                        sys.exit(0)

        except OSError as e:
            logger.error(f"I2C read error: {e}. Will retry next cycle.")
        except Exception as e:
            logger.error(f"Unexpected error: {e}", exc_info=True)

        for _ in range(CHECK_INTERVAL):
            if not running:
                break
            time.sleep(1)

    logger.info("UPS Monitor stopped cleanly.")


if __name__ == "__main__":
    main()

Save with Ctrl+O, Enter, Ctrl+X.

The settings you're most likely to want to tweak are near the top of the script:

CHECK_INTERVAL = 60
How often (in seconds) to read the battery sensor.
LOW_BATTERY_PERCENT = 15
The percentage at which the warning fires and the countdown begins.
SHUTDOWN_DELAY = 60
How many seconds you have to plug the charger back in before the Pi shuts itself down.

Step 5: Create the systemd Service File

The service file tells systemd to run the monitor script automatically on boot and restart it if it ever crashes.

nano ~/ups-monitor.service

Paste in:

[Unit]
Description=Waveshare UPS HAT (B) Battery Monitor
Documentation=https://www.waveshare.com/wiki/UPS_HAT_(B)
After=multi-user.target
Wants=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/ups_monitor/ups_monitor.py
Restart=on-failure
RestartSec=10
User=root
StandardOutput=journal
StandardError=journal
StartLimitIntervalSec=300
StartLimitBurst=5

[Install]
WantedBy=multi-user.target

Save and exit.

Step 6: Install and Start the Service

sudo mkdir -p /opt/ups_monitor
sudo cp ~/ups_monitor.py /opt/ups_monitor/ups_monitor.py
sudo chmod 755 /opt/ups_monitor/ups_monitor.py
sudo cp ~/ups-monitor.service /etc/systemd/system/ups-monitor.service
sudo touch /var/log/ups_monitor.log
sudo chmod 644 /var/log/ups_monitor.log

Before enabling the service, it's worth running the script manually once to confirm it's reading the sensor correctly:

sudo python3 /opt/ups_monitor/ups_monitor.py

You should see something like:

16:11:16 [INFO] UPS Monitor starting up
16:11:16 [INFO] INA219 sensor initialised successfully.
16:11:16 [INFO] Battery: 83.5%  |  Voltage: 8.00V  |  Current: 920.9mA

Press Ctrl+C once you're happy. Now enable and start it as a service:

sudo systemctl daemon-reload
sudo systemctl enable ups-monitor.service
sudo systemctl start ups-monitor.service
sudo systemctl status ups-monitor.service

You're looking for active (running) in the status output. Your Pi's SD card is now protected.

How Warnings Reach You

Since the Pi runs headless, warnings come through two ways. If you're logged in via SSH, a broadcast wall message will appear directly in your terminal. If you're not logged in, the script writes to /home/pearlnight/ups_notice.txt, which you can check at any time with:

cat ~/ups_notice.txt

If nobody is logged in at all, the Pi will still shut itself down safely before the power runs out — which is the whole point.

Troubleshooting

HAT not detected / "Cannot reach INA219" errors
Check I2C is enabled (sudo raspi-config nonint get_i2c should print 0) and run sudo i2cdetect -y 1 to see if 42 appears. Make sure the HAT is firmly seated. If the pogo pins still aren't making contact, unscrew the HAT, adjust the alignment slightly, and press down more firmly before screwing it back. Waveshare's docs note you can press each pin gently, or very lightly scratch the GPIO pads with a knife to clear any oxide layer.
Service won't start
Check the logs with journalctl -u ups-monitor.service -n 50. Also verify smbus is installed: python3 -c "import smbus; print('OK')"
SSH "Permission denied" or "No route to host"
Double-check your username and password. If you're getting "No route to host," your VPN is probably on — turn it off before SSHing into your local Pi.

Quick Reference

Check if running
sudo systemctl status ups-monitor.service
Start / Stop / Restart
sudo systemctl start ups-monitor.service
sudo systemctl stop ups-monitor.service
sudo systemctl restart ups-monitor.service
Watch the log live
tail -f /var/log/ups_monitor.log
Check notice file
cat ~/ups_notice.txt
Check I2C / HAT detection
sudo i2cdetect -y 1

Pi-Roject: Running an IRC Server on your Pi

This guide covers setting up Dreadfortress, a self-hosted IRC server running on the Pi using Ergo IRC. It goes from a bare Pi to a fully working setup: a domain that tracks your home IP, a real TLS certificate, Ergo running as a systemd service, and connections working via both desktop client (HexChat) and browser (KiwiIRC). Written based on the actual setup session, mistakes and all.

This is not a beginner guide — it assumes you've already got your Pi set up and can SSH into it comfortably.

Overview

Ergo IRC
The IRC server software running on the Pi. Version used: ergo-2.14.0-linux-arm64.
No-IP DUC
A Dynamic DNS client that keeps your domain (dreadfortress.ddns.net) pointed at your home IP even when it changes.
Win-ACME
Runs on Windows and handles automatic TLS certificate renewal via Let's Encrypt.
HexChat
Desktop IRC client for connecting via port 6697 (TLS).
KiwiIRC
Browser-based IRC client for connecting via port 8097 (websocket over TLS).

What ended up running where: No-IP DUC lives on the Pi as a systemd service. Win-ACME lives on Windows and handles cert renewal, then copies the renewed certs to the Pi automatically via a batch script. Ergo itself lives on the Pi and restarts automatically after certs are renewed.

Step 1: SSH into the Pi

From your Windows terminal (VPN off):

ssh pearlnight@192.168.1.132

Step 2: No-IP DUC on the Pi

This keeps your domain pointing at your home IP automatically. If you've previously been running the No-IP client on Windows, this replaces it.

cd /tmp
wget https://www.noip.com/client/linux/noip-duc-linux.tar.gz
tar xzf noip-duc-linux.tar.gz
cd noip-2.1.9-1
sudo make install

Follow the prompts: enter your email and password, select your hostname group, set the update interval to 30 minutes, and say no to running a script on update.

Create the systemd service file:

sudo nano /etc/systemd/system/noip2.service

Paste in:

[Unit]
Description=No-IP Dynamic DNS Update Client
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/noip2
Restart=on-failure

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable noip2
sudo systemctl start noip2
sudo systemctl status noip2

You should see active (running) and enabled.

Step 3: TLS Certificates (Win-ACME on Windows)

Let's Encrypt certificates are what make TLS connections to the server trustworthy. Win-ACME handles obtaining and automatically renewing them, but there's a catch worth knowing about.

Why cert renewal runs on Windows, not the Pi: When Win-ACME verifies your domain, Let's Encrypt contacts your external IP on port 80. On a UK residential connection, ISPs commonly block inbound port 80 — it's a standard measure to discourage running web servers on home lines. Port 443 and higher ports like 6697 are usually fine. Since Win-ACME was already set up on Windows and working there, the simplest fix is to keep it there and automate pushing the renewed certs across to the Pi.

Important: The router rule for port 80 must point to your Windows machine, not the Pi. Check this before the next renewal date.

Setting Up Passwordless SSH from Windows to Pi

This is needed so the cert copy script can run without a password prompt. Run these on Windows as Administrator:

ssh-keygen -t rsa -b 4096
(hit Enter through all prompts — no passphrase)
type C:\Users\black\.ssh\id_rsa.pub | ssh pearlnight@192.168.1.132 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

Enter the Pi password when prompted. Test it works by running ssh pearlnight@192.168.1.132 — it should connect with no password.

The Cert Copy Script

Create this file at C:\Users\black\Documents\copy-certs-to-pi.bat:

@echo off
scp C:\Users\black\Documents\Ergo\certs\dreadfortress.ddns.net-chain.pem pearlnight@192.168.1.132:/home/pearlnight/ergo/certs/fullchain.pem
scp C:\Users\black\Documents\Ergo\certs\dreadfortress.ddns.net-key.pem pearlnight@192.168.1.132:/home/pearlnight/ergo/certs/privkey.pem
ssh pearlnight@192.168.1.132 "sudo systemctl restart ergo"

This copies the renewed certs to the Pi and restarts Ergo so it picks them up. Add this as a post-renewal installation step in Win-ACME so it runs automatically after every renewal.

Step 4: Create the Ergo systemd Service File

On the Pi:

sudo nano /etc/systemd/system/ergo.service

Paste in:

[Unit]
Description=Ergo IRC Server
After=network.target

[Service]
Type=simple
User=pearlnight
WorkingDirectory=/home/pearlnight/ergo/ergo-2.14.0-linux-arm64
ExecStart=/home/pearlnight/ergo/ergo-2.14.0-linux-arm64/ergo run --conf /home/pearlnight/ergo/ergo-2.14.0-linux-arm64/ircd.yaml
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Note the --conf flag on the ExecStart line. Easy to miss, and Ergo will fail silently without it.

Save with Ctrl+O, Enter, Ctrl+X. The asterisk (*) in nano's title bar means you have unsaved changes — make sure it's gone before you exit.

Step 5: Enable and Start Ergo

sudo systemctl daemon-reload
sudo systemctl enable ergo
sudo systemctl start ergo
sudo systemctl status ergo

You're looking for active (running) in the status output, along with log lines like info : server : Server running. If something goes wrong:

journalctl -u ergo -f

Step 6: Disable Plaintext (TLS Only)

When Ergo first starts, it warns that port 6667 (plaintext IRC) is active. Since we want TLS-only connections, this needs to be commented out.

nano /home/pearlnight/ergo/ergo-2.14.0-linux-arm64/ircd.yaml

Find the listeners section. It will look something like this:

listeners:
    ":6667":
    ":6697":
        tls:
            cert: certs/fullchain.pem
            key: certs/privkey.pem
    ":8097":
        websocket: true
        tls:
            cert: certs/fullchain.pem
            key: certs/privkey.pem

To disable port 6667, add a # at the very start of that line, before the quote mark. Nano may highlight it in a different colour — that doesn't mean it's commented out. Only an actual # character at the start of the line counts:

Wrong:  ":6667":
Right: #":6667":

Save, then restart Ergo:

sudo systemctl restart ergo

Verify port 6667 is no longer listening (this should return nothing):

ss -tlnp | grep 6667

And check the cert expiry while you're at it:

openssl x509 -in /home/pearlnight/ergo/certs/fullchain.pem -noout -dates

Step 7: Port Forwarding on the Router

External connections won't work unless the right ports are forwarded to the Pi at the router level. The router admin page is usually at 192.168.1.254 for a BT Home Hub.

The rules you need:

Port 6697 → Pi
TLS IRC connections (HexChat and other desktop clients).
Port 8097 → Pi
Websocket TLS connections (KiwiIRC and other browser clients).
Port 80 → Windows machine
Used by Win-ACME for Let's Encrypt domain verification at cert renewal time. Must not point to the Pi.

A common mistake here is having 6697 and 8097 pointing at the wrong machine. If external connections aren't working, check these rules first.

Step 8: Connect with HexChat

  1. HexChat menu → Network List (or Ctrl+S)
  2. Click Add, name it "DreadFortress"
  3. Add server: dreadfortress.ddns.net/6697 (or the local IP for LAN only: 192.168.1.132/6697)
  4. Tick Use SSL for all the servers on this network
  5. Tick Accept invalid SSL certificates
  6. Click Connect

If you get "Disconnected (Remote host closed socket)", it's almost certainly because SSL wasn't ticked. The server only accepts TLS — an unencrypted connection gets immediately rejected.

Step 9: Connect with KiwiIRC

  1. Go to kiwiirc.com/nextclient/
  2. Set Server: dreadfortress.ddns.net
  3. Set Port: 8097
  4. Scroll down to Advanced
  5. Tick Direct websocket
  6. Set the websocket URL to: wss://dreadfortress.ddns.net:8097/
  7. Click Network

Two things to watch out for here. First, the direct websocket field defaults to ws:// (unencrypted) — it must be wss://. Second, if it still won't connect, diagnose with:

curl -v https://dreadfortress.ddns.net:8097 2>&1 | head -30

A timeout here usually means port 8097 isn't being forwarded through the router. Fix the port forwarding rule and it'll connect immediately.

Quick Reference

Check if Ergo is running
sudo systemctl status ergo
Start / Stop / Restart Ergo
sudo systemctl start ergo
sudo systemctl stop ergo
sudo systemctl restart ergo
View live Ergo logs
journalctl -u ergo -f
Check which ports are listening
ss -tlnp | grep ergo
Edit Ergo config
nano /home/pearlnight/ergo/ergo-2.14.0-linux-arm64/ircd.yaml
After editing the service file, always run
sudo systemctl daemon-reload
Check cert expiry on Pi
openssl x509 -in /home/pearlnight/ergo/certs/fullchain.pem -noout -dates
Check if No-IP is running
sudo systemctl status noip2
Manually copy certs from Windows
C:\Users\black\Documents\copy-certs-to-pi.bat