Skip to content

RaccoonFacts/kim-ate-my-shorts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

KIM ATE MY SHORTS — Neighborhood Network Node

A fully offline, self-hosted neighborhood file sharing and chat network running on a repurposed router and a mini PC. No internet required. No ISP. No tracking. Just vibes.

Splash Page Main Interface


What Is This?

This project turns a TP-Link Archer C7 router and a Lenovo mini PC into a neighborhood-scale local network. When neighbors connect to the WiFi, they're greeted by a spooky retro captive portal, then dropped into a fully functional file sharing and real-time chat site — all without touching the internet.

Features:

  • Retro CRT-style web interface
  • Real-time WebSocket chat
  • File sharing with upload, download, preview, move, delete, and folder creation
  • Media previews (images, video, audio) inline in the browser
  • Spooky captive portal disclaimer page
  • Password-protected admin panel
  • MAC address activity logging
  • Ban/unban system for bad actors
  • Works on Android, iPhone, Windows, and anything with a browser

Hardware

Component Hardware Used
Router TP-Link Archer C7 v1
Single Board/Mini PC Lenovo Mini PC
Storage 5TB Seagate USB HDD
Router Extra Storage 16GB USB thumbdrive (extroot)

The router handles WiFi broadcasting and the captive portal. The PC does all the heavy lifting — web server, file storage, chat, and admin.


Network Architecture

Phone/Laptop connects to "KIM ATE MY SHORTS" WiFi
        |
        v
Router (192.168.1.1) — TP-Link Archer C7 running OpenWrt
        |
        | nodogsplash intercepts unauthenticated traffic
        |
        v
Captive Portal — Spooky disclaimer page served from router
        |
        | User clicks ENTER THE NETWORK
        |
        v
kimatemyshorts.net resolves to 192.168.1.50 (via dnsmasq)
        |
        v
Ubuntu PC (192.168.1.50) — nginx + Flask
        |
        v
Retro site — chat, file sharing, admin panel
        |
        v
5TB HDD mounted at /mnt/hdd — actual file storage

Router Setup

OpenWrt Installation

Flash OpenWrt 25.12.3 to your Archer C7. The ath79 target is correct for the C7 v1.

Extroot (USB Storage Expansion)

The Archer C7 has very limited internal flash. A USB thumbdrive is used as overlay storage to allow installing packages freely.

# Format your USB drive as ext4 first, then:
apk add block-mount kmod-fs-ext4 e2fsprogs kmod-usb-storage kmod-usb2 kmod-usb3

DEVICE=/dev/sda
eval $(block info ${DEVICE} | grep -o -e 'UUID="\S*"')
uci -q delete fstab.overlay
uci set fstab.overlay="mount"
uci set fstab.overlay.uuid="${UUID}"
uci set fstab.overlay.target="/overlay"
uci commit fstab
mount ${DEVICE} /mnt
tar -C /overlay -cvf - . | tar -C /mnt -xf -
umount /mnt
reboot

After reboot you should have 27GB+ of overlay space.

Required Packages

apk update
apk add nodogsplash dnsmasq-full luci block-mount kmod-fs-ext4 e2fsprogs kmod-usb-storage kmod-usb2 kmod-usb3 openssh-sftp-server

WiFi Configuration

uci set wireless.radio0=wifi-device
uci set wireless.radio0.type='mac80211'
uci set wireless.radio0.path='platform/ahb/18100000.wmac'
uci set wireless.radio0.channel='6'
uci set wireless.radio0.band='2g'
uci set wireless.radio0.htmode='HT20'
uci set wireless.radio0.disabled='0'

uci set wireless.default_radio0=wifi-iface
uci set wireless.default_radio0.device='radio0'
uci set wireless.default_radio0.mode='ap'
uci set wireless.default_radio0.ssid='KIM ATE MY SHORTS'
uci set wireless.default_radio0.encryption='none'
uci set wireless.default_radio0.network='lan'
uci set wireless.default_radio0.disabled='0'

uci commit wireless
wifi reload

DNS Configuration

