Setup your Mac for Development

SSH

Let us first set up SSH. For those unfamiliar with SSH: SSH can work with a shared secret (read: password) but most commonly uses asymmetric encryption. Authentication is done via a key pair. If you do not have a key pair yet, you can easily generate one with the following command. The following command generates both a private key and a public key. The "-t ed25519" specifies the key type. In case you have used SSH before, note that ed25519 keys are preferred over more classic "rsa" keys.

# Generate a new key pair
ssh-keygen -t ed25519 -C "{you}@codifly.be"

By default, the private key is stored in "~/.ssh/id_ed25519" and the public key is stored in "~/.ssh/id_ed25519.pub". Now, very important:

  • You keep the private key secret. By this I mean: you keep it on your computer and store it in your 1Password to avoid losing it. Do this immediately. Huphup!
  • You upload the public key to every platform on which you wish to log on. By this I mean: you upload it to your Gitlab-account (click on your profile image at the top-right and go to Settings -> SSH Keys and paste the public key) so you can clone repositories over SSH and you give it to Arvid via MS Teams so he can give you access to our server machines.

FYI: you can view the contents of your private and public key with the commands and optionally pipe them to pbcopy to copy them to your clipboard for pasting into 1Password and GitLab.

# private key
cat ~/.ssh/id_ed25519 | pbcopy

# public key
cat ~/.ssh/id_ed25519.pub | pbcopy

I won't go into the details but it is important to also add your private key to your SSH agent. Very important here is the "--apple-use-keychain" flag, which is only supported on Apple's version of ssh-add and which adds the key permanently to the MacOS's Keychain. This way, you won't have to add your key over and over again every time you reboot. Also, this way, Sourcetree will be able to use your SSH keys on repositories cloned via SSH.

# Add private key to keychain
ssh-add --apple-use-keychain ~/.ssh/id_ed25519

Git

MacOS comes with a Git pre-installed, which is located at /usr/bin/git. The /usr/bin-directory is heavily protected by Apple's System Integrity Protection, so even the root user is unable to update the pre-installed Git. As a result, I propose to immediately install a second up-to-date Git via Brew, which will be (like all Brew-installed CLI binaries) available as /opt/homebrew/bin/git (previously /usr/local/bin/git for Apple Intel machines).

Then install git-crypt, which we use in most of our projects to encrypt all files that contain sensitive data under Git version control. The git executable will start the git-crypt executable when working on a Git repository with git-crypt enabled. Installing both git and git-crypt via Brew will cause both files to be installed in the same directory, and will result in a git that can easily find git-crypt in all circumstances.

brew install git
brew install git-crypt

Now some basic configuration. You should do this step only once; the configuration will be active for all your Git repositories.

git config --global core.ignorecase false
git config --global user.name "Arvid De Meyer" # Use your own name, obviously
git config --global user.email arvid.de.meyer@codifly.be # Use your own email, obviously

SourceTree

We strongly recommend SourceTree as a Git GUI, especially for new developers. This way it will be easier for your colleagues to assist with any issues. If you don't, you risk Arvid's wrath.

brew install --cask sourcetree

When opening SourceTree for the first time, you will be asked to log in with your Atlassian ID; you can skip this step.

I would ask you to go to Preferences... and to enter your full name and Codifly email address on the General tab, but as you can see it is already filled in. Indeed, SourceTree has read your global Git configuration! Neat.

At the same time, on the tab Git, check Do not fast-forward when merging, always create a commit.

On the same tab, click Use System Git (by default, SourceTree uses a Git binary that comes shipped with it) and select the executable that you installed in the previous section. Not doing this step will render SourceTree to complain and misbehave on repositories using git-crypt. You don't want that, so listen to me.

RULE: The Good Developer uses a Git GUI, unless he really really knows what he is doing. If you think you know what you are doing, statistics say you are probably wrong.

However, if you ever feel like wanting to use the CLI more, Sam can always help you with that.

Visual Studio Code

When, in a single project, people use different editors, several problems always tend to arise: we get inconsistencies in indentations, there is always someone who isn't able to integrate a linter in his editor, the Git repo becomes polluted with hidden project files,... For this reason, we had to pick a single editor, and use that one in our team. Since differences between modern editors are small, we made our choice almost arbitrarily, but we made a choice and we stick to it.

Within Codifly, we use Visual Studio Code as main source code editor. We do not want to be a dictator, but use of Visual Studio Code is not optional.

RULE: The Good Developer applies the company's policy to use VS Code.

Installation

To install Visual Studio Code, run:

brew install --cask visual-studio-code

The "code" command

After launching Visual Studio Code for the first time, press CMD + SHIFT + P and select Shell command: install 'code' command in PATH.

From now onwards, to open Visual Studio Code in a specific directory from the terminal, simply execute code ..

