Thursday | 21 NOV 2024
[ previous ]
[ next ]

Notes - MacBook Pro Server

Title:
Date: 2023-12-01
Tags:  mac, sysadmin

I'm not sure how this will go but this will be my running documentation of setting up a macbook pro as a server. The immediate goal is to get nginx serving out a web page to the public internet. This will be the base case as I would like to move my blog to the macbook.

This is going to involve a couple of things from what I can tell:

  • Apple Silicon specific stuff
  • Mac needs to run with the lid closed
  • It will need ssh
  • Need some security
  • Need nginx installed
  • Set up dynamic dns
  • letsencrypt for SSL certificate
  • A domain name

These things aren't in a specific order but get the point across.

I'm going to label the below as steps but they aren't really a path. It's just the order that I took when doing this.

Step 1 - Updating the OS

The first thing I did was upgrade my macOS Ventura (13) to macOS Sonoma (14).

The Sonoma background is quite stunning and the moving screensaver is slick.

Step 2 - Resetting the Mac

The second thing I did was reset my macbook. I don't want my apple account tied to what will be a public server. This involved having to log in to my apple account which is always worrying as I never rememeber the password. Luckily I got it after a couple of errors.

Reset Mac to Factory Settings

I set up the macbook and skipped the Apple ID stuff.

Step 3 - Enabling ssh

I don't want to have to work directly on the mac so this is the most useful step.

Click the Apple icon -> Search for Remote Login -> Toggle this on and make sure to give full disk access.

I already gave my macbook a static IP on the router.

Step 4 - Closing the lid

I want to keep the macbook close while staying on, this was straightforward to do from the command line.

sudo pmset -a disablesleep 1

Source

I wonder if the screen is turning off as I don't think it is.

At this point I can close the macbook and ssh in to configure everything else!

Step 5 - Brew

I want to use the brew package manager to manage the various other things I'm going to be installing so the next step is to install brew. Luckily this is really straightforward.

Homebrew

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Follow the installer and make sure to update the profile for your shell so brew is available.

I tested brew by installing wget.

brew install wget

Step 6 - Fish

I like the fish shell for the suggestions and auto complete.

brew install fish

We need to add fish to the list of available shells, to do this, update /etc/shells:

sudo vim /etc/shells

and add fish after the rest:

...
/bin/zsh
/opt/homebrew/bin/fish

We also need to update fish to have access to brew. Open ~/.config/fish/config.fish and add the following:

if status is-interactive
   fish_add_path /opt/homebrew/bin
end

Now we can use chsh to change the shell we log in to:

Shell: /opt/homebrew/bin/fish

Now we can log out and back in and we should be in Fish.

Step 7 - Vim

I need my usual vim stuff.

My Vim Configuration

I try to keep my vim configuration simple. The biggest thing is that I use the Seuol256 colorscheme, undo directory and vim-polygot to get syntax highlighting.

Step 8 - nginx

Now to finally install nginx!

brew install nginx
brew services start nginx

Let's see if nginx is running:

brew services list
Name  Status  User      File
nginx started server ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist

Now we should be able to navigate to our server on port 8080:

192.168.13.79:8080

This is the standard nginx web page. The configuration for this page can be found in /opt/homebrew/etc/nginx.

You can add more applications by adding to the /opt/homebrew/etc/nginx/servers folder.

I created a simple test directory and test page and set it up to be served on 8081.

I needed to add an extra option to nginx so that going to urls didn't result in the port number getting added in:

server {
   listen 8081;
   listen [::]:8081;
   server_name example.com;
            
   port_in_redirect off;
   autoindex on;
    
   root /Users/server/example.com/;
   index index.html;
   
   location / {
      try_files $uri $uri/ =404;
   }
}

Step 9 - Securing ssh

Time to secure ssh. Mostly this is going to changing the default port and switching to using ssh keys.

ssh-keygen -f ~/.ssh/servername