dnsmasq is used to resolve kimatemyshorts.net to the PC's IP so users never see a raw IP address.

uci add_list dhcp.@dnsmasq[0].address='/kimatemyshorts.net/192.168.1.50'
uci add_list dhcp.@dnsmasq[0].address='/status.client/192.168.1.1'
uci add_list dhcp.@dnsmasq[0].address='/captive.apple.com/192.168.1.1'
uci add_list dhcp.@dnsmasq[0].address='/www.apple.com/192.168.1.1'
uci commit dhcp
/etc/init.d/dnsmasq restart

The Apple addresses point to the router so iPhones trigger the captive portal notification.

Trusted MAC (PC Bypasses Portal)

The PC should never be sent to the captive portal. Add its MAC to the trusted list:

uci add_list nodogsplash.@nodogsplash[0].trustedmac='XX:XX:XX:XX:XX:XX'
uci commit nodogsplash

Replace with your PC's actual MAC address (ip link show).

nodogsplash Configuration

nodogsplash was chosen over openNDS because it is significantly faster on the MIPS-based Archer C7. openNDS runs a massive shell script on every portal request which bogs down the old hardware. nodogsplash is a lightweight C binary.

Create /etc/nodogsplash/nodogsplash.conf:

GatewayInterface br-lan
MaxClients 20
SessionTimeout 1440
CheckInterval 30
RedirectURL http://kimatemyshorts.net
PreAuthIdleTimeout 30
AuthIdleTimeout 480

FirewallRuleSet users-to-router {
    FirewallRule allow tcp port 2050
    FirewallRule allow tcp port 80
    FirewallRule allow udp port 53
    FirewallRule allow udp port 67
    FirewallRule allow tcp port 22
}

Key decisions:

  • SessionTimeout 1440 — 24 hour sessions so users don't get kicked every minute
  • CheckInterval 30 — check every 30 seconds not every 1 second (reduces CPU load)
  • FirewallRuleSet — allows unauthenticated clients to reach the portal on port 2050

The Captive Portal Splash Page

The splash page lives at /etc/nodogsplash/htdocs/splash.html.

Critical design decision: Do NOT actually authenticate users through nodogsplash's $authaction flow. Instead, clicking ENTER THE NETWORK simply redirects to http://kimatemyshorts.net via JavaScript. This means:

  1. The user is never authenticated in nodogsplash's eyes
  2. Android/iPhone never detects "internet access"
  3. The captive portal browser stays open showing our site
  4. Users stay in the portal experience instead of being dropped to their home screen
<script>
function startAccess(){
  // Show loading animation
  document.getElementById('main-content').style.display='none';
  document.getElementById('loading-box').style.display='block';
  
  // After animation completes, redirect to our site
  setTimeout(function(){
    window.location.href='http://kimatemyshorts.net';
  },2000);
}
</script>

See splash.html in this repo for the full file.

Start nodogsplash:

/etc/init.d/nodogsplash enable
/etc/init.d/nodogsplash start

Ubuntu PC Setup

Installation

Install Ubuntu Server 26.04 LTS. During installation:

  • Set hostname to kimshare
  • Enable OpenSSH server
  • Set a static IP of 192.168.1.50 (or configure via netplan after install)

Static IP via Netplan

# /etc/netplan/00-installer-config.yaml
network:
  ethernets:
    eno1:
      addresses:
        - 192.168.1.50/24
      routes:
        - to: default
          via: 192.168.1.1
      nameservers:
        addresses: [192.168.1.1]
  version: 2
sudo netplan apply

HDD Mounting

The 5TB HDD stores all files and logs. Mount it permanently:

sudo mkdir -p /mnt/hdd
sudo mount /dev/sdc2 /mnt/hdd

# Get UUID
sudo blkid /dev/sdc2

# Add to fstab for auto-mount on boot
echo 'UUID=YOUR-UUID-HERE /mnt/hdd ext4 defaults 0 2' | sudo tee -a /etc/fstab

Give your user ownership:

sudo chown -R rac:rac /mnt/hdd
sudo chmod -R 755 /mnt/hdd

Required Packages

