All homelab notes and experiments.

QR Check-in Auto-Printer for WordCamp PH

QR check-in auto-printer setup at WordCamp Philippines

A couple of weeks before WordCamp Philippines, Arvin John and Jhim came over to my office to co-work, flash a laptop BIOS (to remove password), and see me rehearse my upcoming talk (didn’t happen).

At some point, Jhim mentioned in passing that he wanted to try automatically printing attendee badges during QR check-in.

I happened to have a similar project for an inventory system. I knew immediately that it was possible, and I was pretty sure we could pull it off.

It got me so excited that right then and there, based on my existing project and code, I made him a proof-of-concept using a smaller printer that I have available around.

The idea was: I would expose an HTTP endpoint, and his QR check-in code would send the attendee details as GET parameters. My side would take care of the actual printing.

We had to do it this way because

  • It’s simple, it will be just a link in his QR check-in code with pre-filled GET parameters
  • No printer drivers to mess around with
  • We knew we probably would not be able to test everything together in person.

For remote testing, I exposed the printer endpoint at printernijhim.aragon.ph. Jhim could trigger a print from wherever he was, and a sticker would come out of the printer sitting in my office.

Jhim triggering print at his house.

I have been very fascinated with thermal printers and the cheapness of the sticker papers available. I was even willing to buy the printer and stickers if the organizer don’t have a budget for, just to try it out in “production”. I really need an excuse for practical use before I purchase anything.

Cost

ItemPrice
A6 Waybill Printer OC-9260₱2,399
80x50cm Thermal Label Stickers, 1000 pcs₱169
Sticker Holder₱146
Fujitsu Lifebook Q509 (tablet, printer server)₱2,199

The organizers also bought another set of printer and stickers, which is ideal in case one printer malfunctions.

The tablet doesn’t really count toward this project. I bought it a couple of months back for another purpose, on a Shopee sale. I just happened to have it lying around.

Getting it event-ready

The only thing I asked Jhim to add to his check-in system was a configurable printer URL. That way, we could change where print requests were sent without changing actual code during the event.

On my side, I used a tablet with a USB-A port connected directly to the printer.

It runs Ubuntu, CUPS, and a small web server.

I configured it so that when the tablet boots, it automatically:

  • logs in
  • starts the print server
  • opens Firefox in kiosk mode
  • waits for HTTP GET requests

The goal was for the whole thing to be basically plug and play on event day.

On the first day, I stayed around the registration area to make sure everything was working on the printer side.

On the second day, I did not have the head space to check because it’s the day I will present. It still worked.

Pulled it off for 400+ attendees

Did I mention that I’m sure it will work? It did! It was nice seeing badges around with printed tags.

Scan, check-in, badge prints. Live at registration. Video provided by Jhim.

There were a couple of things we can improve, though that’s for version 2.

Version 2

For version 2, I think the tablet itself could be the QR code scanner. It has a webcam, and it can be configured to be always-on.

We could build a small local backend where CampTix attendee data is uploaded before the event. At that point, ticket sales are mostly done anyway.

The flow could then be:

  • QR scan
  • Check-in attendee, record locally
  • Print badge

Everything entirely on the tablet. The biggest advantage is that registration would have no internet dependency at all. We could also deploy multiple tablets independent of each other (horizontal scaling!).

After the event, we could sync the check-in data back to CampTix.

For the next event!

-

The QR check-in and automatic printing idea was Jhim’s, I was just a big enabler. I just enjoy these kind of projects.

Jhim wrote up the check-in system from his side: Building a QR Check-in System for WordCamp Philippines.

Gate Watcher

The sliding gate fully closed, slats spanning the whole opening

I have an IoT-controlled gate in my office. I can open and close it from Apple Home through Home Assistant. It’s working fine, never had a problem with it… technically.

There were two instances, though, that I somehow left it open, or open button was accidentally pressed. I only found out the next day that I left the gate opened all night. Both times, I slowly walked into the office hoping nothing was missing.

Thankfully, the two times it happened nobody took anything.

My gate setup is not smart enough that it knows the actual state of the gate (open or close).

But I have a CCTV camera pointing at the gate.

Making it smart

I’ve been looking for practical use of small models like gemma-4-e4b (supports vision!) which easily fits the GPU in my homelab server. I have it exposed via llama.cpp with llama-swap, an OpenAI-compatible endpoint, and it’s a vision model accessible to any service running in my lab.

I’ve thought of pulling a snapshot every minute and have it process the image using a prompt, then automatically close the gate if left open for 15 minutes.

How it works

Detection is one vision-model call, once a minute.

Each cycle pulls a keyframe over RTSP, crops to the lower-center of the gate, and sends the crop to the model. Temperature 0, thinking off. About a second per call.

You are looking through a carport at a sliding driveway gate, at the lower-center where the driveway meets the exit.

If you see open road/pavement/ground leading outside (gate slid away) it is OPEN.

If a solid row of vertical metal slats/bars blocks the driveway at ground level it is CLOSED.

A gate only partly open (any visible gap to the outside) also counts as OPEN.

Thin bright light-lines BETWEEN the vertical bars are still CLOSED.

When unsure, answer OPEN.

Respond with STRICT JSON only, no prose:
{"state":"OPEN"|"CLOSED","confidence":0-100,"reason":"<short>"}

“When unsure, answer OPEN” is the fail-safe. Better to have a false-positive open, than a false positive close.

The gate slid part-way open: slats on the left, a clear gap to the street on the right Even a partial gap counts as open.

The reply is JSON, so I log a state, a confidence, and a reason. One frame decides nothing. A change needs two polls in a row to agree.

Making it safe

Moving a physical gate is the part to be careful with. The alert always lands before anything moves.

  • Home Assistant pages me after the gate has been open for 15 minutes.
  • It keeps checking for the next 3 minutes. If the gate is still open for three consecutive checks, it triggers the gate to close.

There is currently no obstruction sensor. So before it closes, it takes a fresh frame and asks:

This is a driveway gate opening that is about to be auto-closed.

Is there ANY person, animal, or vehicle in or partly in the gate opening / directly in its path (anything that could be hit by a closing gate)? Be cautious.

Strict JSON: {"blocked":true|false,"what":"<short>"}

If something is blocking, it does not close.

I watched it run on its own once. After 18 minutes, the path was clear, so it closed the gate.

Making it super safe

I still have doubts about the prompt. Even if it double-checks or triple-checks it could still be wrong and cause an accident.

I already have an IR obstacle sensor that can be wired into the gate mechanism to stop it if something is in the way, I just need the time to install it.

Unfortunately, I can’t ask Claude/Codex to do it for me 😛

It’s on my to do list!

Setting up Traefik as a Reverse Proxy in Proxmox

I wanted one place to receive requests for my homelab services and send each request to the right machine. Traefik now does that job in an LXC on Proxmox. It replaced Nginx Proxy Manager in my setup.

This is the setup that worked for me: install Traefik with the Proxmox community script, keep the main settings in one file, and add a small file for each service. I’ll use nvr.example.com pointing to an NVR at 192.168.0.155:5000 as the example. Replace those with your own domain and service address.

Before you start

For the Let’s Encrypt method in this guide, you need:

  • A domain you control, with a DNS record for the hostname pointing to your public IP.
  • Ports 80 and 443 forwarded from your router to the Traefik LXC’s IP address.
  • A service that Traefik can reach from the LXC, such as http://192.168.0.155:5000.

Let’s Encrypt must be able to reach Traefik on port 80 for the HTTP-01 challenge. If that isn’t possible in your network, use a different challenge method; the configuration below won’t issue a certificate on its own.

1. Install Traefik in an LXC

Run the command shown on the Traefik community script page in the Proxmox host shell, not inside an existing container:

var_os='debian' bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/traefik.sh)"
Proxmox community script options for Traefik

The script creates the LXC and installs Traefik. Note the LXC’s IP address. Inside the container, the main configuration file is /etc/traefik/traefik.yaml, and Traefik runs as a systemd service.

2. Set up Traefik’s main configuration

