Khurram

Building a Minimal HAProxy Load Balancer on Rocky Linux 10

A practical guide to building a small, hardened HAProxy load balancer on Rocky Linux 10 using Layer 4 TCP load balancing, TLS passthrough, multiple frontends and a separate DMZ interface. HAProxy is one of those tools that can look intimidating when you first open the configuration file. In reality, a basic Layer 4 load balancer can be surprisingly small and lightweight. I recently built a small, production-style HAProxy load balancer on a minimal Rocky Linux 10 installation. The server fronts several applications, including an application hosted in a DMZ, and passes HTTPS traffic through to the backend servers without terminating TLS. That last point is important. The HAProxy server doesn’t hold any certificates and doesn’t decrypt the application traffic. It simply accepts the TCP connection, selects a healthy backend and forwards the encrypted connection to it. TLS remains between the client and the application server. This guide documents the build from start to finish, including the networking, HAProxy configuration, SELinux, firewalld, logging, Active Directory integration, hardening, testing and — perhaps most importantly — the problems I encountered along the way. Everything in this article uses deliberately generic hostnames, IP addresses and account names. Replace them with values appropriate for your own environment. What we’re building The finished system is a single Rocky Linux 10 VM running HAProxy. Each application has its own frontend IP address and its own backend pool. HAProxy operates in TCP mode, so the encrypted HTTPS traffic is passed through without HAProxy terminating TLS. The certificates remain installed on the application servers. The basic design looks like this: The three application pools are: Pool Frontend Backends Production app 10.0.3.10:443 app-prod-01 (10.0.0.108), app-prod-02 (10.0.0.109) Test / API 10.0.3.11:443 app-test-01 (10.0.0.115), app-test-02 (10.0.0.124) DMZ web 10.0.16.251:443 web-dmz-01 (10.0.16.30), web-dmz-02 (10.0.16.31) Placeholder values Throughout this article I’ll use the following example values: Item Value Load balancer hostname lb01.corp.example.com Local fallback account localadmin AD domain corp.example.com Internal network 10.0.0.0/22 Internal gateway 10.0.3.254 DNS/NTP 10.0.0.11, 10.0.0.10 DMZ network 10.0.16.0/24 Admin workstation subnet 10.0.52.0/24 These are examples only. Don’t copy them into a production environment without adapting them to your own network. Why use TLS passthrough? There are two common ways of putting HAProxy in front of an HTTPS application. The first is TLS termination. HAProxy receives the HTTPS connection, decrypts it and then establishes another connection to the backend. The second is TLS passthrough. With passthrough, HAProxy doesn’t need to know anything about the application’s certificates. It operates at Layer 4 and simply proxies the TCP connection. For this particular deployment, TLS passthrough made sense for several reasons. No certificates on the load balancer The certificates remain on the application servers. That means there is another machine that doesn’t need certificate renewal, private-key protection or certificate deployment. Very low resource requirements HAProxy isn’t performing TLS encryption and decryption for client connections. It’s primarily moving TCP traffic between sockets. This makes it possible to run the load balancer on a very small VM. The downside: source IP visibility There is an important trade-off. From the backend server’s perspective, the TCP connection originates from the HAProxy server rather than directly from the client. HAProxy knows the original client address and can record it in its own logs, but the backend application won’t automatically see the client’s real source IP. If the backend needs that information, there are alternatives such as the PROXY protocol, provided the backend supports it, or terminating TLS on HAProxy instead. VM sizing The VM I used was deliberately small: For this type of workload, there’s little benefit in throwing a large amount of hardware at HAProxy. In my testing, HAProxy itself used only around 13 MB of RAM. Obviously, real resource requirements depend on connection counts, traffic levels, logging and configuration. Once you’re dealing with very large numbers of concurrent connections, memory and file descriptors become increasingly important, while network throughput can become the limiting factor. For this type of deployment, however, adding a second load balancer for availability is much more useful than simply making the first VM larger. One important virtualisation consideration Rocky Linux 10 and other RHEL 10-based distributions have newer x86-64 CPU requirements than some older Linux releases. If you’re installing the VM on an older hypervisor or with a deliberately old virtual CPU model, the installer may complain about unsupported CPU features. If that happens, expose a newer CPU model to the VM. On VMware, using the host CPU where appropriate is one option. Step 1 — Install Rocky Linux 10 Install Rocky Linux 10 using the Minimal Install profile. The goal here is to keep the server small. There is no reason to install a graphical desktop or a collection of applications that the load balancer doesn’t need. Once the operating system is installed, install the packages required for HAProxy and administration: If you’re not running VMware, open-vm-tools isn’t required. Enable the services: Check the HAProxy version: The build documented here used HAProxy 3.0.5 from AppStream. Your version may differ depending on the Rocky Linux repositories available at the time you build the server. Step 2 — Configure the network This server has two network interfaces: The internal interface carries the normal server traffic and hosts the production and test frontend addresses. The DMZ interface carries the DMZ frontend. The important thing here is that the DMZ interface must not become a second default route. Internal interface In this example, ens33 has two IP addresses: Configure the NetworkManager connection: Check that both addresses are actually present: Check the address before using it Before assigning an additional address to a server, make sure another device isn’t already using it. For example: No response doesn’t guarantee an address is unused, but it is a useful first check. Also check your DHCP scopes and IP address management system. An address that doesn’t answer an ARP request today can still be part of a DHCP scope and become a problem later. Configure the DMZ interface The second interface in this example is ens35. The DMZ network is:

