diff --git a/apple/fastlane/README.md b/apple/fastlane/README.md new file mode 100644 index 0000000000..188a51c41e --- /dev/null +++ b/apple/fastlane/README.md @@ -0,0 +1,72 @@ +fastlane documentation +---- + +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +```sh +xcode-select --install +``` + +For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) + +# Available Actions + +## iOS + +### ios meta + +```sh +[bundle exec] fastlane ios meta +``` + + + +### ios shots + +```sh +[bundle exec] fastlane ios shots +``` + + + +### ios build + +```sh +[bundle exec] fastlane ios build +``` + + + +### ios upload + +```sh +[bundle exec] fastlane ios upload +``` + + + +### ios privacy + +```sh +[bundle exec] fastlane ios privacy +``` + + + +### ios ship + +```sh +[bundle exec] fastlane ios ship +``` + + + +---- + +This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. + +More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). + +The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/dotfiles/fish/native-macos.fish b/dotfiles/fish/native-macos.fish new file mode 100644 index 0000000000..9761e0d755 --- /dev/null +++ b/dotfiles/fish/native-macos.fish @@ -0,0 +1,29 @@ +# Shared native shell for the Mac family. Machine overrides: config.local.fish. +set -gx AC_ROOT (path resolve (status dirname)/../..) +set -gx AESTHETIC $USER +set -gx PATH /opt/homebrew/bin /opt/homebrew/sbin /usr/local/bin $HOME/.local/bin $PATH +if test -d $AC_ROOT/aesthetic-computer-vault + set -gx AESTHETIC_VAULT $AC_ROOT/aesthetic-computer-vault +else if test -d $HOME/aesthetic-computer-vault + set -gx AESTHETIC_VAULT $HOME/aesthetic-computer-vault +end +if not status is-interactive + set -gx nogreet true +end +if type -q fnm + fnm env --use-on-cd --shell fish | source +end +set -gx PAGER cat +set -gx GIT_PAGER cat +set -gx MANPAGER cat +source $AC_ROOT/.devcontainer/config.fish +set -g fish_function_path $AC_ROOT/dotfiles/fish/functions $fish_function_path +source $AC_ROOT/dotfiles/fish/functions/ac-piece-logs.fish +# The shared devcontainer config still carries a Linux-only reload alias. +alias reload 'source ~/.config/fish/config.fish' +if test -f $AC_ROOT/easel/shell/easel.fish + source $AC_ROOT/easel/shell/easel.fish +end +if test -f $HOME/.config/fish/config.local.fish + source $HOME/.config/fish/config.local.fish +end diff --git a/dotfiles/install.sh b/dotfiles/install.sh index b9f738f7c7..9ab64abdaf 100755 --- a/dotfiles/install.sh +++ b/dotfiles/install.sh @@ -6,6 +6,12 @@ # -u: exit on unset variables set -eu +# Native Macs share the family setup; the remaining path is for Linux. +if [ "$(uname -s)" = Darwin ]; then + script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) + exec bash "$script_dir/../scripts/mac-family-setup.sh" --install +fi + create_symlinks() { # Get the directory in which this script lives. script_dir=$(dirname "$(readlink -f "$0")") diff --git a/dotfiles/symlink.sh b/dotfiles/symlink.sh index 116fa79b1a..11a8382d03 100755 --- a/dotfiles/symlink.sh +++ b/dotfiles/symlink.sh @@ -4,6 +4,12 @@ # -u: exit on unset variables set -eu +# Native Macs share the family setup; the remaining path is for Linux. +if [ "$(uname -s)" = Darwin ]; then + script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) + exec bash "$script_dir/../scripts/mac-family-setup.sh" --dotfiles-only +fi + create_symlinks() { # Get the directory in which this script lives. script_dir=$(dirname "$(readlink -f "$0")") diff --git a/scripts/mac-family-setup.sh b/scripts/mac-family-setup.sh new file mode 100644 index 0000000000..5bd324b7f0 --- /dev/null +++ b/scripts/mac-family-setup.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# Native Mac shell + tracked dotfiles. Run as the login user, never with sudo. +set -euo pipefail +AC_ROOT=$(cd "$(dirname "$0")/.." && pwd) +export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:$HOME/.local/bin:$PATH" +mode=${1:---install} +case "$mode" in --install|--dotfiles-only|--check) ;; *) echo 'Usage: mac-family-setup.sh [--install|--dotfiles-only|--check]' >&2; exit 2;; esac +[[ $(uname -s) == Darwin ]] || { echo 'macOS required' >&2; exit 1; } +[[ $EUID != 0 ]] || { echo 'Run as your login user, not root.' >&2; exit 1; } +[[ -f "$AC_ROOT/.devcontainer/config.fish" ]] || { echo 'Expand the checkout first: git sparse-checkout disable' >&2; exit 1; } +if [[ $mode == --check ]]; then + failed=0 + for cmd in git brew fish fnm node npm jq rg starship nvim; do + if command -v "$cmd" >/dev/null; then printf '%s: %s\n' "$cmd" "$(command -v "$cmd")"; else echo "MISSING: $cmd"; failed=1; fi + done + for entry in '.config/starship.toml:starship.toml' '.config/nvim:nvim' '.emacs:emacs.el'; do + dest="$HOME/${entry%%:*}"; src="$AC_ROOT/dotfiles/dot_config/${entry#*:}" + if [[ -L "$dest" && $(readlink "$dest") == "$src" ]]; then echo "linked: $dest"; else echo "UNLINKED: $dest"; failed=1; fi + done + if command -v fish >/dev/null; then + fish -c 'test "$AC_ROOT" = $argv[1]; and functions -q ac ac-help ac-site ac-piece-logs; and type -q c co cr cor; and node --version; and npm --version' -- "$AC_ROOT" || failed=1 + login_shell=$(dscl . -read "/Users/$(id -un)" UserShell | awk '{print $2}') + [[ $login_shell == "$(command -v fish)" ]] || { echo "Login shell still $login_shell"; failed=1; } + fi + if [[ $(git -C "$AC_ROOT" config --get core.sparseCheckout || true) == true ]]; then + echo 'AC checkout is still sparse'; failed=1 + fi + if command -v fish >/dev/null; then + expected_node="v$(tr -d '[:space:]' < "$AC_ROOT/.node-version")" + actual_node=$(fish -c 'node --version') + [[ $actual_node == "$expected_node" ]] || { echo "Node: expected $expected_node, found $actual_node"; failed=1; } + fi + if [[ -x "$HOME/.local/bin/claude-sleep" ]]; then + if sudo -n -l /usr/bin/pmset -a disablesleep 1 >/dev/null 2>&1; then + echo 'Slab sleep-control permission: OK' + else + echo 'Slab sleep-control permission missing: run bash slab/install.sh --sleep-control-only' + failed=1 + fi + fi + git -C "$AC_ROOT" log -1 --format='AC: %h %s' + exit "$failed" +fi +if [[ $mode == --install ]]; then + if ! command -v brew >/dev/null; then + # Homebrew owns the administrator prompt; never store a password. + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + fi + eval "$(brew shellenv)" + brew install fish fnm jq ripgrep starship neovim coreutils + eval "$(fnm env --shell bash)" + node_version=$(tr -d '[:space:]' < "$AC_ROOT/.node-version") + fnm install "$node_version" + fnm default "$node_version" + fnm use "$node_version" +fi +backup_dir='' +backup() { + if [[ -e "$1" || -L "$1" ]]; then + if [[ -z $backup_dir ]]; then + mkdir -p "$HOME/.local/state/ac-dotfiles" + backup_dir=$(mktemp -d "$HOME/.local/state/ac-dotfiles/backup.XXXXXXXX") + echo "Previous settings: $backup_dir" + fi + mkdir -p "$backup_dir/$(dirname "${1#"$HOME/"}")" + mv "$1" "$backup_dir/${1#"$HOME/"}" + fi +} +link() { + [[ -L "$2" && $(readlink "$2") == "$1" ]] && return 0 + backup "$2" + mkdir -p "$(dirname "$2")" + ln -s "$1" "$2" +} +# Older dotfiles linked the entire Fish directory into Git. Detach that +# link before editing config, retaining local completions and variables. +if [[ -L "$HOME/.config/fish" ]]; then + previous_fish='' + if [[ -d "$HOME/.config/fish" ]]; then previous_fish=$(cd "$HOME/.config/fish" && pwd -P); fi + backup "$HOME/.config/fish" + # Do not put subsequent file backups beneath the saved directory symlink. + backup_dir='' + mkdir -p "$HOME/.config/fish" + if [[ -n $previous_fish ]]; then cp -R "$previous_fish/." "$HOME/.config/fish/"; fi +fi +mkdir -p "$HOME/.config/fish" +config=$(mktemp) +# Fish single-quoted paths only need backslash and quote escaping. +escaped_root=$(printf '%s' "$AC_ROOT" | sed "s/\\\\/\\\\\\\\/g; s/'/\\\\'/g") +printf "# Managed by scripts/mac-family-setup.sh\nsource '%s/dotfiles/fish/native-macos.fish'\n" "$escaped_root" > "$config" +if ! cmp -s "$config" "$HOME/.config/fish/config.fish"; then + backup "$HOME/.config/fish/config.fish" + mv "$config" "$HOME/.config/fish/config.fish" +else + rm "$config" +fi +link "$AC_ROOT/dotfiles/dot_config/nvim" "$HOME/.config/nvim" +link "$AC_ROOT/dotfiles/dot_config/starship.toml" "$HOME/.config/starship.toml" +link "$AC_ROOT/dotfiles/dot_config/emacs.el" "$HOME/.emacs" +if [[ $mode == --install ]]; then + fish_bin=$(command -v fish) + if ! grep -Fxq "$fish_bin" /etc/shells; then printf '%s\n' "$fish_bin" | sudo tee -a /etc/shells >/dev/null; fi + login_shell=$(dscl . -read "/Users/$(id -un)" UserShell | awk '{print $2}') + if [[ $login_shell != "$fish_bin" ]]; then sudo dscl . -change "/Users/$(id -un)" UserShell "$login_shell" "$fish_bin"; fi + if [[ -x "$HOME/.local/bin/claude-sleep" ]]; then + bash "$AC_ROOT/slab/install.sh" --sleep-control-only + fi + exec bash "$0" --check +fi +echo 'Dotfiles linked. Run --install for packages and login shell, or --check to audit.' diff --git a/scripts/mac-native-bootstrap.sh b/scripts/mac-native-bootstrap.sh index 648517ac9f..dcf8d866d7 100755 --- a/scripts/mac-native-bootstrap.sh +++ b/scripts/mac-native-bootstrap.sh @@ -136,30 +136,8 @@ ok "login shell: $(dscl . -read /Users/$USER UserShell | awk '{print $2}')" # ----------------------------------------------------------------------------- step "9. ~/.config/fish/config.fish" # ----------------------------------------------------------------------------- -mkdir -p "$HOME/.config/fish/conf.d" "$HOME/.config/fish/functions" -if ! [[ -f "$HOME/.config/fish/config.fish" ]] || \ - ! grep -q "$AC_ROOT/.devcontainer/config.fish" "$HOME/.config/fish/config.fish"; then - cat > "$HOME/.config/fish/config.fish" < the tailnet by name/IP (e.g. blueberry = `100.79.75.53`); the tailnet gives the > network path, `authorized_keys` gives the auth. -## Blueberry bootstrap +## Historical Blueberry SSH bootstrap -Blueberry does **not** trust the fleet key yet and isn't SSH-reachable, so it can't -be pushed to. `blueberry-join.sh` is a self-contained one-shot that adds the fleet +During Blueberry's original setup it did not yet trust the fleet key. `blueberry-join.sh` is a self-contained one-shot that adds the fleet keys to its `authorized_keys` and configures its cursor identity. It's staged on neo, so run this **on blueberry** (blueberry already holds a key to neo): diff --git a/toolchain/macos/gfn-window.sh b/toolchain/macos/gfn-window.sh new file mode 100755 index 0000000000..bb7e64a416 --- /dev/null +++ b/toolchain/macos/gfn-window.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# gfn-window.sh — let the GeForce NOW window on a Mac get small. +# +# gfn-window.sh shrink [W,H] lower the window floor (default 480,300) and +# keep the stream windowed; relaunches GFN +# gfn-window.sh restore put NVIDIA's original config back +# gfn-window.sh status show the current floor and whether it's applied +# +# Why: GeForce NOW's Mac client refuses to go below 900×600 no matter how you +# drag it, and the floor is not in Settings. It lives in a switch list inside +# the app bundle, /Applications/GeForceNOW.app/Contents/Resources/GeForceNOW.json: +# +# "nv-min-window-size=900,600" the floor (this script lowers it) +# "nv-sdl-force-windowed=true" added: the stream stays a window instead +# of taking its own full-screen Space +# +# Passing --nv-min-window-size on the command line does nothing; the bundle +# JSON wins (measured 2026-09-18). Editing the file is the only route. +# +# Caveats: +# * Editing the bundle breaks its code signature. Already-installed GFN +# keeps launching fine (tested macOS 26/27); a fresh download would need +# to be opened once before shrinking. +# * GFN self-updates on launch ("nv-startup-autoupdate"). An update rewrites +# this file — run `shrink` again if the floor comes back. +# * The floor is a minimum, not a size: after shrinking, drag the window to +# whatever you like. Both axes clamp independently; no aspect lock. +# +# Nothing else about the app is touched. The original file is saved once to +# ~/.config/gfn-window/GeForceNOW.json.stock and `restore` copies it back. + +set -eu +APP=/Applications/GeForceNOW.app +JSON="$APP/Contents/Resources/GeForceNOW.json" +STOCK="$HOME/.config/gfn-window/GeForceNOW.json.stock" +DEFAULT_FLOOR=480,300 + +die() { echo "gfn-window: $*" >&2; exit 1; } +[ -f "$JSON" ] || die "GeForce NOW is not installed at $APP" + +floor() { grep -o '"nv-min-window-size=[0-9]*,[0-9]*"' "$JSON" | tr -d '"' | cut -d= -f2; } +windowed() { grep -q '"nv-sdl-force-windowed=true"' "$JSON" && echo yes || echo no; } +running() { pgrep -x GeForceNOW >/dev/null; } + +# The file is admin-writable on a normal install; fall back to sudo otherwise. +write() { # $1 = temp file with the new contents + if [ -w "$JSON" ]; then cp "$1" "$JSON"; else sudo cp "$1" "$JSON"; fi +} + +relaunch() { + if running; then + echo "quitting GeForce NOW (any active stream ends)" + osascript -e 'tell application "GeForceNOW" to quit' 2>/dev/null || pkill -x GeForceNOW + for _ in 1 2 3 4 5 6 7 8 9 10; do running || break; sleep 1; done + fi + open -a GeForceNOW +} + +status() { + echo "floor: $(floor) (stock 900,600)" + echo "windowed: $(windowed)" + echo "backup: $( [ -f "$STOCK" ] && echo "$STOCK" || echo none )" + echo "running: $( running && echo yes || echo no )" +} + +case "${1:-status}" in + shrink) + NEW="${2:-$DEFAULT_FLOOR}" + echo "$NEW" | grep -Eq '^[0-9]+,[0-9]+$' || die "size must look like W,H — got '$NEW'" + if [ ! -f "$STOCK" ]; then + mkdir -p "$(dirname "$STOCK")"; cp "$JSON" "$STOCK"; echo "saved original to $STOCK" + fi + TMP=$(mktemp) + sed -e "s/\"nv-min-window-size=[0-9]*,[0-9]*\"/\"nv-min-window-size=$NEW\"/" "$JSON" > "$TMP" + if ! grep -q '"nv-sdl-force-windowed=true"' "$TMP"; then + # Slot it after the resizable switch; both lines carry a trailing comma. + sed -i '' 's/^\( *\)"nv-sdl-resizable=true",$/&\ +\1"nv-sdl-force-windowed=true",/' "$TMP" + fi + grep -q "nv-min-window-size=$NEW" "$TMP" || die "could not find the floor switch in $JSON — NVIDIA changed the file?" + write "$TMP"; rm -f "$TMP" + echo "floor is now $NEW, stream stays windowed" + relaunch; status ;; + restore) + [ -f "$STOCK" ] || die "no backup at $STOCK — nothing to restore" + write "$STOCK"; echo "original config restored" + relaunch; status ;; + status) status ;; + *) sed -n '2,7p' "$0"; exit 1 ;; +esac