To have Git CLI commands (e.g. commit, rebase) use Visual Studio Code instead of Vim or Nano, set this in your global git config. Note the --wait option, this will make the cli command wait until you close the file in your editor.

git config --global core.editor "code --wait"

Extensions

To install an extension, run CMD + SHIFT + P and select Extensions: install extensions.

Always install the following extensions:

Package Description
ESLint Integrates ESLint into VS Code.
EditorConfig for VS Code EditorConfig Support for Visual Studio Code.
vscode-styled-components Syntax highlighting for styled-components. Download the most popular one.
Git Blame See git blame information in the status bar.
Error Lens ErrorLens turbo-charges language diagnostic features by making diagnostics stand out more prominently.
Dev Container Develop inside of a container. We use this in our seed project.
Claude Code AI coding assistant powered by Claude.

Furthermore, consider the following extensions (but don't install them without first reading what they do and determining if you will use them or not):

Package Description
GitLens - Git Supercharged Supercharge the Git capabilities built into Visual Studio Code
Docker Docker extension for VS Code.
Auto-Open Markdown Preview This VS Code extension automatically shows Markdown preview whenever you open new Markdown file
vscode-icons Icons for Visual Studio Code
Excel Viewer Show a preview of csv, xls, and xlsx files
Import cost Display import/require package size in the editor
Color Highlight Highlights colors in your code.
graphql.vscode-graphql GraphQL extension for VSCode adds syntax highlighting, validation, and language features.
simonsiefke.svg-preview Svg Preview for VSCode.
Code Spell Checker A quick way to identify typo's.

Settings

Some settings are essential. To change your settings, run CMD + SHIFT + P and select Preferences: open user settings. Visual Studio Code now has a fancy UI for editing your settings, but when you click the three dots at the right, you can choose Open settings.json... which is easier for bulk editing.

{
    "javascript.validate.enable": true,
    "javascript.format.enable": false,
    "javascript.updateImportsOnFileMove.enabled": "never",
    "workbench.colorCustomizations": {
        "editorWarning.foreground": "#FFFF00",
        "editorWarning.border": "#FFFF00",
        "editorError.foreground": "#FF0000",
        "editorError.border": "#FF0000",
    },

    "editor.codeActionsOnSave": {
      "source.fixAll.eslint": true
    },
    "eslint.packageManager": "npm"
}

Some other settings which you might or might not like:

{
    "editor.cursorBlinking": "smooth",
    "editor.cursorSmoothCaretAnimation": true,
    "editor.cursorStyle": "line",
    "editor.renderIndentGuides": true,
    "editor.renderWhitespace": "all",
    "editor.renderControlCharacters": true,
    "editor.scrollBeyondLastLine": false,
    "editor.rulers": [80],

    "explorer.confirmDragAndDrop": false,
    "explorer.autoReveal": false,

    "workbench.editor.highlightModifiedTabs": true,
    "workbench.editor.wrapTabs": true,
    "workbench.sideBar.location": "left",
    "window.zoomLevel": 0,
    "extensions.ignoreRecommendations": true,

    "workbench.colorCustomizations": {
        "editor.lineHighlightBackground": "#333333"
    }
}

Disabling GitHub Copilot

VS Code has built-in AI features powered by GitHub Copilot (chat, inline suggestions, code completions). Since we use Claude Code as our AI assistant, these must be disabled. In the settings, search for Chat: Disable AI Features and check the option. This disables all built-in Copilot functionality and hides the related interface elements.

Fancy font

As a font, consider using Fira Code which supports super fancy ligatures. Install the font via HomeBrew:

brew install font-fira-code

and add the following settings in Visual Studio Code:

{
    "editor.fontFamily": "Fira Code",
    "editor.fontLigatures": true,
    "editor.fontSize": 11
}

Node.js

Install Node.js and related command line tools:

brew install n
sudo n latest
npm install -g svgo react-native-cli http-server

Note: Yarn is considered legacy at Codifly. For new projects, use npm instead. Existing projects may still use Yarn (classic or berry).

Private NPM registry

We have our very own NPM registry. To use it instead of npm (the official registry of NPM) for Node.js packages with a name starting with @codifly/, we will have to do some configuration.

First, make an auth token on Gitlab. Add your token to your 1Password Gitlab entry as a custom field, so you don't accidentally lose it. Set all scopes (although I think that api scope is the one we need here, but this hypothesis is untested).

Then add the following lines to your ~/.npmrc file (create the file via touch ~/.npmrc if necessary). Substitute <YOUR_PERSONAL_ACCESS_TOKEN> by your personal access token; including removing the < and >. The first line specifies where to find @codifly/ packages, the second line specifies the auth token to use for installing dependencies, the third line specifies the auth token to use for uploading dependencies with npm publish.

@codifly:registry=https://gitlab.codifly.be/api/v4/packages/npm/
//gitlab.codifly.be/api/v4/packages/npm/:_authToken=<YOUR_PERSONAL_ACCESS_TOKEN>
//gitlab.codifly.be/api/v4/projects/191/packages/npm/:_authToken=<YOUR_PERSONAL_ACCESS_TOKEN>
fund=false

But... if you think we are ready... think again! At Codifly, in the older projects we use the Yarn "berry", which uses another dotfile called ~/.yarnrc.yml. Create this file too:

npmScopes:
  codifly:
    npmRegistryServer: "https://gitlab.codifly.be/api/v4/packages/npm/"
    npmPublishRegistry: "https://gitlab.codifly.be/api/v4/projects/191/packages/npm/"

npmRegistries:
  //gitlab.codifly.be/api/v4/projects/191/packages/npm/:
    npmAlwaysAuth: true
    npmAuthToken: "<YOUR_PERSONAL_ACCESS_TOKEN>"
  //gitlab.codifly.be/api/v4/packages/npm/:
    npmAlwaysAuth: true
    npmAuthToken: "<YOUR_PERSONAL_ACCESS_TOKEN>"

To test your new configuration, you can execute the following commands. If no weird red texts appear, you did everything correctly.

mkdir ~/Desktop/test
cd ~/Desktop/test
yarn init
yarn add @codifly/ui-button

If you get a 404 but are sure you did everything correctly, check with Evelyn: someone may have forgotten to add you to the correct Gitlab group, causing you to have no permissions.

Podman (Docker-compatible)

Installation

Install "podman-desktop" via Brew:

brew install --cask podman-desktop

After opening the desktop application, a short wizard "Get started with Podman Desktop" appears. Make sure that "Podman" and "Compose" are enabled and "kubectl CLI" and "Telemetry" are disabled. A few steps later, you will be asked to create the Podman machine. Ensure that "CPU(s)" is set to the maximum and "Memory" is set to at least 8GB.

After installation, go to Settings and enable "Minimize on login".

Docker compatibility

In Podman, go to Settings and enable "Docker Compatibility". For "Docker CLI: Context", choose the "default" socket. Indeed, we use the "old" Docker CLI against the "new" Podman Docker-compatible Socket.

brew install docker
brew install docker-compose
mkdir -p ~/.docker
cat <<EOF > ~/.docker/config.json
{
  "cliPluginsExtraDirs": [
    "/opt/homebrew/lib/docker/cli-plugins"
  ]
}
EOF

We have our very own Docker registry, try it with your Gitlab email address and a personal access token. It is a good idea to add your token to your Gitlab-entry in 1Password.

docker login gitlab.codifly.be:444

Special scenarios

Reïnstalling Podman

The deïnstallation of Podman seems not to delete the Podman VM. Do this manually first: podman machine rm podman-machine-default.

Migrating from an existing Docker Desktop

Remove Docker Desktop very thorougly:

/Applications/Docker.app/Contents/MacOS/uninstall
brew uninstall docker
brew uninstall docker-compose

sudo rm -f /usr/local/bin/docker
sudo rm -f /usr/local/bin/docker-compose
sudo rm -f /usr/local/bin/docker-machine
sudo rm -f /usr/local/bin/docker-credential-osxkeychain

rm -rf ~/Library/Group\ Containers/group.com.docker
rm -rf ~/Library/Containers/com.docker.docker
rm -rf ~/Library/Preferences/com.docker.docker.plist
rm -rf ~/Library/Preferences/com.docker.vmnetd.plist
rm -rf ~/Library/Logs/Docker\ Desktop
rm -rf ~/Library/Caches/com.docker.docker

rm -rf ~/.docker
rm -rf ~/.kube

Claude

We use Claude Code as our AI coding assistant. Install it via npm:

npm install -g @anthropic-ai/claude-code
brew install jq

Note for interns and recent graduates: The use of AI tools is not permitted during the first 3 weeks of employment. This is to ensure you build foundational understanding before relying on AI assistance.

As a Codifly employee, you are required to configure Claude with our company settings. This enforces privacy-conscious defaults (telemetry disabled, sensitive file protection) and adds a reminder of our AI policy.

Create the directory structure:

mkdir -p ~/.claude/hooks

Create ~/.claude/settings.json with the following contents:

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "model": "sonnet",
  "env": {
    "DISABLE_TELEMETRY": "1",
    "DISABLE_ERROR_REPORTING": "1",
    "DISABLE_BUG_COMMAND": "1",
    "CLAUDE_CODE_ENABLE_TELEMETRY": "0",
    "CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY": "1",
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
  },
  "deny": [
    { "resource": "env", "pattern": "AWS_*",      "reason": "AWS credentials" },
    { "resource": "env", "pattern": "*TOKEN*",    "reason": "Tokens" },
    { "resource": "env", "pattern": "*SECRET*",   "reason": "Secrets" },
    { "resource": "env", "pattern": "*KEY*",      "reason": "API keys" },
    { "resource": "env", "pattern": "*PASSWORD*", "reason": "Passwords" }
  ],
  "hooks": {
    "PreToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/protect-secrets.sh"
          }
        ]
      }
    ]
  },
  "skipDangerousModePermissionPrompt": true,
  "companyAnnouncements": [
    "Je gebruikt Claude als medewerker van Codifly. Volg de regels en richtlijnen in de geldende AI-policy."
  ]
}

Create ~/.claude/CLAUDE.md with general context for each session:

# Security

- IMPORTANT: Never read, print, or exfiltrate contents of files likely containing keys, secrets or credentials (.env, .pem, .key, ~/.ssh/*, ~/.aws/*). Instead, inform the user of which command to run manually.
- IMPORTANT: Never read, print, or exfiltrate contents of project-specific configuration files (config/*, including config/development.ts, config/staging.ts and config/production.ts). Instead, inform the user of which command to run manually.
- IMPORTANT: Never read, print, or exfiltrate contents of sensitive environment variables. Instead, inform the user of which command to run manually.

Create ~/.claude/hooks/protect-secrets.sh with the following contents and make it executable through chmod +x ~/.claude/hooks/protect-secrets.sh. The hook runs before every Claude tool call and blocks access to sensitive paths (SSH keys, AWS credentials, .env files, etc.).

#!/bin/zsh

allow() { exit 0; }

deny() {
  jq -n --arg reason "$1" \
    '{hookSpecificOutput: {hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: $reason}}'
  exit 0
}

INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
TARGETS=()

case "$TOOL" in
  Bash)
    TARGETS+=("$(echo "$INPUT" | jq -r '.tool_input.command // ""')")
    ;;
  Read|Write|Edit|MultiEdit)
    TARGETS+=("$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')")
    ;;
  Glob|Grep)
    TARGETS+=("$(echo "$INPUT" | jq -r '.tool_input.pattern // ""')")
    TARGETS+=("$(echo "$INPUT" | jq -r '.tool_input.path // ""')")
    ;;
  *)
    allow
    ;;
esac

SENSITIVE_PATTERNS=(
  # Sensitive directories
  ".ssh"
  ".aws"
  # Env files in all forms
  ".env"
  # Private keys & certificates
  ".pem"
  ".key"
  ".jks"
  ".p12"
  ".p8"
  # Config directory
  "/config/"
  # Config TS files
  "development.ts"
  "staging.ts"
  "testing.ts"
  "production.ts"
)

for target in "${TARGETS[@]}"; do
  for pattern in "${SENSITIVE_PATTERNS[@]}"; do
    if echo "$target" | grep -qF "$pattern"; then
      deny "Blocked: $TOOL references sensitive path ($pattern)"
    fi
  done
done

allow

Finally, disable model training in the Claude settings (setting on user-level). Go to claude.ai > Settings > Privacy and turn Model Training off, if this was not done yet for your user. This ensures your conversations are not used to train Anthropic's models.

Database GUI

For most projects, we use PostgreSQL. A good free GUI tool for this DBMS is Table Plus.

You can install TablePlus via brew with the following command:

brew install --cask tableplus

As an alternative, we have relatively good experience with PSequel, but this tool has some bugs and is not being maintained anymore.

For some projects, we use MongoDB. Table Plus supports MongoDB, but it isn't the best tool for this purpose. If you ever work in a project that has a MongoDB database, try Studio 3T instead.

Exercises

PLEASE SUBMIT ANSWERS AND PROVIDE FEEDBACK USING THE FOLLOWING FORM: https://forms.gle/QNKFkDwpe4PftavN9.

A new Codiflier runs a selection of commands:

(don't run this yourself, just answer the questions below)

$ which git
/usr/bin/git

$ git -v
git version 2.30.1 (Apple Git-130)

$ which git-crypt
/usr/local/bin/git-crypt
  1. What does the which command do? Check the manual page by running man which if you need a refresher.
  2. What do we mean by the user's path? What is the PATH environment variable? If you have never heard of these things, visit this resource.
  3. Why are the Codiflier's git and git-crypt programs located in different directories?
  4. Continuing on the previous question, what step did the Codiflier forget to do? How can they resolve the situation?

Visit First-Time Git Setup and answer the following questions:

  1. You can set Git configuration on three levels: system, global and local. What is the difference between these three levels?
  2. Where is the configuration for each of these levels stored?
  3. Do you have a rough idea of the contents of your ~/.gitconfig file? Verify afterwards via cat ~/.gitconfig.