Setting up a Mac Laptop for a Software Engineer Coming from a Linux Desktop

I’ve recently started a new job and am setting up a MacBook Pro M4. I’ve been using either a native Linux Desktop, or a Linux Desktop as a VM on a Windows box for most of my career. There are a number things about the Mac that are different than Intel based machines running Linux following are the list of changes that I have made to make it usable as a development environment.

Show all files in an “Open” dialog box

Press Cmd + Shift + .

Set system time to UTC

sudo ln -sf /usr/share/zoneinfo/UTC /etc/localtime

To display time in seconds go to System Settings > Menu Bar > Clock and select “Display the time with seconds”

If you also want to display a clock that shows your local time install Clocker.

Disable the “Press and Hold” Feature

By default, holding down a given key will display a pop-up/tooltip that displays accented version of the given character. I use a Vim plugin with the IDEs that I use and when trying to navigate by holding down either the w or b chars the cursor only moves by one work and then shows an accented character. To disable it

  1. Run the following command in a terminal and then restarting applications. This turns off the feature that will show accented characters: defaults write -g ApplePressAndHoldEnabled -bool false
  2. Turn up the Key Repeat rate by going to System Setting > Keyboard and adjust the slider for the “Key repeat rate”. If this still feels too slow you can crank it up with the following terminal settings which enable values much lower than the UI allows to be set
# Sets a very short delay before repeating
defaults write -g InitialKeyRepeat -int 15 

# Sets a very fast repeat rate
defaults write -g KeyRepeat -int 2

Configuring Home and End Keys

  1. Create the following directory: mkdir -p ~/Library/KeyBindings
  2. Create and edit the binding file vi ~/Library/KeyBindings/DefaultKeyBinding.dict
  3. Paste the following content into it
{
    "\UF729" = "moveToBeginningOfLine:";
    "\UF72B" = "moveToEndOfLine:";
    "$\UF729" = "moveToBeginningOfLineAndModifySelection:";
    "$\UF72B" = "moveToEndOfLineAndModifySelection:";
}

Then save and close that file and restart applications and/or restart the Mac.

iTerm2 Specific Configurations

Configure Home and End keys to work properly

  1. Go to Settings > Profiles > Keys > Key Bindings and click the + at the bottom left.
  2. For the Home Key:
    • Keyboard Shortcut: Press your physical Home key.
    • Action: Select Send Hex Code.
    • Code: 0x01 (This is the hex for Ctrl+A).
  3. For the End Key:
    • Keyboard Shortcut: Press your physical End key.
    • Action: Select Send Hex Code.
    • Code: 0x05 (This is the hex for Ctrl+E).

Configure F-keys to work without pressing fn

  1. Open System Setting > Keyboard and click on Keyboard shortcuts
  2. Click on Function Keys in the left-hand nav
  3. Toggle the Use F1, F2, etc. keys as standard function keys

Getting Alt-Tab to Switch Applications

Download and install the AltTab application.

Configuring Primary Selection and Middle Click Paste

One of the things that I use all of the time under Linux is to select text and then middle-click to paste it. This is one of those Linux features that once you get used to, you just have to have.

Configure iTerm2

  1. Open iTerm2 settings
  2. Go to General > Selections
  3. Check the box “Applications in terminal may access clipboard”
  4. Check the box “Copy to clipboard on selection”
  5. Go to Pointer > Bindings
  6. If “Middle Button” is not there, click the “+” button at the bottom of the window
  7. Set the following
    • Button/Gesture: Middle Button
    • Click Type: Single Click
    • Action: Paste from Selection…: There are a whole host of additional options from there that you can choose from. I left the default settings.

Screenshots

Configure the OS to write screenshots to a specific directory

# Set the new location
mkdir -p $HOME/Pictures/Screenshots
defaults write com.apple.screencapture location $HOME/Pictures/Screenshots

# Apply the changes (restarts the UI server)
killall SystemUIServer

Additional brew Packages

  • watch

Helpful Keyboard Shortcuts

  • Enter a path to a file in a dialog box: By default, Mac makes you click everything. Many times you know exactly where the file is on the filesystem and you just want to type it in and hit Enter. To do so, you need to open the dialog box and then press Command + Shift + G and then you can enter the path to the file.
  • View hidden files and directories in a dialog box: By default, Mac always hides “dotfiles” and directories in a dialog box. To see them open the dialog box and then press Command + Shift + Period.

Development SDKs

Installing Go

Initially, I just went with brew install go, but this turned into a big mess. While working on a project where I was writing a K8s Custom Resource Definition I ran into a buzz saw of mismatched versions and edge cases with the Apple Silicon/ARM architectural problems with the go development toolchain.