Traefik has two kinds of configuration. The static configuration tells it which ports to listen on, where to load route files, and how to request certificates. The dynamic configuration describes which hostname goes to which service.

Merge these settings into /etc/traefik/traefik.yaml inside the LXC. Keep any settings from the install script that you still need, and replace the example email address:

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"

providers:
  file:
    directory: /etc/traefik/conf.d/
    watch: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: "[email protected]"
      storage: /etc/traefik/ssl/acme.json
      httpChallenge:
        entryPoint: web

Create /etc/traefik/conf.d/ and /etc/traefik/ssl/ if they don’t exist. Make sure Traefik can write to the ACME storage path. The path in the configuration has to match the file Traefik uses; I once had a typo there and spent time looking for a certificate in the wrong place.

The web entry point receives HTTP traffic on port 80. The websecure entry point receives HTTPS traffic on port 443. Traefik watches conf.d so route changes can be loaded from separate files.

3. Add a route for one service

Create /etc/traefik/conf.d/nvr.yaml:

http:
  routers:
    nvr:
      rule: "Host(`nvr.example.com`)"
      entryPoints:
        - websecure
      service: nvr
      tls:
        certResolver: letsencrypt

  services:
    nvr:
      loadBalancer:
        servers:
          - url: "http://192.168.0.155:5000"

The router matches requests for nvr.example.com on HTTPS. The service is where Traefik sends them. certResolver: letsencrypt tells Traefik to request and manage a certificate for the hostname in the router rule. Defining the resolver in the main file isn’t enough; a TLS router must use it.

Repeat this pattern in another file for each service, changing the router name, hostname, service name, and backend URL.

4. Restart and test

Restart Traefik after changing its main configuration:

systemctl restart traefik
systemctl status traefik

Then visit https://nvr.example.com from outside your network. The request should reach your router, pass to the Traefik LXC, match the nvr router, and reach the NVR. Traefik requests the certificate when it needs one for the matching TLS router; an empty acme.json immediately after startup doesn’t by itself mean the setup is broken.

If the page doesn’t load, follow the request in that same order:

  1. Does the hostname resolve to your public IP?
  2. Do ports 80 and 443 reach the Traefik LXC?
  3. Can the LXC reach the backend URL directly?
  4. Did Traefik load the route file, and does its Host rule match the hostname you visited?

Read the service logs while testing:

journalctl -u traefik -f

I temporarily set log.level: DEBUG in the main configuration when I needed more detail. One useful error was HTTP challenge is not enabled, which pointed back to the missing httpChallenge setting. Remove the extra logging once you’ve found the problem.

Optional: redirect HTTP to HTTPS

I left redirects out while getting certificate issuance working so there was one less setting to debug. If you want HTTP requests to redirect to HTTPS, Traefik supports an entry point redirect:

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

Merge that into your existing entryPoints section rather than adding a second one. I previously thought this kind of redirect broke HTTP-01. Traefik’s ACME documentation says the redirect is compatible with the challenge. The real requirements are that port 80 reaches Traefik and that the resolver has httpChallenge.entryPoint set to web.

For more detail on route files, see Traefik’s file provider documentation.

Migrating from a TP-Link Managed Switch to Juniper EX2200

I hit the VLAN 1 failure pattern while migrating from a consumer TP-Link managed switch to a Juniper EX2200. The configs looked equivalent, but the behavior was not.

Consumer switches are forgiving:

  • Trunk ports behave like access ports with extras
  • “Allow all VLANs” plus PVID feels safe
  • End devices often work on trunks accidentally

On the EX2200:

  • Trunks are for VLAN-aware devices only
  • Native VLAN is not a substitute for access ports
  • VLAN 1 must not be implicitly tagged
  • Ambiguous configs are accepted but behave differently

The fix was mostly mental model. Once I treated trunks as trunk-only and made VLAN intent explicit everywhere, VLAN 1 behaved and DHCP stopped failing silently.

For the working port and VLAN patterns, see: Juniper EX2200 VLAN Reference.

Showing 4 of 56 notes