# Cross Platform Config Manager
A small dotfiles system that handles Linux/macOS differences via symlinks — idempotent, organised by specificity.
Introduction
This is a short write-up on dotfiles management for developers working across multiple machines (different operating systems). Dotfiles are configuration files that customize your development environment, and when building a dotfiles system, we need to handle OS differences and ensure consistency across machines.
Dotfiles Management
Generally, dotfiles management systems require two properties to be useful:
Consistency: Your configurations should work the same way across different machines.
Portability: Your setup should work across different operating systems without manual tweaking.
A dotfiles system requires both properties. A simple approach would be to copy and paste config files from an external backup (i.e. a github repository), but this can be tiring, and maintaining the dotfiles themselves can become annoying. As such, symlinking copies configuration files to their target locations, and allows us to modify dotfiles directly from the dotfile folder, simplifying git versioning.
File Structure
We can organize configs by specificity. We have two main ways to differentiate a config folder/file. General dotfiles are configurations that are OS-agnostic, meaning that they may work on both platforms. For example, my neovim configurations do not have any OS-specific features, thus my linux machine and macos machine both share the same configurations. OS-specific configurations represent configuration files unique to that platform.
The script processes OS-specific configs first, then general configs. This allows general configs to override OS-specific ones where conflicts exist.
# Process OS-specific dotfiles first
create_config_symlinks "$DOTFILES_DIR"
create_home_symlinks "$DOTFILES_DIR"
# Then process general dotfiles
create_config_symlinks "$GENERAL_DIR"
create_home_symlinks "$GENERAL_DIR"
The directory structure:
~/Documents/dotFiles/
├── general/
│ └── package_A_config/
├── linux/
│ └── package_B_config/
└── mac/
└── package_C_config/
OS Detection
The script detects the operating system:
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
OS_DIR="linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
OS_DIR="mac"
else
OS_DIR="mac"
fi
This allows the same script to work on different platforms.
Symlink Management
The script creates symbolic links from target locations to the repository. Before creating each symlink, it removes any existing file:
if [ -e "$CONFIG_DIR/$folder_name" ]; then
rm -rf "$CONFIG_DIR/$folder_name"
fi
ln -sf "$folder" "$CONFIG_DIR/$folder_name"
Configuration Types
The script handles two types of configurations:
Config directories: Applications store configs in ~/.config/. These are handled by create_config_symlinks().
Dotfiles: Traditional dotfiles like .bashrc and .vimrc go in the home directory. These are handled by create_home_symlinks().
Config directories are entire folders, while dotfiles are individual files.
Functions
create_config_symlinks() {
local source_dir=$1
if [ ! -d "$source_dir" ]; then
return
fi
for folder in $(find "$source_dir" -maxdepth 1 -type d -not -path "$source_dir"); do
folder_name=$(basename "$folder")
if [[ "$folder_name" != .* ]]; then
if [ -e "$CONFIG_DIR/$folder_name" ]; then
rm -rf "$CONFIG_DIR/$folder_name"
fi
ln -sf "$folder" "$CONFIG_DIR/$folder_name"
fi
done
}
create_home_symlinks() {
local source_dir=$1
if [ ! -d "$source_dir" ]; then
return
fi
for file in $(find "$source_dir" -maxdepth 1 -type f -name ".*"); do
file_name=$(basename "$file")
if [ -e "$HOME/$file_name" ]; then
rm -f "$HOME/$file_name"
fi
ln -sf "$file" "$HOME/$file_name"
done
}
Deployment Process
The deployment follows these steps:
- Detect OS and set directory paths
- Create
~/.configif it doesn’t exist - Check directory existence
- Process OS-specific configs first
- Process general configs to override where needed
The script is idempotent, meaning that it can be run multiple times safely. Symbolic links maintain a live connection to your repository, so changes are immediately reflected.
Full Script
#!/bin/bash
# Detect operating system and set the source directory accordingly
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
OS_DIR="linux"
echo "Linux detected, using linux configuration."
elif [[ "$OSTYPE" == "darwin"* ]]; then
OS_DIR="mac"
echo "macOS detected, using mac configuration."
else
echo "Unknown operating system. Defaulting to mac configuration."
OS_DIR="mac"
fi
# Define source directories
DOTFILES_DIR="$HOME/Documents/dotFiles/$OS_DIR"
GENERAL_DIR="$HOME/Documents/dotFiles/general"
CONFIG_DIR="$HOME/.config"
# Create .config directory if it doesn't exist
mkdir -p "$CONFIG_DIR"
# Check if OS-specific dotfiles directory exists
if [ ! -d "$DOTFILES_DIR" ]; then
echo "Warning: OS-specific dotfiles directory $DOTFILES_DIR does not exist."
fi
# Check if general dotfiles directory exists
if [ ! -d "$GENERAL_DIR" ]; then
echo "Warning: General dotfiles directory $GENERAL_DIR does not exist."
fi
# Function to create symlinks for config folders
create_config_symlinks() {
local source_dir=$1
if [ ! -d "$source_dir" ]; then
return
fi
echo "Creating symlinks for config folders from $source_dir..."
for folder in $(find "$source_dir" -maxdepth 1 -type d -not -path "$source_dir"); do
folder_name=$(basename "$folder")
if [[ "$folder_name" != .* ]]; then
if [ -e "$CONFIG_DIR/$folder_name" ]; then
echo "Removing existing $CONFIG_DIR/$folder_name"
rm -rf "$CONFIG_DIR/$folder_name"
fi
echo "Creating symlink: $CONFIG_DIR/$folder_name -> $folder"
ln -sf "$folder" "$CONFIG_DIR/$folder_name"
fi
done
}
# Function to create symlinks for home directory files (dotfiles)
create_home_symlinks() {
local source_dir=$1
if [ ! -d "$source_dir" ]; then
return
fi
echo "Creating symlinks for home directory files from $source_dir..."
for file in $(find "$source_dir" -maxdepth 1 -type f -name ".*"); do
file_name=$(basename "$file")
if [ -e "$HOME/$file_name" ]; then
echo "Removing existing $HOME/$file_name"
rm -f "$HOME/$file_name"
fi
echo "Creating symlink: $HOME/$file_name -> $file"
ln -sf "$file" "$HOME/$file_name"
done
}
# Process OS-specific dotfiles first
create_config_symlinks "$DOTFILES_DIR"
create_home_symlinks "$DOTFILES_DIR"
# Then process general dotfiles
create_config_symlinks "$GENERAL_DIR"
create_home_symlinks "$GENERAL_DIR"
echo "Symlinks created successfully!"