sudo apt update
sudo apt install -y nginx python3 python3-flask python3-pip
sudo pip3 install flask-sock --break-system-packages

nginx Configuration

nginx serves the static HTML files and proxies API and WebSocket requests to Flask.

# /etc/nginx/sites-available/kimnode
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/kimnode;
    index index.html;

    server_name _;
    client_max_body_size 10G;

    location / {
        try_files $uri $uri/ =404;
    }

    location /api/ {
        proxy_pass http://127.0.0.1:5000/api/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /admin/ {
        auth_basic "RESTRICTED";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass http://127.0.0.1:5000/admin/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /ws/ {
        proxy_pass http://127.0.0.1:5000/ws/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400;
    }
}
sudo ln -s /etc/nginx/sites-available/kimnode /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Admin Password

sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd raccoon

Flask Application

The Flask app handles all dynamic functionality — file operations, logging, chat, and admin.

See app.py in this repo for the full file. Key endpoints:

Endpoint Method Description
/api/files GET List files in a directory
/api/upload POST Upload files
/api/download GET Download a file
/api/preview/<path> GET Serve file for inline preview
/api/delete POST Delete file or folder
/api/mkdir POST Create a folder
/api/move POST Move a file
/api/log POST Write to activity log
/api/checkban GET Check if a MAC is banned
/api/ban POST Ban a MAC address
/api/unban POST Unban a MAC address
/api/chat/history GET Get last 100 chat messages
/api/chat/clear POST Clear chat (admin only)
/ws/chat WebSocket Real-time chat
/admin/kimadmin_raccoon GET Admin panel (nginx auth required)

Systemd Service

Run Flask as a persistent service:

# /etc/systemd/system/kimnode.service
[Unit]
Description=KimNode Flask API
After=network.target

[Service]
User=rac
WorkingDirectory=/var/www/kimnode/api
ExecStart=/usr/bin/python3 app.py
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable kimnode
sudo systemctl start kimnode

Web Files

Files go in /var/www/kimnode/:

  • index.html — main retro interface (handle login, home/chat/files/info tabs)
  • disclaimer.html — NOT used by the portal (kept as backup)

index.html Features

Handle Page Chat File Share

  • Green/cyan/orange/purple tab color scheme
  • Handle-based login (no accounts, no passwords)
  • Real-time WebSocket chat with persistent message history
  • File browser with breadcrumb navigation and back button
  • Inline media previews — images, video, audio
  • Upload, download, move, delete, mkdir
  • Activity logging on all actions

Admin Panel

Admin Panel

Access at: http://kimatemyshorts.net/admin/kimadmin_raccoon

You will be prompted for the nginx basic auth password you set earlier.

Features:

  • Ban a user — enter their MAC address and a reason
  • Banned users list — see all bans with dates and reasons, unban with one click
  • Clear chat — wipe all chat messages for all users
  • Activity log — full log of every join, upload, download, delete, move, and chat message with IP address, MAC address, user agent, and handle

How MAC Addresses Work

When users perform actions on the site, the JavaScript sends a POST to /api/log. nginx forwards the request to Flask and includes the X-Real-IP header with the client's real IP. Flask then looks up that IP in /proc/net/arp to find the associated MAC address and writes it to the log.

This means visible in the log is exactly which physical device did what, even if they use a different handle each time.


Banning Users

  1. Go to the admin panel
  2. Find the MAC address of the offending device in the activity log
  3. Enter the MAC and a reason in the BAN section
  4. Click BAN

The ban is stored in /mnt/hdd/banned_macs.json. The site checks the ban list on page load — banned users see an ACCESS DENIED page instead of the normal site.

To unban, click UNBAN next to their entry in the admin panel.


File Structure

Router (OpenWrt)
├── /etc/nodogsplash/
│   ├── nodogsplash.conf          # Portal configuration
│   └── htdocs/
│       └── splash.html           # Captive portal page
└── /etc/config/
    ├── wireless                  # WiFi config
    └── dhcp                      # DNS config

