setup.sh (9002B)
1 #!/usr/bin/env bash 2 set -euo pipefail 3 4 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 5 6 # Detect OS and run appropriate script 7 if [[ "$(uname -s)" == "Darwin" ]]; then 8 echo "macOS detected, running macos/setup.sh..." 9 exec "$SCRIPT_DIR/macos/setup.sh" 10 exit 11 fi 12 13 FEDORA_DIR="$SCRIPT_DIR/fedora" 14 15 # COPR repos 16 echo "Enabling COPR repositories..." 17 while IFS= read -r repo || [ -n "$repo" ]; do 18 [ -z "$repo" ] && continue 19 owner="${repo%%/*}" 20 name="${repo##*/}" 21 repo_id="copr:copr.fedorainfracloud.org:${owner}:${name}" 22 repo_file="/etc/yum.repos.d/_copr_${owner}-${name}.repo" 23 24 if [ -f "$repo_file" ]; then 25 echo "COPR already enabled: $repo" 26 continue 27 fi 28 29 echo "Adding COPR: $repo" 30 sudo tee "$repo_file" >/dev/null <<EOF 31 [$repo_id] 32 name=Copr repo for $name owned by $owner 33 baseurl=https://download.copr.fedorainfracloud.org/results/$repo/fedora-\$releasever-\$basearch/ 34 type=rpm-md 35 skip_if_unavailable=True 36 gpgcheck=1 37 gpgkey=https://download.copr.fedorainfracloud.org/results/$repo/pubkey.gpg 38 repo_gpgcheck=0 39 enabled=1 40 enabled_metadata=1 41 EOF 42 done <"$FEDORA_DIR/copr.txt" 43 44 # OBS repos 45 echo "Enabling OBS repositories..." 46 while IFS= read -r repo || [ -n "$repo" ]; do 47 [ -z "$repo" ] && continue 48 name="${repo##*:}" 49 repo_file="/etc/yum.repos.d/${name}.repo" 50 51 if [ -f "$repo_file" ]; then 52 echo "OBS already enabled: $repo" 53 continue 54 fi 55 56 echo "Adding OBS: $repo" 57 obs_path="${repo//:/:}" 58 sudo tee "$repo_file" >/dev/null <<EOF 59 [home_${name}] 60 name=${repo} (Fedora_\$releasever) 61 type=rpm-md 62 baseurl=https://download.opensuse.org/repositories/${obs_path}/Fedora_\$releasever/ 63 gpgcheck=1 64 gpgkey=https://download.opensuse.org/repositories/${obs_path}/Fedora_\$releasever/repodata/repomd.xml.key 65 enabled=1 66 EOF 67 done <"$FEDORA_DIR/obs.txt" 68 69 # Vendor repos 70 echo "Checking vendor repositories..." 71 while IFS= read -r repo || [ -n "$repo" ]; do 72 [ -z "$repo" ] && continue 73 case "$repo" in 74 1password) 75 repo_file="/etc/yum.repos.d/1password.repo" 76 if [ ! -f "$repo_file" ]; then 77 echo "Adding 1Password repo..." 78 sudo rpm --import https://downloads.1password.com/linux/keys/1password.asc 79 sudo tee "$repo_file" >/dev/null <<EOF 80 [1password] 81 name=1Password Stable Channel 82 baseurl=https://downloads.1password.com/linux/rpm/stable/\$basearch 83 enabled=1 84 gpgcheck=1 85 repo_gpgcheck=1 86 gpgkey=file:///etc/pki/rpm-gpg/1password.asc 87 EOF 88 else 89 echo "Already enabled: 1password" 90 fi 91 ;; 92 google-chrome) 93 repo_file="/etc/yum.repos.d/google-chrome.repo" 94 if [ ! -f "$repo_file" ]; then 95 echo "Adding Google Chrome repo..." 96 sudo tee "$repo_file" >/dev/null <<EOF 97 [google-chrome] 98 name=google-chrome 99 baseurl=https://dl.google.com/linux/chrome/rpm/stable/x86_64 100 skip_if_unavailable=True 101 gpgcheck=1 102 gpgkey=https://dl.google.com/linux/linux_signing_key.pub 103 enabled=0 104 EOF 105 else 106 echo "Already enabled: google-chrome" 107 fi 108 ;; 109 *) 110 echo "Unknown vendor repo: $repo" 111 ;; 112 esac 113 done <"$FEDORA_DIR/repos.txt" 114 115 # RPM Fusion 116 echo "Checking RPM Fusion repositories..." 117 while IFS= read -r repo || [ -n "$repo" ]; do 118 [ -z "$repo" ] && continue 119 if [ -f "/etc/yum.repos.d/${repo}.repo" ]; then 120 echo "Already enabled: $repo" 121 continue 122 fi 123 124 echo "Adding $repo..." 125 type="${repo#rpmfusion-}" 126 sudo rpm-ostree install --idempotent \ 127 "https://mirrors.rpmfusion.org/${type}/fedora/rpmfusion-${type}-release-$(rpm -E %fedora).noarch.rpm" 128 done <"$FEDORA_DIR/rpmfusion.txt" 129 130 # Layer RPM packages 131 echo "Layering packages with rpm-ostree..." 132 mapfile -t packages < <(sed 's/#.*//;s/[[:space:]]*$//;/^$/d' "$FEDORA_DIR/packages.txt") 133 134 # Filter out already installed packages 135 packages_to_install=() 136 for pkg in "${packages[@]}"; do 137 if ! rpm -q "$pkg" &>/dev/null; then 138 packages_to_install+=("$pkg") 139 else 140 echo "Already installed: $pkg" 141 fi 142 done 143 144 if [ ${#packages_to_install[@]} -gt 0 ]; then 145 sudo rpm-ostree install --idempotent "${packages_to_install[@]}" 146 else 147 echo "All packages already installed" 148 fi 149 150 # Remove base packages with rpm-ostree overrides 151 echo "Applying base package removal overrides..." 152 mapfile -t overrides < <(sed 's/#.*//;s/[[:space:]]*$//;/^$/d' "$FEDORA_DIR/overrides.txt") 153 154 if [ ${#overrides[@]} -gt 0 ]; then 155 mapfile -t current_overrides < <( 156 rpm-ostree status --json | python3 -c ' 157 import json, sys 158 data = json.load(sys.stdin) 159 seen = set() 160 for deployment in data.get("deployments", []): 161 for pkg in deployment.get("requested-base-removals", []): 162 if pkg not in seen: 163 seen.add(pkg) 164 print(pkg) 165 ' 166 ) 167 168 overrides_to_apply=() 169 for pkg in "${overrides[@]}"; do 170 already_removed=false 171 for removed in "${current_overrides[@]}"; do 172 if [ "$pkg" = "$removed" ]; then 173 already_removed=true 174 break 175 fi 176 done 177 178 if [ "$already_removed" = true ]; then 179 echo "Override already applied: $pkg" 180 else 181 overrides_to_apply+=("$pkg") 182 fi 183 done 184 185 if [ ${#overrides_to_apply[@]} -gt 0 ]; then 186 sudo rpm-ostree override remove "${overrides_to_apply[@]}" 187 else 188 echo "All base package removal overrides already applied" 189 fi 190 else 191 echo "No base package removal overrides configured" 192 fi 193 194 # Manual installs 195 196 ## bun 197 if ! command -v bun &>/dev/null; then 198 echo "Installing bun..." 199 curl -fsSL https://bun.sh/install | bash 200 fi 201 202 ## opencode 203 if ! command -v opencode &>/dev/null; then 204 echo "Installing opencode..." 205 curl -fsSL https://opencode.ai/install | bash 206 fi 207 208 ## codex 209 if command -v codex &>/dev/null; then 210 echo "Updating codex..." 211 codex update || true 212 else 213 echo "Installing codex..." 214 sudo npm install -g @openai/codex 215 fi 216 217 ## neovim-remote 218 if ! command -v nvr &>/dev/null; then 219 echo "Installing neovim-remote..." 220 pip install --user neovim-remote 221 fi 222 223 # Install local RPMs 224 RPMS_DIR="$SCRIPT_DIR/rpms" 225 if [ -d "$RPMS_DIR" ]; then 226 echo "Checking local RPMs from $RPMS_DIR..." 227 228 # Build list of RPMs to install 229 rpms_to_install=() 230 for rpm in "$RPMS_DIR"/*.rpm; do 231 [ -f "$rpm" ] || continue 232 233 pkg_name=$(rpm -qp --queryformat '%{NAME}' "$rpm" 2>/dev/null) 234 if [ -z "$pkg_name" ]; then 235 echo "Warning: Could not read package name from $rpm" 236 continue 237 fi 238 239 # Check if this exact version is installed 240 pkg_version=$(rpm -qp --queryformat '%{VERSION}-%{RELEASE}' "$rpm" 2>/dev/null) 241 installed_version=$(rpm -q --queryformat '%{VERSION}-%{RELEASE}' "$pkg_name" 2>/dev/null || echo "not-installed") 242 243 if [ "$installed_version" = "$pkg_version" ]; then 244 echo "Already installed: $pkg_name-$pkg_version" 245 else 246 if [ "$installed_version" = "not-installed" ]; then 247 echo "Will install: $pkg_name-$pkg_version" 248 else 249 echo "Will update: $pkg_name from $installed_version to $pkg_version" 250 fi 251 rpms_to_install+=("$rpm") 252 fi 253 done 254 255 # Build uninstall flags for updates and collect all rpms to install 256 uninstall_flags=() 257 all_rpms=() 258 for rpm in "${rpms_to_install[@]}"; do 259 pkg_name=$(rpm -qp --queryformat '%{NAME}' "$rpm" 2>/dev/null) 260 if rpm -q "$pkg_name" &>/dev/null; then 261 uninstall_flags+=("--uninstall=$pkg_name") 262 fi 263 all_rpms+=("$rpm") 264 done 265 266 # Single transaction for all operations 267 if [ ${#rpms_to_install[@]} -gt 0 ]; then 268 if [ ${#uninstall_flags[@]} -gt 0 ]; then 269 echo "Updating ${#uninstall_flags[@]} and installing ${#all_rpms[@]} package(s)..." 270 sudo rpm-ostree install --idempotent --force-replacefiles "${uninstall_flags[@]}" "${all_rpms[@]}" 271 else 272 echo "Installing ${#all_rpms[@]} package(s)..." 273 sudo rpm-ostree install --idempotent "${all_rpms[@]}" 274 fi 275 else 276 echo "All local RPMs already installed" 277 fi 278 fi 279 280 # Update Flatpaks 281 flatpak update -y --noninteractive 282 283 # Enable hibernation support 284 SWAP_DEV="/dev/nvme0n1p3" 285 if [ -b "$SWAP_DEV" ]; then 286 SWAP_UUID=$(lsblk -no UUID "$SWAP_DEV" 2>/dev/null || echo "") 287 if [ -n "$SWAP_UUID" ]; then 288 if ! rpm-ostree kargs | grep -q "resume=UUID=$SWAP_UUID"; then 289 echo "Adding resume kernel argument for hibernation..." 290 sudo rpm-ostree kargs --append="resume=UUID=$SWAP_UUID" 291 echo "Hibernation enabled. Reboot required to apply changes." 292 else 293 echo "Resume kernel argument already set" 294 fi 295 else 296 echo "Warning: Could not determine UUID for $SWAP_DEV" 297 fi 298 else 299 echo "Warning: Swap partition $SWAP_DEV not found" 300 fi 301 302 # Codex 303 mkdir -p "$HOME/.codex" 304 305 # Symlink configs 306 "$SCRIPT_DIR/link.sh" 307 308 # Map tuned-ppd balanced mode to battery-optimized profile 309 if [ -d "/etc/tuned/profiles/battery-optimized" ]; then 310 echo "Configuring tuned-ppd profile mapping..." 311 sudo tee /etc/tuned/ppd.conf >/dev/null <<'EOF' 312 [main] 313 # The default PPD profile 314 default=balanced 315 battery_detection=true 316 sysfs_acpi_monitor=true 317 318 [profiles] 319 # PPD = TuneD 320 power-saver=powersave 321 balanced=battery-optimized 322 performance=throughput-performance 323 324 [battery] 325 # PPD = TuneD 326 balanced=battery-optimized 327 EOF 328 329 if systemctl list-unit-files --type=service | grep -q '^tuned-ppd\.service'; then 330 sudo systemctl restart tuned-ppd.service || true 331 fi 332 fi 333 334 # Change default shell to fish 335 if command -v fish &>/dev/null; then 336 if [ "$SHELL" != "$(which fish)" ]; then 337 echo "Changing default shell to fish..." 338 chsh -s "$(which fish)" 339 fi 340 fi 341 342 echo "Setup complete!"