10 year old MacBook as a Server with NixOS

· 959 words · 5 minute read

1. Intro 🔗

Kubernetes is designed so that at least three nodes are needed for a high-availability cluster. This setup ensures that if one node goes down, the other two can keep things running until the third is back online.

Occasionally, I need to reboot my NAS, which causes the website to go down. In this case a third, separate machine for a node is becoming essential need.

I considered buying another Raspberry Pi on the aftermarket. However, I’m not happy with the one I have already and don’t want to invest in another one. Instead, I’m looking into small PCs like Dell Wyse. Plenty of them available, and I’ve heard good things about them. My priority is low power consumption, ideally under 10 watts at idle and light load.

Due to my professional background, I have several old Macbook machines. Thought to myself: How hard would it be to convert one into a server and configure optimal power consumption?

So here I am with MacBook Pro (Retina, 15-inch, Mid 2012).
Spec:

  • 2.7GHz quad-core Intel Core i7
  • 120 GB for nixOS out of 768GB
  • Intel and Nvidia GPUs

Note: I have a Thunderbolt-to-Ethernet adapter and plan to use it as my main connection. Wi-Fi didn’t work in NixOS out of the box in my case, and not critical for me.

2. Preparing Partition 🔗

I want to keep macOS and install NixOS on a new partition.

  1. Open Disk Utility from the Applications > Utilities folder in Finder.
  2. In the top-left corner, select View > Show All Devices.
  3. Select the highest-level drive on your Mac, then click Partition.
  4. Use + button to create a new partition and “Add Partition”. Give it name Nix.
  5. Apply

In my case, I couldn’t create a partition because I had local Time Machine snapshots, which caused the disk to be full.
Message was shown: “This volume can not be split because the resulting volumes would be too small.”

Use tmutil to resolve this issue.

  • To list snapshots: tmutil listlocalsnapshots /
  • Delete old snapshots tmutil deletelocalsnapshots <date-from-name-of-file>

3. USB Stick with NixOS 🔗

Download the NixOS image from the official website. Recommendation is to stick with the recommended option 🙂. It has a headless OS option to install via installation wizard.

Insert a USB drive and create bootable installer. Source.

  1. Plug in the USB flash drive.

  2. Find the corresponding device with diskutil list. You can distinguish them by their size.

  3. Make sure all partitions on the device are properly unmounted. Replace diskX with your device (e.g. disk1).

diskutil unmountDisk diskX
  1. Then use the dd utility to write the image to the USB flash drive.
sudo dd if=<path-to-image> of=/dev/rdiskX bs=4m

After dd completes, a GUI dialog “The disk you inserted was not readable by this computer” will pop up, which can be ignored.

Note: Using the ‘raw’ rdiskX device instead of diskX with dd completes in minutes instead of hours.

  1. Eject the disk when it is finished.
diskutil eject /dev/diskX

Some instruction might suggest to install rEFInd or other bootloaders. This is up to you.

4. Installation and Configuration 🔗

Insert the USB stick and reboot your MacBook. Hold the Alt (Option) key to select the USB drive to boot from.
Ensure you have an internet connection.

Proceed with the installation wizard.

After the installation is complete, reboot, and the fun part begins.

Login to the system.

Start edit configuration:

sudo nano /etc/nixos/configuration.nix
  1. Add the following to enable the SSH agent:
  # Enable the OpenSSH daemon.
  programs.ssh.startAgent = true;  
  services.openssh.enable = true;
  1. To prevent from sleep add the following lines. source
  systemd.sleep.extraConfig = ''
    AllowSuspend = no
    AllowHibernation = no
    AllowHybridSleep = no
    AllowSuspendThenHibernate = no
  '';

and lid switch (some report this is not required)

  services.logind = {
    lidSwitch = "ignore";
    lidSwitchExternalPower = "ignore";
  };
  1. Now disable screen when lid is closed
services.acpid = {
    enable = true;
    lidEventCommands =
    ''
      export PATH=$PATH:/run/current-system/sw/bin

      lid_state=$(cat /proc/acpi/button/lid/LID0/state | awk '{print $NF}')
      if [ $lid_state = "closed" ]; then
        # Set brightness to zero
        echo 0  > /sys/class/backlight/gmux_backlight/brightness
      else
        # Reset the brightness
        echo 500  > /sys/class/backlight/gmux_backlight/brightness
      fi
    '';

    powerEventCommands =
    ''
      systemctl suspend
    '';
  };

In the original post author uses acpi_video0 but in my case is gmux_backlight, just check internals of
/sys/class/backlight/ to validate what is yours.

  1. Check max brightness.
cat /sys/class/backlight/gmux_backlight/max_brightness

Adjust value 500 in the script from the step 3. Mine max was 1023 and I took half, yours range could be different.

  1. Check which GPUs you have. nix-shell -p pciutils --run "lspci -nn | grep VGA" gave me this info about mine.
00:02.0 VGA compatible controller [0300]: Intel Corporation 3rd Gen Core processor Graphics Controller [8086:0166] (rev 09)
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GK107M [GeForce GT 650M Mac Edition] [10de:0fd5] (rev a1)

Copy file from here nvidia/disable.nix and import in main configuration.nix file.

  1. Run to apply changes
sudo nixos-rebuild switch

Important
Connect with `ssh` if not connect already, and reboot it with the lid closed.

With the lid closed and the Nvidia GPU disabled, my MacBook server consumes approximately 12 watts at idle!

5. K3s node 🔗

Configure server node.
Similar to the previous post.

  services.k3s = {  
    enable = true;
    role = "server";
    token = "<token>";
    serverAddr = "https://<ip of first node>:6443";
  };

  services.k3s.extraFlags = toString [
    "--write-kubeconfig-mode" "644" # "--kubelet-arg=v=4" # Optionally add additional args to k3s
  ];

Make sure host name is different from the server!

networking.hostName = "nixos_mac";

Here is the resulting cluster:

mac % kubectl get nodes
NAME       STATUS   ROLES                       AGE    VERSION
nixos      Ready    control-plane,etcd,master   4d5h   v1.30.4+k3s1
nixosmac   Ready    control-plane,etcd,master   18h    v1.31.4+k3s1
nixosrpi   Ready    control-plane,etcd,master   4d5h   v1.30.4+k3s1

You can find my nix configurations here.


Additional sources:

comments powered by Disqus