Libux banner

Libux

10 devlogs
27h 10m 48s

Binary available on the release page.
GitHub release v1.1.0
Libux is a TUI tool for Debian that saves you from endlessly hammering away at the keyboard. It handles firewalls, netwo…

Binary available on the release page.
GitHub release v1.1.0
Libux is a TUI tool for Debian that saves you from endlessly hammering away at the keyboard. It handles firewalls, networking, DHCP, SSH and other services through a simple menu. I’m building this for people who are learning Linux administration, understand the logic behind things like firewalls, but don’t want to memorize commands. The point is to have everything under control while keeping it simple.

This project uses AI

I use AI as a technical mentor to sanity-check my logic, especially with security-heavy features like iptables rules and DHCP configuration. It helps me brainstorm features, refine the TUI flow, and make sure commands are safe and won’t lock anyone out. For features I had no prior experience with - like VLANs. AI helped me understand the concepts before writing a any of the of code. A significant part of the collaboration was also debugging, figuring out why things like DHCP service startup were failing and what the actual cause was.
Later in the project as mentioned in the devlog I fully vibecoded the rest of feutures due to time limitations.

Demo Repository

Loading README...

mforbelak

Shipped this project!

Hours: 2.9
Cookies: 🍪 62
Multiplier: 21.32 cookies/hr

Added another feature PM2 management for my node servers.

mforbelak

v1.1.0 - PM2

First release after finals. No deadline, no pressure, just wanted to add something useful.

PM2 felt like the right next step. Node apps are everywhere on servers and there was no way to manage them from Libux. Now there is.

What’s in it: install and remove PM2 (with a Node.js auto-install prompt if it’s missing), status, start, stop, restart, delete, logs, startup persistence via pm2 startup + pm2 save, ecosystem config view, reload all without downtime.

The module follows the same split-file structure as SSH and netwerk — shared.py, install.py, apps.py, ecosystem.py. I wrote it the same way as before: I set the structure, described what each function should do, and typed it out myself. Claude caught my bugs — inverted logic in remove_pm2, missing if app is None guards, capture_output=True on pm2 list that silently ate all output, a missing – on –nostream. Small things that add up.

Attachment
0
mforbelak

what came after SSH

I’ll be honest. SSH was the last thing I wrote myself. Everything from v0.5.0 onwards, I vibecoded with Claude. It’s not an excuse, it’s a fact.

What came out of this approach:

Apache2 (v0.5.0) - virtual hosts, self-signed SSL certs, HTTP-to-HTTPS redirect, Basic Auth, ServerAlias, listen IP picker, directory listing toggle, site-centric refactor. I set the direction, the AI delivered those thirty files.

Routing (v0.6.0) - show/add/remove static routes, default gateway, IP forwarding toggle with persistence to sysctl.d, onlink fallback when the gateway is not directly reachable.

Users and groups (v0.7.0) - local users, sudo toggle, group membership, group filtering.

Quotas and Permissions (v0.8.0) - interactive file browser pick_path, step-by-step chmod wizard including setuid/setgid/sticky, disk quotas with repquota parsing.

MariaDB (v0.9.0) - installation, root password prompt.

Packages module (v1.0.0) - multi-select TUI, unified detection, noninteractive apt.

v1.0.1 - bash default for useradd, IP forwarding auto-prompt, two-phase enable quotas for rootfs (remounting usrquota on a live root doesn’t work), quotacheck -cuMf fallback, gateway interface picker, Service submenu in Network, static route persistence fix (my old bug - up ip route add went to the end of the file outside the iface block, ifup ignored it, the route disappeared after reboot).

I did that because the project needed to be finished just in a few days for my finals as an backup plan as we can use anything except help from other people.

Attachment
0
mforbelak

Shipped this project!

Hours: 6.79
Cookies: 🍪 148
Multiplier: 21.8 cookies/hr

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

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