Once the key is generated we need to push it to the server:

ssh-copy-id -i ~/.ssh/servername 192.168.13.79

Now we should be able to use ssh without giving a password.

Once that is working we can disable password authentication. We first need to update /etc/ssh/sshd_config.

Add/Update the following:

PermitRootLogin no
AllowUsers username
PasswordAuthentication no
KbdInteractiveAuthentication no
UsePAM no

Now restart sshd:

sudo launchctl unload  /System/Library/LaunchDaemons/ssh.plist
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist

The -w let's the service start up on boot.

Now we should have ssh secured so that only connections made with the valid key can get it.

Step 10 - Dynamic DNS

Time to make the server public! I'm running this server from home so I need to set the DNS directly and ideally make it automatic so that if my IP changes, my DNS record will also change.

I use namecheap and so the URL that needs to be hit to set a dynamic IP is:

https://dynamicdns.park-your-domain.com/update?host={@,www}&domain={example.com}&password={password}

This will set the Dynamic DNS A record in namecheap to the IP address the request is from.

NameCheap DynamicDNS

This will set the DNS record to point to my home's IP address. I'll need to do some port forwarding on the router to actually get that traffic sent to the mac server.

Step 11 - Port forwarding

This is going to be specific to a router. You will need to go into something like the firewall or port forwarding and add a rule to take any traffic coming in for port 80 and port 443 and pass it to the server.

Once this step is done, we should be able to go to the URL and see something from our server. In my case I forwarded port 8081 and was able to see my test application from the public internet!

Step 12 - Automatic Dynamic DNS

brew install inadyn

I added a custom configuration for namecheap to /opt/homebrew/etc/inadyn.conf:

custom namecheap {
   username    = example.com
   password    = password
   ddns-server = dynamicdns.park-your-domain.com
   ddns-path   = "/update?domain=%u&password=%p&host=%h&ip=%i"
   hostname    = { "@", "www" }
}

Step 13 - Certbot

Now I need to get SSL certificates set up.

brew install certbot

Official Instructions for nginx on macos

I had to create the .well-known file with full permissions to get certbot to successfully generate certificates.

sudo certbot certonly --webroot

This will prompt you for the domain name and the path to the web folder.

The certificates will be saved to /etc/letsencrypt/live/.

I had to change the permissions of letsencrypt as I'm running nginx as a non-root user.

sudo chmod -R 755 /etc/letsencrypt/archive
sudo chmod -R 755 /etc/letsencrypt/live

Now we should be able to do update the nginx configuration with the ssl certificates:

server {
   listen 8081 ssl;
   listen [::]:8081 ssl;
   server_name example.com;
            
   port_in_redirect off;
   autoindex on;
    
   root /Users/server/example.com/;
   index index.html;
   
   ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
   ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
   
   location / {
      try_files $uri $uri/ =404;
   }
}

Check the configuration:

nginx -t

Restart nginx:

brew services restart nginx

If everything went well we should now be able to go to the domain and have everything work!

https://example.com

The final step is to add the certbot renewal to the root crontab.

sudo crontab -e

Add the following:

0 */12 * * * certbot renew --nginx

This will try doing a renewal every 12 hours.

The Thrilling Conclusion

At this point I have everything set up and my mac is functioning as a server.

The most difficult part was once again dealing with the SSL stuff. Hopefully it gets easier next time. I also learned quite a bit about the mac now and how it works. The differences between it and Linux look to be small but are important.

Brew has been fantastic as a package manager

I still need to figure how to get nginx to start up automatically along with inadyn to do the dynamic dns. I was also surprised to learn how simple dynamic dns looks to be as a user. I need to hit a namecheap url to trigger a dns record update. I wonder how fast it is, probably it depends on the caches.

The next thing is to set up ScarletDME so I can build out the real application that I want to self host. This part is probably going to be a bit more involved but I'm looking forward to it.