[{"content":"My N100 soft router was finally running a stable router OS – iStoreOS – within a Proxmox VE virtual machine. The core networking was solid. Now came the rewarding part: deploying the services I actually wanted and tackling the inevitable advanced troubleshooting challenges.\nAdGuard Home: Taming the DNS Loop Installing AdGuard Home (AGH) via the iStoreOS app store was straightforward. The initial setup wizard (:3000) guided me through setting ports. I changed AGH\u0026rsquo;s DNS port to :5353 to avoid conflict with iStoreOS\u0026rsquo;s built-in dnsmasq (running on :53).\nThe goal: Clients -\u0026gt; iStoreOS (dnsmasq :53) -\u0026gt; AdGuard Home (:5353) -\u0026gt; Public DNS (e.g., 1.1.1.1).\nConfiguration involved:\ndnsmasq: Set it to not forward upstream queries found in /etc/resolv.conf, and explicitly forward all queries to AGH listening on the loopback address: server=127.0.0.1#5353. AdGuard Home: Set its upstream DNS servers to Cloudflare/Google. DHCP: Configure iStoreOS\u0026rsquo;s DHCP server (Option 6) to advertise the router\u0026rsquo;s IP (10.0.0.1) as the DNS server for all clients. Initially, this resulted in DNS resolution failures and context deadline exceeded errors in the AGH logs. Diagnosis: a DNS loop. Dnsmasq was forwarding to AGH, but AGH, possibly due to a misconfiguration or startup order issue, was somehow trying to query dnsmasq back.\nAdding to the confusion was a \u0026ldquo;ghost configuration.\u0026rdquo; I had initially tried installing AGH via a curl script before using the app store version. This seemed to leave conflicting settings. The breakthrough came almost accidentally: running the AdGuard Home binary directly from the command line with the -s run flag (./AdGuardHome -s run) triggered some kind of self-reset or reinitialization. After this, restarting the service properly allowed the correct DNS chain (dnsmasq -\u0026gt; AGH -\u0026gt; Public) to establish, and DNS resolution started working beautifully across my network.\nThe Network Speed Crisis: A Virtualization Bottleneck With DNS filtering active, I ran a speed test. Disaster! My gigabit fiber connection, which previously delivered ~950 Mbps, was now capped at a measly 400 Mbps.\nThe troubleshooting began:\nDisable AdGuard Home: No change. DNS filtering wasn\u0026rsquo;t the bottleneck. Check Physical Links: Used ethtool eth0 and ethtool eth1 within the iStoreOS VM to confirm the virtual NICs were negotiating at 2500 Mbps with the PVE bridges. Links were fine. Enable Software Flow Offloading: Toggled this common OpenWrt optimization. No significant change. The problem had to lie deeper, likely within the virtualization layer itself. Research pointed towards a common issue with high-speed networking in VMs: the CPU struggling to handle the sheer volume of network packets being processed by a single core for the virtual NIC.\nThe Solution: Multiqueue Proxmox VE (and the underlying KVM/QEMU) supports a feature called Multiqueue VirtIO-Net. This allows the network packet processing load for a virtual NIC to be spread across multiple vCPU cores assigned to the VM.\nShut down the iStoreOS VM. In the PVE web UI, go to the VM\u0026rsquo;s \u0026ldquo;Hardware\u0026rdquo; tab. Select the VirtIO network device corresponding to the LAN interface (net0 in my case, connected to vmbr1). Click \u0026ldquo;Edit\u0026rdquo;. Check the \u0026ldquo;Multiqueue\u0026rdquo; box. Set the \u0026ldquo;Queues\u0026rdquo; number equal to the number of vCPU cores assigned to the VM (e.g., if you gave it 4 cores, set Queues to 4). Repeat steps 3-6 for the WAN network device (net1, connected to vmbr0). Start the iStoreOS VM. Ran the speed test again. Success! Speeds jumped right back up to the ~950 Mbps range. The virtualization bottleneck was eliminated.\nTailscale Triumph: Conquering Remote Access My final major goal was setting up remote access, primarily to manage services like my NAS (planned OMV VM) and potentially the router itself when away from home. I opted for Tailscale due to its ease of use and availability in the iStoreOS app store.\nInstallation was simple. Following the command-line instructions (tailscale up --advertise-routes=10.0.0.0/24) connected the router to my Tailnet and advertised my home LAN subnet. However, while the router appeared online in the Tailscale admin console, I couldn\u0026rsquo;t access any devices on my home LAN (like 10.0.0.1) from my phone connected via Tailscale.\nTroubleshooting Round 1: Firewall Pinging 10.0.0.1 from my phone resulted in \u0026ldquo;Destination Port Unreachable.\u0026rdquo; This screamed firewall. iStoreOS hadn\u0026rsquo;t automatically created a firewall zone or rules for the new tailscale0 interface.\nTroubleshooting Round 2: Interface \u0026amp; Zone Following guidance from GitHub issues, I manually edited /etc/config/network to define the tailscale0 interface:\nconfig interface \u0026#39;tailscale\u0026#39; option proto \u0026#39;none\u0026#39; option device \u0026#39;tailscale0\u0026#39; Then, in the LuCI Firewall settings (Network -\u0026gt; Firewall), I:\nCreated a new firewall zone named tailscale. Set its Input/Output/Forward policies to ACCEPT. Assigned the tailscale0 network interface to this zone. Crucially, created a Forwarding rule allowing traffic from the tailscale zone to the lan zone. Saved and applied. Tried connecting again. The \u0026ldquo;Port Unreachable\u0026rdquo; error was gone, but now attempts to access 10.0.0.1just\u0026hellip; hung. Loading indefinitely.\nTroubleshooting Round 3: MTU/MSS This new symptom – indefinite loading – strongly suggested an MTU (Maximum Transmission Unit) mismatch problem, common with VPNs and tunnels like Tailscale. Packets were likely getting fragmented or dropped because their size exceeded what the tunnel could handle. The standard fix is MSS Clamping (Maximum Segment Size), where the router rewrites TCP packet headers to request smaller segment sizes.\nThe Final Fix: iptables MSS Clamping Added the following rules via Network -\u0026gt; Firewall -\u0026gt; Custom Rules:\n# Tailscale MSS Clamping for IPv4 iptables -t mangle -A FORWARD -o tailscale0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu # Tailscale MSS Clamping for IPv6 (if using IPv6 with Tailscale) ip6tables -t mangle -A FORWARD -o tailscale0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu Restarted the firewall. Tried accessing 10.0.0.1 from my phone via Tailscale one last time.\nVictory! The iStoreOS login page loaded instantly. Remote access was finally conquered.\nConclusion: A System Forged in Debugging This journey, from a simple IPv6 issue to a fully virtualized N100 soft router with custom services and robust remote access, was far more challenging than I initially anticipated. Every step seemed to uncover a new problem, requiring deep dives into networking concepts, Linux internals, and virtualization quirks.\nHowever, each problem solved solidified my understanding and resulted in a final system that is incredibly powerful, stable, and perfectly tailored to my needs. The N100 platform with Proxmox and iStoreOS has proven to be an outstanding foundation. The debugging war is over, and the era of enjoying a truly capable home lab has begun.\n","permalink":"https://frankblogs.com/posts/article-6/","summary":"\u003cp\u003eMy N100 soft router was finally running a stable router OS – iStoreOS – within a Proxmox VE virtual machine. The core networking was solid. Now came the rewarding part: deploying the services I actually wanted and tackling the inevitable advanced troubleshooting challenges.\u003c/p\u003e\n\u003ch2 id=\"adguard-home-taming-the-dns-loop\"\u003eAdGuard Home: Taming the DNS Loop\u003c/h2\u003e\n\u003cp\u003eInstalling AdGuard Home (AGH) via the iStoreOS app store was straightforward. The initial setup wizard (\u003ccode\u003e:3000\u003c/code\u003e) guided me through setting ports. I changed AGH\u0026rsquo;s DNS port to \u003ccode\u003e:5353\u003c/code\u003e to avoid conflict with iStoreOS\u0026rsquo;s built-in \u003ccode\u003ednsmasq\u003c/code\u003e (running on \u003ccode\u003e:53\u003c/code\u003e).\u003c/p\u003e","title":"Service Deployment \u0026 Advanced Troubleshooting on iStoreOS"},{"content":"My Proxmox VE (PVE) hypervisor was installed and the host networking configured on my N100 box. Now it was time to create the core component: the virtual machine that would act as my main home router. My initial plan was to use vanilla OpenWrt, the classic, highly customizable Linux distribution for routers.\nAct I: The Struggles with Vanilla OpenWrt Creating the VM in PVE (giving it VM ID 100) involved several steps:\nDownload Firmware: Get the official OpenWrt x86-64 combined-ext4 image (.img.gz). Upload \u0026amp; Import: Upload the .img.gz file to PVE (e.g., via SCP or the web UI\u0026rsquo;s storage browser – note: uploading directly as an ISO image type fails). Import the disk image into the VM using qm importdisk 100 openwrt.img.gz local-lvm. VM Hardware Config: Assign CPU cores, RAM. Remove the default SCSI disk. Attach the imported OpenWrt disk as a VirtIO block device. Add two VirtIO network cards – one connected to PVE\u0026rsquo;s vmbr0 (WAN) and one to vmbr1 (LAN). Set the boot order to use the OpenWrt disk. Disk Resizing: The default image is tiny. Resize the virtual disk in PVE\u0026rsquo;s hardware tab. Boot the VM with a GParted Live ISO attached, use GParted to expand the OpenWrt partition to fill the larger virtual disk. Initial Network Config: Boot OpenWrt. Access the console (via PVE\u0026rsquo;s web UI or qm terminal 100 - not qm console). Use the vi editor to modify /etc/config/network, changing the LAN interface\u0026rsquo;s IP address to 10.0.0.1. So far, so good. I could access the OpenWrt LuCI web interface at 10.0.0.1. I configured PPPoE on eth1 (WAN, connected to vmbr0) and set up the LAN interface (eth0, connected to vmbr1) with DHCP and IPv6 server settings. Basic routing worked!\nThe Dependency Hell The problems began when I tried to customize OpenWrt by installing packages. My goal was simple: install luci-app-adguardhome. But opkg update often failed to connect to the repositories. When it did connect, attempting to install AdGuard Home resulted in a cascade of errors about incompatible architectures or missing dependencies (luci-lua-runtime was a frequent culprit). Even manually downloading .ipk packages failed with similar architecture errors.\nI spent hours trying different package versions, compiling dependencies, and scouring forums. I even managed to \u0026ldquo;force install\u0026rdquo; some packages, but this led to a broken LuCI interface – the \u0026ldquo;Services\u0026rdquo; menu simply wouldn\u0026rsquo;t appear, despite the package seemingly being installed.\n[Placeholder: Add a screenshot of the terminal showing the opkg errors or the LuCI interface missing the Services menu.]\nClearing LuCI caches (rm /tmp/luci-indexcache) did nothing. Log files (logread) showed no obvious errors. File permissions seemed correct. The final nail in the coffin was a detailed error log clearly stating that key LuCI components and their dependencies were fundamentally incompatible with the base system. Trying to manually build a complex setup on vanilla OpenWrt felt like assembling a car from mismatched parts found in a junkyard.\nAct II: The Turn Towards iStoreOS Frustrated and realizing the \u0026ldquo;DIY package management\u0026rdquo; route was unsustainable, I revisited earlier advice: use a pre-integrated firmware like iStoreOS. Based on OpenWrt but comes with a user-friendly interface and, crucially, a curated app store where dependencies should just work.\nThe Migration Plan The challenge: perform this migration without disrupting my family\u0026rsquo;s internet access for too long.\nPreparation: In PVE, create a new VM (e.g., VM ID 101) for iStoreOS. Download the iStoreOS x86 .img.gz firmware. Import \u0026amp; Basic Config: Upload and import the iStoreOS disk into VM 101. Configure its hardware similarly to the OpenWrt VM (CPU, RAM, 2x VirtIO NICs connected to vmbr0 and vmbr1). Offline Config Attempt (Failed): Boot VM 101 with GParted. Try to mount the iStoreOS partition to change the default IP (192.168.100.1) to 10.0.0.1 offline. Failure! GParted couldn\u0026rsquo;t identify the filesystem. Manual mount commands failed, complaining about unknown filesystem types or missing superblocks. Offline config was impossible. The Switch: Plan B - configure online. Physically disconnect the WAN cable from the N100. Shut down the old OpenWrt VM (100). Start the new iStoreOS VM (101). Set my Mac\u0026rsquo;s IP statically to 192.168.100.5. Connect my Mac to one of the N100\u0026rsquo;s LAN ports (connected to vmbr1). Try accessing 192.168.100.1. The WAN/LAN Inversion Twist Failure again! 192.168.100.1 was unreachable. In a moment of desperation (or maybe just random plugging), I moved my Mac\u0026rsquo;s cable to the N100 port I had designated for WAN (connected to vmbr0). And\u0026hellip; success! I reached the iStoreOS login page.\nDiagnosis: iStoreOS had, for some reason, reversed the network interface identification compared to OpenWrt. It saw eth0 (connected to PVE\u0026rsquo;s vmbr1, my LAN bridge) as its WAN interface, and eth1 (connected to PVE\u0026rsquo;s vmbr0, my WAN bridge) as its LAN interface, assigning the 192.168.100.1 address there.\nResolution: Logged into iStoreOS via the \u0026ldquo;wrong\u0026rdquo; port. Navigated to Network -\u0026gt; Interfaces. Edited the LAN and WAN interfaces, swapping their assigned physical devices (eth0 \u0026lt;-\u0026gt; eth1). Saved and applied. Moved my Mac\u0026rsquo;s cable back to a correct LAN port. Set my Mac back to DHCP. Accessed 192.168.100.1 (the default iStore IP). Success! Changed the iStoreOS LAN IP to 10.0.0.1. Plugged the WAN cable back into the N100\u0026rsquo;s WAN port.\nThe network migration was complete in under 15 minutes. The foundation was finally stable. iStoreOS provided the pre-packaged environment I needed. Now, it was time to deploy the services that started this whole journey.\n","permalink":"https://frankblogs.com/posts/article-5/","summary":"\u003cp\u003eMy Proxmox VE (PVE) hypervisor was installed and the host networking configured on my N100 box. Now it was time to create the core component: the virtual machine that would act as my main home router. My initial plan was to use vanilla \u003cstrong\u003eOpenWrt\u003c/strong\u003e, the classic, highly customizable Linux distribution for routers.\u003c/p\u003e\n\u003ch2 id=\"act-i-the-struggles-with-vanilla-openwrt\"\u003eAct I: The Struggles with Vanilla OpenWrt\u003c/h2\u003e\n\u003cp\u003eCreating the VM in PVE (giving it VM ID 100) involved several steps:\u003c/p\u003e","title":"The Router VM Saga: OpenWrt vs. iStoreOS \u0026 The Great Migration"},{"content":"The N100 hardware for my new soft router/home server was assembled and ready. The grand vision involved virtualization, allowing this single machine to run my router, NAS, and potentially other services simultaneously. The chosen foundation for this was Proxmox Virtual Environment (PVE), a powerful, open-source hypervisor based on Debian Linux.\nInstalling PVE: The First Hurdle Installing PVE itself is relatively straightforward, similar to installing any Linux distribution:\nCreate Installation Media: Download the PVE ISO image. On my Mac, I used Balena Etcher to write the ISO to a USB drive. My first attempt failed – turns out the old USB drive I grabbed was faulty. A newer drive worked perfectly. Boot from USB: Connect a monitor and keyboard to the N100 box, plug in the USB drive, and boot into the BIOS/UEFI settings (usually by pressing DEL or F2 during startup). Set the USB drive as the primary boot device. PVE Installer: Follow the graphical installer prompts. Key settings during installation: Target Hard Disk: Select the Fanxiang NVMe SSD. Location/Timezone: Set appropriately. Hostname: Set to something memorable, like pve.local. Network Configuration: Crucially, assign a static IP address for PVE\u0026rsquo;s management interface (e.g., 10.0.0.2), along with the gateway (10.0.0.1 - my eventual router VM\u0026rsquo;s address) and DNS servers. I chose one of the four physical ports (e.g., enp1s0) for this initial management access. The installation completed smoothly, and PVE rebooted. I could access the command line directly on the N100, and more importantly, access the web UI from my Mac at https://10.0.0.2:8006. Stage one complete!\nThe Networking Nightmare: Reconfiguring for VMs PVE\u0026rsquo;s default network setup uses one physical port (enp1s0 in my case) solely for its own management. However, to allow my virtual machines (like the router VM) to connect to both the internet (WAN) and my home network (LAN), I needed to create Linux Bridges.\nMy plan:\nvmbr0: Use a different physical port (e.g., enp2s0) for the WAN connection (connecting to my ISP modem). vmbr1: Use the remaining two physical ports (e.g., enp3s0, enp4s0) bridged together for the LAN connection (connecting to my home switch). Crucially: Move PVE\u0026rsquo;s own management interface from the physical port enp1s0 onto the newly created LAN bridge (vmbr1). This way, I could manage PVE from any computer on my home network using the 10.0.0.2 address, freeing up enp1s0 perhaps for other uses. This reconfiguration is done by editing the /etc/network/interfaces file directly via the PVE console (or SSH). I carefully modified the file, creating the bridge definitions and changing the management interface settings.\nI applied the changes (systemctl restart networking or a reboot). And then\u0026hellip; disaster.\nI could no longer access the PVE web UI at 10.0.0.2. Pings failed. It seemed I had completely locked myself out.\nTroubleshooting in the Dark Panic set in. Had I made a typo in the config file? Did the bridge fail to come up? I had to go back to the \u0026ldquo;direct connection\u0026rdquo; method: plug a monitor and keyboard directly into the N100 box.\nLogging into the PVE console, I ran ip a to check the network interface status. The output revealed the problem: vmbr1 (my intended LAN bridge and new management interface) had no IP address. My configuration changes hadn\u0026rsquo;t been applied correctly, or something had gone wrong during the network restart.\nCarefully re-examining /etc/network/interfaces line by line, I eventually spotted the error – perhaps a typo, a missing auto vmbr1, or incorrect bridge port settings. I corrected the file using the nano editor (a bit friendlier than vi for quick edits).\nAfter saving the corrected file and restarting the networking service again (systemctl restart networking), I ran ip a one more time. Success! vmbr1 now showed the correct 10.0.0.2 address.\nBack on my Mac, I refreshed https://10.0.0.2:8006, and the Proxmox login screen reappeared. Access restored!\nThis harrowing experience was my first real taste of configuring Linux networking and the importance of meticulousness when editing critical system files. With the PVE host networking finally stable, I was ready for the main event: creating the virtual machine that would become my new home router.\n","permalink":"https://frankblogs.com/posts/article-4/","summary":"\u003cp\u003eThe N100 hardware for my new soft router/home server was assembled and ready. The grand vision involved virtualization, allowing this single machine to run my router, NAS, and potentially other services simultaneously. The chosen foundation for this was \u003cstrong\u003eProxmox Virtual Environment (PVE)\u003c/strong\u003e, a powerful, open-source hypervisor based on Debian Linux.\u003c/p\u003e\n\u003ch2 id=\"installing-pve-the-first-hurdle\"\u003eInstalling PVE: The First Hurdle\u003c/h2\u003e\n\u003cp\u003eInstalling PVE itself is relatively straightforward, similar to installing any Linux distribution:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eCreate Installation Media:\u003c/strong\u003e Download the PVE ISO image. On my Mac, I used \u003cstrong\u003eBalena Etcher\u003c/strong\u003e to write the ISO to a USB drive. My first attempt failed – turns out the old USB drive I grabbed was faulty. A newer drive worked perfectly.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBoot from USB:\u003c/strong\u003e Connect a monitor and keyboard to the N100 box, plug in the USB drive, and boot into the BIOS/UEFI settings (usually by pressing DEL or F2 during startup). Set the USB drive as the primary boot device.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePVE Installer:\u003c/strong\u003e Follow the graphical installer prompts. Key settings during installation:\n\u003cul\u003e\n\u003cli\u003eTarget Hard Disk: Select the Fanxiang NVMe SSD.\u003c/li\u003e\n\u003cli\u003eLocation/Timezone: Set appropriately.\u003c/li\u003e\n\u003cli\u003eHostname: Set to something memorable, like \u003ccode\u003epve.local\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eNetwork Configuration: \u003cstrong\u003eCrucially\u003c/strong\u003e, assign a \u003cstrong\u003estatic IP address\u003c/strong\u003e for PVE\u0026rsquo;s management interface (e.g., \u003ccode\u003e10.0.0.2\u003c/code\u003e), along with the gateway (\u003ccode\u003e10.0.0.1\u003c/code\u003e - my eventual router VM\u0026rsquo;s address) and DNS servers. I chose one of the four physical ports (e.g., \u003ccode\u003eenp1s0\u003c/code\u003e) for this initial management access.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThe installation completed smoothly, and PVE rebooted. I could access the command line directly on the N100, and more importantly, access the web UI from my Mac at \u003ccode\u003ehttps://10.0.0.2:8006\u003c/code\u003e. Stage one complete!\u003c/p\u003e","title":"Proxmox VE Initiation: Taming the Virtualization Hypervisor"},{"content":"My journey to achieve reliable home networking, particularly stable IPv6, had hit roadblocks with both my ISP\u0026rsquo;s modem and a powerful-but-faulty Netgear R8000 running OpenWrt. It became clear that consumer-grade hardware, even with custom firmware, might not offer the raw power, flexibility, and reliability I craved. It was time to explore the next level: x86 Soft Routers.\nWhat is a Soft Router? Unlike traditional \u0026ldquo;hard\u0026rdquo; routers with proprietary hardware and software, a soft router is essentially a small, dedicated PC running a specialized router operating system (like OpenWrt, pfSense, OPNsense, or RouterOS). The \u0026ldquo;x86\u0026rdquo; refers to the standard PC processor architecture (Intel/AMD), offering significantly more power and flexibility than the ARM or MIPS chips found in most consumer routers.\nThe Allure of x86 The potential benefits were immense:\nRaw Performance: Capable of handling gigabit+ speeds with complex firewall rules, VPNs, and traffic shaping without breaking a sweat. Flexibility: Run virtually any router OS or even standard Linux distributions. Expandability: Add more RAM, faster storage, or even specialized network cards. Virtualization: This was the game-changer. An x86 platform could potentially run multiple operating systems simultaneously using a hypervisor like Proxmox VE (PVE). I could have my router, a NAS, a media server, and more, all running on one efficient box. Choosing the Platform: N5105/J4125 vs. N100 My research focused on popular low-power x86 chips commonly used in mini PCs and soft routers:\nOlder Generation (Jasper Lake N5105, Celeron J4125): Widely available, often cheaper, proven platforms. Decent performance for routing and light server tasks. New Generation (Alder Lake-N N100): Significant performance uplift (especially single-core, crucial for routing), much better integrated graphics (useful for PVE console/light desktop tasks), generally more power-efficient, supports faster DDR5 RAM. Often comes paired with newer Intel i226-V 2.5GbE network controllers, known for better driver support and stability compared to the sometimes-problematic Realtek chips found in cheaper units. Given the relatively small price difference for new units and the significant performance and efficiency gains, the Intel N100 quickly became the clear winner for future-proofing.\nNavigating the Hardware Maze Choosing the N100 platform opened up a dizzying array of options, mostly from Chinese manufacturers like Beikong (Topton/Kingnovy), CWWK, etc., often sold as barebones systems (no RAM/SSD). Key considerations:\nPorts: How many? What speed? The standard was 4x 2.5GbE Intel i226-V ports, perfect for high-speed LAN and future ISP upgrades. Some offered 2x 2.5GbE + 2x 10GbE SFP+ (fiber) – tempting, but overkill and more expensive for my current needs. Cooling: Passive (fanless) is preferred for silent operation, requiring a well-designed chassis. RAM/SSD: DDR5 SO-DIMM and NVMe M.2 slots were standard. Price: Barebones N100 boxes with 4x 2.5GbE ports were readily available. Used Market: While tempting for cost savings, diagnosing potential issues on used hardware felt risky after my R8000 experience. My Final Choice: Beikong H30W (N100) After much comparison, I settled on a brand new Beikong H30W N100 barebones unit featuring:\nIntel N100 Processor 4x Intel i226-V 2.5GbE ports Passive cooling chassis DDR5 SO-DIMM slot NVMe M.2 slot To complete the build with maximum cost-effectiveness:\nRAM: I salvaged a compatible 16GB DDR5 stick from an old laptop – a huge saving! SSD: I purchased a new, budget-friendly but well-regarded Fanxiang S500 Pro 256GB NVMe SSD. Plenty of space for PVE and multiple VMs. The Grand Vision: Virtualization With the hardware assembled, the real excitement began. This N100 wasn\u0026rsquo;t just going to be a router. It was going to be the heart of my home lab. The plan: install Proxmox VE (PVE) as the bare-metal hypervisor, then create virtual machines for:\nOpenWrt/iStoreOS: My primary router, firewall, and network manager. OpenMediaVault (OMV): A NAS for file storage and backups. Future Possibilities: Home Assistant, AdGuard Home, a web server, maybe even AI experiments? This N100 box represented a convergence of all my needs and aspirations. The hardware was ready. Now came the software challenge: installing and taming the Proxmox hypervisor.\n","permalink":"https://frankblogs.com/posts/article-3/","summary":"\u003cp\u003eMy journey to achieve reliable home networking, particularly stable IPv6, had hit roadblocks with both my ISP\u0026rsquo;s modem and a powerful-but-faulty Netgear R8000 running OpenWrt. It became clear that consumer-grade hardware, even with custom firmware, might not offer the raw power, flexibility, and reliability I craved. It was time to explore the next level: \u003cstrong\u003ex86 Soft Routers\u003c/strong\u003e.\u003c/p\u003e\n\u003ch2 id=\"what-is-a-soft-router\"\u003eWhat is a Soft Router?\u003c/h2\u003e\n\u003cp\u003eUnlike traditional \u0026ldquo;hard\u0026rdquo; routers with proprietary hardware and software, a soft router is essentially a small, dedicated PC running a specialized router operating system (like OpenWrt, pfSense, OPNsense, or RouterOS). The \u0026ldquo;x86\u0026rdquo; refers to the standard PC processor architecture (Intel/AMD), offering significantly more power and flexibility than the ARM or MIPS chips found in most consumer routers.\u003c/p\u003e","title":"Entering the World of x86 Soft Routers: Choosing the N100 Beast"},{"content":"My quest for functional IPv6 hit a wall with the ISP\u0026rsquo;s faulty modem (ONT). Since I couldn\u0026rsquo;t get a proper Prefix Delegation (PD) from it, I needed a router capable enough to potentially handle the connection differently, perhaps even taking over PPPoE duties if necessary. My gaze fell upon a Netgear R8000 Nighthawk X6 collecting dust – a once-powerful router I had previously flashed with OpenWrt, only to seemingly \u0026ldquo;brick\u0026rdquo; it during a configuration mishap. Could this be its moment of redemption?\nOperation: Unbrick Reviving a bricked OpenWrt router usually involves entering Failsafe Mode, a minimal environment allowing command-line access. The process is timing-sensitive and requires precision:\nStatic IP Setup: I configured my computer with a static IP address in the 192.168.1.x range (e.g., 192.168.1.2), subnet mask 255.255.255.0.\nDirect Connection: Connected the computer directly to one of the R8000\u0026rsquo;s LAN ports.\nPower Cycle \u0026amp; Trigger: Powered on the R8000. As soon as a specific LED (usually the Power or WPS light) started blinking rapidly, I repeatedly pressed the WPS button. This triggers Failsafe Mode.\nSSH Connection: Opened a terminal and attempted to SSH into the router\u0026rsquo;s default failsafe IP:\nssh root@192.168.1.1 After a few tense tries, catching the blinking light at just the right moment, I got a connection! The familiar OpenWrt command prompt appeared. From there, the standard unbricking procedure followed:\n# Mount the root filesystem read-write mount_root # Reset configuration to defaults firstboot # Force reboot reboot -f The Mysterious 10.0.0.1 After the reboot, I expected to access the OpenWrt LuCI web interface at the standard 192.168.1.1. But\u0026hellip; nothing. The page wouldn\u0026rsquo;t load. Puzzled, I opened the terminal again and ran ping 192.168.1.1. No response.\nWhat was going on? I vaguely remembered the OpenWrt firmware I had flashed wasn\u0026rsquo;t an official build, but a custom one from a forum. Could it have a different default IP? I decided to check my computer\u0026rsquo;s network settings – had it received an IP via DHCP?\nIndeed, it had received 10.0.0.141. The gateway? 10.0.0.1.\nA quick ping 10.0.0.1 confirmed it – the router was alive and well, just hiding at a non-standard address! This custom firmware developer had decided to use 10.0.0.1 as the default management IP. Lesson learned: always check DHCP when standard IPs fail.\nConfiguring the R8000 (Hope Rises) Logging into the LuCI interface at 10.0.0.1, I proceeded to configure the R8000 as my main router:\nPPPoE: Set up the WAN interface for PPPoE dialing using my ISP credentials. Success – it connected and got an IPv4 address (still CGNAT, of course). IPv6: Configured the WAN6 interface for \u0026ldquo;Native IPv6\u0026rdquo;. As expected, due to the ONT issue, it didn\u0026rsquo;t get a delegated prefix, but it did acquire a WAN IPv6 address. Interfaces: Mapped the physical ports correctly (identifying eth0.2 as WAN and eth0.1/br-lan as LAN). Wi-Fi: Configured the SSIDs and security. Interestingly, I initially set WPA3, but some older devices failed to connect. Downgrading to WPA2-PSK resolved this compatibility issue. Set appropriate channel widths (20MHz for 2.4GHz to reduce interference, 80MHz for 5GHz for speed). For a moment, things looked promising. The router was routing traffic, Wi-Fi was working (albeit with some WPA3 issues). Could this be the solution?\nThe Final Failure (Hope Dashed) My optimism was short-lived. As I started exploring further configuration – trying to set up firewall rules or install necessary packages (opkg update often failed or was incredibly slow) – the R8000 started acting erratically. Sometimes the interface would become unresponsive. Other times, the lights would start flashing indefinitely, requiring a hard reboot.\nAfter hours of troubleshooting, observing the inconsistent behavior, I had to face the harsh reality: this router wasn\u0026rsquo;t just bricked by configuration before; it likely had an underlying, unrecoverable hardware fault. The flash memory was probably damaged, leading to corrupted data and instability under load.\nThe rescue attempt had failed. The Netgear R8000 was truly destined for the electronics recycling bin.\nIt seemed consumer-grade hardware, even powerful ones running flexible firmware like OpenWrt, had reached its limit for my needs. If I wanted reliability, performance, and the ability to truly overcome my network\u0026rsquo;s challenges, I needed to look towards a different class of hardware altogether: the world of x86 soft routers.\n","permalink":"https://frankblogs.com/posts/article-2/","summary":"\u003cp\u003eMy quest for functional IPv6 hit a wall with the ISP\u0026rsquo;s faulty modem (ONT). Since I couldn\u0026rsquo;t get a proper \u003cstrong\u003ePrefix Delegation (PD)\u003c/strong\u003e from it, I needed a router capable enough to potentially handle the connection differently, perhaps even taking over PPPoE duties if necessary. My gaze fell upon a Netgear R8000 Nighthawk X6 collecting dust – a once-powerful router I had previously flashed with OpenWrt, only to seemingly \u0026ldquo;brick\u0026rdquo; it during a configuration mishap. Could this be its moment of redemption?\u003c/p\u003e","title":"The Failed Rescue of a Bricked Router: An OpenWrt Adventure"},{"content":"It all started with a seemingly simple problem. My home network, centered around a Xiaomi AX6000 router running its stock firmware, was supposed to support IPv6. I had enabled it, and indeed, the router\u0026rsquo;s WAN port proudly displayed a public IPv6 address. Yet, none of my devices – my computers, my phone, my game consoles – could get one. Accessing IPv6-only websites was impossible.\nDiving Deep into IPv6 and Network Structure After some initial head-scratching, research pointed towards a concept called Prefix Delegation (PD). In simple terms, my ISP assigns a block (prefix) of IPv6 addresses to my router, and the router is then responsible for distributing individual addresses from that block to my devices. The symptom – WAN gets an address, LAN doesn\u0026rsquo;t – strongly suggested PD was failing.\nWhy? The prime suspect became my ISP\u0026rsquo;s modem (Optical Network Terminal or ONT). Was it running in router mode instead of bridge mode? If the modem itself was acting as a router and didn\u0026rsquo;t properly support delegating the prefix downstream, my AX6000 would never receive the necessary block.\nI confirmed my network topology: ONT (in router mode) -\u0026gt; Switch -\u0026gt; Multiple Routers (including the AX6000). Unfortunately, due to other devices directly connected to the ONT via the switch, simply switching the ONT to bridge mode wasn\u0026rsquo;t feasible without restructuring everything.\nAs a temporary workaround, I enabled NAT6 on the AX6000. Success! My devices could now access IPv6 websites. However, I knew this wasn\u0026rsquo;t true end-to-end IPv6. NAT6 essentially translates internal IPv6 addresses, similar to how traditional NAT works for IPv4. It solved the immediate browsing issue but didn\u0026rsquo;t address the underlying PD failure and wouldn\u0026rsquo;t help with services requiring direct public IPv6 reachability.\nEnter the Double NAT Monster Around the same time, my Xbox started complaining: \u0026ldquo;Double NAT detected.\u0026rdquo; This made perfect sense. My network traffic was going through two layers of Network Address Translation: first at the AX6000, and then again at the ISP\u0026rsquo;s ONT (which was also acting as a router). This configuration is notorious for causing issues with online gaming, peer-to-peer connections, and services requiring open ports.\nWe discussed potential mitigations like UPnP, manual port forwarding, and placing the AX6000 in the ONT\u0026rsquo;s DMZ, but none felt like a clean solution.\nThe CGNAT Revelation The real blow came when investigating the Double NAT further. By comparing the WAN IP address shown on my ONT\u0026rsquo;s status page with the public IP address reported by websites like \u0026ldquo;whatismyip.com\u0026rdquo;, I confirmed a dreaded reality: I was behind Carrier-Grade NAT (CGNAT). My ISP wasn\u0026rsquo;t assigning my ONT a unique, public IPv4 address. Instead, I was sharing one with potentially hundreds of other customers.\n[Placeholder: Add a screenshot here showing the discrepancy between your ONT\u0026rsquo;s WAN IP (likely a private 10.x or 100.x address) and your public IP, confirming CGNAT.]\nThis revelation made two things crystal clear:\nSolving the Xbox NAT issue reliably via IPv4 was going to be nearly impossible without resorting to complex (and often unreliable) tunneling solutions. Getting native, public IPv6 working correctly was no longer just desirable; it was essential for any kind of future-proof home networking or self-hosting. Back to the IPv6 Root: The Faulty ONT With renewed focus, the investigation circled back to the ONT. I confirmed the ONT itself was receiving a /64 or similar public IPv6 prefix starting with 240e:. The problem was solely its inability to delegate this prefix downstream.\nAnalyzing the ONT\u0026rsquo;s configuration page revealed a peculiar \u0026ldquo;Port Binding\u0026rdquo; setting. Initially, only LAN1 was bound for internet services.\nI modified the settings to bind all LAN ports. Hope surged\u0026hellip; but was quickly dashed. Even after ensuring all ports were bound, testing with the AX6000 and other routers on different ports yielded the same result: no PD, no LAN-side public IPv6.\nThe conclusion was unavoidable: the ONT\u0026rsquo;s Prefix Delegation functionality was faulty, likely due to a hardware limitation or firmware bug. Relying on it was a dead end.\nI needed a router powerful and flexible enough to potentially work around the ONT\u0026rsquo;s limitations, or perhaps even replace its routing functions altogether. This led me to revisit an old piece of hardware sitting in my closet\u0026hellip; a supposedly bricked Netgear R8000 running OpenWrt. Could it be resurrected?\n","permalink":"https://frankblogs.com/posts/article-1/","summary":"\u003cp\u003eIt all started with a seemingly simple problem. My home network, centered around a Xiaomi AX6000 router running its stock firmware, was supposed to support IPv6. I had enabled it, and indeed, the router\u0026rsquo;s WAN port proudly displayed a public IPv6 address. Yet, none of my devices – my computers, my phone, my game consoles – could get one. Accessing IPv6-only websites was impossible.\u003c/p\u003e\n\u003ch2 id=\"diving-deep-into-ipv6-and-network-structure\"\u003eDiving Deep into IPv6 and Network Structure\u003c/h2\u003e\n\u003cp\u003eAfter some initial head-scratching, research pointed towards a concept called \u003cstrong\u003ePrefix Delegation (PD)\u003c/strong\u003e. In simple terms, my ISP assigns a block (prefix) of IPv6 addresses to my router, and the router is then responsible for distributing individual addresses from that block to my devices. The symptom – WAN gets an address, LAN doesn\u0026rsquo;t – strongly suggested PD was failing.\u003c/p\u003e","title":"The IPv6 Nightmare and the Double NAT Trap: My Quest for True Connectivity"},{"content":"In my last post, I declared that I had killed my \u0026ldquo;About\u0026rdquo; page. It was a pragmatic decision. The page was a ghost: it rendered as raw HTML, all its links pointed to localhost:1313, and it pulled in a phantom Chinese site title that didn\u0026rsquo;t exist in my configuration.\nThen, immediately after publishing that post, I discovered my \u0026ldquo;Tags\u0026rdquo; page was broken in the exact same way.\nWhen I clicked the \u0026ldquo;Tags\u0026rdquo; link in my (perfectly functional) menu, it loaded a page that also had no CSS and contained only a giant \u0026ldquo;#Tags\u0026rdquo; title, with no actual tags listed—even though my post was correctly tagged and published.\nThis was the final, definitive clue.\nThe Unified Bug Theory This confirmed the problem wasn\u0026rsquo;t a series of isolated glitches. It was one, single, catastrophic bug. My chosen theme, hugo-paper, had a fatal flaw.\nIts \u0026ldquo;list\u0026rdquo; templates (like the homepage) worked perfectly. But its \u0026ldquo;single\u0026rdquo; template (for the About page) and its \u0026ldquo;terms\u0026rdquo; template (for the Tags page) were both fundamentally broken. They contained code that, for some reason, failed only in the Cloudflare Pages production environment, defaulting all asset paths to localhost.\nMy local build (hugo server -D) worked perfectly, which had masked this problem for days and led me down a debugging rabbit hole of config files, environment variables, and build flags. None of those fixes worked, because the theme\u0026rsquo;s engine itself was defective.\nThe Theme Transplant The solution was obvious. You don\u0026rsquo;t fix a broken engine; you replace it.\nI decided to perform a \u0026ldquo;theme transplant.\u0026rdquo; I ripped out hugo-paper completely. This involved:\nRunning git submodule deinit and git rm to purge the old theme from my project. Choosing a new, robust, and extremely popular theme: hugo-PaperMod. Adding it as a new submodule: git submodule add ... themes/PaperMod. Of course, this came with its own final hurdles. My brand new Hugo version (v0.150.0) immediately threw an error because the new theme\u0026rsquo;s config used a deprecated paginate key. After fixing that (it\u0026rsquo;s now [pagination].pagerSize), the Tags page still showed up empty.\nOne final search revealed the truth: in our quest to create a \u0026ldquo;clean\u0026rdquo; config, we had removed the block that activates taxonomies. The final piece of the puzzle was adding this back to hugo.toml:\n[taxonomies] tag = \u0026#34;tags\u0026#34; category = \u0026#34;categories\u0026#34; ","permalink":"https://frankblogs.com/posts/the-about-page-is-back/","summary":"\u003cp\u003eIn my last post, I declared that I had killed my \u0026ldquo;About\u0026rdquo; page. It was a pragmatic decision. The page was a ghost: it rendered as raw HTML, all its links pointed to \u003ccode\u003elocalhost:1313\u003c/code\u003e, and it pulled in a phantom Chinese site title that didn\u0026rsquo;t exist in my configuration.\u003c/p\u003e\n\u003cp\u003eThen, immediately after publishing that post, I discovered my \u0026ldquo;Tags\u0026rdquo; page was broken in the exact same way.\u003c/p\u003e\n\u003cp\u003eWhen I clicked the \u0026ldquo;Tags\u0026rdquo; link in my (perfectly functional) menu, it loaded a page that \u003cem\u003ealso\u003c/em\u003e had no CSS and contained only a giant \u0026ldquo;#Tags\u0026rdquo; title, with no actual tags listed—even though my post was correctly tagged and published.\u003c/p\u003e","title":"The About Page Is Back"},{"content":"Welcome to my corner of the internet!\nMy name is Frank, and I\u0026rsquo;m currently navigating high school while exploring my deep fascination with technology, problem-solving, and the intricate process of building things – both digital and sometimes physical.\nThis blog serves as my personal notebook and digital playground. It\u0026rsquo;s where I plan to document my journey as I dive into coding projects, experiment with web development (like the creation and ongoing refinement of this site itself!), tackle interesting technical challenges I encounter, and share insights or concepts I\u0026rsquo;m learning along the way. You might also find occasional posts about gaming or other topics that capture my interest.\nBuilding this website using Hugo, deploying it globally via Cloudflare Pages, and integrating features like search, comments with Giscus, and even LaTeX rendering has been an incredible learning adventure in itself. It pushed me to understand web infrastructure, Git workflows (CI/CD), and the realities of debugging in a way no textbook could.\nHere, you\u0026rsquo;ll find posts reflecting that journey: projects I\u0026rsquo;m working on, interesting tech I\u0026rsquo;m exploring, perhaps breakdowns of problems I\u0026rsquo;ve solved (or spectacularly failed to solve!), and things I simply find cool.\nMy hope is that you\u0026rsquo;ll find something interesting, useful, or perhaps even relatable in these digital pages.\nFeel free to explore using the navigation or the search function. Thanks for stopping by!\n","permalink":"https://frankblogs.com/about/","summary":"\u003cp\u003eWelcome to my corner of the internet!\u003c/p\u003e\n\u003cp\u003eMy name is Frank, and I\u0026rsquo;m currently navigating high school while exploring my deep fascination with technology, problem-solving, and the intricate process of building things – both digital and sometimes physical.\u003c/p\u003e\n\u003cp\u003eThis blog serves as my personal notebook and digital playground. It\u0026rsquo;s where I plan to document my journey as I dive into coding projects, experiment with web development (like the creation and ongoing refinement of this site itself!), tackle interesting technical challenges I encounter, and share insights or concepts I\u0026rsquo;m learning along the way. You might also find occasional posts about gaming or other topics that capture my interest.\u003c/p\u003e","title":"About Me"},{"content":"This is my first post. It was supposed to be my second.\nMy very first post should have been the \u0026ldquo;About\u0026rdquo; page. It’s the standard, simple, “Hello, this is who I am” page that every blog is supposed to have.\nBut I deleted it. I killed it before this blog ever saw the light of day.\nThis isn’t a great start, but it was a necessary one.\nThis Was Supposed to Be Easy My goal was simple: set up a personal blog using the \u0026ldquo;golden stack\u0026rdquo; of modern static sites: Hugo (as the engine), the hugo-paper theme (for the clean look), Git, and Cloudflare Pages (for fast, free, global hosting).\nAnd to be honest, it worked. Mostly.\nExcept for one, single, impossibly stubborn page.\nThe Ghost in the Machine After I pushed my code, a bizarre problem appeared. My homepage loaded perfectly. The /posts/ list page loaded perfectly. They had the correct styles (CSS) and the correct English site title (\u0026ldquo;Frank\u0026rsquo;s Blog\u0026rdquo;).\nBut the /about/ page was a ghost.\nIt rendered as raw, unstyled HTML. Worse, every single link on that one page (the CSS file, the link back to the homepage, the footer links) was broken, pointing to an impossible address: http://localhost:1313/.\nTo make it even weirder, the footer—which was supposed to pull my site\u0026rsquo;s English title—instead showed an old, non-existent Chinese title: \u0026ldquo;Frank 的博客\u0026rdquo;.\nMy site was having a split-personality crisis. The homepage knew it was an English site deployed to production, while the About page thought it was a Chinese site running on a local test server.\nThe Debugging Rabbit Hole So began an epic debugging saga. An AI assistant and I went down every conceivable rabbit hole to fix this. We tried:\nFixing the Config File? We triple-checked hugo.toml. We fixed deprecated keys (paginate), corrected TOML syntax, and scoured the file for invisible whitespace characters. The problem remained. A \u0026ldquo;Ghost\u0026rdquo; Config? We hypothesized a \u0026ldquo;ghost\u0026rdquo; multilingual file (languages.toml) left over from the theme\u0026rsquo;s example site was forcing the build into a broken state. We hunted it down and deleted the entire config directory. The problem remained. Build Environment Mismatch? The most maddening clue was that hugo server -D on my local Mac worked perfectly. The About page was beautiful. This suggested a conflict with Cloudflare\u0026rsquo;s build environment. So, we set the HUGO_VERSION environment variable in Cloudflare to match my exact local version. The problem remained. A Build Command Override? We thought maybe Cloudflare was ignoring the baseURL in the config. So we manually forced it in the build command: hugo --baseURL https://frankblogs.com/. The problem remained. A File Structure Problem? Our final theory. Maybe the theme\u0026rsquo;s \u0026ldquo;single page\u0026rdquo; template was buggy, but its \u0026ldquo;list page\u0026rdquo; template (used by the working homepage) was fine. We changed the file structure from content/about.md to content/about/_index.md to \u0026ldquo;trick\u0026rdquo; Hugo into using the working template. And still, it failed. The Verdict After trying every logical configuration fix, the conclusion was inescapable.\nThe problem wasn\u0026rsquo;t my config. It wasn\u0026rsquo;t Cloudflare. It was a deep, unfixable bug in the hugo-paper theme itself—a flaw in its single-page template that, for some reason, fails catastrophically only when built on the Cloudflare Pages environment, defaulting all its paths to localhost. It was invisible locally, but fatal in production.\nSo I had a choice: spend the next week learning the Go Template language to debug a third-party theme I didn\u0026rsquo;t write\u0026hellip; or just delete the broken feature.\nI chose deletion.\nThis blog is going to be a record of building things that work. The first lesson is knowing when to cut your losses and pick the pragmatic path.\nSo, hello, world. There is no \u0026ldquo;About\u0026rdquo; page here. There is only this \u0026ldquo;Posts\u0026rdquo; page.\nBecause I know for a fact: this part works.\n","permalink":"https://frankblogs.com/posts/why-i-killed-my-about-page/","summary":"\u003cp\u003eThis is my first post. It was supposed to be my second.\u003c/p\u003e\n\u003cp\u003eMy very first post should have been the \u0026ldquo;About\u0026rdquo; page. It’s the standard, simple, “Hello, this is who I am” page that every blog is supposed to have.\u003c/p\u003e\n\u003cp\u003eBut I deleted it. I killed it before this blog ever saw the light of day.\u003c/p\u003e\n\u003cp\u003eThis isn’t a great start, but it was a necessary one.\u003c/p\u003e\n\u003ch2 id=\"this-was-supposed-to-be-easy\"\u003eThis Was Supposed to Be Easy\u003c/h2\u003e\n\u003cp\u003eMy goal was simple: set up a personal blog using the \u0026ldquo;golden stack\u0026rdquo; of modern static sites: Hugo (as the engine), the \u003ccode\u003ehugo-paper\u003c/code\u003e theme (for the clean look), Git, and Cloudflare Pages (for fast, free, global hosting).\u003c/p\u003e","title":"Why I Killed My About Page"}]