Ubuntu PC
├── /var/www/kimnode/
│   ├── index.html                # Main retro site
│   └── api/
│       └── app.py                # Flask backend
├── /etc/nginx/sites-available/
│   └── kimnode                   # nginx config
├── /etc/systemd/system/
│   └── kimnode.service           # Systemd service
└── /mnt/hdd/
    ├── files/                    # User uploaded files
    ├── admin.log                 # Activity log
    └── banned_macs.json          # Ban list

Troubleshooting

WiFi Not Showing Up

ls /sys/class/ieee80211/
iw phy phy0 interface add wlan0 type managed
wifi reload

Portal Not Appearing

Check nodogsplash is running and the conf file exists:

ps | grep nodo
nodogsplash -f 2>&1 | head -10

Site Slow to Load

Make sure session timeouts are set correctly in nodogsplash.conf:

SessionTimeout 1440
CheckInterval 30

If timeouts are set to 1 minute, users get kicked every minute causing constant reconnects.

Flask Not Starting

sudo systemctl status kimnode
sudo journalctl -u kimnode -n 20

Common issues:

  • Port 5000 already in use: sudo fuser -k 5000/tcp
  • Permission denied on HDD: sudo chown -R rac:rac /mnt/hdd

Log Not Updating

The X-Real-IP header must be passed from nginx to Flask. Verify:

grep "X-Real-IP" /etc/nginx/sites-available/kimnode

PC Has No Internet (During Setup)

Add the PC's MAC to the trusted list so nodogsplash doesn't block it:

uci add_list nodogsplash.@nodogsplash[0].trustedmac='XX:XX:XX:XX:XX:XX'
uci commit nodogsplash
/etc/init.d/nodogsplash restart

Design Decisions & Lessons Learned

Why nodogsplash instead of openNDS? Started with openNDS but found it extremely slow on the MIPS-based Archer C7. Every portal request runs a large shell script through libopennds.sh which the old hardware struggles with. nodogsplash is a lightweight C binary that handles the same job much faster.

Why not authenticate users at the portal? When nodogsplash authenticates a user, Android and iOS detect "internet access" and automatically close the captive portal browser. The goal is for users to stay inside the portal experience. By never authenticating — just redirecting to our site — the portal browser stays open and users get the full retro experience without needing to open a separate browser.

Why a separate PC instead of serving from the router? The Archer C7 has 128MB RAM and a slow MIPS processor. Running nginx, Flask, WebSockets, and serving large files from it would be painful. The mini PC handles everything with ease and the 5TB HDD provides massive storage that would be impossible on the router.

Why Ubuntu over other options? Ubuntu Server LTS is stable, well-documented, and has great package support for everything needed — nginx, Python, Flask, flask-sock. No compilation required, everything installs cleanly.

Why VT323 and Share Tech Mono? The whole aesthetic is a neighborhood BBS from the early 90s. VT323 is a proper terminal font, Share Tech Mono handles smaller body text. The green-on-black CRT scanline effect is achieved with a CSS repeating-linear-gradient overlay fixed to the viewport.


Security Notes

  • The admin panel URL contains the admin secret as a path component (/admin/kimadmin_raccoon). Change kimadmin_raccoon in app.py to something unique.
  • nginx basic auth adds a second layer of password protection to the admin panel.
  • The ban system uses MAC addresses which can be spoofed. It is not a hard security barrier, just a deterrent for casual bad actors on a neighborhood network.
  • This system is designed for a trusted local community. Do not expose it to the internet.

Credits

Built through extensive trial and error. The hardest part is getting the captive portal to behave on modern Android and iOS — the trick is to never authenticate at all.

Stack:

  • OpenWrt 25.12.3 (ath79)
  • nodogsplash 5.0.2
  • Ubuntu 26.04 LTS
  • nginx
  • Python 3 / Flask / flask-sock
  • VT323 + Share Tech Mono (Google Fonts)

KIM ATE MY SHORTS — a free network for the people, by the autists.

About

A self-hosted offline neighborhood network with captive portal, file sharing, and real-time chat. Built on OpenWrt + Ubuntu + Flask. Works on Android, Apple, Windows, and linux systems.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors