Activity

mforbelak

What’s new

Put together a live demo mode so anyone reviewing the project doesn’t have
to set up Mosquitto or flash a Pico just to click around. The whole thing
runs on Render’s free tier. Every visitor gets their own isolated in-memory
SQLite seeded with demo MCUs, a DHT11 sensor, 24 hours of backfilled
readings, and a per-session ticker that generates simulated data every 10
seconds. MQTT, Wake-on-LAN and shell command execution are all mocked.

The session isolation was the part I was most worried about. The db module
is imported from 15+ files and I really didn’t want to refactor all of
them. Ended up wrapping everything in AsyncLocalStorage and putting a Proxy
over the db export, so every request transparently hits its own session’s
database without any of the callers knowing. Socket.io broadcasts are
scoped to per-session rooms so visitors never see each other’s data.

Got bitten by Linux case sensitivity on the first deploy (mcuRepository.js
vs MCURepository imports). Worked fine on Windows, crashed immediately
on Render. Also ran into a template literal syntax bug where \" inside a
${} interpolation silently broke the whole MCU detail page, but only in
production. That one took a while to find.

Added an English landing page at / that explains what the project does
and embeds a YouTube link for the hardware demo video. Included a
render.yaml blueprint so forking and deploying is basically one click.

Used Claude Code a lot for the AsyncLocalStorage architecture and the
Render-specific gotchas I wouldn’t have known to look for. Wiring and
testing was still on me, which is where most of the time went anyway.

Attachment
0
mforbelak

Shipped this project!

There were few features missing in my last ship.

mforbelak

SSH Management

This update adds the SSH module - install, configure and manage openssh-server from one place.

Service

Install openssh-server, start, stop, restart and check status. Service management works the same way as the rest of Libux.

Config

Edit sshd_config without touching the file directly. Port, PermitRootLogin, PasswordAuthentication, MaxAuthTries, AllowUsers and DenyUsers - all shown with current values and SSH defaults in brackets. AllowUsers and DenyUsers have a dedicated user list manager - add and remove individual users, the last user removes the directive entirely to avoid invalid config.

Authorized keys

Per-user authorized key management. List, add and remove keys for any system user with a login shell. Keys are shown with type and comment for easy identification.

Key generation

Generate ed25519, rsa or ecdsa keypairs for any user. Choose filename, key is saved to ~/.ssh/ and optionally added to authorized_keys immediately. Instructions shown after generation - what to copy where and how to connect.

Sessions

Live view of active SSH sessions via who - user, TTY, login time and source IP.

Attachment
0
mforbelak

DHCP is here

This update adds the DHCP module - install, configure and manage isc-dhcp-server without touching a config file. This was a fully vibecoded feature - the entire module was written with AI from scratch.

Subnets
No manual config editing. Pick an interface, the subnet and gateway are auto-detected from its IP address. You just fill in the DHCP range start/end, optionally DNS and lease times. Multi-interface support - each subnet gets its own interface and INTERFACESv4 is managed automatically. Edit any subnet later to change range, gateway, DNS or lease times - current values are shown inline, Enter keeps them. Remove subnets with a confirmation prompt. Gateway has three modes: use this server (default), custom IP, or none for LAN-only setups.

Leases
Live view of /var/lib/dhcp/dhcpd.leases. Active leases shown first in green with IP, MAC, hostname, state and expiration. Expired leases listed separately in gray.

Reservations
Static MAC -> IP reservations for devices that aren’t connected yet. Add a hostname, MAC address and fixed IP - the host block is written to dhcpd.conf automatically. MAC validation accepts all common formats (Linux AA:BB:CC:DD:EE:FF, Windows AA-BB-CC-DD-EE-FF, Cisco AABB.CCDD.EEFF, raw AABBCCDDEEFF) and normalizes to lowercase colon format.