Instead of using brew for the go SDK

  1. Download the .tar.gz file from https://go.dev/dl/ for your architecture
  2. Unpack it, rename the go directory go1.x.x and move the directory to /usr/local/
  3. Create a symlink, /usr/local/go to point to the version specific go directory
  4. Ensure that /usr/local/go/bin is in your PATH
  5. Install any other required go libraries with go install or by downloading the binaries and putting them in your $GOPATH
    • go install golang.org/x/tools/gopls@latest
    • go install github.com/go-delve/delve/cmd/dlv@latest

How to Install Windows 11 on a Dell Optiplex 990 (and perhaps other older hardware)

To perform a clean install

  1. Configure the BIOS
    • Set boot mode to UEFI
    • Set SATA configuration to AHCI
    • Enable Secure Boot/TPM
  2. Prepare a USB drive
    • Use Ventoy to create a USB drive
    • Copy the win11 ISO to the data partition of the drive
  3. Wipe the drive during the install process
    • Boot from the USB
    • When you see the “Where do you want to install Windows?” screen press Shift + F10 to open a command prompt
    • Then type the following commands, in this order
      • diskpart
      • list disk and choose the drive onto which you want to install. Most likely Disk 0
      • clean, this will remove all data from the drive
      • convert gpt, this is the command that will make the drive UEFI-compatible
      • exit
      • exit
  4. Continue with the installation. When it asks you where to install, select the drive you just wiped. It should list a large “Unallocated Space”.
  5. Select it and click Next
  6. It will then show a blue screen with “n% installing”
  7. Keep an eye on it and when it reboots, remove the installation USB
  8. You should see a “Getting Ready”, or “Installing 0%, Please keep your computer on” message. If you get there you on your way to installing Win 11 on your computer.

Mount External, Encrypted, LMV drives

First decrypt the drive. When I plugged it in GNOME displayed a dialog and asked for the key. I’m guessing that cryptsetup luksOpen /dev/device device_crypt would work as well.

Run lvdisplay to get the LV Path for the logical volume that you want to mount.

Then, activate the volumes , there is a way to activate a specific one volume, but the following activates them all.

vgchange -ay

Then mount it

mount <lv-path> /mnt

PDF Commands Cheat Sheet

Following are a number of common PDF processing commands on a Linux box

Convert a series of images to a PDF

convert image-1 image-2 ... images.pdf