Building a Minimal HAProxy Load Balancer on Rocky Linux 10 Read More »

Home Lab Monitoring with a Raspberry Pi and Check_MK

Affiliate Disclosure: This post contains Amazon affiliate links. If you purchase a product through one of these links, I may earn a small commission at no additional cost to you. I only recommend products that I have personally used or believe are worth considering. My server rack doesn’t have a cooling solution like a data centre does. That isn’t practical in a home lab, but I still need to be conscious of temperature. Summers in England can bring scorching heat and winters can get very cold, and I want to be alerted when something is wrong before my servers crash. I also want to keep an eye on the power the rack uses, especially with electricity costing what it does now. I’ve used Check_MK to monitor my lab for years, and it’s already set up to email me alerts. So rather than build a separate system, this project feeds temperature, humidity and power readings from a Raspberry Pi into Check_MK, where I can set warning and critical thresholds like any other service. By the end you’ll have: Parts list The links in this list are Amazon affiliate links (see the disclosure at the top of the article). How it works Only two small services touch hardware: one reads the DHT22, one receives the radio messages from the power adapter. Each writes its latest reading to a tiny text file. Everything else, the display and the Check_MK agent plugin, just reads those files. That keeps the pieces independent, and it means two programs never fight over the same sensor. A few design decisions worth explaining: Building the unit The order of the build matters, because of how the Pi-mote fits. Fit the Pi-mote first The Pi-mote is a HAT: a board that plugs onto the Pi’s 40-pin GPIO header and sits over the top of it. I fitted it first. It covers pins 1 to 26 of the header, and every pin in the wiring table below (3.3V, GND, GPIO 4, SDA and SCL) is among them, so there was nowhere to push jumper leads onto those pins for the DHT22 or the display. Connecting the DHT22 and the display Because those pins are covered, I soldered the wires for the DHT22 and for the display straight onto the Pi-mote, at the pins that carry the connections we need. The pin numbers below are the usual ones for a Raspberry Pi 40-pin header. Check them against a pinout for your own board. The Pi-mote’s radio uses SPI and a few other pins of its own; in my build none of those clash with the connections below. Device Pin Header pin DHT22 VCC / + 3.3V (pin 1) DHT22 OUT / data GPIO 4 (pin 7) DHT22 GND / – GND (pin 9) OLED VDD 3.3V (pin 17) OLED GND GND (pin 14) OLED SDA GPIO 2 / SDA (pin 3) OLED SCK GPIO 3 / SCL (pin 5) Power the DHT22 from 3.3V, not 5V. With a 5V supply its data line can rise above what the Pi’s GPIO pins tolerate. A few practical tips for soldering onto a board that’s already fitted: Making room for the antenna The Pi-mote has a copper coil, its antenna, which sticks up from the board. A normal case lid would press down on it. So I cut a long rectangular opening in the lid with a Dremel rotary tool. The coil sits in the opening, and the lid closes properly without touching it. Take your time with this part. Mark the opening from the coil’s actual position and size, make the cut a little larger than the coil, and smooth the edges so they can’t catch the wires or the antenna. If you use a rotary tool as I did, clamp the lid down, wear eye protection, and use a cutting disc at a low speed, because plastic melts if the tool runs too fast. Finish the edges with a file or a sanding bit. Then close the lid and check nothing is pressing on the coil. The coil is how the unit hears the power adapter, so you don’t want it squashed or bent. Sensor placement: a lesson learned I first attached the DHT22 straight onto the Raspberry Pi case. It read about 25 °C while the room was 20 °C. The Pi itself was running at about 42 °C, and its heat was adding roughly 5 °C to the sensor’s reading, so my sensor was partly measuring the Pi, not the room. Humidity was affected as well. Relative humidity depends on temperature, so air that is warmed reads drier than it really is, which means a temperature error carries over into the humidity reading. Five degrees might not sound like much, but alert thresholds depend on it. With a warning level at 30 °C, an error of 5 °C uses up a large part of your margin. It isn’t a fixed offset you can simply subtract either, because the Pi’s temperature changes with how hard it’s working. So put the sensor on a cable and place it where it measures what matters, which is the air your servers breathe. That usually means the intake side of the rack at about mid-height. Avoid the exhaust, the top of a piece of equipment, and anywhere near a power supply or the Pi. The DHT22 uses a single data wire, so a few practical notes on the cable: If a longer cable causes read errors, the reader service logs a warning (shown later in the troubleshooting section) and Check_MK raises a stale-data alert. I moved the sensor off the case and onto its own extension cable, away from the unit. The only change to the build was the cable: no software changes were needed. Preparing the Pi With the hardware built, it’s time for the software. Install Raspberry Pi OS, then enable the interfaces we need. The OLED uses I2C and the Pi-mote’s radio uses SPI: Install the supporting packages and the libraries the scripts

Home Lab Monitoring with a Raspberry Pi and Check_MK Read More »

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 8: Final Build & Results

Part 8: Bringing It All Together Seven parts ago, this started as a question: could an old Dell OptiPlex 5060 SFF — a business desktop, not a gaming machine — actually be turned into something capable of PS3 and Xbox 360 emulation? This part is the answer. The final hardware list, what it actually cost, how everything performed once it all came together, and an honest look at whether this project was worth doing. The Final Hardware Component Specification System Dell OptiPlex 5060 SFF CPU Intel Core i7-8700 (6 cores / 12 threads) RAM 16GB Primary Storage 512GB Samsung NVMe (Batocera OS + dedicated cache partition) Secondary Storage 4TB Seagate IronWolf (ROM library) PSU 240W (upgraded from Dell’s stock 200W) GPU MSI RTX 3050 LP 6G OC — running in the board’s x4 slot Cooling Stock cooling + additional front exhaust fan Controllers GameSir Cyclone 2, EasySMX D05 OS Batocera Linux (x86_64), official Nvidia driver active That GPU line is worth restating plainly one more time: it’s physically limited to a PCIe 3.0 x4 connection rather than the x16 the card is capable of, because the PSU sits directly in the path of the only x16 slot on this board, leaving just 1–2cm of clearance. That single physical constraint shaped a good chunk of the troubleshooting across this whole series. What It Actually Cost Reusing hardware was the whole point of this project, and the final numbers reflect that. Item Cost Dell OptiPlex 5060 SFF Already owned Intel Core i7-8700 Already owned RAM, NVMe, IronWolf HDD Already owned PSU upgrade (240W) Already owned Additional front fan Already owned MSI RTX 3050 LP 6G OC £179.99 GameSir Cyclone 2 / EasySMX D05 Already owned The only cost I can put a confident figure against is the GPU. Everything else going into this build was hardware I already had sitting around, which was the entire premise back in Part 1 — this genuinely was not a “spend hundreds of pounds on a gaming PC” project, it was “how far can existing hardware go with one deliberate purchase.” What It Would Cost to Build From Scratch Since most of this build relied on hardware I already owned, it’s worth answering the question a new reader will actually have: what would this cost if you were starting with nothing? I’ve priced out equivalent parts, including a better-specced OptiPlex than my starting point (i7 already fitted, 32GB RAM rather than 16GB, a 512GB NVMe, and a proper PSU as standard — no separate PSU upgrade needed) and a cheaper SATA drive in place of the NAS-grade IronWolf I happened to have spare, since that’s genuinely unnecessary for this use case as I flagged back in Part 1. Prices below were accurate at the time of checking and will vary — Amazon pricing shifts often, so treat these as a snapshot rather than something to hold me to months from now. 💰 What Would This Cost From Scratch? Every part needed to replicate this build — priced individually so you can swap anything out. Dell OptiPlex SFF i7 · 32GB RAM · 512GB NVMe · upgraded PSU £340.00 View → Seagate Barracuda 4TB Main ROM library storage £154.00 View → Noctua 80mm Fan Additional front exhaust cooling £11.95 View → GameSir Cyclone 2 Controller #1 £40.00 View → EasySMX D05 Controller #2 £44.99 View → Rii Mini Wireless Keyboard SSH, BIOS access & the Xenia HUD hotkey £17.83 View → MSI RTX 3050 LP 6G OC Low-profile GPU (x4 slot) £179.99 View → RECOMMENDED Total with the MSI RTX 3050 £788.76 Alternative: swap the MSI for the Maxsun RTX 3050 SLP 6G Single-slot — fits the full x16 slot instead of x4 £397.44 View → Total with the Maxsun instead £1,006.21 Affiliate Disclosure: This post contains Amazon affiliate links. If you purchase a product through one of these links, I may earn a small commission at no additional cost to you. Prices shown were accurate at the time of checking and will vary — I only recommend products that I have personally used or believe are worth considering. A few notes on that table: Either way, that’s the real number if you’re starting from nothing. It’s worth being honest about how it stacks up against the obvious alternative, though: an actual second-hand PS3 or Xbox 360, plus a decent stack of physical games, can be picked up for a fraction of £788 — this build was never really about being the cheapest route into these libraries. It’s about the other things emulation gets you that original hardware doesn’t: everything running off one machine, faster load times, save states, upscaling, and not being at the mercy of ageing console hardware or increasingly worn discs. PS3 Results — The Real Highlight Game Compatibility Status Result on this hardware Burnout Paradise Playable 30–60fps, no issues Red Dead Redemption — 12–15fps in busy areas; the roughest result of the build Assassin’s Creed II Ingame Stable 30fps, no crashes or artifacts seen God of War III Not Playable 20–40fps, played very well overall Army of TWO: The Devil’s Cartel Ingame Unplayable — stopped testing Army of TWO: The 40th Day Playable Startup judder only; smooth in gameplay Prince of Persia: The Forgotten Sands Playable Stable 30fps Tekken 6 / Super Street Fighter IV / TMNT: Turtles in Time Re-Shelled Playable Stable 60fps across all three Gran Turismo 6 — Unplayable — persistent screen flicker Six playable-or-better results out of nine titles tested, including two (AC2, God of War III) that outperformed their own official compatibility status on this specific hardware. That’s a genuinely strong outcome for a business desktop with one bolted-on low-profile GPU. Xbox 360 Results — Still Finding Its Feet Xbox 360 emulation via Xenia Canary is at an earlier stage of this project, and it shows. Game Result Halo 3 Playable, but froze a handful of times during longer sessions Gears of War 3 Working, but performance wasn’t great even with patches applied Compared with PS3, Xbox

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 8: Final Build & Results Read More »

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 7: PS3 & Xbox 360 Testing

Part 7: Putting RPCS3 Through Its Paces Everything up to this point — the hardware, the OS, the storage, the controllers — has been building toward this. This is the part where I find out whether the OptiPlex is actually capable of what I set out to build it for. A quick scope note before diving in: most of this part covers the PS3 titles I’ve spent the most time with via RPCS3, since that’s where the bulk of the troubleshooting and configuration work has happened. There’s also a first look at Xbox 360 emulation via Xenia toward the end, covering the two titles I’ve gotten working properly so far. (A note on the games themselves: every title named below is one I own — see Part 1 for the full note on that.) The Baseline Settings Before getting into individual games, it’s worth covering the general RPCS3 configuration I’m starting every title from, since most of what follows is either “this works fine on the baseline” or “here’s what needed to change from it.” Every game below starts from this baseline, and I’ll only call out what changes from it. Burnout Paradise Compatibility status: Playable — no reported issues, no deviation from default settings recommended. This one is about as clean as PS3 emulation gets. It’s a 2008 launch-era title, nowhere near as CPU-demanding as some of what’s below, and it just runs. In my own testing, frame rate held between 30–60fps depending on the scene — consistent with what a fairly light, launch-era PS3 title should give on this hardware. The one thing that wasn’t perfectly clean was audio — there’s some slight crackling that’s audible in the gameplay video above, which may need further tweaking to fully resolve. The community-reported fix for audio issues in this title is adjusting the Audio Buffering duration in RPCS3 rather than touching CPU/GPU settings, so that’s the next thing I’ll try if it doesn’t clear up on its own. The one thing worth documenting here isn’t really about the game itself — it’s the diagnostic process I went through after a crash, since it’s a useful general troubleshooting checklist for RPCS3 on this specific machine: Red Dead Redemption Known as one of the more demanding titles on RPCS3, and the one that showed the most judder on this hardware. The i7-8700’s 6 cores sit right at the community-recommended minimum for RDR, so some judder in dense open-world areas is expected even with ideal settings — this is a case of getting close to this hardware’s ceiling, not fixing a misconfiguration. What actually helped: In practice, this was the roughest result of the whole testing run. Performance is fine in quieter areas, but it judders noticeably in the more built-up parts of the map, with frame rate dropping to around 12–15fps at most in those sections. That’s well below the 30–45fps I’d expected going in based on community benchmarks for similarly specced systems — a clear sign this particular hardware combination, and specifically the CPU, is genuinely struggling with RDR’s busier scenes rather than just showing minor judder. Assassin’s Creed II Compatibility status: Ingame (not Playable) — a genuine regression, down from Playable in 2022. This is the title that best illustrates the difference between a config problem and an actual unresolved emulator bug. There are documented, persistent graphical artifacts (broken water textures, horizontal lines) that show up even with every recommended fix applied, alongside intermittent crashes. What I tried: Despite that Ingame status and the documented issues elsewhere, my own experience with it was genuinely good: a stable 30fps throughout, with no crashes and no visible artifacts during my playtime. Worth taking that as a “your mileage may vary” data point rather than a contradiction of the wider compatibility notes above — specific hardware, drivers and build versions can all shift where an individual game lands within its documented issues, and in this case I came out ahead of what the compatibility list would suggest. God of War III Compatibility status: Not considered Playable. Another title where managing expectations matters as much as the settings themselves. What’s needed: I also checked the Patch Manager for optional community fixes to some remaining visual artifacts, though those are treated as experimental extras rather than part of the core recommended setup. In practice, this landed around 20–30fps during gameplay — not a locked frame rate by any means, but it played very well overall despite the “Not Playable” label. Genuinely one of the more pleasant surprises of this testing run given where it sits on the official compatibility list. Army of TWO: The Devil’s Cartel Compatibility status: Ingame — the roughest title I’ve tested so far. This one has a known, currently unresolved RSX rendering bug: a severe green lighting artifact covering half the screen, tracked as an open issue on RPCS3’s GitHub. Worth knowing before troubleshooting this one: the community patches that exist for this game (Disable MLAA, Disable Bloom, a green-artifact fix) are documented for the Demo release specifically, not the full retail game — so they may not apply cleanly. In my own testing, this was the one title I didn’t push further with. What I saw was straightforwardly unplayable — so rather than working through every possible mitigation, I called it there. For anyone who wants to push further than I did, it’s worth trying: This is the one title in this build where I’d genuinely say: don’t expect it to be fixable through settings alone right now. It’s waiting on emulator-side progress, not configuration. Army of TWO: The 40th Day Compatibility status: Playable — the most stable of the three Army of Two titles. A much better story than Devil’s Cartel: In my own testing, there was some judder during the startup video specifically, but once actual gameplay began it ran smoothly and consistently — worth knowing so that early judder doesn’t get mistaken for a sign the rest of the session will be rough. One thing worth

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 7: PS3 & Xbox 360 Testing Read More »

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 6: Controllers & Initial Testing

Part 6: Getting Input Working, and a First Look at What This Machine Can Do With Batocera installed and storage sorted in Parts 4 and 5, it was finally time to actually play something. This part covers getting both controllers working properly, setting up on-screen performance monitoring so I’ve got real numbers to work with going forward, and a first pass of baseline testing before the dedicated PS3 and Xbox 360 deep-dive in Part 7. The Controllers For this build I’m using two controllers: a GameSir Cyclone 2 and an EasySMX D05. GameSir Cyclone 2 The Cyclone 2 isn’t on Batocera’s officially documented controller list, but in practice it paired and worked immediately in Batocera — genuinely plug and play, no manual mapping needed at this stage. Check the current price and availability on Amazon UK. Check Price on Amazon UK Affiliate Disclosure: This post contains Amazon affiliate links. If you purchase a product through one of these links, I may earn a small commission at no additional cost to you. I only recommend products that I have personally used or believe are worth considering. EasySMX D05 The EasySMX paired and mapped without any fuss in Batocera — it’s actually detected and labelled as an Xbox 360 gamepad, which gets it picked up automatically with a sensible default button layout. Check the current price and availability on Amazon UK. Check Price on Amazon UK Affiliate Disclosure: This post contains Amazon affiliate links. If you purchase a product through one of these links, I may earn a small commission at no additional cost to you. I only recommend products that I have personally used or believe are worth considering. Both controllers being usable matters for later testing too — some of the heavier PS3 titles I’ll be covering in Part 7 support local co-op, and I wanted two working pads ready to go rather than sorting this out mid-session. Setting Up On-Screen Performance Monitoring Before doing any real testing, I wanted actual numbers on screen rather than judging performance by feel. Batocera has this built in, using MangoHud under the hood. Quick option: System Settings → Frontend Developer Options → Show FPS Counter gives a bare FPS number, nothing else. Full option: for FPS plus CPU/GPU load, temperatures and RAM, Game Settings → Decorations → Heads Up Display, set to Performance. This can be applied per-system or per-game, or made the default everywhere by SSHing in and adding global.hud=perf to /userdata/system/batocera.conf. Once it’s active, a few useful hotkeys: This global HUD covers RetroArch-based systems well, but it’s worth knowing upfront that it doesn’t reliably reach standalone emulators like RPCS3 — I’ll come back to that specifically below, since it turned out to matter for one of the controllers too. Baseline Testing With input and monitoring both sorted, I ran a first pass across the older, less demanding systems — PS2, GameCube, Dreamcast, and the usual arcade/retro range — mostly to confirm everything was configured correctly rather than to push the hardware. Unsurprisingly, none of this troubled the i7-8700 or the RTX 3050 in the slightest. These systems are comfortably within reach of this hardware, and I don’t want to spend the word count on results that were never really in question. The real test — PS3 and Xbox 360 — gets its own dedicated part next, where the CPU, GPU and PCIe bandwidth all actually start to matter. Getting Both Controllers Working in RPCS3 Since some of the PS3 testing coming up in Part 7 needed both pads recognised specifically inside RPCS3 — not just Batocera’s front end — I set this up now rather than mid-testing later. Both controllers needed some attention here, for different reasons. The Cyclone 2 was recognised by RPCS3 without any handler changes, but unlike its plug-and-play behaviour in Batocera, it didn’t come through with a working default button layout — it needed manual button mapping inside RPCS3’s own pad configuration before it was actually usable. The EasySMX D05 was a different problem entirely: despite Batocera itself correctly identifying it as an Xbox 360 gamepad, RPCS3’s own controller dropdown showed nothing for it at all. The explanation for the EasySMX turned out to come down to how the two systems recognise controllers differently. Batocera bundles an extensive, community-built controller mapping database for EmulationStation, which is how it can label the EasySMX with a friendly, accurate name even though it isn’t a genuine Microsoft-licensed device. RPCS3, on the other hand, runs its own separate SDL runtime with SDL’s standard mapping database — and if a controller’s actual USB vendor/product ID isn’t in that database, RPCS3 simply won’t list it, regardless of what Batocera calls it. The fix was switching RPCS3’s Player 2 handler from SDL to Evdev. Evdev reads the raw Linux input device directly rather than relying on any mapping database, so it sidesteps the whole problem. Once switched, the EasySMX showed up immediately. Worth remembering both of these generally: a controller working perfectly in Batocera doesn’t guarantee it’ll be instantly usable in a standalone emulator like RPCS3 — sometimes it just needs its buttons mapped manually, and sometimes, particularly for less mainstream or budget-friendly controllers, the handler itself needs switching to Evdev before it’s even visible. RPCS3’s Own Performance Overlay While setting up controllers inside RPCS3, it was also the natural point to sort out performance monitoring specifically for it, since — as mentioned above — Batocera’s global HUD doesn’t reliably reach standalone emulators. RPCS3 has its own built-in overlay, separate from MangoHud entirely: Settings → GPU tab → Performance Overlay. Turning this on and setting the detail level to Medium or High gives FPS, frame time, and — more usefully than a generic HUD — individual PPU, SPU and RSX thread load. Genuinely more useful for PS3 troubleshooting than a generic CPU/GPU number, since it shows exactly which subsystem is under pressure at any given moment. Where the Build Stands Now Item Status GameSir Cyclone 2 Plug and play in Batocera; needed manual

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 6: Controllers & Initial Testing Read More »

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 5: ROM Storage & NVMe Cache

Part 5: Splitting Storage the Right Way With Batocera installed and configured in Part 4, the next job was getting the storage set up properly: a large disk for the ROM library, and something faster for the data that actually benefits from speed. The Plan I touched on the reasoning back in Part 1, but to restate it plainly: not all the data Batocera and its emulators handle benefits equally from fast storage. So the plan was: Setting Up the ROM Disk The IronWolf became the main ROM disk. Batocera expects ROMs under its share structure, and rather than trying to relocate that entire structure onto a second drive after the fact, I set the IronWolf up as the target from the start and pointed Batocera’s storage device at it through the System Settings storage device option. Creating the NVMe Cache Partition The NVMe already had Batocera itself installed on it from Part 4. Rather than risk that install, I created a second partition on the same drive specifically for cache data, sized with plenty of headroom for RPCS3 and Xenia caches to grow over time as more games get played. That’s a lot of room relative to what shader/game caches actually need — deliberately so. RPCS3 and Xenia caches grow fast once you’ve got a few dozen titles installed, and running out of space mid-session turns into a genuinely confusing failure to debug. Mounting It: Batocera’s Custom Service Approach Batocera doesn’t persist arbitrary changes across reboots the way a normal desktop Linux install does — most of the filesystem is read-only, with /userdata as the persistent, writable area. To get a custom partition mounted automatically at boot, and the RPCS3/Xenia cache folders redirected onto it, Batocera provides a custom services mechanism: scripts placed under /userdata/system/services/ that run as part of the boot process. Here’s the script I set up (filename custom_service): A few things worth explaining about how this is put together: Verifying It’s Actually Working Once the service is running, it’s worth confirming both bind mounts are actually active rather than just assuming the script did what it was supposed to: Both should show up pointing at the NVMe partition paths, not the default boot-drive locations. Worth checking this after any future Batocera update too, rather than assuming a service that worked once will keep working forever. A Couple of Ongoing Habits A few things I’ve kept up since fixing this, worth mentioning for anyone setting up something similar: Where the Build Stands Now Storage Purpose 512GB NVMe (partition 1) Batocera OS install 512GB NVMe (partition 2) RPCS3 + Xenia cache data (459GB) 4TB Seagate IronWolf Main ROM library Storage is now properly split, both cache paths are verified and working, and there’s a repeatable, logged process behind it rather than something I’d have to rebuild from memory if it ever needs touching again. What’s Next? With storage sorted, it’s time to get input working properly. Part 6 covers controllers and initial testing — getting the GameSir Cyclone 2 and EasySMX D05 both recognised and mapped correctly, including a detection quirk with the EasySMX that took a bit of digging to sort out. That’s next.

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 5: ROM Storage & NVMe Cache Read More »

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 4: Installing Batocera

Part 4: Getting Batocera Onto the OptiPlex With the hardware upgrades done in Part 2 and the RTX 3050 installed in Part 3, the OptiPlex was finally ready for the operating system this entire project is built around: Batocera Linux. This part covers the install itself and the initial configuration I did before touching ROMs, controllers or emulator settings — all of which get their own dedicated parts later in the series. Why Batocera I touched on this briefly back in Part 1, but it’s worth restating here: Batocera turns the machine into a dedicated emulation platform rather than a general-purpose desktop OS with emulators bolted on. Everything from the boot process to the front-end is built around that single purpose, which is exactly what this project needed. For a standard PC like the OptiPlex, Batocera provides an x86_64 build — the architecture that matches the i7-8700, as opposed to the ARM builds intended for Raspberry Pi and similar single-board hardware. Downloading the Image The current image is available from batocera.org, where you select the architecture matching your hardware — x86_64 in this case — and download the corresponding .img.gz file. Worth taking the extra minute to verify the checksum after downloading, particularly given how large the image is and how little fun it is to discover a corrupted write partway through flashing. Preparing the Install Media Batocera’s own documentation recommends flashing the downloaded image to a USB drive using balenaEtcher, then booting from that USB drive to actually install onto the target disk — rather than trying to flash the NVMe directly from another machine. The process: I’m doing this as a completely clean install rather than migrating settings from anywhere else, which gives me a known, documented starting point for the rest of this series — useful both for troubleshooting and for anyone following along who wants to replicate the exact steps. BIOS and Boot Order With Batocera written to the NVMe, the OptiPlex needed to actually be told to boot from it. Dell’s BIOS is reached with the usual F2 on startup, and boot order/priority lives under the boot configuration menu from there. I set the NVMe as the primary boot device and left the boot mode on UEFI, which Batocera boots fine under and is generally the preferred option over Legacy/MBR where available. First Boot The first proper boot into Batocera goes through EmulationStation’s initial setup — confirming the display output, running through basic input configuration (I’ll cover the actual controller setup properly in Part 6, so I kept this minimal for now — enough to navigate the menus), and connecting to the network. This video isn’t technically the first boot after installation. It’s the first boot after the ROMs have been transferred and the graphics have been scraped, giving you a look at the fully set-up system and how everything looks when navigating through the console. Activating the Proper Nvidia Driver This is the one step from this stage that’s specific to having the RTX 3050 already installed by this point. Batocera runs fine on the default open-source nouveau driver, but it’s not activated for full Nvidia performance by default — and getting proper performance out of the RTX 3050 matters a great deal once RPCS3 and Xenia come into play later in this series, so this is worth doing here rather than leaving it for later. This isn’t a toggle in the System Settings menu — Batocera documents it as a boot config file edit. The steps: Batocera actually tries to auto-detect and use the right driver already, so this step is really about confirming it, or overriding an incorrect automatic guess, rather than switching something on from nothing. Worth verifying it’s taken effect either way — go to System Settings → Information and check the OpenGL version. If it names NVIDIA, the proper driver is active; if it still says MESA, the setting hasn’t taken. Batocera also logs which driver actually loaded at /userdata/system/logs/nvidia.log, which is worth a quick check if the Information screen is ambiguous. Checking for Updates Before going any further, I made sure the system was running the latest available Batocera build rather than whatever version happened to be current at time of download. This matters more than it might seem for this particular project — RPCS3 and Xenia driver support, and the underlying Vulkan/OpenGL stack Batocera ships, have both moved forward meaningfully across recent Batocera releases, and being even one version behind has been enough to affect Nvidia RTX 30-series compatibility in ways I’d rather not have to debug later. Where the Build Stands Now Component / Setting Status OS Batocera Linux, x86_64, latest build Install target 512GB Samsung NVMe (clean install) Boot mode UEFI, NVMe set as primary boot device GPU driver Official Nvidia driver active (RTX 3050) Controllers Basic input configured — full setup in Part 6 At this point the machine is a genuinely functioning Batocera system, running on the proper Nvidia driver, with the RTX 3050 doing the graphics work rather than the Intel iGPU that carried the earliest testing back in Part 1. What’s Next? With Batocera installed and configured, the next job is getting the actual game library organised — and, more specifically, setting up the storage split I planned back in Part 1: the 4TB IronWolf as the main ROM disk, and a dedicated NVMe partition for PS3 and Xbox 360 cache data. Part 5 covers ROM storage and the NVMe cache setup That’s next.

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 4: Installing Batocera Read More »

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 3: MSI RTX 3050 Low Profile