Service management
Install isc-dhcp-server with autostart prevention during installation (the service can’t start without a subnet anyway). Start, stop, restart, status. Config is validated with dhcpd -t before every restart — if there’s a syntax error, you see the actual dhcpd output instead of a generic “failed” message. If the service still fails, the last 5 journal lines are shown.

Installation
Clean install flow - just installs the package, no interface selection needed upfront. Interfaces are added automatically when you create your first subnet. The main menu shows a warning if no subnet is configured yet.

Attachment
0
mforbelak

Network Connectivity is here

This release adds the Network Connectivity module - everything you need to manage your network interfaces, addresses, DNS, gateway, hostname and VLANs from one place.

Interfaces

Full interface management. See all interfaces with colored ip addr output - interface names in purple, IPs in yellow, IPv6 in pink, MACs in gray, UP in green, DOWN in red. Enable or disable interfaces, add multiple static IP addresses per interface with proper /etc/network/interfaces persistence using up/down labels, remove individual IPs with automatic primary address promotion, and switch to DHCP which cleans up the static config automatically.

DNS

Read and write /etc/resolv.conf directly. Show current nameservers, add a new one, remove one from a list. Simple.

Gateway

Show the current default gateway, set a new one (applied live via ip route replace and persisted to /etc/network/interfaces), or remove it entirely.

Hostname

Show full hostnamectl status output and set a new hostname.

VLANs

VLAN implementation was heavily assisted by AI - I had no prior experience with how VLAN tagging works on Linux and needed a lot of explaining before writing a single line.

Ping test

Quick connectivity check from the main Network menu.

Attachment
0
mforbelak

Shipped this project!

Hours: 17.5
Cookies: 🍪 313
Multiplier: 17.9 cookies/hr

For now, I have finished the part which focuses on setting up the firewall. I tried making it as versatile as possible. I hope it will help me and my friends finish our finals. The hardest part was making it as flexible as possible - not just a static script that would work for one use case.

mforbelak

Libux: The great cleanup and a few things that should have existed already

Spent the last few sessions finishing features that were on the list for quite long and did a proper refactor before the codebase became unmanageable.

Things that should have existed already

Save and restore across reboots is done. OUTPUT chain works. ip_forward toggle is there with an option to make it persistent. You can now edit rules directly in nano with automatic backup and rollback if the restore fails. Discard changes if you made a mess. Confirmation before deleting a rule - turns out accidentally removing a rule without making sure you want to do so is a bad idea.

Logging

When you toggle a chain policy to DROP, Libux now asks if you want to log dropped traffic. Adds a LOG rule at the end of the chain with optional prefix and rate limit. Nothing fancy, just dmesg | grep YOUR_PREFIX and you can see what’s being blocked.

The refactor which was needed

fw_shared.py was turning into a junk pile. allow_port() and allow_icmp() are now proper shared functions instead of copy-pasted blocks in every chain file. ask_ip(), ask(), ask_required() all moved to utils.py. utils.pause() replaced 20 identical try/except input blocks. utils.run_cmd() handles returncode checking in one place instead of ignoring it everywhere.

Quality of life

Chain policy now shows directly in the main firewall menu - DROP is red, ACCEPT is green. Settings menu with verbose mode that prints every iptables command before it runs. Auto backup before flush and toggle policy. install.sh and start.sh so you don’t have to remember the venv setup every time.

Up next
Network Configuration
Interfaces
- Show interfaces
- Set IP address (static/DHCP)
- Enable/Disable interface
DNS
- Show current DNS
- Set DNS servers
Gateway
- Show gateway
- Set default gateway
Hostname
- Show hostname
- Set hostname

Attachment
0
mforbelak

Libux: Chains, policies, and actually finishing things

Spent most of today closing out features that were half-done and making the firewall feel like a real tool instead of a proof of concept.

Flush and policy toggle

Every chain menu now has two new options — flush the chain and toggle the policy between ACCEPT and DROP. Simple stuff but it was annoying to not have it. The toggle reads the current policy first so you always know what you’re switching to. Show also now displays the policy line even when the chain is empty, which was kind of essential information to just not show.