convert-im6.q16: attempt to perform an operation not allowed by the security policy `PDF’ @ error/constitute.c/IsCoderAuthorized/426.

ImageMagick, specifically version 6 (im6), is preventing the conversion of PDF files due to a security policy. To address this

  1. As root, edit /etc/ImageMagick-6/policy.xml
  2. Search for the line <policy domain="coder" rights="none" pattern="PDF" />
  3. Update rights="none" to rights="read|write"
  4. Save the xml file

Debian Package Management Cheat Sheet

  • List version of an installed package: dpkg -s docker-ce | grep Version
  • Show all available versions of a package: apt-cache madison <package>

Packages being held back because of phasing

Run the following command replacing the package in question

apt-get --with-new-pkgs upgrade <package-name>

The following packages have been kept back

Try the following, in order. Each is less risky than the next. Stop as soon as you have fixed the problem.

Solution 1

apt-get --with-new-pkgs upgrade <list of packages kept back>

Solution 2

apt-get install <list of packages kept back>

Failed to fetch . . . Hash Sum mismatch

If you see an error similar to the following

Reading package lists... Done
E: Failed to fetch https://packages.cloud.google.com/apt/dists/cloud-sdk/main/binary-all/Packages  Hash Sum mismatch
   Hashes of expected file:
    - Filesize:1677314 [weak]
    - SHA256:09c3377dd2740e63c831dea4a86ccb1253ab6a41ec71eaa1cd4c7e52ee8c9255
    - MD5Sum:16ab83da58155f40c9a5a18a2a8c6587 [weak]
   Hashes of received file:
    - SHA256:ee6eabdfda398ab36e6fa8cd777d5d02b45dbac892dacdf6f1be1ca5ad0cbb65
    - MD5Sum:6dd0c5ac95bc55b7864e66986b71b8fa [weak]
    - Filesize:1677314 [weak]
   Last modification reported: Fri, 04 Apr 2025 01:54:07 +0000
   Release file created at: Tue, 01 Apr 2025 12:57:58 +0000
E: Failed to fetch https://packages.cloud.google.com/apt/dists/cloud-sdk/main/binary-amd64/Packages
E: Some index files failed to download. They have been ignored, or old ones used instead.

Try removing all of the apt list data

rm -rf /var/lib/apt/lists/* && apt-get update

How to partially cherry-pick a commit in git

There are times when you need to cherry-pick a commit from another branch. Then there are times when you only need parts of that commit.

Following is how you can partly cherry-pick a commit to get only the changes that you need.

Get the patch for the entire commit. Include the -n for no commit so that it does not add it as a commit to your branch

git cherry-pick -n <commit>

Then unstage the the changes from the cherry-picked commit so that you can choose which lines/hunks that you want to include

git reset

From here you can either git add -p to specify specific files or use git gui or some other tool to selectively stage and commit lines or hunks of the current set of changes.

Then just use git commit to commit your changes and reset the rest that you do not want and you are done.

Go template that properly renders delimiters for a slice of structs

It is common to write go text/templates that range over a slice of objects to write out a list or array of items that are separated by some delimiter defined in the template.

In the case of a JSON array of objects, the cleanest output would be a , separating each object without a leading or trailing comma.

Because go template if statements are falsey in that a 0 value will evaluate to false you can write a template as follows and it will only render a comma between items as it ranges over the slice

{
  "items":[
  {{- range $index, $item := .Items -}}
    {{- if $index -}},{{ end }}
    {
      "id": {{ $item.Id }}, "name": {{ $item.Name }}
    }
    {{- end }}
  ]
}

Click here for a working example.

Query nested arrays in PostgreSQL JSON data

The following is an example showing how to query multiple nested arrays in JSON data in a PostgreSQL database.

Given

CREATE TABLE sample_json (
  id serial PRIMARY KEY,
  name varchar(64),
  json_data json
);


INSERT INTO sample_json (name, json_data)
VALUES
	(
	'NA',
	'
	{
	    "location": "US",
	    "topLevelArray": [
	        {
	            "id": 1,
	            "secondLevelArray": [
	                {
	                    "key": "someKey",
	                    "operator": "=",
	                    "value": 10
	                },
	                {
	                    "key": "foo",
	                    "operator": ">=",
	                    "value": 5
	                },
	                {
	                    "key": "someOtherKey",
	                    "operator": ">",
	                    "value": 647
	                }
	            ]
	        },
	        {
	            "id": 2,
	            "secondLevelArray": [
	                {
	                    "key": "blah",
	                    "operator": "<",
	                    "value": 7
	                }
	            ]
	        }
	    ]
	}
	'
	),
	(
	'EU',
	'
	{
	    "location": "poland",
	    "topLevelArray": [
	        {
	            "id": 2,
	            "secondLevelArray": [
	                {
	                    "key": "bar",
	                    "operator": "<",
	                    "value": 10
	                },
	                {
	                    "key": "moo",
	                    "operator": ">=",
	                    "value": 16
	                },
	                {
	                    "key": "baz",
	                    "operator": "!=",
	                    "value": 9
	                }
	            ]
	        }
	    ]
	}
	'	
	)
;

With the aforementioned data, let’s say we want to know the id of the rows that have an object in the secondLevelArray with operator equal to >= and the value of the key field.

The concept to understand to be able to search for all of the keys in the secondLevelArray where the operator is >= is the lateral join. The TLDR; is that a subquery appearing in the FROM clause can reference columns provided by preceding items. Or, you can write clauses in the FROM clause that read from the result of previous FROM clauses.

Let’s run some queries and go through them, line-by-line. First we will just select everything in the table.

SELECT * FROM sample_json;

|id |name|json_data                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
|---|----|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|1  |NA  |"
	{
	    \"location\": \"US\",
	    \"topLevelArray\": [
	        {
	            \"id\": 1,
	            \"secondLevelArray\": [
	                {
	                    \"key\": \"someKey\",
	                    \"operator\": \"=\",
	                    \"value\": 10
	                },
	                {
	                    \"key\": \"foo\",
	                    \"operator\": \">=\",
	                    \"value\": 5
	                },
	                {
	                    \"key\": \"someOtherKey\",
	                    \"operator\": \">\",
	                    \"value\": 647
	                }
	            ]
	        },
	        {
	            \"id\": 2,
	            \"secondLevelArray\": [
	                {
	                    \"key\": \"blah\",
	                    \"operator\": \"<\",
	                    \"value\": 7
	                }
	            ]
	        }
	    ]
	}
	"|
|2  |EU  |"
	{
	    \"location\": \"poland\",
	    \"topLevelArray\": [
	        {
	            \"id\": 2,
	            \"secondLevelArray\": [
	                {
	                    \"key\": \"bar\",
	                    \"operator\": \"<\",
	                    \"value\": 10
	                },
	                {
	                    \"key\": \"moo\",
	                    \"operator\": \">=\",
	                    \"value\": 16
	                },
	                {
	                    \"key\": \"baz\",
	                    \"operator\": \"!=\",
	                    \"value\": 9
	                }
	            ]
	        }
	    ]
	}
	"                                                                                                                                                                                                                                                                      |

As expected, we just get back everything.

Now, let’s start to drill down into the JSON object. First we will select the row id and just the data from the topLevelArray.

SELECT
	sj.id
	topLevelArray
FROM
	sample_json sj,
	json_array_elements(json_data -> 'topLevelArray') topLevelArray
;

|id |toplevelarray                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|1  |"{
	            \"id\": 1,
	            \"secondLevelArray\": [
	                {
	                    \"key\": \"someKey\",
	                    \"operator\": \"=\",
	                    \"value\": 10
	                },
	                {
	                    \"key\": \"foo\",
	                    \"operator\": \">=\",
	                    \"value\": 5
	                },
	                {
	                    \"key\": \"someOtherKey\",
	                    \"operator\": \">\",
	                    \"value\": 647
	                }
	            ]
	        }"|
|1  |"{
	            \"id\": 2,
	            \"secondLevelArray\": [
	                {
	                    \"key\": \"blah\",
	                    \"operator\": \"<\",
	                    \"value\": 7
	                }
	            ]
	        }"                                                                                                                                                                                                                                                                                                                                    |
|2  |"{
	            \"id\": 2,
	            \"secondLevelArray\": [
	                {
	                    \"key\": \"bar\",
	                    \"operator\": \"<\",
	                    \"value\": 10
	                },
	                {
	                    \"key\": \"moo\",
	                    \"operator\": \">=\",
	                    \"value\": 16
	                },
	                {
	                    \"key\": \"baz\",
	                    \"operator\": \"!=\",
	                    \"value\": 9
	                }
	            ]
	        }"             |

The “columns” selected are the id and topLevelArray. The id is straightforward. The topLevelArray is a lateral join clause statement and we use the json_array_elements() function to select the contents of the json_data.topLevelArray key.

We can continue to traverse deeper into the JSON object with another lateral join clause that accesses the next nested array from the topLevelArray key.

SELECT
	sj.id,
	secondLevelElements
FROM
	sample_json sj,
	json_array_elements(json_data -> 'topLevelArray') topLevelArray,
	json_array_elements(topLevelArray -> 'secondLevelArray') secondLevelElements
;

|id |secondlevelelements                                                                                                                                   |
|---|----------------------------------------------------------------------------------------------------------------------------------------------------|
|1  |"{
	                    \"key\": \"someKey\",
	                    \"operator\": \"=\",
	                    \"value\": 10
	                }"      |
|1  |"{
	                    \"key\": \"foo\",
	                    \"operator\": \">=\",
	                    \"value\": 5
	                }"          |
|1  |"{
	                    \"key\": \"someOtherKey\",
	                    \"operator\": \">\",
	                    \"value\": 647
	                }"|
|1  |"{
	                    \"key\": \"blah\",
	                    \"operator\": \"<\",
	                    \"value\": 7
	                }"          |
|2  |"{
	                    \"key\": \"bar\",
	                    \"operator\": \"<\",
	                    \"value\": 10
	                }"          |
|2  |"{
	                    \"key\": \"moo\",
	                    \"operator\": \">=\",
	                    \"value\": 16
	                }"         |
|2  |"{
	                    \"key\": \"baz\",
	                    \"operator\": \"!=\",
	                    \"value\": 9
	                }"          |

We add an additional FROM clause. This one referencing the result of the previous FROM clause.

json_array_elements(json_data -> 'topLevelArray') topLevelArray,
json_array_elements(topLevelArray -> 'secondLevelArray') secondLevelElements

Now we have access to the elements in the secondLevelArray and we can add a WHERE clause to select only what we want from that nested array.

select
	sj.id,
	secondLevelElements ->> 'key'
from
	sample_json sj,
	json_array_elements(json_data -> 'topLevelArray') topLevelArray,
	json_array_elements(topLevelArray -> 'secondLevelArray') secondLevelElements
where
	secondLevelElements ->> 'operator' = '>='
;

|id |?column?|
|---|--------|
|1  |foo     |
|2  |moo     |

The result being the row id, and the value of the key field in inner most nested object.