Part 3: Installing the GPU — and a Slot Discovery I Wasn’t Expecting In Part 2, I got the OptiPlex physically ready: new CPU, fast NVMe storage, a large ROM drive, an upgraded PSU and some extra cooling. This part is about the component that could make the biggest difference to the whole project — the MSI GeForce RTX 3050 Low Profile, specifically the LP 6G OC variant. It didn’t go quite as straightforwardly as I expected, and the reason why turned into one of the more interesting technical detours of this entire build. MSI GeForce RTX 3050 LP 6G OC This is the low-profile GPU I installed for this build — small enough to fit the SFF chassis, and it draws its full 70W straight from the PCIe slot itself, so no supplementary power connector is needed. Check the current price and availability on Amazon UK. Check Price on Amazon UK Affiliate Disclosure: This post contains Amazon affiliate links. If you purchase a product through one of these links, I may earn a small commission at no additional cost to you. I only recommend products that I have personally used or believe are worth considering. Why the RTX 3050 LP 6G OC I covered the reasoning behind a low-profile card in Part 1, but it’s worth restating exactly why this specific model matters for a build like this. The 5060 SFF chassis imposes two hard constraints on any GPU: The LP 6G OC is specifically built for this kind of scenario: That last point matters more than it might seem. The OptiPlex’s PSU doesn’t have a spare PCIe power connector to offer a GPU in the first place, so a card that draws its full power from the slot was effectively a requirement, not just a nice-to-have. An Alternative I Considered Before settling on the MSI card, I also looked at the Maxsun RTX 3050 SLP 6G. The interesting thing about the Maxsun card is that it’s a genuine single-slot design — physically slimmer than the MSI, and (as it turned out) would actually have fit into the x16 slot rather than being restricted to the x4 slot the way the MSI ended up being, given the PSU clearance issue I ran into below. In the end, cost decided it. The Maxsun card came in considerably more expensive than the MSI, which I picked up for £179.99. Given the price difference, and without yet knowing for certain the x16 slot wasn’t usable anyway, going with the MSI was the more sensible call at the time. With the benefit of hindsight — knowing what I now know about the x4 bandwidth ceiling — the Maxsun would likely have been the technically better choice if budget allowed. Worth keeping in mind if you’re planning a similar build and the PSU-clearance problem I hit doesn’t apply to your specific case. Maxsun GeForce RTX 3050 SLP 6G (Single-Slot) The alternative I considered before going with the MSI card. This one’s a genuine single-slot design that would actually fit the OptiPlex’s full x16 slot rather than being restricted to x4 — the technically better option if the price difference works for your budget. Check the current price and availability on Amazon UK. Check Price on Amazon UK Affiliate Disclosure: This post contains Amazon affiliate links. If you purchase a product through one of these links, I may earn a small commission at no additional cost to you. I only recommend products that I have personally used or believe are worth considering. Fitting the Card Physically installing the card was straightforward — remove the expansion slot cover, seat the card in the PCIe slot, secure the bracket, connect nothing else since there’s no supplementary power cable required. Except it wasn’t quite that simple. The Slot Problem The 5060 SFF motherboard actually has two PCIe slots: a full-length x16 slot, and a second, physically shorter x4 slot. I’d assumed — reasonably, I thought — that the GPU would go into the x16 slot, since that’s the one built for full-bandwidth expansion cards. It turned out that wasn’t an option in my case. The PSU physically sits right in the path of the x16 slot’s position, and there simply isn’t room for a card there — even with the GPU sitting in the x4 slot instead, the clearance between the GPU and the PSU is only around 1–2cm. Putting a card into Slot 1 would have meant it colliding directly with the power supply itself. The card physically fits into the shorter x4 slot at all because consumer GPUs always use a full x16-length edge connector regardless of how many lanes are actually wired underneath — and this particular slot is “open-ended,” meaning there’s no plastic stop blocking the extra length of the connector from sitting in it. The unused portion of the card’s edge connector simply overhangs past the end of the slot, doing nothing. Electrically, though, that means the card is only running at PCIe x4, not x16. Does This Actually Matter? My first instinct was that this was a real problem — quartering the available bandwidth to the GPU sounded like exactly the kind of thing that could undermine the whole point of adding a discrete card. After digging into it properly, the picture turned out to be more nuanced than that. So: not a disaster, but not nothing either — and definitely something worth actually verifying rather than assuming either way. Verifying the Link Is Actually Healthy Bandwidth ceiling aside, the next question was whether the x4 link was even running correctly at its full rated speed, or whether something about the fit — an open-ended slot, an internal riser, a marginal contact — was causing it to negotiate down to something worse. This is where I turned to the terminal rather than guesswork. SSHing into Batocera, I ran: (replacing the bus ID with whatever lspci reports for the GPU on your own system) The Confusing First Result My first check, run

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 3: MSI RTX 3050 Low Profile Read More »

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 2: Hardware Upgrades

