I Revived My Broken MacBook into My Own Personal Cloud

matsmoll1 pts0 comments

I Revived My Broken MacBook Into My Own Personal Cloud - MatsMoll's blogI Revived My Broken MacBook Into My Own Personal Cloud<br>June 14, 2026KubernetesHomelabSelf-Hosted

As a tech enthusiast, I have a habit of accumulating "retired" hardware. Over the years, several iPhones and Macs have ended up in drawers, collecting dust. Rather than let them keep collecting dust, I started thinking about how to get some actual use out of them.

So I wanted to build a private cloud, complete with a personal ChatGPT interface (via Open WebUI) and a private "Google Drive/Dropbox" (via OpenCloud). I wanted my data on my own hardware, under my control, rather than in an external cloud.

I looked at a 2017 MacBook Pro sitting in my drawer. The keyboard was broken, but it still ran, so I decided it would be a good machine to set up Kubernetes on.

Setting up Kubernetes

Before anything else could happen, I needed an OS on the machine. I didn't have a spare USB drive handy, so I flashed Ubuntu 24.04 Desktop onto an old Raspberry Pi SD card and used that as a stand-in installer to get Ubuntu onto the Mac. It was slow, but it worked.

The first real hurdle was the hardware itself, specifically getting the Broadcom Wi-Fi drivers working. After trying to modify the official driver source code with Claude's help, I found a script that worked: https://gist.github.com/torresashjian/e97d954c7f1554b6a017f07d69a66374. I set it up to run on every restart, so the Wi-Fi comes back up whenever the Mac boots.

Once online, I enabled SSH for remote management, restricted to my internal network. For updates, I wanted a "GitOps" workflow instead of manual changes.

With SSH reliably reachable, I deployed k3s for lightweight orchestration and connected it to Flux CD. This meant that instead of manual configuration, Flux would pull directly from my GitLab repository and automatically apply any changes to the cluster.

Unfortunately, my new server kept ignoring me whenever I closed the lid. Ofc, this was due to the computer entering sleep mode. So I modified the power settings for Ubuntu by editing /etc/systemd/logind.conf and setting HandleLidSwitch=ignore, to make sure it always kept running. No matter what.

The Network Issues

Once the core system was stable, I needed my domain to actually point at my server. I didn't want to pay my ISP for a static IP, so I wrote a small Python script that checks my public IP every 15 minutes and updates my domain's A records through an API, running as a takk job.

It didn't work though, as I wasn't aware my network was set up using CG-NAT (Carrier-Grade NAT), meaning I didn't have a public IP of my own. It was shared across a whole range of customers behind my ISP's NAT gateway. So the script was correctly updating my domain, but to an IP address that didn't actually lead back to my house.

A quick, slightly humbling phone call to my network provider solved it. Turns out they hadn't opened a single port. They'd taken me off CG-NAT and given me my own public IP, free of charge. They can still swap my IP anytime though, so I kept the script running to keep the server reachable.

I could have kept everything on my local network behind a VPN, but getting a trusted HTTPS certificate without a public endpoint isn't straightforward, and OpenCloud requires HTTPS to function properly.

To get real SSL certificates, I needed a proper public endpoint, which led me to Domene.shop, a local DNS provider with a solid API for my IP-update script and a webhook adaptor for Let's Encrypt. Once my DNS pointed to my host, the cluster could handle SSL certificates automatically.

Managing k8s Manifests

With the network and DNS stable, I started deploying the "big" services, OpenCloud for my files and Open WebUI for my AI.

However, as I added more services, requiring Postgres databases, Redis caches, MinIO storage, and SSL certificates, the Kubernetes YAML became hard to manage. I kept mistyping service names, misconfiguring environment variables, and mismatching metadata.

This is where the project shifted from "playing with k8s" to building an easier to maintain system. To manage this complexity, I turned to takk, an infrastructure-as-code framework that I maintain.

Instead of writing hundreds of lines of brittle YAML, I define my infrastructure in Python. takk handles the heavy lifting. It generates the k8s configs, ensures all secrets and environment variables are consistent across services, and manages the relationships between third-party resources like databases and the apps that need them.

python<br>btn.innerHTML = originalHTML, 2000)"<br>data-code="from pydantic import PostgresDsn<br>from pydantic_settings import BaseSettings<br>from takk import Project, NetworkApp<br>from takk.secrets import S3AccessKey, S3BucketName, S3Endpoint, S3RegionName, S3SecretKey

class OpenWebUISettings(BaseSettings):<br>database_url: PostgresDsn<br>s3_bucket_name: S3BucketName<br>s3_region_name: S3RegionName<br>s3_endpoint_url: S3Endpoint<br>s3_secret_access_key:...

network from cloud didn script kept

Related Articles