Configure project after cloning.
git config core.hooksPath .githooks
chmod +x ./macos/bin/*.shThe chmod step is required for launchd and direct runs. The pre-commit hook sets +x on staged .sh/.ps1 files; entry scripts in macos/bin/ are also tracked executable in git.
Bash scripts that install/upgrade Raspberry Pi/Ubuntu packages.
The Ubuntu workflow uses systemd instead of launchd so the daily package update job runs as root at 4:00 AM.
Files live in ubuntu/ and include:
ubuntu/run-package-manage.shfor lock/log management and workflow executionubuntu/package-manage.servicefor the systemd service unitubuntu/package-manage.timerfor the 4:00 AM daily triggerubuntu/bin/install-chad.shfor the actual Ubuntu install/update step
First-time setup
sudo cp ./ubuntu/package-manage.service /etc/systemd/system/package-manage.service
sudo cp ./ubuntu/package-manage.timer /etc/systemd/system/package-manage.timer
sudo systemctl daemon-reload
sudo systemctl enable --now package-manage.timer
sudo systemctl status package-manage.timerUpdate existing setup after changes
sudo cp ./ubuntu/package-manage.service /etc/systemd/system/package-manage.service
sudo cp ./ubuntu/package-manage.timer /etc/systemd/system/package-manage.timer
sudo systemctl daemon-reload
sudo systemctl restart package-manage.timer
sudo systemctl status package-manage.timerManual run
sudo systemctl start package-manage.service
sudo journalctl -u package-manage.service -fCheck logs
tail -f ./logs/package-manage.logA successful run ends with systemd finish in the log.
Bash scripts that install/upgrade Mac packages.
Install brew via Terminal
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Some brew cask installs/upgrades require sudo. For launchd runs (no terminal), create an askpass script outside this repo and make it executable for the user that launches the job:
mkdir -p ~/.ssh/secrets; cat > ~/.ssh/secrets/.supwd.sh <<'EOF'
#!/opt/homebrew/bin/bash
echo 'YOUR_MACOS_PASSWORD'
EOF
chmod 700 ~/.ssh/secrets/.supwd.shValidate that the script works before relying on it:
export SUDO_ASKPASS="$HOME/.ssh/secrets/.supwd.sh"
sudo -A -vThe mac launchd setup relies on sudo -A and SUDO_ASKPASS for privileged operations, so the password script must exist and be executable for the user under ~/Library/LaunchAgents and the matching systemd/launchd environment.
Machine-specific install scripts live in macos/bin/ (e.g. install-chad.sh). Shared helpers are in macos/lib/install-helpers.sh.
Source libs in this order:
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${script_dir}/../lib/sudo-askpass.sh"
source "${script_dir}/../lib/install-helpers.sh"install-helpers.sh provides setup_brew_env, brew_bootstrap, brew_install_formula, cask helpers (brew_install_cask, brew_ensure_cask, brew_upgrade_force_cask), nvm_install_lts_prune, git_clone_or_pull, log_section, and brew_cleanup. Cask helpers call refresh_sudo from sudo-askpass.sh.
To add a new machine, copy install-chad.sh, keep the shared lib sourcing and helper calls, and swap the package lists plus machine-specific libs (e.g. copy-projects-chad.sh → copy-projects-lisa.sh). Set PACKAGE_MANAGE_LOG_PREFIX before sourcing helpers to distinguish machines in logs.
Use launchd to run the daily mac package workflow via run-package-manage.sh. The wrapper:
- prevents overlapping runs with a lock file at
macos/state/package-manage.lock - uses
caffeinateto keep the Mac awake during long installs - refreshes sudo before cask installs
- writes start/finish markers to
logs/package-manage.logfor each phase (launchd,install-chad.sh,Update-Modules-Mac.ps1)
First-time setup
cp ./macos/launchd/package-manage.plist ~/Library/LaunchAgents/; launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/package-manage.plistUpdate existing setup after script path changes
launchctl bootout gui/$(id -u)/com.appfire-chadscribner.package-manage; \
cp ./macos/launchd/package-manage.plist ~/Library/LaunchAgents/; \
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/package-manage.plistManual run (same as scheduled launchd job)
Run the full daily workflow now with the same environment and logging as launchd (git pull, install-chad.sh, and Update-Modules-Mac.ps1 via the wrapper):
launchctl kickstart -k gui/$(id -u)/com.appfire-chadscribner.package-manageOr run the wrapper directly:
./macos/bin/run-package-manage.shVerify and monitor logs
launchctl print gui/$(id -u)/com.appfire-chadscribner.package-manage; \
tail -f ./logs/package-manage.log; \
tail -f ./logs/package-manage-launchd.logIf launchctl print shows last exit code = 78: EX_CONFIG, the wrapper scripts are not executable. Run chmod +x ./macos/bin/*.sh, then repeat the update steps (bootout, copy plist, bootstrap) before kickstart.
A successful run ends with launchd finish in package-manage.log.
Use launchd to back up ~/Library/KeyBindings/DefaultKeyBinding.dict into ~/projects/KeyBindings/ daily at 3:00 AM via run-keybindings-backup.sh. The wrapper:
- creates the destination directory if needed
- copies the live keybindings file into
~/projects/KeyBindings/ - writes start/finish markers to
logs/keybindings-backup.log
First-time setup
cp ./macos/launchd/keybindings-backup.plist ~/Library/LaunchAgents/; \
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/keybindings-backup.plistUpdate existing setup after script path changes
launchctl bootout gui/$(id -u)/com.appfire-chadscribner.keybindings-backup; \
cp ./macos/launchd/keybindings-backup.plist ~/Library/LaunchAgents/; \
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/keybindings-backup.plistManual run (same as scheduled launchd job)
launchctl kickstart -k gui/$(id -u)/com.appfire-chadscribner.keybindings-backupOr run the wrapper directly:
./macos/bin/run-keybindings-backup.shVerify and monitor logs
launchctl print gui/$(id -u)/com.appfire-chadscribner.keybindings-backup; \
tail -f ./logs/keybindings-backup.logA successful run ends with launchd finish in keybindings-backup.log.
Powershell scripts that install/upgrade Windows packages. Different files are used for different machines.
Install Chocolatey via PowerShell
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))Download and install the lates PowerShell Core release from GitHub.
Set the Execution Policy in PowerShell Core
Set-ExecutionPolicy UnrestrictedUnblock all script files in the package-manage directory
dir -r | Unblock-FileRun all scripts using PowerShell Core