In Part 1, I introduced the project: turning a Dell OptiPlex 5060 SFF into a dedicated Batocera retro gaming PC, capable of pushing well beyond the usual emulation systems and into PS3 and Xbox 360 territory. Before any of that testing could happen, though, the machine needed some work. This part covers the physical upgrades — swapping the CPU, installing the storage, upgrading the power supply and adding extra cooling — before Batocera or the RTX 3050 come into the picture at all. None of this is complicated if you’ve built or upgraded a PC before, but the OptiPlex’s Small Form Factor chassis does add a few extra considerations that a standard ATX tower wouldn’t. Opening Up the OptiPlex The 5060 SFF uses Dell’s tool-less chassis design, so getting inside doesn’t require a screwdriver for the outer panel. Inside, everything is noticeably more tightly packed than a standard tower — the drive cage sits directly above the motherboard, and cable routing options are limited. Worth taking a moment here to note where everything currently sits before you start moving cables and components, particularly the front-panel connectors and the original drive cabling. Swapping the CPU The OptiPlex originally came with an Intel Core i5, which I replaced with the i7-8700 discussed in Part 1. The process itself is standard LGA1151 socket work, but a few things are worth flagging specifically for this chassis: Noctua Thermal Paste This is what I used when reapplying thermal paste during the CPU swap. Check the current price and availability on Amazon UK. Check Price on Amazon UK Affiliate Disclosure: This post contains Amazon affiliate links. If you purchase a product through one of these links, I may earn a small commission at no additional cost to you. I only recommend products that I have personally used or believe are worth considering. Once the new CPU was seated, cooler reattached and the drive cage put back, I did a quick test boot into the BIOS before going any further — no point installing storage and a new PSU onto a CPU that isn’t actually working. Installing the Storage With the CPU confirmed working, next came the storage split I outlined in Part 1: the 512GB Samsung NVMe for Batocera and cache data, and the 4TB Seagate IronWolf for the ROM library. The NVMe The 5060 SFF motherboard has an M.2 slot, so the NVMe drive installs directly onto the board rather than needing a drive bay. A single retention screw holds it in place — worth double-checking this is properly seated and screwed down, since a loose M.2 drive can work itself free over time from case vibration, which is exactly the kind of intermittent fault that’s a nightmare to diagnose later. The 4TB IronWolf The IronWolf is a standard 3.5″ drive, and the SFF drive cage has a dedicated bay for it. This went in using the existing SATA data and power cabling already routed inside the case — one advantage of reusing the stock chassis layout rather than trying to route entirely new cable runs through an already-cramped case. With both drives installed, I did a second test boot to confirm the BIOS was detecting both correctly before moving on. The Power Supply Upgrade This is the step I’d flag as the most important in this entire part, and the one I’d encourage anyone following along to not skip or underestimate. The OptiPlex 5060 SFF ships from Dell with a 200W power supply — adequate for the original business-desktop configuration, but not something I wanted to rely on once a discrete GPU entered the picture later in this project. I upgraded to a 240W PSU as part of this stage of the build, giving some additional headroom before the RTX 3050 goes in. A couple of things worth knowing if you’re doing this yourself: I’ll come back to power supply headroom again in Part 3 once the RTX 3050 is actually installed and I can measure real-world draw under load — 240W may turn out to be enough, or it may turn out to be a figure worth revisiting. I don’t want to get ahead of the testing at this stage. Adding Extra Cooling The last physical change at this stage was cooling. SFF cases are not known for generous airflow, and I wanted to get ahead of any thermal issues before putting the system under sustained load — particularly once the GPU is added, which is going to introduce a meaningful new heat source into an already tightly packed case. I added an additional fan at the front of the chassis, oriented to push air outward and help move warm air out of the case rather than letting it build up around the components. Noctua 80mm Cooling Fan Here’s a similar fan for the OptiPlex to help push warm air out of the case — a small addition, but worthwhile. Check the current price and availability on Amazon UK. Check Price on Amazon UK Affiliate Disclosure: This post contains Amazon affiliate links. If you purchase a product through one of these links, I may earn a small commission at no additional cost to you. I only recommend products that I have personally used or believe are worth considering. I’m not expecting a dramatic change to idle temperatures from this alone. The real test will be sustained CPU and GPU load once everything is in place — I’ll be tracking temperatures properly from Part 3 onward using Batocera’s built-in performance overlay, so there’ll be actual numbers to look at rather than guesswork. Where the Hardware Stands Now With the CPU, storage, PSU and cooling all done, the physical upgrade stage of this project is complete. Updated Specification Component Before After CPU Intel Core i5 (stock) Intel Core i7-8700 RAM 16GB 16GB (unchanged) Primary Storage Stock Dell drive 512GB Samsung NVMe Secondary Storage None 4TB Seagate IronWolf Power Supply 200W (stock) 240W Cooling CPU fan only CPU fan + additional front exhaust fan Graphics

Dell OptiPlex 5060 Batocera Build: PS3 & Xbox 360 Emulation – Part 2: Hardware Upgrades Read More »