Last active
January 22, 2026 13:33
-
-
Save FalconNL93/fd54cc7f553902743d99d03c744ce5ec to your computer and use it in GitHub Desktop.
fedora-setup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| fd54cc7f553902743d99d03c744ce5ec |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Flatpak applications to install system-wide | |
| # One application ID per line. | |
| # Browser | |
| io.gitlab.librewolf-community | |
| # Office / docs / mail | |
| org.libreoffice.LibreOffice | |
| org.kde.okular | |
| org.mozilla.Thunderbird | |
| org.gnome.Calculator | |
| # Archives | |
| io.github.peazip.PeaZip | |
| # Media | |
| org.videolan.VLC | |
| com.spotify.Client | |
| # Password manager | |
| com.bitwarden.desktop | |
| # Flatpak permissions manager | |
| com.github.tchx84.Flatseal | |
| # GTK theme runtimes (light/dark sync) | |
| org.gtk.Gtk3theme.adw-gtk3 | |
| org.gtk.Gtk3theme.adw-gtk3-dark |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Flatpak remotes (system-wide) | |
| # Format: name|url | |
| flathub|https://flathub.org/repo/flathub.flatpakrepo |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Host-layer packages to install via rpm-ostree | |
| # Keep this list low-churn (system tools, drivers, services) | |
| # Core utilities / diagnostics | |
| wireguard-tools | |
| rsync | |
| tmux | |
| htop | |
| pciutils | |
| usbutils | |
| lm_sensors | |
| glibc-langpack-nl | |
| glibc-langpack-en | |
| NetworkManager-wireguard | |
| # Requested basics | |
| git | |
| openssl | |
| unzip | |
| zip | |
| lsof | |
| # NVIDIA + CUDA runtime (RPM Fusion) | |
| akmod-nvidia | |
| xorg-x11-drv-nvidia | |
| xorg-x11-drv-nvidia-cuda | |
| # Vulkan | |
| vulkan | |
| vulkan-tools | |
| # Networking | |
| nmap | |
| bind-utils | |
| # Virtualization | |
| libvirt | |
| virt-install | |
| virt-viewer |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # Runs after post-reboot steps (as root). | |
| # Put your personalization here (clone repos, toolbox setup, etc). | |
| exit 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # Timezone + NTP | |
| sudo timedatectl set-timezone Europe/Amsterdam | |
| sudo timedatectl set-ntp true | |
| # System language: English | |
| sudo localectl set-locale LANG=en_US.UTF-8 | |
| # Time/date formatting: Dutch (fixes 14.08 -> 14:08) | |
| sudo localectl set-locale LC_TIME=nl_NL.UTF-8 | |
| sudo groupadd -f printeradmins | |
| sudo tee /etc/polkit-1/rules.d/50-printeradmins.rules >/dev/null <<'EOF' | |
| polkit.addRule(function(action, subject) { | |
| if ( | |
| subject.isInGroup("printeradmins") && | |
| ( | |
| action.id == "org.opensuse.cupspkhelper.mechanism.all-edit" || | |
| action.id == "org.opensuse.cupspkhelper.mechanism.printer-add" || | |
| action.id == "org.opensuse.cupspkhelper.mechanism.printer-remove" || | |
| action.id == "org.opensuse.cupspkhelper.mechanism.printer-modify" | |
| ) | |
| ) { | |
| return polkit.Result.YES; | |
| } | |
| }); | |
| EOF | |
| exit 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Repository release RPMs (installed via rpm-ostree) | |
| # Token supported: {{FEDORA}} -> rpm -E %fedora | |
| https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-{{FEDORA}}.noarch.rpm | |
| https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-{{FEDORA}}.noarch.rpm |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # Unattended Fedora Atomic (Kinoite/Silverblue) setup: | |
| # - Updates base OS to latest (rpm-ostree upgrade) | |
| # - Installs repo RPMs from repositories.txt (rpm-ostree) | |
| # - Layers host packages from ostree-packages.txt (rpm-ostree) | |
| # - Adds Flatpak remotes from flatpak-remotes.txt (system-wide) | |
| # - Prefers Flathub (priority + install-from-flathub-first) to avoid prompts | |
| # - Installs Flatpaks from flatpak-packages.txt (system-wide), unattended | |
| # - Configures Flatpak theming (GTK config RO) for light/dark sync | |
| # - If NVIDIA is in ostree-packages.txt, applies kargs (nouveau blacklist + nvidia-drm modeset) | |
| # - Creates a one-shot systemd service to run post-reboot steps automatically | |
| # - Reboots automatically if rpm-ostree staged changes | |
| # - Supports optional hook scripts: | |
| # pre-install.sh (runs once before any installs; as root) | |
| # post-install.sh (runs once after post-reboot steps; as root) | |
| # - Sets LibreWolf as default browser system-wide (and best-effort for primary user) if installed | |
| # - Sets PeaZip as default archive manager system-wide if installed | |
| # | |
| # Files expected next to this script (all optional): | |
| # - repositories.txt | |
| # - ostree-packages.txt | |
| # - flatpak-remotes.txt | |
| # - flatpak-packages.txt | |
| # - pre-install.sh | |
| # - post-install.sh | |
| # | |
| # List file format: | |
| # - One entry per line | |
| # - Blank lines ok | |
| # - Comments supported with leading '#' | |
| # | |
| # repositories.txt tokens: | |
| # - {{FEDORA}} is replaced with `rpm -E %fedora` | |
| log() { printf "\n==> %s\n" "$*"; } | |
| warn() { printf "\nWARNING: %s\n" "$*" >&2; } | |
| die() { printf "\nERROR: %s\n" "$*" >&2; exit 1; } | |
| require_cmd() { | |
| command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1" | |
| } | |
| script_dir() { | |
| cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 | |
| pwd | |
| } | |
| read_list_file_if_exists() { | |
| local file_path="$1" | |
| local -n out_arr="$2" | |
| out_arr=() | |
| [[ -f "$file_path" ]] || return 0 | |
| while IFS= read -r line || [[ -n "$line" ]]; do | |
| line="${line#"${line%%[![:space:]]*}"}" | |
| line="${line%"${line##*[![:space:]]}"}" | |
| [[ -z "$line" ]] && continue | |
| [[ "$line" == \#* ]] && continue | |
| out_arr+=("$line") | |
| done < "$file_path" | |
| } | |
| apply_repo_tokens() { | |
| local -n arr="$1" | |
| local fedora_ver | |
| fedora_ver="$(rpm -E %fedora)" | |
| local i | |
| for i in "${!arr[@]}"; do | |
| arr[$i]="${arr[$i]//\{\{FEDORA\}\}/$fedora_ver}" | |
| done | |
| } | |
| is_secure_boot_enabled() { | |
| if command -v mokutil >/dev/null 2>&1; then | |
| mokutil --sb-state 2>/dev/null | grep -qi "enabled" | |
| return $? | |
| fi | |
| return 1 | |
| } | |
| flatpak_remote_exists() { | |
| local name="$1" | |
| flatpak remotes --system --columns=name 2>/dev/null | awk '{print $1}' | grep -qx "$name" | |
| } | |
| flatpak_app_installed() { | |
| local app_id="$1" | |
| flatpak list --system --app --columns=application 2>/dev/null | awk '{print $1}' | grep -qx "$app_id" | |
| } | |
| ostree_has_pending() { | |
| rpm-ostree status --json 2>/dev/null | grep -q '"pending":true' | |
| } | |
| kargs_contains() { | |
| local needle="$1" | |
| rpm-ostree kargs 2>/dev/null | tr ' ' '\n' | grep -qx -- "$needle" | |
| } | |
| append_karg_if_missing() { | |
| local arg="$1" | |
| if kargs_contains "$arg"; then | |
| log "Kernel arg already present: $arg" | |
| return 0 | |
| fi | |
| log "Appending kernel arg: $arg" | |
| rpm-ostree kargs --append="$arg" | |
| } | |
| list_contains() { | |
| local wanted="$1"; shift | |
| local item | |
| for item in "$@"; do | |
| if [[ "$item" == "$wanted" ]]; then | |
| return 0 | |
| fi | |
| done | |
| return 1 | |
| } | |
| detect_primary_user() { | |
| awk -F: '$3>=1000 && $1!="nobody" {print $1; exit}' /etc/passwd || true | |
| } | |
| run_hook_if_present() { | |
| local hook_path="$1" | |
| local hook_name="$2" | |
| if [[ -f "$hook_path" ]]; then | |
| if [[ ! -x "$hook_path" ]]; then | |
| log "Making hook executable: $hook_name" | |
| chmod +x "$hook_path" || die "Failed to chmod +x $hook_name" | |
| fi | |
| log "Running hook: $hook_name" | |
| "$hook_path" | |
| else | |
| log "Hook not found (skipping): $hook_name" | |
| fi | |
| } | |
| update_base_os() { | |
| log "Updating base OS to latest (rpm-ostree upgrade)" | |
| rpm-ostree upgrade | |
| } | |
| install_repositories() { | |
| local -a repos=() | |
| read_list_file_if_exists "$REPOS_FILE" repos | |
| apply_repo_tokens repos | |
| if [[ "${#repos[@]}" -eq 0 ]]; then | |
| log "No repositories.txt (or empty). Skipping repo install." | |
| return 0 | |
| fi | |
| log "Installing repository release RPMs (rpm-ostree)" | |
| printf " - %s\n" "${repos[@]}" | |
| rpm-ostree install --idempotent --allow-inactive "${repos[@]}" | |
| } | |
| install_ostree_packages() { | |
| local -a pkgs=() | |
| read_list_file_if_exists "$PKGS_FILE" pkgs | |
| if [[ "${#pkgs[@]}" -eq 0 ]]; then | |
| log "No ostree-packages.txt (or empty). Skipping host package layering." | |
| return 0 | |
| fi | |
| log "Layering host packages (rpm-ostree)" | |
| printf " - %s\n" "${pkgs[@]}" | |
| rpm-ostree install --idempotent --allow-inactive "${pkgs[@]}" | |
| OSTREE_PACKAGES=("${pkgs[@]}") | |
| } | |
| apply_nvidia_kargs_if_needed() { | |
| if [[ "${#OSTREE_PACKAGES[@]}" -eq 0 ]]; then | |
| return 0 | |
| fi | |
| if ! list_contains "akmod-nvidia" "${OSTREE_PACKAGES[@]}"; then | |
| log "akmod-nvidia not in ostree-packages.txt. Skipping NVIDIA kernel args." | |
| return 0 | |
| fi | |
| log "NVIDIA detected in package list; applying kernel args (nouveau blacklist + modeset)." | |
| append_karg_if_missing "rd.driver.blacklist=nouveau,nova_core" | |
| append_karg_if_missing "modprobe.blacklist=nouveau,nova_core" | |
| append_karg_if_missing "nvidia-drm.modeset=1" | |
| } | |
| add_flatpak_remotes() { | |
| local -a remotes=() | |
| read_list_file_if_exists "$FLATPAK_REMOTES_FILE" remotes | |
| if [[ "${#remotes[@]}" -eq 0 ]]; then | |
| log "No flatpak-remotes.txt (or empty). Skipping Flatpak remotes." | |
| return 0 | |
| fi | |
| require_cmd flatpak | |
| log "Ensuring Flatpak remotes exist (system-wide)" | |
| local entry name url | |
| for entry in "${remotes[@]}"; do | |
| name="${entry%%|*}" | |
| url="${entry#*|}" | |
| if [[ -z "$name" || -z "$url" || "$name" == "$entry" ]]; then | |
| die "Invalid remote entry: '$entry' (expected: name|url)" | |
| fi | |
| if flatpak_remote_exists "$name"; then | |
| log "Flatpak remote already exists: $name" | |
| continue | |
| fi | |
| log "Adding Flatpak remote: $name" | |
| flatpak remote-add --system --if-not-exists "$name" "$url" | |
| done | |
| } | |
| prefer_flathub() { | |
| require_cmd flatpak | |
| if flatpak_remote_exists "flathub"; then | |
| log "Setting Flatpak remote priority: flathub preferred" | |
| flatpak remote-modify --system --prio=1 flathub | |
| else | |
| warn "Flathub remote not found; cannot set priority." | |
| fi | |
| if flatpak_remote_exists "fedora"; then | |
| flatpak remote-modify --system --prio=2 fedora || true | |
| fi | |
| } | |
| install_flatpak_from_preferred_remote() { | |
| local app="$1" | |
| if [[ "$app" == */* ]]; then | |
| flatpak install --system -y "$app" | |
| return 0 | |
| fi | |
| if flatpak_remote_exists "flathub"; then | |
| if flatpak install --system -y flathub "$app" >/dev/null 2>&1; then | |
| return 0 | |
| fi | |
| fi | |
| if flatpak_remote_exists "fedora"; then | |
| if flatpak install --system -y fedora "$app" >/dev/null 2>&1; then | |
| return 0 | |
| fi | |
| fi | |
| die "Failed to install Flatpak '$app' from flathub or fedora." | |
| } | |
| install_flatpak_packages() { | |
| local -a apps=() | |
| read_list_file_if_exists "$FLATPAK_PACKAGES_FILE" apps | |
| if [[ "${#apps[@]}" -eq 0 ]]; then | |
| log "No flatpak-packages.txt (or empty). Skipping Flatpak installs." | |
| return 0 | |
| fi | |
| require_cmd flatpak | |
| log "Installing Flatpak apps system-wide (prefer flathub, fallback fedora)" | |
| local app | |
| for app in "${apps[@]}"; do | |
| if flatpak_app_installed "$app"; then | |
| log "Flatpak already installed: $app" | |
| continue | |
| fi | |
| log "Installing Flatpak: $app" | |
| install_flatpak_from_preferred_remote "$app" | |
| done | |
| } | |
| configure_flatpak_theming() { | |
| require_cmd flatpak | |
| log "Configuring Flatpak theming (GTK config RO access)" | |
| flatpak override --system \ | |
| --filesystem=xdg-config/gtkrc:ro \ | |
| --filesystem=xdg-config/gtkrc-2.0:ro \ | |
| --filesystem=xdg-config/gtk-3.0:ro \ | |
| --filesystem=xdg-config/gtk-4.0:ro \ | |
| --filesystem=xdg-data/icons:ro \ | |
| --filesystem=xdg-data/themes:ro | |
| } | |
| create_post_reboot_service() { | |
| local unit_path="/etc/systemd/system/fedora-atomic-firstboot-setup.service" | |
| local script_path | |
| script_path="$(realpath "$0")" | |
| log "Creating one-shot post-reboot service: fedora-atomic-firstboot-setup.service" | |
| cat > "$unit_path" <<EOF | |
| [Unit] | |
| Description=Fedora Atomic first-boot setup (post-reboot steps) | |
| After=network-online.target | |
| Wants=network-online.target | |
| [Service] | |
| Type=oneshot | |
| ExecStart=${script_path} --post-reboot-internal | |
| RemainAfterExit=no | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| systemctl daemon-reload | |
| systemctl enable fedora-atomic-firstboot-setup.service | |
| } | |
| ensure_mimeapps_default() { | |
| local key="$1" | |
| local value="$2" | |
| local file="/etc/xdg/mimeapps.list" | |
| mkdir -p /etc/xdg | |
| if [[ ! -f "$file" ]]; then | |
| cat > "$file" <<EOF | |
| [Default Applications] | |
| ${key}=${value} | |
| EOF | |
| return 0 | |
| fi | |
| if ! grep -q '^\[Default Applications\]$' "$file"; then | |
| printf "\n[Default Applications]\n" >> "$file" | |
| fi | |
| sed -i -E "s|^${key}=.*$||g" "$file" | |
| awk -v k="$key" -v v="$value" ' | |
| BEGIN { added=0 } | |
| /^\[Default Applications\]$/ { | |
| if (!added) { print k "=" v; added=1 } | |
| next | |
| } | |
| { print } | |
| END { | |
| if (!added) { | |
| print "[Default Applications]" | |
| print k "=" v | |
| } | |
| } | |
| ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" | |
| } | |
| set_librewolf_system_default_browser() { | |
| local desktop_id="io.gitlab.librewolf-community.desktop" | |
| log "Setting LibreWolf as system-wide default browser via /etc/xdg/mimeapps.list" | |
| ensure_mimeapps_default "x-scheme-handler/http" "$desktop_id" | |
| ensure_mimeapps_default "x-scheme-handler/https" "$desktop_id" | |
| ensure_mimeapps_default "text/html" "$desktop_id" | |
| } | |
| set_peazip_system_default_archives() { | |
| # NOTE: Desktop ID may vary by Flatpak packaging. This is the common one. | |
| # If it doesn't work for you, run: | |
| # flatpak info --show-metadata io.github.peazip.PeaZip | grep -i desktop | |
| local desktop_id="io.github.peazip.PeaZip.desktop" | |
| log "Setting PeaZip as system-wide default archive manager via /etc/xdg/mimeapps.list" | |
| ensure_mimeapps_default "application/zip" "$desktop_id" | |
| ensure_mimeapps_default "application/x-zip-compressed" "$desktop_id" | |
| ensure_mimeapps_default "application/x-7z-compressed" "$desktop_id" | |
| ensure_mimeapps_default "application/x-rar" "$desktop_id" | |
| ensure_mimeapps_default "application/vnd.rar" "$desktop_id" | |
| ensure_mimeapps_default "application/x-rar-compressed" "$desktop_id" | |
| ensure_mimeapps_default "application/x-tar" "$desktop_id" | |
| ensure_mimeapps_default "application/gzip" "$desktop_id" | |
| ensure_mimeapps_default "application/x-gzip" "$desktop_id" | |
| ensure_mimeapps_default "application/x-bzip2" "$desktop_id" | |
| ensure_mimeapps_default "application/x-xz" "$desktop_id" | |
| ensure_mimeapps_default "application/x-gtar" "$desktop_id" | |
| ensure_mimeapps_default "application/x-compressed-tar" "$desktop_id" | |
| } | |
| set_librewolf_default_browser_for_user() { | |
| local user="$1" | |
| local desktop_id="io.gitlab.librewolf-community.desktop" | |
| if [[ -z "$user" ]]; then | |
| warn "No primary user detected; skipping per-user default browser configuration." | |
| return 0 | |
| fi | |
| log "Setting LibreWolf as default browser for user: $user" | |
| runuser -l "$user" -c "command -v xdg-settings >/dev/null 2>&1 && xdg-settings set default-web-browser '$desktop_id' || true" | |
| runuser -l "$user" -c "command -v xdg-mime >/dev/null 2>&1 && xdg-mime default '$desktop_id' x-scheme-handler/http x-scheme-handler/https text/html || true" | |
| } | |
| post_reboot_internal() { | |
| require_cmd systemctl | |
| log "Post-reboot: enabling services (non-fatal if already enabled)" | |
| if systemctl list-unit-files | grep -q '^libvirtd\.service'; then | |
| systemctl enable --now libvirtd || true | |
| else | |
| warn "libvirtd.service not found. If libvirt wasn't layered, that's expected." | |
| fi | |
| log "Post-reboot: optional libvirt group assignment" | |
| local primary_user | |
| primary_user="$(detect_primary_user)" | |
| if getent group libvirt >/dev/null 2>&1; then | |
| if [[ -n "$primary_user" ]]; then | |
| usermod -aG libvirt "$primary_user" || true | |
| log "Added user '$primary_user' to libvirt group. Log out/in to apply group changes." | |
| else | |
| warn "Could not determine a primary user to add to libvirt group." | |
| fi | |
| else | |
| warn "Group 'libvirt' not found (may vary). Skipping." | |
| fi | |
| if command -v flatpak >/dev/null 2>&1; then | |
| if flatpak list --system --app --columns=application 2>/dev/null | awk '{print $1}' | grep -qx "io.gitlab.librewolf-community"; then | |
| set_librewolf_system_default_browser | |
| set_librewolf_default_browser_for_user "$primary_user" | |
| else | |
| log "LibreWolf Flatpak not installed; skipping default browser configuration." | |
| fi | |
| if flatpak list --system --app --columns=application 2>/dev/null | awk '{print $1}' | grep -qx "io.github.peazip.PeaZip"; then | |
| set_peazip_system_default_archives | |
| else | |
| log "PeaZip Flatpak not installed; skipping default archive manager configuration." | |
| fi | |
| fi | |
| run_hook_if_present "$POST_HOOK" "post-install.sh" | |
| log "Disabling one-shot service so it won't run again" | |
| systemctl disable fedora-atomic-firstboot-setup.service || true | |
| rm -f /etc/systemd/system/fedora-atomic-firstboot-setup.service || true | |
| systemctl daemon-reload | |
| log "Post-reboot steps complete." | |
| } | |
| main() { | |
| require_cmd rpm-ostree | |
| require_cmd rpm | |
| if [[ $EUID -ne 0 ]]; then | |
| die "Run as root (use: sudo $0)" | |
| fi | |
| local dir | |
| dir="$(script_dir)" | |
| REPOS_FILE="${dir}/repositories.txt" | |
| PKGS_FILE="${dir}/ostree-packages.txt" | |
| FLATPAK_REMOTES_FILE="${dir}/flatpak-remotes.txt" | |
| FLATPAK_PACKAGES_FILE="${dir}/flatpak-packages.txt" | |
| PRE_HOOK="${dir}/pre-install.sh" | |
| POST_HOOK="${dir}/post-install.sh" | |
| OSTREE_PACKAGES=() | |
| log "Unattended setup starting (Fedora Atomic)" | |
| log "Using files (if present):" | |
| log " ${REPOS_FILE}" | |
| log " ${PKGS_FILE}" | |
| log " ${FLATPAK_REMOTES_FILE}" | |
| log " ${FLATPAK_PACKAGES_FILE}" | |
| log "Hooks (if present):" | |
| log " ${PRE_HOOK}" | |
| log " ${POST_HOOK}" | |
| log "Checking Secure Boot" | |
| if is_secure_boot_enabled; then | |
| warn "Secure Boot appears ENABLED. NVIDIA modules may not load unless you sign them (MOK) or disable Secure Boot in BIOS." | |
| else | |
| log "Secure Boot not detected as enabled (or mokutil not installed)." | |
| fi | |
| run_hook_if_present "$PRE_HOOK" "pre-install.sh" | |
| update_base_os | |
| install_repositories | |
| install_ostree_packages | |
| apply_nvidia_kargs_if_needed | |
| add_flatpak_remotes | |
| prefer_flathub | |
| install_flatpak_packages | |
| configure_flatpak_theming | |
| if ostree_has_pending; then | |
| log "rpm-ostree staged a pending deployment -> reboot is required." | |
| create_post_reboot_service | |
| log "Rebooting automatically in 5 seconds..." | |
| sleep 5 | |
| systemctl reboot | |
| else | |
| log "No pending rpm-ostree deployment detected. No reboot required." | |
| log "Running post-install hook now (since no reboot is happening)." | |
| run_hook_if_present "$POST_HOOK" "post-install.sh" | |
| log "Setup complete." | |
| fi | |
| } | |
| if [[ "${1:-}" == "--post-reboot-internal" ]]; then | |
| post_reboot_internal | |
| else | |
| main | |
| fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| die() { echo "ERROR: $*" >&2; exit 1; } | |
| log() { printf "\n==> %s\n" "$*"; } | |
| require_cmd() { | |
| command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1" | |
| } | |
| script_basename="$(basename "$0")" | |
| gist_ref_file=".gist" | |
| SELF_UPDATE="false" | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --self) | |
| SELF_UPDATE="true" | |
| shift | |
| ;; | |
| -h|--help) | |
| cat <<'EOF' | |
| Usage: | |
| ./update.sh [--self] | |
| Behavior: | |
| - Downloads all files from the gist referenced by .gist into the current directory. | |
| - Prefers GitHub CLI (gh) if available/authenticated (avoids API rate limits). | |
| - If gh is missing, installs the official gh binary to ~/.local/bin (no sudo). | |
| - Falls back to curl + GitHub API if gh isn't authenticated. | |
| - By default, does NOT overwrite update.sh itself; use --self to allow. | |
| Notes: | |
| - Public/unlisted gists work without auth. | |
| - If you hit rate limits in fallback mode, authenticate gh: `gh auth login` | |
| EOF | |
| exit 0 | |
| ;; | |
| *) | |
| die "Unknown argument: $1" | |
| ;; | |
| esac | |
| done | |
| require_cmd curl | |
| require_cmd python3 | |
| require_cmd grep | |
| require_cmd head | |
| require_cmd tr | |
| require_cmd tar | |
| require_cmd uname | |
| require_cmd mktemp | |
| install_gh_if_missing() { | |
| if command -v gh >/dev/null 2>&1; then | |
| return 0 | |
| fi | |
| log "gh not found. Installing GitHub CLI (official binary) to ~/.local/bin" | |
| mkdir -p "$HOME/.local/bin" | |
| export PATH="$HOME/.local/bin:$PATH" | |
| arch="$(uname -m)" | |
| case "$arch" in | |
| x86_64|amd64) gh_arch="amd64" ;; | |
| aarch64|arm64) gh_arch="arm64" ;; | |
| *) die "Unsupported architecture for gh binary install: $arch" ;; | |
| esac | |
| # Get latest release tag (e.g., v2.XX.X) | |
| latest_tag="$( | |
| curl -fsSL -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/cli/cli/releases/latest" \ | |
| | python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])' | |
| )" || die "Failed to query latest gh release" | |
| ver="${latest_tag#v}" | |
| asset="gh_${ver}_linux_${gh_arch}.tar.gz" | |
| url="https://github.com/cli/cli/releases/download/${latest_tag}/${asset}" | |
| tmpdir="$(mktemp -d)" | |
| cleanup_install() { rm -rf "$tmpdir"; } | |
| trap cleanup_install EXIT | |
| log "Downloading: $asset" | |
| curl -fsSL "$url" -o "$tmpdir/$asset" || die "Failed to download gh asset: $url" | |
| tar -xzf "$tmpdir/$asset" -C "$tmpdir" || die "Failed to extract gh tarball" | |
| src="$tmpdir/gh_${ver}_linux_${gh_arch}/bin/gh" | |
| [[ -f "$src" ]] || die "gh binary not found after extraction" | |
| install -m 0755 "$src" "$HOME/.local/bin/gh" || die "Failed to install gh to ~/.local/bin/gh" | |
| log "Installed gh: $HOME/.local/bin/gh" | |
| } | |
| extract_gist_id() { | |
| local ref="$1" | |
| printf "%s" "$ref" | grep -Eo '[0-9a-fA-F]{20,40}' | head -n 1 || true | |
| } | |
| fetch_gist_json() { | |
| local id="$1" | |
| # Prefer gh (authenticated) if available | |
| if command -v gh >/dev/null 2>&1; then | |
| if gh auth status >/dev/null 2>&1; then | |
| log "Using gh (authenticated) to fetch gist metadata" | |
| gh api -H "Accept: application/vnd.github+json" "/gists/$id" | |
| return 0 | |
| fi | |
| log "gh found but not authenticated; falling back to curl" | |
| else | |
| log "gh not found; falling back to curl" | |
| fi | |
| # Fallback: unauthenticated GitHub API | |
| curl -fsSL -H "Accept: application/vnd.github+json" "https://api.github.com/gists/$id" | |
| } | |
| download_one() { | |
| local filename="$1" | |
| local raw_url="$2" | |
| # Self-update behavior | |
| if [[ "$filename" == "$script_basename" && "$SELF_UPDATE" != "true" ]]; then | |
| log "Skipping self: $filename (use --self to update it)" | |
| return 0 | |
| fi | |
| tmp="${filename}.tmp.$$" | |
| log "Downloading: $filename" | |
| curl -fsSL "$raw_url" -o "$tmp" || die "Failed downloading: $filename" | |
| # If self-update, basic sanity check to avoid bricking the script | |
| if [[ "$filename" == "$script_basename" ]]; then | |
| head_line="$(head -n 1 "$tmp" || true)" | |
| [[ "$head_line" == \#!* ]] || die "Self-update sanity check failed: $filename has no shebang" | |
| fi | |
| mv -f "$tmp" "$filename" | |
| case "$filename" in | |
| *.sh) chmod +x "$filename" 2>/dev/null || true ;; | |
| esac | |
| } | |
| # Main | |
| [[ -f "$gist_ref_file" ]] || die "No .gist file found in $(pwd)." | |
| gist_ref="$(tr -d ' \t\r\n' < "$gist_ref_file")" | |
| [[ -n "$gist_ref" ]] || die ".gist is empty" | |
| gist_id="$(extract_gist_id "$gist_ref")" | |
| [[ -n "$gist_id" ]] || die "Could not extract gist id from .gist value: $gist_ref" | |
| log "Updating from gist: $gist_id" | |
| if [[ "$SELF_UPDATE" == "true" ]]; then | |
| log "Self-update: enabled (--self)" | |
| else | |
| log "Self-update: disabled (won't overwrite $script_basename)" | |
| fi | |
| # Try to ensure gh exists (no sudo). If install fails, we still can curl fallback. | |
| if ! command -v gh >/dev/null 2>&1; then | |
| install_gh_if_missing || log "gh install failed; continuing with curl fallback" | |
| fi | |
| json="$(fetch_gist_json "$gist_id")" || die "Failed to fetch gist metadata." | |
| # Parse JSON -> "<filename>\t<raw_url>" | |
| file_list="$(python3 - <<'PY' | |
| import json, sys | |
| data = json.loads(sys.stdin.read()) | |
| files = data.get("files", {}) | |
| for name, info in files.items(): | |
| raw = info.get("raw_url") | |
| if raw: | |
| print(f"{name}\t{raw}") | |
| PY | |
| <<<"$json")" || die "Failed to parse gist JSON (rate limit / invalid response?)" | |
| [[ -n "$file_list" ]] || die "No files found in gist." | |
| log "Downloading files into: $(pwd)" | |
| count_total=0 | |
| count_written=0 | |
| count_skipped=0 | |
| while IFS=$'\t' read -r filename raw_url; do | |
| [[ -n "${filename:-}" ]] || continue | |
| [[ -n "${raw_url:-}" ]] || continue | |
| count_total=$((count_total + 1)) | |
| if [[ "$filename" == "$script_basename" && "$SELF_UPDATE" != "true" ]]; then | |
| count_skipped=$((count_skipped + 1)) | |
| download_one "$filename" "$raw_url" || true | |
| continue | |
| fi | |
| download_one "$filename" "$raw_url" | |
| count_written=$((count_written + 1)) | |
| done <<< "$file_list" | |
| log "Done." | |
| log "Files in gist: $count_total" | |
| log "Written/updated: $count_written" | |
| log "Skipped: $count_skipped" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment