176 lines
4.1 KiB
Bash
Executable file
176 lines
4.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# dotfiles-link - Symlink dotfiles without Nix
|
|
#
|
|
# Usage: dotfiles-link [--dry-run] [--force]
|
|
#
|
|
# Reads MANIFEST file from dotfiles root and creates symlinks accordingly.
|
|
# Works on both Linux and macOS (Darwin).
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
# ============================================================================
|
|
# Configuration
|
|
# ============================================================================
|
|
|
|
DOTFILES_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
CONFIG_DIR="${DOTFILES_DIR}/config"
|
|
MANIFEST="${DOTFILES_DIR}/MANIFEST"
|
|
|
|
# Detect OS
|
|
case "$(uname -s)" in
|
|
Darwin) OS="darwin" ;;
|
|
Linux) OS="linux" ;;
|
|
*)
|
|
echo "Unsupported OS: $(uname -s)" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# XDG config directory
|
|
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
|
|
# ============================================================================
|
|
# Functions
|
|
# ============================================================================
|
|
|
|
DRY_RUN=true
|
|
FORCE=false
|
|
|
|
usage() {
|
|
echo "Usage: $(basename "$0") [--apply] [--force]"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --apply Actually create symlinks (dry-run is default)"
|
|
echo " --force Remove existing files/symlinks before linking"
|
|
echo ""
|
|
echo "Manifest: ${MANIFEST}"
|
|
echo "Detected OS: ${OS}"
|
|
}
|
|
|
|
log() {
|
|
echo "[dotfiles-link] $*"
|
|
}
|
|
|
|
# Expand variables in target path
|
|
expand_path() {
|
|
local path="$1"
|
|
# Use eval to expand $HOME and $XDG_CONFIG_HOME
|
|
eval echo "$path"
|
|
}
|
|
|
|
create_symlink() {
|
|
local source="$1"
|
|
local target="$2"
|
|
local target_dir
|
|
target_dir="$(dirname "$target")"
|
|
|
|
# Check if source exists
|
|
if [[ ! -e $source ]]; then
|
|
log "SKIP: Source does not exist: $source"
|
|
return
|
|
fi
|
|
|
|
# Create parent directory if needed
|
|
if [[ ! -d $target_dir ]]; then
|
|
if $DRY_RUN; then
|
|
log "WOULD CREATE DIR: $target_dir"
|
|
else
|
|
log "CREATE DIR: $target_dir"
|
|
mkdir -p "$target_dir"
|
|
fi
|
|
fi
|
|
|
|
# Handle existing target
|
|
if [[ -e $target || -L $target ]]; then
|
|
if [[ -L $target ]] && [[ "$(readlink "$target")" == "$source" ]]; then
|
|
log "OK: $target (already linked)"
|
|
return
|
|
fi
|
|
|
|
if $FORCE; then
|
|
if $DRY_RUN; then
|
|
log "WOULD REMOVE: $target"
|
|
else
|
|
log "REMOVE: $target"
|
|
rm -rf "$target"
|
|
fi
|
|
else
|
|
log "SKIP: $target exists (use --force to override)"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# Create symlink
|
|
if $DRY_RUN; then
|
|
log "WOULD LINK: $target -> $source"
|
|
else
|
|
log "LINK: $target -> $source"
|
|
ln -s "$source" "$target"
|
|
fi
|
|
}
|
|
|
|
process_manifest() {
|
|
if [[ ! -f $MANIFEST ]]; then
|
|
log "ERROR: Manifest not found: $MANIFEST"
|
|
exit 1
|
|
fi
|
|
|
|
while IFS= read -r line || [[ -n $line ]]; do
|
|
# Skip comments and empty lines
|
|
[[ -z $line || $line =~ ^[[:space:]]*# ]] && continue
|
|
|
|
# Parse: os:source:target
|
|
local entry_os="${line%%:*}"
|
|
local rest="${line#*:}"
|
|
local source="${rest%%:*}"
|
|
local target="${rest#*:}"
|
|
|
|
# Check if this entry applies to current OS
|
|
if [[ $entry_os != "common" && $entry_os != "$OS" ]]; then
|
|
continue
|
|
fi
|
|
|
|
# Expand variables and create symlink
|
|
target="$(expand_path "$target")"
|
|
create_symlink "${CONFIG_DIR}/${source}" "$target"
|
|
done <"$MANIFEST"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Main
|
|
# ============================================================================
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--apply)
|
|
DRY_RUN=false
|
|
shift
|
|
;;
|
|
--force)
|
|
FORCE=true
|
|
shift
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
log "Dotfiles directory: ${DOTFILES_DIR}"
|
|
log "OS: ${OS}"
|
|
$DRY_RUN && log "DRY RUN MODE - no changes will be made"
|
|
echo ""
|
|
|
|
process_manifest
|
|
|
|
echo ""
|
|
log "Done!"
|