Source IP filtering everywhere

HTTP, HTTPS, ICMP, MASQUERADE, DNAT, established/related on FORWARD — all of them now optionally take a source IP or subnet. Enter to skip, validation loop if you type something wrong. The DNAT one was the most interesting because the source IP needs to propagate to the auto-generated FORWARD rule too, otherwise you’d have a DNAT rule restricted to one subnet but the FORWARD rule letting everything through anyway.

Protocol choice in PREROUTING

DNAT was TCP only before. Now it asks. Relevant for things like WireGuard which runs on UDP.

The spaghetti problem

firewall.py was becoming unreadable. Split into fw_input.py, fw_forward.py, fw_nat.py and fw_shared.py. shared has the stuff that gets used everywhere — remove_rule, show_chain, rule_exists, ask, flush_chain, toggle_policy.

Up next

Save and restore across reboots, OUTPUT chain, ip_forward toggle.

Attachment
0
mforbelak

Validation, validation, validation

DNAT is working. Root check is in. Ctrl+C no longer bricks subprocess calls. This time was mostly about making sure the firewall actually rejects bad input instead of just passing garbage straight to iptables and hoping for the best.

No more typing interface names

Added pick_interface() — instead of typing eth0 and praying you didn’t make a typo, the app now reads /sys/class/net/ and shows you a menu. Obvious in hindsight. Applied it everywhere interfaces were previously typed manually: FORWARD, MASQUERADE, DNAT.

check_port and check_ip

Two new validation functions in utils. check_port() checks it’s actually a number between 1 and 65535. check_ip() uses Python’s ipaddress module and handles both plain IPs and CIDR notation since that’s what iptables expects anyway. Both are now used consistently across all modules instead of raw isdigit() scattered around.

The baseline wizard got smarter

The secure setup wizard now asks if you even want SSH before asking which port. Port validation loops until you give it something valid instead of silently failing. Also fixed a rule that was supposed to handle DNS but was actually just punching a hole for anything coming from port 53. That was embarrassing.

Ctrl+C inside subprocess

show_firewall() was calling subprocess.run() with capture_output=True which blocks — hitting Ctrl+C mid-execution threw a traceback instead of just cancelling. Wrapped it in try/except. Done.

Up next

Prevent duplicate rules. iptables -C exists for exactly this reason. One shared function in fw_shared.py, applied everywhere rules are added.

Attachment
0
mforbelak

The firewall starts to work

Yesterday I got the menus talking to each other. Today I made them actually useful.

Purple is the new orange
Spent way too long picking a color. Settled on purple. It’s everywhere now and it looks clean.

Ctrl+C no longer kills everything
This was embarrassing — hitting Ctrl+C anywhere would crash the whole script with a giant traceback. Fixed. Now it just goes back to the previous menu like a normal person would expect.

The firewall does stuff now
You can actually add and remove rules for INPUT and FORWARD chains. Allow HTTP, HTTPS, custom ports, ping, traffic between specific interfaces — it all works. Also added MASQUERADE for NAT which took like 10 minutes once I stopped second-guessing myself.

500 lines in one file is not it
firewall.py got out of hand fast. Split it into 4 separate files. Instantly felt better.

The colored output is genuinely cool
The firewall rules viewer now color-codes everything. ACCEPT is green, DROP is red, chain names are pink. Small thing but it makes it actually readable.

Tomorrow
Port forwarding (DNAT), per-chain views, and making sure you can’t accidentally add the same rule twice.

Attachment
0
mforbelak

Devlog — v1.1.0

Spent some time this sprint polishing things that were bugging me for a while.

Biggest addition is the multilingual UI — the app now supports Czech and English,
switchable from settings. Took some work to get event logs and server-side error
messages translating properly since those come from the DB and backend, not just
the frontend templates.

Also shipped the Linux server agent startup script (StartLinuxScript.sh) —
it handles venv creation, dependency install and launches the executor
automatically. Debugging WSL networking was a pain.

Fixed some sneaky JS syntax errors (mixing || and ?? without parens) that crept
in with the i18n commit and broke the frontend silently.

Used Claude Code pretty heavily for the i18n architecture and debugging — good for
bouncing ideas and catching things I’d miss at 1am. Still had to do all the
actual wiring and testing myself, which is where most of the time went.

Small things: data/ directory now committed via .gitkeep, docs updated in both
languages.

Attachment
0
mforbelak

I’ve finally finished the base for my project! It might look like just a few menus, but getting the logic of switching between them without the script crashing or looping infinitely was a real challenge.

The “Textual” Incident
I’m going to be honest here: I almost fell into the trap of “scope creep.” I spent some time looking into Textual because it looks incredibly fancy and pro. But after a few hours, I realized I have no idea how to actually use it properly yet. It’s a whole different world of asynchronous programming and CSS-like styling

What’s working now:

  • The Base: I have a solid foundation where the main menu, installation checks, and firewall sub-menus all talk to each other correctly.

  • Navigation: Switching between different chains (INPUT, FORWARD, etc.) works smoothly now.

  • The “Aha!” moment: I fixed the bug where special characters and ANSI colors were breaking the terminal rendering.

Attachment
0
mforbelak

This update focuses on making the project easier to test without physical hardware. I added startup scripts for both Windows and Linux that automatically handle dependencies and launch the dashboard. The virtual Pico simulator now has a configurable .env file so the broker IP, API key, and MAC address can be set without touching the code. The README was rewritten with a Quick Start section and reviewer notes explaining what can and can’t be tested without hardware. Released as v1.0.1.

Attachment
0
mforbelak

Devlog #3 – Flavortown shipped

Since the last devlog, the project went from a dashboard and a database to a full IoT management platform. My teammate didn’t end up touching the web side, so I built everything myself — with help from Claude Haiku in my IDE early on, and Claude Opus via Claude Code for the later features.

The core is MQTT. Sensor MCUs publish readings to sensor/data with an API key, the server routes values to the right channels, buffers them in memory, and aggregates per-minute AVG/MIN/MAX into SQLite. Channels have configurable thresholds — breach fires an alert event, recovery fires another, no spam.

There are two MCU roles: sensor MCUs collect data and report status (online/passive/offline), deck MCUs are physical Pico W displays that receive config and live values over MQTT and can trigger server commands.

Servers are Linux machines with defined shell commands. A Python agent (debian_executor.py) runs on each, subscribes to its MQTT topic, executes whitelisted commands, and reports back. Wake-on-LAN routes through a Pico on the LAN which sends the UDP magic packet.

MicroPython flashing is handled client-side via the Web Serial API. The server renders a template with placeholders (API key, WiFi, broker IP) and returns the final .py — the browser flashes it directly. Windows + Chrome only.

Rounding it out: session auth with forced password change on first login, per-MCU event log, notifications, and a settings page (timezone, dark mode, ping interval, broker IP with live reconnect).

I spent way more time on this than planned. Shipping it.
Translated with Claude Sonnet 4.6

Attachment
1

Comments

Dhruv J
Dhruv J 19 days ago

logging for 65 hours and then posting an devlog is menacing btw how do you get this text font and underline when making devlogs (debian_executor.py)? with the green highlight

mforbelak

I have generated a dashboard page for the main content using Gemini PRO. Since then, I have added frontend JavaScript for a simple form that sends a POST request to the server with basic information about the MCU to be added. I have also created a database and further structured my MVC architecture. Regarding models, I added a database entity, a repository to interact directly with the DB, and a service to format and validate the data.

Translated with Gemini

Attachment
Attachment
Attachment
0
mforbelak

I’ve created my project before knowing about HackClub so I created it here and made basic description. For now its just a basic web created as a middle ware for server script, MCU for collecting user input or data from sensors. For now its just a simple page and I am trying to make communication between all the parts working so I can move to the data collection and their visualisation and storing.

Attachment
0