weehong revidoval tento gist 4 days ago. Přejít na revizi
3 files changed, 90 insertions, 3 deletions
README.md
| @@ -42,6 +42,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 42 | 42 | | 18 | [`install-ibus-pinyin.sh`](install-ibus-pinyin.sh) | **user** | Installs `ibus-libpinyin` and Simplified Chinese language packs, restarts the IBus daemon, and idempotently adds `('ibus', 'libpinyin')` to GNOME's input sources via `gsettings`. | | |
| 43 | 43 | | 19 | [`install-font.sh`](install-font.sh) | **user** | Installs the latest Ubuntu Sans Nerd Font and JetBrains Mono Nerd Font to `~/.local/share/fonts`, then refreshes the font cache. No sudo needed. | | |
| 44 | 44 | | 20 | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. | | |
| 45 | + | | 21 | [`install-screenshot-cleanup-cron.sh`](install-screenshot-cleanup-cron.sh) | **user** | Installs an idempotent per-user cron job that clears `$HOME/Pictures/Screenshots` every 5 minutes while preserving the directory itself. | | |
| 45 | 46 | ||
| 46 | 47 | "Runs as **user**" entries must be invoked as your normal desktop user, not via `sudo`. The other entries elevate via `sudo` internally and the menu primes `sudo -v` up-front, so you'll only be prompted once. | |
| 47 | 48 | ||
| @@ -53,7 +54,7 @@ If you'd rather skip the menu, each script can be run on its own. Use the right | |||
| 53 | 54 | # sudo scripts (1-12, 14-17, 20) — pipe through sudo bash | |
| 54 | 55 | curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 55 | 56 | ||
| 56 | - | # user scripts (13, 18, 19) — DO NOT use sudo; they install per-user | |
| 57 | + | # user scripts (13, 18, 19, 21) — DO NOT use sudo; they install per-user | |
| 57 | 58 | bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)" | |
| 58 | 59 | ``` | |
| 59 | 60 | ||
install-screenshot-cleanup-cron.sh(vytvořil soubor)
| @@ -0,0 +1,84 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # install-screenshot-cleanup-cron.sh — Clear ~/Pictures/Screenshots every 5 minutes. | |
| 3 | + | # Idempotent per-user crontab installer. | |
| 4 | + | ||
| 5 | + | set -euo pipefail | |
| 6 | + | IFS=$'\n\t' | |
| 7 | + | ||
| 8 | + | readonly SCRIPT_NAME="${0##*/}" | |
| 9 | + | readonly BEGIN_MARKER="# BEGIN managed by install-screenshot-cleanup-cron.sh" | |
| 10 | + | readonly END_MARKER="# END managed by install-screenshot-cleanup-cron.sh" | |
| 11 | + | DRY_RUN=0 | |
| 12 | + | ||
| 13 | + | usage() { | |
| 14 | + | cat <<EOF | |
| 15 | + | Usage: $SCRIPT_NAME [--dry-run] [--help] | |
| 16 | + | ||
| 17 | + | Installs a per-user cron job that deletes everything inside: | |
| 18 | + | ||
| 19 | + | \$HOME/Pictures/Screenshots | |
| 20 | + | ||
| 21 | + | every 5 minutes. The Screenshots directory itself is preserved. | |
| 22 | + | ||
| 23 | + | Options: | |
| 24 | + | --dry-run Print actions without executing. | |
| 25 | + | --help, -h Show this help. | |
| 26 | + | EOF | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; } | |
| 30 | + | die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } | |
| 31 | + | ||
| 32 | + | while (( $# )); do | |
| 33 | + | case "$1" in | |
| 34 | + | --dry-run) DRY_RUN=1 ;; | |
| 35 | + | -h|--help) usage; exit 0 ;; | |
| 36 | + | *) die "Unknown argument: $1 (try --help)" ;; | |
| 37 | + | esac | |
| 38 | + | shift | |
| 39 | + | done | |
| 40 | + | ||
| 41 | + | (( EUID != 0 )) || die "Do not run as root. Run as your normal desktop user so the cron job uses the correct \$HOME." | |
| 42 | + | ||
| 43 | + | for tool in crontab awk mktemp mkdir; do | |
| 44 | + | command -v "$tool" >/dev/null 2>&1 || die "Missing required tool: $tool" | |
| 45 | + | done | |
| 46 | + | ||
| 47 | + | SCREENSHOT_DIR="$HOME/Pictures/Screenshots" | |
| 48 | + | CRON_LINE="*/5 * * * * find \"$SCREENSHOT_DIR\" -mindepth 1 -delete" | |
| 49 | + | STAGE="$(mktemp -d -t screenshot-cron.XXXXXX)" | |
| 50 | + | trap 'rm -rf "$STAGE"' EXIT | |
| 51 | + | ||
| 52 | + | CURRENT="$STAGE/current" | |
| 53 | + | NEXT="$STAGE/next" | |
| 54 | + | crontab -l >"$CURRENT" 2>/dev/null || true | |
| 55 | + | ||
| 56 | + | awk -v begin="$BEGIN_MARKER" -v end="$END_MARKER" ' | |
| 57 | + | $0 == begin { skip = 1; next } | |
| 58 | + | $0 == end { skip = 0; next } | |
| 59 | + | skip { next } | |
| 60 | + | $0 == "# Delete screenshot contents every 5 minutes" { next } | |
| 61 | + | index($0, "Pictures/Screenshots") && index($0, "-mindepth 1") && index($0, "-delete") { next } | |
| 62 | + | { print } | |
| 63 | + | ' "$CURRENT" >"$NEXT" | |
| 64 | + | ||
| 65 | + | { | |
| 66 | + | printf '%s\n' "$BEGIN_MARKER" | |
| 67 | + | printf '# Delete screenshot contents every 5 minutes\n' | |
| 68 | + | printf '%s\n' "$CRON_LINE" | |
| 69 | + | printf '%s\n' "$END_MARKER" | |
| 70 | + | } >>"$NEXT" | |
| 71 | + | ||
| 72 | + | if (( DRY_RUN )); then | |
| 73 | + | log "Would ensure directory exists: $SCREENSHOT_DIR" | |
| 74 | + | log "Would install this user crontab:" | |
| 75 | + | sed 's/^/ /' "$NEXT" | |
| 76 | + | exit 0 | |
| 77 | + | fi | |
| 78 | + | ||
| 79 | + | mkdir -p "$SCREENSHOT_DIR" | |
| 80 | + | crontab "$NEXT" | |
| 81 | + | ||
| 82 | + | log "Installed screenshot cleanup cron job:" | |
| 83 | + | printf ' %s\n' "$CRON_LINE" | |
| 84 | + | log "Done." | |
menu.sh
| @@ -126,6 +126,7 @@ OPTIONS=( | |||
| 126 | 126 | "18|Install IBus Intelligent Pinyin (per-user)|install-ibus-pinyin.sh|user" | |
| 127 | 127 | "19|Install Nerd Fonts (per-user)|install-font.sh|user" | |
| 128 | 128 | "20|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 129 | + | "21|Install Screenshots Cleanup Cron (per-user)|install-screenshot-cleanup-cron.sh|user" | |
| 129 | 130 | ) | |
| 130 | 131 | ||
| 131 | 132 | print_menu() { | |
| @@ -157,8 +158,9 @@ print_menu() { | |||
| 157 | 158 | echo " 18) Install IBus Intelligent Pinyin (runs as you, not root)" | |
| 158 | 159 | echo " 19) Install Nerd Fonts (runs as you, not root)" | |
| 159 | 160 | echo " 20) Fix Dual-Boot Time (RTC to UTC)" | |
| 161 | + | echo " 21) Install Screenshots Cleanup Cron (runs as you, not root)" | |
| 160 | 162 | hr | |
| 161 | - | echo " 0) Run ALL options (1-20)" | |
| 163 | + | echo " 0) Run ALL options (1-21)" | |
| 162 | 164 | echo " -1) Exit" | |
| 163 | 165 | hr | |
| 164 | 166 | } | |
| @@ -177,7 +179,7 @@ resolve_choice() { | |||
| 177 | 179 | return 1 | |
| 178 | 180 | } | |
| 179 | 181 | ||
| 180 | - | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) | |
| 182 | + | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21) | |
| 181 | 183 | ||
| 182 | 184 | prime_sudo || exit 1 | |
| 183 | 185 | ||
weehong revidoval tento gist 4 days ago. Přejít na revizi
2 files changed, 53 insertions, 53 deletions
README.md
| @@ -25,23 +25,23 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 25 | 25 | | 1 | [`install-chrome.sh`](install-chrome.sh) | sudo | Installs Google Chrome Stable from Google's official APT repo using a `signed-by` keyring. | | |
| 26 | 26 | | 2 | [`install-firefox.sh`](install-firefox.sh) | sudo | Removes the Firefox Snap and installs Firefox from Mozilla's official APT repo, with APT pinning so it stays on the Mozilla build. Verifies the Mozilla signing-key fingerprint. | | |
| 27 | 27 | | 3 | [`install-thunderbird.sh`](install-thunderbird.sh) | sudo | Ubuntu: removes the Snap, adds the Mozilla Team PPA, and pins it. Debian: installs from the standard repos. | | |
| 28 | - | | 4 | [`install-1password.sh`](install-1password.sh) | sudo | Configures the 1Password APT repo with `debsig` signature policy. Arch-aware (amd64 / arm64). | | |
| 29 | - | | 5 | [`install-espanso.sh`](install-espanso.sh) | sudo | Installs Espanso. Auto-detects Wayland vs X11 from `$XDG_SESSION_TYPE` and registers the systemd-user service as the invoking desktop user (not root). | | |
| 30 | - | | 6 | [`install-libreoffice.sh`](install-libreoffice.sh) | sudo | Detects the latest stable release on documentfoundation.org, downloads the matching `.deb` tarball, verifies the published MD5, and installs. Purges the distro's `libreoffice*` first to avoid library conflicts (opt-out with `--keep-distro-libreoffice`). | | |
| 31 | - | | 7 | [`install-obsidian.sh`](install-obsidian.sh) | sudo | Installs the latest official Obsidian amd64 `.deb` from `obsidianmd/obsidian-releases`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 32 | - | | 8 | [`install-drawio.sh`](install-drawio.sh) | sudo | Installs the latest official draw.io Desktop `.deb` from `jgraph/drawio-desktop`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 33 | - | | 9 | [`install-vscode.sh`](install-vscode.sh) | sudo | Microsoft's official `code` APT repo, signed-by keyring. `--insiders` flag installs `code-insiders` instead. | | |
| 34 | - | | 10 | [`install-jetbrains-toolbox.sh`](install-jetbrains-toolbox.sh) | **user** | Per-user install into `~/.local/share/JetBrains/Toolbox`. SHA-256 verified against JetBrains' release feed. x86_64 + aarch64. Drops a `.desktop` launcher. | | |
| 35 | - | | 11 | [`install-bruno.sh`](install-bruno.sh) | sudo | Bruno API client from the official APT repo. Keyserver fetch is wrapped in a 5-attempt retry/backoff because `keyserver.ubuntu.com` is occasionally flaky. | | |
| 36 | - | | 12 | [`install-ipatool.sh`](install-ipatool.sh) | sudo | Installs the latest release of `majd/ipatool` from GitHub. SHA-256 verified against the release `checksums.txt`. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 37 | - | | 13 | [`install-qbittorrent.sh`](install-qbittorrent.sh) | sudo | Installs the latest official qBittorrent x86_64 AppImage from `qbittorrent/qBittorrent`. SHA-256 verified against the GitHub release asset digest when available. Installs launcher, icons, and torrent/magnet handlers. | | |
| 38 | - | | 14 | [`install-network_drive.sh`](install-network_drive.sh) | sudo | Discovers SMB shares on a Synology NAS and adds them to `/etc/fstab` under `/mnt/Synology` with `x-systemd.automount`. fstab block is managed via begin/end markers so re-runs replace rather than duplicate. Credentials file is `0600`. Best-effort GNOME Dock pin. | | |
| 39 | - | | 15 | [`install-ibus-pinyin.sh`](install-ibus-pinyin.sh) | **user** | Installs `ibus-libpinyin` and Simplified Chinese language packs, restarts the IBus daemon, and idempotently adds `('ibus', 'libpinyin')` to GNOME's input sources via `gsettings`. | | |
| 40 | - | | 16 | [`install-font.sh`](install-font.sh) | **user** | Installs the latest Ubuntu Sans Nerd Font and JetBrains Mono Nerd Font to `~/.local/share/fonts`, then refreshes the font cache. No sudo needed. | | |
| 41 | - | | 17 | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. | | |
| 42 | - | | 18 | [`install-localsend.sh`](install-localsend.sh) | sudo | Installs the latest official LocalSend `.deb` from `localsend/localsend`. SHA-256 verified against the GitHub release asset digest when available. | | |
| 43 | - | | 19 | [`install-telegram.sh`](install-telegram.sh) | sudo | Installs the latest official Telegram Desktop Linux build from Telegram's latest download endpoint. Adds launcher and `tg:` URL handler. | | |
| 44 | - | | 20 | [`install-discord.sh`](install-discord.sh) | sudo | Installs the latest official Discord Linux `.deb` from Discord's latest download endpoint. | | |
| 28 | + | | 4 | [`install-localsend.sh`](install-localsend.sh) | sudo | Installs the latest official LocalSend `.deb` from `localsend/localsend`. SHA-256 verified against the GitHub release asset digest when available. | | |
| 29 | + | | 5 | [`install-telegram.sh`](install-telegram.sh) | sudo | Installs the latest official Telegram Desktop Linux build from Telegram's latest download endpoint. Adds launcher and `tg:` URL handler. | | |
| 30 | + | | 6 | [`install-discord.sh`](install-discord.sh) | sudo | Installs the latest official Discord Linux `.deb` from Discord's latest download endpoint. | | |
| 31 | + | | 7 | [`install-1password.sh`](install-1password.sh) | sudo | Configures the 1Password APT repo with `debsig` signature policy. Arch-aware (amd64 / arm64). | | |
| 32 | + | | 8 | [`install-espanso.sh`](install-espanso.sh) | sudo | Installs Espanso. Auto-detects Wayland vs X11 from `$XDG_SESSION_TYPE` and registers the systemd-user service as the invoking desktop user (not root). | | |
| 33 | + | | 9 | [`install-libreoffice.sh`](install-libreoffice.sh) | sudo | Detects the latest stable release on documentfoundation.org, downloads the matching `.deb` tarball, verifies the published MD5, and installs. Purges the distro's `libreoffice*` first to avoid library conflicts (opt-out with `--keep-distro-libreoffice`). | | |
| 34 | + | | 10 | [`install-obsidian.sh`](install-obsidian.sh) | sudo | Installs the latest official Obsidian amd64 `.deb` from `obsidianmd/obsidian-releases`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 35 | + | | 11 | [`install-drawio.sh`](install-drawio.sh) | sudo | Installs the latest official draw.io Desktop `.deb` from `jgraph/drawio-desktop`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 36 | + | | 12 | [`install-vscode.sh`](install-vscode.sh) | sudo | Microsoft's official `code` APT repo, signed-by keyring. `--insiders` flag installs `code-insiders` instead. | | |
| 37 | + | | 13 | [`install-jetbrains-toolbox.sh`](install-jetbrains-toolbox.sh) | **user** | Per-user install into `~/.local/share/JetBrains/Toolbox`. SHA-256 verified against JetBrains' release feed. x86_64 + aarch64. Drops a `.desktop` launcher. | | |
| 38 | + | | 14 | [`install-bruno.sh`](install-bruno.sh) | sudo | Bruno API client from the official APT repo. Keyserver fetch is wrapped in a 5-attempt retry/backoff because `keyserver.ubuntu.com` is occasionally flaky. | | |
| 39 | + | | 15 | [`install-ipatool.sh`](install-ipatool.sh) | sudo | Installs the latest release of `majd/ipatool` from GitHub. SHA-256 verified against the release `checksums.txt`. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 40 | + | | 16 | [`install-qbittorrent.sh`](install-qbittorrent.sh) | sudo | Installs the latest official qBittorrent x86_64 AppImage from `qbittorrent/qBittorrent`. SHA-256 verified against the GitHub release asset digest when available. Installs launcher, icons, and torrent/magnet handlers. | | |
| 41 | + | | 17 | [`install-network_drive.sh`](install-network_drive.sh) | sudo | Discovers SMB shares on a Synology NAS and adds them to `/etc/fstab` under `/mnt/Synology` with `x-systemd.automount`. fstab block is managed via begin/end markers so re-runs replace rather than duplicate. Credentials file is `0600`. Best-effort GNOME Dock pin. | | |
| 42 | + | | 18 | [`install-ibus-pinyin.sh`](install-ibus-pinyin.sh) | **user** | Installs `ibus-libpinyin` and Simplified Chinese language packs, restarts the IBus daemon, and idempotently adds `('ibus', 'libpinyin')` to GNOME's input sources via `gsettings`. | | |
| 43 | + | | 19 | [`install-font.sh`](install-font.sh) | **user** | Installs the latest Ubuntu Sans Nerd Font and JetBrains Mono Nerd Font to `~/.local/share/fonts`, then refreshes the font cache. No sudo needed. | | |
| 44 | + | | 20 | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. | | |
| 45 | 45 | ||
| 46 | 46 | "Runs as **user**" entries must be invoked as your normal desktop user, not via `sudo`. The other entries elevate via `sudo` internally and the menu primes `sudo -v` up-front, so you'll only be prompted once. | |
| 47 | 47 | ||
| @@ -50,10 +50,10 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 50 | 50 | If you'd rather skip the menu, each script can be run on its own. Use the right invocation pattern for that script's privilege mode: | |
| 51 | 51 | ||
| 52 | 52 | ```bash | |
| 53 | - | # sudo scripts (1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 17, 18, 19, 20) — pipe through sudo bash | |
| 53 | + | # sudo scripts (1-12, 14-17, 20) — pipe through sudo bash | |
| 54 | 54 | curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 55 | 55 | ||
| 56 | - | # user scripts (10, 15, 16) — DO NOT use sudo; they install per-user | |
| 56 | + | # user scripts (13, 18, 19) — DO NOT use sudo; they install per-user | |
| 57 | 57 | bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)" | |
| 58 | 58 | ``` | |
| 59 | 59 | ||
menu.sh
| @@ -109,23 +109,23 @@ OPTIONS=( | |||
| 109 | 109 | "1|Install Google Chrome|install-chrome.sh|sudo" | |
| 110 | 110 | "2|Install Firefox (Mozilla APT)|install-firefox.sh|sudo" | |
| 111 | 111 | "3|Install Thunderbird|install-thunderbird.sh|sudo" | |
| 112 | - | "4|Install 1Password|install-1password.sh|sudo" | |
| 113 | - | "5|Install Espanso (text expander)|install-espanso.sh|sudo" | |
| 114 | - | "6|Install LibreOffice (latest stable .deb)|install-libreoffice.sh|sudo" | |
| 115 | - | "7|Install Obsidian|install-obsidian.sh|sudo" | |
| 116 | - | "8|Install draw.io Desktop|install-drawio.sh|sudo" | |
| 117 | - | "9|Install Visual Studio Code|install-vscode.sh|sudo" | |
| 118 | - | "10|Install JetBrains Toolbox (per-user)|install-jetbrains-toolbox.sh|user" | |
| 119 | - | "11|Install Bruno (API client)|install-bruno.sh|sudo" | |
| 120 | - | "12|Install IPATool|install-ipatool.sh|sudo" | |
| 121 | - | "13|Install qBittorrent (latest AppImage)|install-qbittorrent.sh|sudo" | |
| 122 | - | "14|Mount Synology Network Drive|install-network_drive.sh|sudo" | |
| 123 | - | "15|Install IBus Intelligent Pinyin (per-user)|install-ibus-pinyin.sh|user" | |
| 124 | - | "16|Install Nerd Fonts (per-user)|install-font.sh|user" | |
| 125 | - | "17|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 126 | - | "18|Install LocalSend|install-localsend.sh|sudo" | |
| 127 | - | "19|Install Telegram Desktop|install-telegram.sh|sudo" | |
| 128 | - | "20|Install Discord|install-discord.sh|sudo" | |
| 112 | + | "4|Install LocalSend|install-localsend.sh|sudo" | |
| 113 | + | "5|Install Telegram Desktop|install-telegram.sh|sudo" | |
| 114 | + | "6|Install Discord|install-discord.sh|sudo" | |
| 115 | + | "7|Install 1Password|install-1password.sh|sudo" | |
| 116 | + | "8|Install Espanso (text expander)|install-espanso.sh|sudo" | |
| 117 | + | "9|Install LibreOffice (latest stable .deb)|install-libreoffice.sh|sudo" | |
| 118 | + | "10|Install Obsidian|install-obsidian.sh|sudo" | |
| 119 | + | "11|Install draw.io Desktop|install-drawio.sh|sudo" | |
| 120 | + | "12|Install Visual Studio Code|install-vscode.sh|sudo" | |
| 121 | + | "13|Install JetBrains Toolbox (per-user)|install-jetbrains-toolbox.sh|user" | |
| 122 | + | "14|Install Bruno (API client)|install-bruno.sh|sudo" | |
| 123 | + | "15|Install IPATool|install-ipatool.sh|sudo" | |
| 124 | + | "16|Install qBittorrent (latest AppImage)|install-qbittorrent.sh|sudo" | |
| 125 | + | "17|Mount Synology Network Drive|install-network_drive.sh|sudo" | |
| 126 | + | "18|Install IBus Intelligent Pinyin (per-user)|install-ibus-pinyin.sh|user" | |
| 127 | + | "19|Install Nerd Fonts (per-user)|install-font.sh|user" | |
| 128 | + | "20|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 129 | 129 | ) | |
| 130 | 130 | ||
| 131 | 131 | print_menu() { | |
| @@ -137,26 +137,26 @@ print_menu() { | |||
| 137 | 137 | echo " 2) Install Firefox" | |
| 138 | 138 | echo " 3) Install Thunderbird" | |
| 139 | 139 | echo "--- [ Communication ] ---" | |
| 140 | - | echo " 18) Install LocalSend" | |
| 141 | - | echo " 19) Install Telegram Desktop" | |
| 142 | - | echo " 20) Install Discord" | |
| 140 | + | echo " 4) Install LocalSend" | |
| 141 | + | echo " 5) Install Telegram Desktop" | |
| 142 | + | echo " 6) Install Discord" | |
| 143 | 143 | echo "--- [ Productivity & Security ] ---" | |
| 144 | - | echo " 4) Install 1Password" | |
| 145 | - | echo " 5) Install Espanso" | |
| 146 | - | echo " 6) Install LibreOffice" | |
| 147 | - | echo " 7) Install Obsidian" | |
| 148 | - | echo " 8) Install draw.io Desktop" | |
| 144 | + | echo " 7) Install 1Password" | |
| 145 | + | echo " 8) Install Espanso" | |
| 146 | + | echo " 9) Install LibreOffice" | |
| 147 | + | echo " 10) Install Obsidian" | |
| 148 | + | echo " 11) Install draw.io Desktop" | |
| 149 | 149 | echo "--- [ Development Tools ] ---" | |
| 150 | - | echo " 9) Install Visual Studio Code" | |
| 151 | - | echo " 10) Install JetBrains Toolbox (runs as you, not root)" | |
| 152 | - | echo " 11) Install Bruno" | |
| 153 | - | echo " 12) Install IPATool" | |
| 154 | - | echo " 13) Install qBittorrent" | |
| 150 | + | echo " 12) Install Visual Studio Code" | |
| 151 | + | echo " 13) Install JetBrains Toolbox (runs as you, not root)" | |
| 152 | + | echo " 14) Install Bruno" | |
| 153 | + | echo " 15) Install IPATool" | |
| 154 | + | echo " 16) Install qBittorrent" | |
| 155 | 155 | echo "--- [ System ] ---" | |
| 156 | - | echo " 14) Mount Synology Network Drive" | |
| 157 | - | echo " 15) Install IBus Intelligent Pinyin (runs as you, not root)" | |
| 158 | - | echo " 16) Install Nerd Fonts (runs as you, not root)" | |
| 159 | - | echo " 17) Fix Dual-Boot Time (RTC to UTC)" | |
| 156 | + | echo " 17) Mount Synology Network Drive" | |
| 157 | + | echo " 18) Install IBus Intelligent Pinyin (runs as you, not root)" | |
| 158 | + | echo " 19) Install Nerd Fonts (runs as you, not root)" | |
| 159 | + | echo " 20) Fix Dual-Boot Time (RTC to UTC)" | |
| 160 | 160 | hr | |
| 161 | 161 | echo " 0) Run ALL options (1-20)" | |
| 162 | 162 | echo " -1) Exit" | |
weehong revidoval tento gist 4 days ago. Přejít na revizi
5 files changed, 325 insertions, 4 deletions
README.md
| @@ -39,6 +39,9 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 39 | 39 | | 15 | [`install-ibus-pinyin.sh`](install-ibus-pinyin.sh) | **user** | Installs `ibus-libpinyin` and Simplified Chinese language packs, restarts the IBus daemon, and idempotently adds `('ibus', 'libpinyin')` to GNOME's input sources via `gsettings`. | | |
| 40 | 40 | | 16 | [`install-font.sh`](install-font.sh) | **user** | Installs the latest Ubuntu Sans Nerd Font and JetBrains Mono Nerd Font to `~/.local/share/fonts`, then refreshes the font cache. No sudo needed. | | |
| 41 | 41 | | 17 | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. | | |
| 42 | + | | 18 | [`install-localsend.sh`](install-localsend.sh) | sudo | Installs the latest official LocalSend `.deb` from `localsend/localsend`. SHA-256 verified against the GitHub release asset digest when available. | | |
| 43 | + | | 19 | [`install-telegram.sh`](install-telegram.sh) | sudo | Installs the latest official Telegram Desktop Linux build from Telegram's latest download endpoint. Adds launcher and `tg:` URL handler. | | |
| 44 | + | | 20 | [`install-discord.sh`](install-discord.sh) | sudo | Installs the latest official Discord Linux `.deb` from Discord's latest download endpoint. | | |
| 42 | 45 | ||
| 43 | 46 | "Runs as **user**" entries must be invoked as your normal desktop user, not via `sudo`. The other entries elevate via `sudo` internally and the menu primes `sudo -v` up-front, so you'll only be prompted once. | |
| 44 | 47 | ||
| @@ -47,7 +50,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 47 | 50 | If you'd rather skip the menu, each script can be run on its own. Use the right invocation pattern for that script's privilege mode: | |
| 48 | 51 | ||
| 49 | 52 | ```bash | |
| 50 | - | # sudo scripts (1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 17) — pipe through sudo bash | |
| 53 | + | # sudo scripts (1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 17, 18, 19, 20) — pipe through sudo bash | |
| 51 | 54 | curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 52 | 55 | ||
| 53 | 56 | # user scripts (10, 15, 16) — DO NOT use sudo; they install per-user | |
| @@ -70,7 +73,7 @@ The hardened installers generally share this robustness baseline: | |||
| 70 | 73 | - `dpkg --print-architecture` / `uname -m` for architecture (amd64 / arm64 / armhf / x86_64 / aarch64 as applicable to each upstream) | |
| 71 | 74 | - Idempotent — re-running a script does not duplicate APT sources, fstab entries, gsettings entries, or `.desktop` files | |
| 72 | 75 | - `signed-by` keyrings in `/etc/apt/keyrings` (no deprecated `apt-key add`) | |
| 73 | - | - Checksum verification where the upstream publishes one (JetBrains SHA-256, LibreOffice MD5, Obsidian SHA-256, ipatool SHA-256, draw.io SHA-256, qBittorrent SHA-256) | |
| 76 | + | - Checksum verification where the upstream publishes one (JetBrains SHA-256, LibreOffice MD5, Obsidian SHA-256, ipatool SHA-256, draw.io SHA-256, qBittorrent SHA-256, LocalSend SHA-256) | |
| 74 | 77 | - Per-user installers refuse to run as root; system installers refuse to run as non-root | |
| 75 | 78 | ||
| 76 | 79 | ## Supported distros | |
install-discord.sh(vytvořil soubor)
| @@ -0,0 +1,79 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # install-discord.sh — Install Discord from the latest official Linux .deb. | |
| 3 | + | # Hardened: official latest endpoint, idempotent, --dry-run. | |
| 4 | + | ||
| 5 | + | set -euo pipefail | |
| 6 | + | IFS=$'\n\t' | |
| 7 | + | ||
| 8 | + | readonly SCRIPT_NAME="${0##*/}" | |
| 9 | + | DRY_RUN=0 | |
| 10 | + | DOWNLOAD_URL="https://discord.com/api/download?platform=linux&format=deb" | |
| 11 | + | ||
| 12 | + | usage() { | |
| 13 | + | cat <<EOF | |
| 14 | + | Usage: sudo $SCRIPT_NAME [--dry-run] [--help] | |
| 15 | + | ||
| 16 | + | Downloads Discord from Discord's official latest Linux .deb endpoint and | |
| 17 | + | installs it with apt-get. | |
| 18 | + | ||
| 19 | + | Options: | |
| 20 | + | --dry-run Print actions without executing. | |
| 21 | + | --help, -h Show this help. | |
| 22 | + | EOF | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; } | |
| 26 | + | die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } | |
| 27 | + | run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } | |
| 28 | + | trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR | |
| 29 | + | ||
| 30 | + | while (( $# )); do | |
| 31 | + | case "$1" in | |
| 32 | + | --dry-run) DRY_RUN=1 ;; | |
| 33 | + | -h|--help) usage; exit 0 ;; | |
| 34 | + | *) die "Unknown argument: $1 (try --help)" ;; | |
| 35 | + | esac | |
| 36 | + | shift | |
| 37 | + | done | |
| 38 | + | ||
| 39 | + | (( EUID == 0 )) || die "Must run as root. Try: sudo $SCRIPT_NAME" | |
| 40 | + | ||
| 41 | + | [[ -r /etc/os-release ]] || die "/etc/os-release not found." | |
| 42 | + | # shellcheck disable=SC1091 | |
| 43 | + | . /etc/os-release | |
| 44 | + | case "${ID:-}:${ID_LIKE:-}" in | |
| 45 | + | *ubuntu*|*debian*) : ;; | |
| 46 | + | *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; | |
| 47 | + | esac | |
| 48 | + | ||
| 49 | + | ARCH="$(dpkg --print-architecture)" | |
| 50 | + | [[ "$ARCH" == "amd64" ]] || die "Discord's official Linux .deb targets amd64 only (detected: $ARCH)." | |
| 51 | + | log "Detected: ${PRETTY_NAME:-unknown}, arch: $ARCH" | |
| 52 | + | ||
| 53 | + | export DEBIAN_FRONTEND=noninteractive | |
| 54 | + | log "Installing prerequisites..." | |
| 55 | + | run "apt-get update -qq" | |
| 56 | + | run "apt-get install -y curl ca-certificates" | |
| 57 | + | ||
| 58 | + | STAGE="$(mktemp -d -t discord.XXXXXX)" | |
| 59 | + | trap 'rm -rf "$STAGE"' EXIT | |
| 60 | + | DEB="$STAGE/discord.deb" | |
| 61 | + | ||
| 62 | + | log "Downloading latest Discord .deb..." | |
| 63 | + | run "curl -fL -o '$DEB' '$DOWNLOAD_URL'" | |
| 64 | + | ||
| 65 | + | if (( DRY_RUN )); then | |
| 66 | + | printf ' DRY-RUN: apt-get install -y %s\n' "$DEB" | |
| 67 | + | log "Done." | |
| 68 | + | exit 0 | |
| 69 | + | fi | |
| 70 | + | ||
| 71 | + | log "Installing Discord..." | |
| 72 | + | run "apt-get install -y '$DEB'" | |
| 73 | + | ||
| 74 | + | if command -v discord >/dev/null 2>&1; then | |
| 75 | + | log "Installed: $(discord --version 2>/dev/null || dpkg-query -W -f='${Version}' discord 2>/dev/null || echo Discord)" | |
| 76 | + | else | |
| 77 | + | log "Installed: $(dpkg-query -W -f='${Version}' discord 2>/dev/null || echo Discord)" | |
| 78 | + | fi | |
| 79 | + | log "Done." | |
install-localsend.sh(vytvořil soubor)
| @@ -0,0 +1,116 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # install-localsend.sh — Install LocalSend from the latest official GitHub release. | |
| 3 | + | # Hardened: arch-aware, GitHub API token support, SHA-256 verification from the | |
| 4 | + | # release asset digest, idempotent, --dry-run. | |
| 5 | + | ||
| 6 | + | set -euo pipefail | |
| 7 | + | IFS=$'\n\t' | |
| 8 | + | ||
| 9 | + | readonly SCRIPT_NAME="${0##*/}" | |
| 10 | + | DRY_RUN=0 | |
| 11 | + | REPO="localsend/localsend" | |
| 12 | + | ||
| 13 | + | usage() { | |
| 14 | + | cat <<EOF | |
| 15 | + | Usage: sudo $SCRIPT_NAME [--dry-run] [--help] | |
| 16 | + | ||
| 17 | + | Resolves the latest release of localsend/localsend, downloads the official | |
| 18 | + | Linux .deb package for your architecture, verifies its SHA-256 against the | |
| 19 | + | GitHub release asset digest when available, and installs it with apt-get. | |
| 20 | + | ||
| 21 | + | If \$GITHUB_TOKEN is set in the environment, it is used to authenticate the | |
| 22 | + | GitHub API request (avoids rate limits). | |
| 23 | + | ||
| 24 | + | Options: | |
| 25 | + | --dry-run Print actions without executing. | |
| 26 | + | --help, -h Show this help. | |
| 27 | + | EOF | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; } | |
| 31 | + | warn() { printf '\033[1;33m[%s] WARN:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; } | |
| 32 | + | die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } | |
| 33 | + | run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } | |
| 34 | + | trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR | |
| 35 | + | ||
| 36 | + | while (( $# )); do | |
| 37 | + | case "$1" in | |
| 38 | + | --dry-run) DRY_RUN=1 ;; | |
| 39 | + | -h|--help) usage; exit 0 ;; | |
| 40 | + | *) die "Unknown argument: $1 (try --help)" ;; | |
| 41 | + | esac | |
| 42 | + | shift | |
| 43 | + | done | |
| 44 | + | ||
| 45 | + | (( EUID == 0 )) || die "Must run as root. Try: sudo $SCRIPT_NAME" | |
| 46 | + | ||
| 47 | + | [[ -r /etc/os-release ]] || die "/etc/os-release not found." | |
| 48 | + | # shellcheck disable=SC1091 | |
| 49 | + | . /etc/os-release | |
| 50 | + | case "${ID:-}:${ID_LIKE:-}" in | |
| 51 | + | *ubuntu*|*debian*) : ;; | |
| 52 | + | *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; | |
| 53 | + | esac | |
| 54 | + | ||
| 55 | + | ARCH="$(dpkg --print-architecture)" | |
| 56 | + | case "$ARCH" in | |
| 57 | + | amd64) ASSET_ARCH="x86-64" ;; | |
| 58 | + | arm64) ASSET_ARCH="arm-64" ;; | |
| 59 | + | *) die "LocalSend publishes Linux .deb assets for amd64 and arm64 only (detected: $ARCH)." ;; | |
| 60 | + | esac | |
| 61 | + | log "Detected: ${PRETTY_NAME:-unknown}, arch: $ARCH" | |
| 62 | + | ||
| 63 | + | export DEBIAN_FRONTEND=noninteractive | |
| 64 | + | log "Installing prerequisites..." | |
| 65 | + | run "apt-get update -qq" | |
| 66 | + | run "apt-get install -y curl jq ca-certificates" | |
| 67 | + | ||
| 68 | + | GH_HDRS=(-H "Accept: application/vnd.github+json") | |
| 69 | + | [[ -n "${GITHUB_TOKEN:-}" ]] && GH_HDRS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") | |
| 70 | + | ||
| 71 | + | log "Querying GitHub API for latest release of $REPO..." | |
| 72 | + | RELEASE_JSON="$(curl -fsSL "${GH_HDRS[@]}" "https://api.github.com/repos/${REPO}/releases/latest")" | |
| 73 | + | TAG="$(jq -r '.tag_name // empty' <<<"$RELEASE_JSON")" | |
| 74 | + | [[ -n "$TAG" ]] || die "Could not parse latest release tag (rate limited? set GITHUB_TOKEN)." | |
| 75 | + | log "Latest release: $TAG" | |
| 76 | + | ||
| 77 | + | ASSET_REGEX="^LocalSend-[^-]+-linux-${ASSET_ARCH}\\.deb$" | |
| 78 | + | DOWNLOAD_URL="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | .browser_download_url' <<<"$RELEASE_JSON" | head -n1)" | |
| 79 | + | ASSET_NAME="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | .name' <<<"$RELEASE_JSON" | head -n1)" | |
| 80 | + | EXPECTED_SHA="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | (.digest // "") | sub("^sha256:"; "")' <<<"$RELEASE_JSON" | head -n1)" | |
| 81 | + | [[ "$DOWNLOAD_URL" =~ ^https:// ]] || die "No $ARCH .deb asset found in latest LocalSend release." | |
| 82 | + | ||
| 83 | + | STAGE="$(mktemp -d -t localsend.XXXXXX)" | |
| 84 | + | trap 'rm -rf "$STAGE"' EXIT | |
| 85 | + | DEB="$STAGE/$ASSET_NAME" | |
| 86 | + | ||
| 87 | + | log "Downloading $ASSET_NAME..." | |
| 88 | + | run "curl -fL -o '$DEB' '$DOWNLOAD_URL'" | |
| 89 | + | ||
| 90 | + | if (( DRY_RUN )); then | |
| 91 | + | [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]] \ | |
| 92 | + | && printf ' DRY-RUN: verify SHA-256 %s\n' "$EXPECTED_SHA" \ | |
| 93 | + | || printf ' DRY-RUN: skip SHA-256 verification; no GitHub asset digest found\n' | |
| 94 | + | printf ' DRY-RUN: apt-get install -y %s\n' "$DEB" | |
| 95 | + | log "Done." | |
| 96 | + | exit 0 | |
| 97 | + | fi | |
| 98 | + | ||
| 99 | + | if [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]]; then | |
| 100 | + | log "Verifying SHA-256..." | |
| 101 | + | ACTUAL_SHA="$(sha256sum "$DEB" | awk '{print $1}')" | |
| 102 | + | [[ "$EXPECTED_SHA" == "$ACTUAL_SHA" ]] || die "SHA-256 mismatch: expected=$EXPECTED_SHA actual=$ACTUAL_SHA" | |
| 103 | + | log "SHA-256 ok." | |
| 104 | + | else | |
| 105 | + | warn "No SHA-256 asset digest in GitHub release metadata; skipping verification." | |
| 106 | + | fi | |
| 107 | + | ||
| 108 | + | log "Installing LocalSend..." | |
| 109 | + | run "apt-get install -y '$DEB'" | |
| 110 | + | ||
| 111 | + | if command -v localsend_app >/dev/null 2>&1; then | |
| 112 | + | log "Installed: $(localsend_app --version 2>/dev/null || echo "$TAG")" | |
| 113 | + | else | |
| 114 | + | log "Installed: $TAG" | |
| 115 | + | fi | |
| 116 | + | log "Done." | |
install-telegram.sh(vytvořil soubor)
| @@ -0,0 +1,116 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # install-telegram.sh — Install Telegram Desktop from the latest official Linux build. | |
| 3 | + | # Hardened: official download endpoint, arch-aware, idempotent, --dry-run. | |
| 4 | + | ||
| 5 | + | set -euo pipefail | |
| 6 | + | IFS=$'\n\t' | |
| 7 | + | ||
| 8 | + | readonly SCRIPT_NAME="${0##*/}" | |
| 9 | + | DRY_RUN=0 | |
| 10 | + | DOWNLOAD_URL="https://telegram.org/dl/desktop/linux" | |
| 11 | + | ||
| 12 | + | usage() { | |
| 13 | + | cat <<EOF | |
| 14 | + | Usage: sudo $SCRIPT_NAME [--dry-run] [--help] | |
| 15 | + | ||
| 16 | + | Downloads Telegram Desktop from Telegram's official latest Linux endpoint, | |
| 17 | + | installs it to /opt/Telegram, creates /usr/local/bin/telegram-desktop, and | |
| 18 | + | installs a desktop launcher. | |
| 19 | + | ||
| 20 | + | Options: | |
| 21 | + | --dry-run Print actions without executing. | |
| 22 | + | --help, -h Show this help. | |
| 23 | + | EOF | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; } | |
| 27 | + | die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } | |
| 28 | + | run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } | |
| 29 | + | trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR | |
| 30 | + | ||
| 31 | + | while (( $# )); do | |
| 32 | + | case "$1" in | |
| 33 | + | --dry-run) DRY_RUN=1 ;; | |
| 34 | + | -h|--help) usage; exit 0 ;; | |
| 35 | + | *) die "Unknown argument: $1 (try --help)" ;; | |
| 36 | + | esac | |
| 37 | + | shift | |
| 38 | + | done | |
| 39 | + | ||
| 40 | + | (( EUID == 0 )) || die "Must run as root. Try: sudo $SCRIPT_NAME" | |
| 41 | + | ||
| 42 | + | [[ -r /etc/os-release ]] || die "/etc/os-release not found." | |
| 43 | + | # shellcheck disable=SC1091 | |
| 44 | + | . /etc/os-release | |
| 45 | + | case "${ID:-}:${ID_LIKE:-}" in | |
| 46 | + | *ubuntu*|*debian*) : ;; | |
| 47 | + | *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; | |
| 48 | + | esac | |
| 49 | + | ||
| 50 | + | MACHINE="$(uname -m)" | |
| 51 | + | case "$MACHINE" in | |
| 52 | + | x86_64|amd64) : ;; | |
| 53 | + | *) die "Telegram's official Linux desktop tarball targets x86_64 (detected: $MACHINE)." ;; | |
| 54 | + | esac | |
| 55 | + | log "Detected: ${PRETTY_NAME:-unknown}, arch: $MACHINE" | |
| 56 | + | ||
| 57 | + | export DEBIAN_FRONTEND=noninteractive | |
| 58 | + | log "Installing prerequisites..." | |
| 59 | + | run "apt-get update -qq" | |
| 60 | + | run "apt-get install -y curl ca-certificates tar xz-utils desktop-file-utils hicolor-icon-theme" | |
| 61 | + | ||
| 62 | + | STAGE="$(mktemp -d -t telegram.XXXXXX)" | |
| 63 | + | trap 'rm -rf "$STAGE"' EXIT | |
| 64 | + | ARCHIVE="$STAGE/telegram.tar.xz" | |
| 65 | + | ||
| 66 | + | log "Downloading latest Telegram Desktop..." | |
| 67 | + | run "curl -fL -o '$ARCHIVE' '$DOWNLOAD_URL'" | |
| 68 | + | ||
| 69 | + | if (( DRY_RUN )); then | |
| 70 | + | printf ' DRY-RUN: extract %s\n' "$ARCHIVE" | |
| 71 | + | printf ' DRY-RUN: install Telegram to /opt/Telegram\n' | |
| 72 | + | printf ' DRY-RUN: create /usr/local/bin/telegram-desktop symlink and desktop launcher\n' | |
| 73 | + | log "Done." | |
| 74 | + | exit 0 | |
| 75 | + | fi | |
| 76 | + | ||
| 77 | + | log "Extracting Telegram..." | |
| 78 | + | run "tar -xJf '$ARCHIVE' -C '$STAGE'" | |
| 79 | + | [[ -x "$STAGE/Telegram/Telegram" ]] || die "Unexpected archive layout: Telegram/Telegram not found." | |
| 80 | + | ||
| 81 | + | log "Installing Telegram..." | |
| 82 | + | run "rm -rf /opt/Telegram" | |
| 83 | + | run "install -d -m 0755 /opt" | |
| 84 | + | run "cp -a '$STAGE/Telegram' /opt/Telegram" | |
| 85 | + | run "ln -sfn /opt/Telegram/Telegram /usr/local/bin/telegram-desktop" | |
| 86 | + | ||
| 87 | + | run "install -d -m 0755 /usr/share/applications /usr/share/icons/hicolor/256x256/apps" | |
| 88 | + | if [[ -f /opt/Telegram/telegram.png ]]; then | |
| 89 | + | run "install -m 0644 /opt/Telegram/telegram.png /usr/share/icons/hicolor/256x256/apps/telegram.png" | |
| 90 | + | fi | |
| 91 | + | ||
| 92 | + | cat >/usr/share/applications/telegram-desktop.desktop <<'EOF' | |
| 93 | + | [Desktop Entry] | |
| 94 | + | Type=Application | |
| 95 | + | Name=Telegram Desktop | |
| 96 | + | GenericName=Messenger | |
| 97 | + | Comment=Official Telegram desktop messaging app | |
| 98 | + | Exec=/opt/Telegram/Telegram -- %u | |
| 99 | + | Icon=telegram | |
| 100 | + | Terminal=false | |
| 101 | + | Categories=Network;InstantMessaging;Chat; | |
| 102 | + | MimeType=x-scheme-handler/tg; | |
| 103 | + | StartupNotify=false | |
| 104 | + | StartupWMClass=TelegramDesktop | |
| 105 | + | Keywords=telegram;chat;messenger; | |
| 106 | + | EOF | |
| 107 | + | ||
| 108 | + | run "desktop-file-validate /usr/share/applications/telegram-desktop.desktop" | |
| 109 | + | run "update-desktop-database /usr/share/applications" | |
| 110 | + | run "gtk-update-icon-cache -f -t /usr/share/icons/hicolor >/dev/null 2>&1 || true" | |
| 111 | + | if command -v xdg-mime >/dev/null 2>&1; then | |
| 112 | + | run "xdg-mime default telegram-desktop.desktop x-scheme-handler/tg || true" | |
| 113 | + | fi | |
| 114 | + | ||
| 115 | + | log "Installed: $(/opt/Telegram/Telegram --version 2>/dev/null || echo Telegram Desktop)" | |
| 116 | + | log "Done." | |
menu.sh
| @@ -123,6 +123,9 @@ OPTIONS=( | |||
| 123 | 123 | "15|Install IBus Intelligent Pinyin (per-user)|install-ibus-pinyin.sh|user" | |
| 124 | 124 | "16|Install Nerd Fonts (per-user)|install-font.sh|user" | |
| 125 | 125 | "17|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 126 | + | "18|Install LocalSend|install-localsend.sh|sudo" | |
| 127 | + | "19|Install Telegram Desktop|install-telegram.sh|sudo" | |
| 128 | + | "20|Install Discord|install-discord.sh|sudo" | |
| 126 | 129 | ) | |
| 127 | 130 | ||
| 128 | 131 | print_menu() { | |
| @@ -133,6 +136,10 @@ print_menu() { | |||
| 133 | 136 | echo " 1) Install Google Chrome" | |
| 134 | 137 | echo " 2) Install Firefox" | |
| 135 | 138 | echo " 3) Install Thunderbird" | |
| 139 | + | echo "--- [ Communication ] ---" | |
| 140 | + | echo " 18) Install LocalSend" | |
| 141 | + | echo " 19) Install Telegram Desktop" | |
| 142 | + | echo " 20) Install Discord" | |
| 136 | 143 | echo "--- [ Productivity & Security ] ---" | |
| 137 | 144 | echo " 4) Install 1Password" | |
| 138 | 145 | echo " 5) Install Espanso" | |
| @@ -151,7 +158,7 @@ print_menu() { | |||
| 151 | 158 | echo " 16) Install Nerd Fonts (runs as you, not root)" | |
| 152 | 159 | echo " 17) Fix Dual-Boot Time (RTC to UTC)" | |
| 153 | 160 | hr | |
| 154 | - | echo " 0) Run ALL options (1-17)" | |
| 161 | + | echo " 0) Run ALL options (1-20)" | |
| 155 | 162 | echo " -1) Exit" | |
| 156 | 163 | hr | |
| 157 | 164 | } | |
| @@ -170,7 +177,7 @@ resolve_choice() { | |||
| 170 | 177 | return 1 | |
| 171 | 178 | } | |
| 172 | 179 | ||
| 173 | - | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17) | |
| 180 | + | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20) | |
| 174 | 181 | ||
| 175 | 182 | prime_sudo || exit 1 | |
| 176 | 183 | ||
weehong revidoval tento gist 4 days ago. Přejít na revizi
2 files changed, 381 insertions, 146 deletions
install-qbittorrent.sh(vytvořil soubor)
| @@ -0,0 +1,160 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # install-qbittorrent.sh — Install qBittorrent from the latest official GitHub release. | |
| 3 | + | # Hardened: arch-aware, GitHub API token support, SHA-256 verification from the | |
| 4 | + | # release asset digest, idempotent, --dry-run. | |
| 5 | + | ||
| 6 | + | set -euo pipefail | |
| 7 | + | IFS=$'\n\t' | |
| 8 | + | ||
| 9 | + | readonly SCRIPT_NAME="${0##*/}" | |
| 10 | + | DRY_RUN=0 | |
| 11 | + | REPO="qbittorrent/qBittorrent" | |
| 12 | + | ||
| 13 | + | usage() { | |
| 14 | + | cat <<EOF | |
| 15 | + | Usage: sudo $SCRIPT_NAME [--dry-run] [--help] | |
| 16 | + | ||
| 17 | + | Resolves the latest release of qbittorrent/qBittorrent, downloads the official | |
| 18 | + | Linux x86_64 AppImage, verifies its SHA-256 against the GitHub release asset | |
| 19 | + | digest when available, and installs it system-wide. | |
| 20 | + | ||
| 21 | + | If \$GITHUB_TOKEN is set in the environment, it is used to authenticate the | |
| 22 | + | GitHub API request (avoids rate limits). | |
| 23 | + | ||
| 24 | + | Options: | |
| 25 | + | --dry-run Print actions without executing. | |
| 26 | + | --help, -h Show this help. | |
| 27 | + | EOF | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; } | |
| 31 | + | warn() { printf '\033[1;33m[%s] WARN:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; } | |
| 32 | + | die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } | |
| 33 | + | run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } | |
| 34 | + | trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR | |
| 35 | + | ||
| 36 | + | while (( $# )); do | |
| 37 | + | case "$1" in | |
| 38 | + | --dry-run) DRY_RUN=1 ;; | |
| 39 | + | -h|--help) usage; exit 0 ;; | |
| 40 | + | *) die "Unknown argument: $1 (try --help)" ;; | |
| 41 | + | esac | |
| 42 | + | shift | |
| 43 | + | done | |
| 44 | + | ||
| 45 | + | (( EUID == 0 )) || die "Must run as root. Try: sudo $SCRIPT_NAME" | |
| 46 | + | ||
| 47 | + | [[ -r /etc/os-release ]] || die "/etc/os-release not found." | |
| 48 | + | # shellcheck disable=SC1091 | |
| 49 | + | . /etc/os-release | |
| 50 | + | case "${ID:-}:${ID_LIKE:-}" in | |
| 51 | + | *ubuntu*|*debian*) : ;; | |
| 52 | + | *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; | |
| 53 | + | esac | |
| 54 | + | ||
| 55 | + | ARCH="$(dpkg --print-architecture)" | |
| 56 | + | [[ "$ARCH" == "amd64" ]] || die "qBittorrent publishes official Linux AppImages for x86_64/amd64 only (detected: $ARCH)." | |
| 57 | + | log "Detected: ${PRETTY_NAME:-unknown}, arch: $ARCH" | |
| 58 | + | ||
| 59 | + | export DEBIAN_FRONTEND=noninteractive | |
| 60 | + | log "Installing prerequisites..." | |
| 61 | + | run "apt-get update -qq" | |
| 62 | + | run "apt-get install -y curl jq ca-certificates desktop-file-utils shared-mime-info hicolor-icon-theme" | |
| 63 | + | ||
| 64 | + | GH_HDRS=(-H "Accept: application/vnd.github+json") | |
| 65 | + | [[ -n "${GITHUB_TOKEN:-}" ]] && GH_HDRS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") | |
| 66 | + | ||
| 67 | + | log "Querying GitHub API for latest release of $REPO..." | |
| 68 | + | RELEASE_JSON="$(curl -fsSL "${GH_HDRS[@]}" "https://api.github.com/repos/${REPO}/releases/latest")" | |
| 69 | + | TAG="$(jq -r '.tag_name // empty' <<<"$RELEASE_JSON")" | |
| 70 | + | [[ -n "$TAG" ]] || die "Could not parse latest release tag (rate limited? set GITHUB_TOKEN)." | |
| 71 | + | VERSION="${TAG#release-}" | |
| 72 | + | log "Latest release: $TAG" | |
| 73 | + | ||
| 74 | + | ASSET_REGEX='^qbittorrent-[0-9][^/]*_x86_64\.AppImage$' | |
| 75 | + | DOWNLOAD_URL="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | select(.name | test("_lt20_") | not) | .browser_download_url' <<<"$RELEASE_JSON" | head -n1)" | |
| 76 | + | ASSET_NAME="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | select(.name | test("_lt20_") | not) | .name' <<<"$RELEASE_JSON" | head -n1)" | |
| 77 | + | EXPECTED_SHA="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | select(.name | test("_lt20_") | not) | (.digest // "") | sub("^sha256:"; "")' <<<"$RELEASE_JSON" | head -n1)" | |
| 78 | + | [[ "$DOWNLOAD_URL" =~ ^https:// ]] || die "No standard x86_64 AppImage asset found in latest qBittorrent release." | |
| 79 | + | ||
| 80 | + | STAGE="$(mktemp -d -t qbittorrent.XXXXXX)" | |
| 81 | + | trap 'rm -rf "$STAGE"' EXIT | |
| 82 | + | APPIMAGE="$STAGE/$ASSET_NAME" | |
| 83 | + | ||
| 84 | + | log "Downloading $ASSET_NAME..." | |
| 85 | + | run "curl -fL -o '$APPIMAGE' '$DOWNLOAD_URL'" | |
| 86 | + | ||
| 87 | + | if (( DRY_RUN )); then | |
| 88 | + | [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]] \ | |
| 89 | + | && printf ' DRY-RUN: verify SHA-256 %s\n' "$EXPECTED_SHA" \ | |
| 90 | + | || printf ' DRY-RUN: skip SHA-256 verification; no GitHub asset digest found\n' | |
| 91 | + | printf ' DRY-RUN: install AppImage to /opt/qbittorrent/qbittorrent\n' | |
| 92 | + | printf ' DRY-RUN: create /usr/local/bin/qbittorrent symlink\n' | |
| 93 | + | printf ' DRY-RUN: install desktop launcher, icons, and MIME handlers\n' | |
| 94 | + | log "Done." | |
| 95 | + | exit 0 | |
| 96 | + | fi | |
| 97 | + | ||
| 98 | + | if [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]]; then | |
| 99 | + | log "Verifying SHA-256..." | |
| 100 | + | ACTUAL_SHA="$(sha256sum "$APPIMAGE" | awk '{print $1}')" | |
| 101 | + | [[ "$EXPECTED_SHA" == "$ACTUAL_SHA" ]] || die "SHA-256 mismatch: expected=$EXPECTED_SHA actual=$ACTUAL_SHA" | |
| 102 | + | log "SHA-256 ok." | |
| 103 | + | else | |
| 104 | + | warn "No SHA-256 asset digest in GitHub release metadata; skipping verification." | |
| 105 | + | fi | |
| 106 | + | ||
| 107 | + | log "Installing qBittorrent AppImage..." | |
| 108 | + | run "install -d -m 0755 /opt/qbittorrent" | |
| 109 | + | run "install -m 0755 '$APPIMAGE' /opt/qbittorrent/qbittorrent" | |
| 110 | + | run "ln -sfn /opt/qbittorrent/qbittorrent /usr/local/bin/qbittorrent" | |
| 111 | + | ||
| 112 | + | log "Extracting desktop metadata and icons..." | |
| 113 | + | EXTRACT_DIR="$STAGE/extract" | |
| 114 | + | run "install -d -m 0755 '$EXTRACT_DIR'" | |
| 115 | + | ( | |
| 116 | + | cd "$EXTRACT_DIR" | |
| 117 | + | /opt/qbittorrent/qbittorrent --appimage-extract >/dev/null | |
| 118 | + | ) | |
| 119 | + | ||
| 120 | + | run "install -d -m 0755 /usr/share/icons/hicolor/scalable/apps" | |
| 121 | + | run "install -m 0644 '$EXTRACT_DIR/squashfs-root/usr/share/icons/hicolor/scalable/apps/qbittorrent.svg' /usr/share/icons/hicolor/scalable/apps/qbittorrent.svg" | |
| 122 | + | for size in 16 22 24 32 36 48 64 72 96 128 192; do | |
| 123 | + | src="$EXTRACT_DIR/squashfs-root/usr/share/icons/hicolor/${size}x${size}/apps/qbittorrent.png" | |
| 124 | + | [[ -f "$src" ]] || continue | |
| 125 | + | run "install -d -m 0755 '/usr/share/icons/hicolor/${size}x${size}/apps'" | |
| 126 | + | run "install -m 0644 '$src' '/usr/share/icons/hicolor/${size}x${size}/apps/qbittorrent.png'" | |
| 127 | + | done | |
| 128 | + | ||
| 129 | + | log "Installing desktop launcher..." | |
| 130 | + | cat >/usr/share/applications/org.qbittorrent.qBittorrent.desktop <<EOF | |
| 131 | + | [Desktop Entry] | |
| 132 | + | Type=Application | |
| 133 | + | Name=qBittorrent | |
| 134 | + | GenericName=BitTorrent client | |
| 135 | + | Comment=Download and share files over BitTorrent | |
| 136 | + | Exec=/opt/qbittorrent/qbittorrent %U | |
| 137 | + | Icon=qbittorrent | |
| 138 | + | Terminal=false | |
| 139 | + | Categories=Network;FileTransfer;P2P;Qt; | |
| 140 | + | MimeType=application/x-bittorrent;x-scheme-handler/magnet; | |
| 141 | + | StartupNotify=false | |
| 142 | + | StartupWMClass=qbittorrent | |
| 143 | + | Keywords=bittorrent;torrent;magnet;download;p2p; | |
| 144 | + | SingleMainWindow=true | |
| 145 | + | EOF | |
| 146 | + | ||
| 147 | + | run "desktop-file-validate /usr/share/applications/org.qbittorrent.qBittorrent.desktop" | |
| 148 | + | run "update-desktop-database /usr/share/applications" | |
| 149 | + | run "update-mime-database /usr/share/mime" | |
| 150 | + | run "gtk-update-icon-cache -f -t /usr/share/icons/hicolor >/dev/null 2>&1 || true" | |
| 151 | + | ||
| 152 | + | if command -v xdg-mime >/dev/null 2>&1; then | |
| 153 | + | run "xdg-mime default org.qbittorrent.qBittorrent.desktop application/x-bittorrent || true" | |
| 154 | + | run "xdg-mime default org.qbittorrent.qBittorrent.desktop x-scheme-handler/magnet || true" | |
| 155 | + | fi | |
| 156 | + | ||
| 157 | + | if command -v qbittorrent >/dev/null 2>&1; then | |
| 158 | + | log "Installed: $(qbittorrent --version 2>/dev/null || echo "$VERSION")" | |
| 159 | + | fi | |
| 160 | + | log "Done." | |
menu.sh
| @@ -1,160 +1,235 @@ | |||
| 1 | 1 | #!/usr/bin/env bash | |
| 2 | - | # install-qbittorrent.sh — Install qBittorrent from the latest official GitHub release. | |
| 3 | - | # Hardened: arch-aware, GitHub API token support, SHA-256 verification from the | |
| 4 | - | # release asset digest, idempotent, --dry-run. | |
| 5 | - | ||
| 6 | - | set -euo pipefail | |
| 2 | + | # menu.sh — Ubuntu/Debian Setup Manager | |
| 3 | + | # | |
| 4 | + | # Interactive menu that fetches and runs the hardened install scripts in this | |
| 5 | + | # Opengist. Each script is downloaded to a temp file (not blindly piped to bash) | |
| 6 | + | # and executed with the appropriate privilege level for that script: | |
| 7 | + | # | |
| 8 | + | # - "sudo" mode for system-wide installers (apt, /etc, /usr/local/bin) | |
| 9 | + | # - "user" mode for per-user installers that must NOT run as root | |
| 10 | + | # (gsettings, ~/.local, JetBrains Toolbox, fonts) | |
| 11 | + | # | |
| 12 | + | # Usage: | |
| 13 | + | # bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/menu.sh)" | |
| 14 | + | # | |
| 15 | + | # Or save and run locally: | |
| 16 | + | # curl -fsSLO https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/menu.sh | |
| 17 | + | # bash menu.sh | |
| 18 | + | ||
| 19 | + | # Note: NOT using `set -e` because we want the menu loop to survive a failed | |
| 20 | + | # sub-script. We do use -u and pipefail to catch real bugs in this file. | |
| 21 | + | set -uo pipefail | |
| 7 | 22 | IFS=$'\n\t' | |
| 8 | 23 | ||
| 9 | 24 | readonly SCRIPT_NAME="${0##*/}" | |
| 10 | - | DRY_RUN=0 | |
| 11 | - | REPO="qbittorrent/qBittorrent" | |
| 12 | - | ||
| 13 | - | usage() { | |
| 14 | - | cat <<EOF | |
| 15 | - | Usage: sudo $SCRIPT_NAME [--dry-run] [--help] | |
| 16 | - | ||
| 17 | - | Resolves the latest release of qbittorrent/qBittorrent, downloads the official | |
| 18 | - | Linux x86_64 AppImage, verifies its SHA-256 against the GitHub release asset | |
| 19 | - | digest when available, and installs it system-wide. | |
| 20 | - | ||
| 21 | - | If \$GITHUB_TOKEN is set in the environment, it is used to authenticate the | |
| 22 | - | GitHub API request (avoids rate limits). | |
| 23 | - | ||
| 24 | - | Options: | |
| 25 | - | --dry-run Print actions without executing. | |
| 26 | - | --help, -h Show this help. | |
| 27 | - | EOF | |
| 28 | - | } | |
| 25 | + | readonly BASE_URL='https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD' | |
| 26 | + | ||
| 27 | + | log() { printf '\033[1;34m[menu]\033[0m %s\n' "$*"; } | |
| 28 | + | warn() { printf '\033[1;33m[menu] WARN:\033[0m %s\n' "$*" >&2; } | |
| 29 | + | err() { printf '\033[1;31m[menu] ERROR:\033[0m %s\n' "$*" >&2; } | |
| 30 | + | hr() { printf '%s\n' "------------------------------------------------------"; } | |
| 31 | + | ||
| 32 | + | # Refuse to run as root: per-user scripts (jetbrains, fonts, ibus) need a real | |
| 33 | + | # desktop user. Sub-scripts elevate via sudo on their own. | |
| 34 | + | if (( EUID == 0 )); then | |
| 35 | + | err "Don't run $SCRIPT_NAME as root. Run as your normal user — it will call sudo for installers that need it." | |
| 36 | + | exit 1 | |
| 37 | + | fi | |
| 29 | 38 | ||
| 30 | - | log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; } | |
| 31 | - | warn() { printf '\033[1;33m[%s] WARN:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; } | |
| 32 | - | die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } | |
| 33 | - | run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } | |
| 34 | - | trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR | |
| 35 | - | ||
| 36 | - | while (( $# )); do | |
| 37 | - | case "$1" in | |
| 38 | - | --dry-run) DRY_RUN=1 ;; | |
| 39 | - | -h|--help) usage; exit 0 ;; | |
| 40 | - | *) die "Unknown argument: $1 (try --help)" ;; | |
| 41 | - | esac | |
| 42 | - | shift | |
| 39 | + | # Sanity-check tools we depend on. | |
| 40 | + | for tool in curl bash mktemp; do | |
| 41 | + | command -v "$tool" >/dev/null 2>&1 || { err "Missing required tool: $tool"; exit 1; } | |
| 43 | 42 | done | |
| 44 | 43 | ||
| 45 | - | (( EUID == 0 )) || die "Must run as root. Try: sudo $SCRIPT_NAME" | |
| 46 | - | ||
| 47 | - | [[ -r /etc/os-release ]] || die "/etc/os-release not found." | |
| 48 | - | # shellcheck disable=SC1091 | |
| 49 | - | . /etc/os-release | |
| 50 | - | case "${ID:-}:${ID_LIKE:-}" in | |
| 51 | - | *ubuntu*|*debian*) : ;; | |
| 52 | - | *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; | |
| 53 | - | esac | |
| 54 | - | ||
| 55 | - | ARCH="$(dpkg --print-architecture)" | |
| 56 | - | [[ "$ARCH" == "amd64" ]] || die "qBittorrent publishes official Linux AppImages for x86_64/amd64 only (detected: $ARCH)." | |
| 57 | - | log "Detected: ${PRETTY_NAME:-unknown}, arch: $ARCH" | |
| 58 | - | ||
| 59 | - | export DEBIAN_FRONTEND=noninteractive | |
| 60 | - | log "Installing prerequisites..." | |
| 61 | - | run "apt-get update -qq" | |
| 62 | - | run "apt-get install -y curl jq ca-certificates desktop-file-utils shared-mime-info hicolor-icon-theme" | |
| 63 | - | ||
| 64 | - | GH_HDRS=(-H "Accept: application/vnd.github+json") | |
| 65 | - | [[ -n "${GITHUB_TOKEN:-}" ]] && GH_HDRS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") | |
| 66 | - | ||
| 67 | - | log "Querying GitHub API for latest release of $REPO..." | |
| 68 | - | RELEASE_JSON="$(curl -fsSL "${GH_HDRS[@]}" "https://api.github.com/repos/${REPO}/releases/latest")" | |
| 69 | - | TAG="$(jq -r '.tag_name // empty' <<<"$RELEASE_JSON")" | |
| 70 | - | [[ -n "$TAG" ]] || die "Could not parse latest release tag (rate limited? set GITHUB_TOKEN)." | |
| 71 | - | VERSION="${TAG#release-}" | |
| 72 | - | log "Latest release: $TAG" | |
| 73 | - | ||
| 74 | - | ASSET_REGEX='^qbittorrent-[0-9][^/]*_x86_64\.AppImage$' | |
| 75 | - | DOWNLOAD_URL="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | select(.name | test("_lt20_") | not) | .browser_download_url' <<<"$RELEASE_JSON" | head -n1)" | |
| 76 | - | ASSET_NAME="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | select(.name | test("_lt20_") | not) | .name' <<<"$RELEASE_JSON" | head -n1)" | |
| 77 | - | EXPECTED_SHA="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | select(.name | test("_lt20_") | not) | (.digest // "") | sub("^sha256:"; "")' <<<"$RELEASE_JSON" | head -n1)" | |
| 78 | - | [[ "$DOWNLOAD_URL" =~ ^https:// ]] || die "No standard x86_64 AppImage asset found in latest qBittorrent release." | |
| 79 | - | ||
| 80 | - | STAGE="$(mktemp -d -t qbittorrent.XXXXXX)" | |
| 81 | - | trap 'rm -rf "$STAGE"' EXIT | |
| 82 | - | APPIMAGE="$STAGE/$ASSET_NAME" | |
| 83 | - | ||
| 84 | - | log "Downloading $ASSET_NAME..." | |
| 85 | - | run "curl -fL -o '$APPIMAGE' '$DOWNLOAD_URL'" | |
| 86 | - | ||
| 87 | - | if (( DRY_RUN )); then | |
| 88 | - | [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]] \ | |
| 89 | - | && printf ' DRY-RUN: verify SHA-256 %s\n' "$EXPECTED_SHA" \ | |
| 90 | - | || printf ' DRY-RUN: skip SHA-256 verification; no GitHub asset digest found\n' | |
| 91 | - | printf ' DRY-RUN: install AppImage to /opt/qbittorrent/qbittorrent\n' | |
| 92 | - | printf ' DRY-RUN: create /usr/local/bin/qbittorrent symlink\n' | |
| 93 | - | printf ' DRY-RUN: install desktop launcher, icons, and MIME handlers\n' | |
| 94 | - | log "Done." | |
| 95 | - | exit 0 | |
| 44 | + | # Distro check (warn-only; sub-scripts enforce strictly). | |
| 45 | + | if [[ -r /etc/os-release ]]; then | |
| 46 | + | # shellcheck disable=SC1091 | |
| 47 | + | . /etc/os-release | |
| 48 | + | case "${ID:-}:${ID_LIKE:-}" in | |
| 49 | + | *ubuntu*|*debian*) : ;; | |
| 50 | + | *) warn "Detected ${PRETTY_NAME:-unknown}. These installers target Debian/Ubuntu; some will refuse to run." ;; | |
| 51 | + | esac | |
| 96 | 52 | fi | |
| 97 | 53 | ||
| 98 | - | if [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]]; then | |
| 99 | - | log "Verifying SHA-256..." | |
| 100 | - | ACTUAL_SHA="$(sha256sum "$APPIMAGE" | awk '{print $1}')" | |
| 101 | - | [[ "$EXPECTED_SHA" == "$ACTUAL_SHA" ]] || die "SHA-256 mismatch: expected=$EXPECTED_SHA actual=$ACTUAL_SHA" | |
| 102 | - | log "SHA-256 ok." | |
| 103 | - | else | |
| 104 | - | warn "No SHA-256 asset digest in GitHub release metadata; skipping verification." | |
| 105 | - | fi | |
| 54 | + | # Cache sudo credentials up-front so sub-scripts that elevate don't keep | |
| 55 | + | # re-prompting in the middle of a multi-task run. | |
| 56 | + | prime_sudo() { | |
| 57 | + | if ! sudo -n true 2>/dev/null; then | |
| 58 | + | log "Caching sudo credentials (you may be prompted)..." | |
| 59 | + | sudo -v || { err "sudo authentication failed."; return 1; } | |
| 60 | + | fi | |
| 61 | + | # Keep sudo timestamp refreshed in the background while the menu runs. | |
| 62 | + | ( while true; do sudo -n true 2>/dev/null; sleep 60; done ) & | |
| 63 | + | SUDO_KEEPALIVE_PID=$! | |
| 64 | + | trap 'kill "${SUDO_KEEPALIVE_PID:-}" 2>/dev/null || true' EXIT | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | # run_script <filename> <mode> | |
| 68 | + | # mode: "sudo" (run as root via sudo) or "user" (run as current user) | |
| 69 | + | # Returns: 0 on success, non-zero on failure (does NOT exit the menu). | |
| 70 | + | run_script() { | |
| 71 | + | local name="$1" mode="$2" | |
| 72 | + | local url="$BASE_URL/$name" | |
| 73 | + | local tmp | |
| 74 | + | tmp="$(mktemp -t "${name%.sh}.XXXXXX.sh")" || { err "mktemp failed"; return 1; } | |
| 75 | + | ||
| 76 | + | log "Fetching $name..." | |
| 77 | + | if ! curl -fsSL --max-time 60 --retry 2 "$url" -o "$tmp"; then | |
| 78 | + | err "Download failed: $url" | |
| 79 | + | rm -f "$tmp" | |
| 80 | + | return 1 | |
| 81 | + | fi | |
| 82 | + | if [[ ! -s "$tmp" ]]; then | |
| 83 | + | err "Downloaded $name is empty." | |
| 84 | + | rm -f "$tmp" | |
| 85 | + | return 1 | |
| 86 | + | fi | |
| 87 | + | # Cheap sanity: confirm it looks like a shell script. | |
| 88 | + | if ! head -n1 "$tmp" | grep -qE '^#!.*sh'; then | |
| 89 | + | warn "$name doesn't start with a shebang; proceeding anyway." | |
| 90 | + | fi | |
| 91 | + | chmod +x "$tmp" | |
| 92 | + | ||
| 93 | + | local rc=0 | |
| 94 | + | if [[ "$mode" == "sudo" ]]; then | |
| 95 | + | sudo bash "$tmp" || rc=$? | |
| 96 | + | else | |
| 97 | + | bash "$tmp" || rc=$? | |
| 98 | + | fi | |
| 99 | + | rm -f "$tmp" | |
| 100 | + | if (( rc != 0 )); then | |
| 101 | + | err "$name exited with status $rc" | |
| 102 | + | fi | |
| 103 | + | return "$rc" | |
| 104 | + | } | |
| 106 | 105 | ||
| 107 | - | log "Installing qBittorrent AppImage..." | |
| 108 | - | run "install -d -m 0755 /opt/qbittorrent" | |
| 109 | - | run "install -m 0755 '$APPIMAGE' /opt/qbittorrent/qbittorrent" | |
| 110 | - | run "ln -sfn /opt/qbittorrent/qbittorrent /usr/local/bin/qbittorrent" | |
| 111 | - | ||
| 112 | - | log "Extracting desktop metadata and icons..." | |
| 113 | - | EXTRACT_DIR="$STAGE/extract" | |
| 114 | - | run "install -d -m 0755 '$EXTRACT_DIR'" | |
| 115 | - | ( | |
| 116 | - | cd "$EXTRACT_DIR" | |
| 117 | - | /opt/qbittorrent/qbittorrent --appimage-extract >/dev/null | |
| 106 | + | # Catalog: number | label | script-filename | mode | |
| 107 | + | # (Edit here to add/remove options — the menu loop is data-driven.) | |
| 108 | + | OPTIONS=( | |
| 109 | + | "1|Install Google Chrome|install-chrome.sh|sudo" | |
| 110 | + | "2|Install Firefox (Mozilla APT)|install-firefox.sh|sudo" | |
| 111 | + | "3|Install Thunderbird|install-thunderbird.sh|sudo" | |
| 112 | + | "4|Install 1Password|install-1password.sh|sudo" | |
| 113 | + | "5|Install Espanso (text expander)|install-espanso.sh|sudo" | |
| 114 | + | "6|Install LibreOffice (latest stable .deb)|install-libreoffice.sh|sudo" | |
| 115 | + | "7|Install Obsidian|install-obsidian.sh|sudo" | |
| 116 | + | "8|Install draw.io Desktop|install-drawio.sh|sudo" | |
| 117 | + | "9|Install Visual Studio Code|install-vscode.sh|sudo" | |
| 118 | + | "10|Install JetBrains Toolbox (per-user)|install-jetbrains-toolbox.sh|user" | |
| 119 | + | "11|Install Bruno (API client)|install-bruno.sh|sudo" | |
| 120 | + | "12|Install IPATool|install-ipatool.sh|sudo" | |
| 121 | + | "13|Install qBittorrent (latest AppImage)|install-qbittorrent.sh|sudo" | |
| 122 | + | "14|Mount Synology Network Drive|install-network_drive.sh|sudo" | |
| 123 | + | "15|Install IBus Intelligent Pinyin (per-user)|install-ibus-pinyin.sh|user" | |
| 124 | + | "16|Install Nerd Fonts (per-user)|install-font.sh|user" | |
| 125 | + | "17|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 118 | 126 | ) | |
| 119 | 127 | ||
| 120 | - | run "install -d -m 0755 /usr/share/icons/hicolor/scalable/apps" | |
| 121 | - | run "install -m 0644 '$EXTRACT_DIR/squashfs-root/usr/share/icons/hicolor/scalable/apps/qbittorrent.svg' /usr/share/icons/hicolor/scalable/apps/qbittorrent.svg" | |
| 122 | - | for size in 16 22 24 32 36 48 64 72 96 128 192; do | |
| 123 | - | src="$EXTRACT_DIR/squashfs-root/usr/share/icons/hicolor/${size}x${size}/apps/qbittorrent.png" | |
| 124 | - | [[ -f "$src" ]] || continue | |
| 125 | - | run "install -d -m 0755 '/usr/share/icons/hicolor/${size}x${size}/apps'" | |
| 126 | - | run "install -m 0644 '$src' '/usr/share/icons/hicolor/${size}x${size}/apps/qbittorrent.png'" | |
| 127 | - | done | |
| 128 | + | print_menu() { | |
| 129 | + | hr | |
| 130 | + | echo " Ubuntu / Debian Setup Manager" | |
| 131 | + | hr | |
| 132 | + | echo "--- [ Browsers & Mail ] ---" | |
| 133 | + | echo " 1) Install Google Chrome" | |
| 134 | + | echo " 2) Install Firefox" | |
| 135 | + | echo " 3) Install Thunderbird" | |
| 136 | + | echo "--- [ Productivity & Security ] ---" | |
| 137 | + | echo " 4) Install 1Password" | |
| 138 | + | echo " 5) Install Espanso" | |
| 139 | + | echo " 6) Install LibreOffice" | |
| 140 | + | echo " 7) Install Obsidian" | |
| 141 | + | echo " 8) Install draw.io Desktop" | |
| 142 | + | echo "--- [ Development Tools ] ---" | |
| 143 | + | echo " 9) Install Visual Studio Code" | |
| 144 | + | echo " 10) Install JetBrains Toolbox (runs as you, not root)" | |
| 145 | + | echo " 11) Install Bruno" | |
| 146 | + | echo " 12) Install IPATool" | |
| 147 | + | echo " 13) Install qBittorrent" | |
| 148 | + | echo "--- [ System ] ---" | |
| 149 | + | echo " 14) Mount Synology Network Drive" | |
| 150 | + | echo " 15) Install IBus Intelligent Pinyin (runs as you, not root)" | |
| 151 | + | echo " 16) Install Nerd Fonts (runs as you, not root)" | |
| 152 | + | echo " 17) Fix Dual-Boot Time (RTC to UTC)" | |
| 153 | + | hr | |
| 154 | + | echo " 0) Run ALL options (1-17)" | |
| 155 | + | echo " -1) Exit" | |
| 156 | + | hr | |
| 157 | + | } | |
| 128 | 158 | ||
| 129 | - | log "Installing desktop launcher..." | |
| 130 | - | cat >/usr/share/applications/org.qbittorrent.qBittorrent.desktop <<EOF | |
| 131 | - | [Desktop Entry] | |
| 132 | - | Type=Application | |
| 133 | - | Name=qBittorrent | |
| 134 | - | GenericName=BitTorrent client | |
| 135 | - | Comment=Download and share files over BitTorrent | |
| 136 | - | Exec=/opt/qbittorrent/qbittorrent %U | |
| 137 | - | Icon=qbittorrent | |
| 138 | - | Terminal=false | |
| 139 | - | Categories=Network;FileTransfer;P2P;Qt; | |
| 140 | - | MimeType=application/x-bittorrent;x-scheme-handler/magnet; | |
| 141 | - | StartupNotify=false | |
| 142 | - | StartupWMClass=qbittorrent | |
| 143 | - | Keywords=bittorrent;torrent;magnet;download;p2p; | |
| 144 | - | SingleMainWindow=true | |
| 145 | - | EOF | |
| 146 | - | ||
| 147 | - | run "desktop-file-validate /usr/share/applications/org.qbittorrent.qBittorrent.desktop" | |
| 148 | - | run "update-desktop-database /usr/share/applications" | |
| 149 | - | run "update-mime-database /usr/share/mime" | |
| 150 | - | run "gtk-update-icon-cache -f -t /usr/share/icons/hicolor >/dev/null 2>&1 || true" | |
| 151 | - | ||
| 152 | - | if command -v xdg-mime >/dev/null 2>&1; then | |
| 153 | - | run "xdg-mime default org.qbittorrent.qBittorrent.desktop application/x-bittorrent || true" | |
| 154 | - | run "xdg-mime default org.qbittorrent.qBittorrent.desktop x-scheme-handler/magnet || true" | |
| 155 | - | fi | |
| 159 | + | # Resolve a numeric choice to its catalog entry; print "name|mode" to stdout. | |
| 160 | + | resolve_choice() { | |
| 161 | + | local want="$1" entry num | |
| 162 | + | for entry in "${OPTIONS[@]}"; do | |
| 163 | + | num="${entry%%|*}" | |
| 164 | + | if [[ "$num" == "$want" ]]; then | |
| 165 | + | # Strip the leading "N|label|"; what remains is "filename|mode" | |
| 166 | + | printf '%s' "${entry#*|*|}" | |
| 167 | + | return 0 | |
| 168 | + | fi | |
| 169 | + | done | |
| 170 | + | return 1 | |
| 171 | + | } | |
| 156 | 172 | ||
| 157 | - | if command -v qbittorrent >/dev/null 2>&1; then | |
| 158 | - | log "Installed: $(qbittorrent --version 2>/dev/null || echo "$VERSION")" | |
| 159 | - | fi | |
| 160 | - | log "Done." | |
| 173 | + | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17) | |
| 174 | + | ||
| 175 | + | prime_sudo || exit 1 | |
| 176 | + | ||
| 177 | + | while true; do | |
| 178 | + | print_menu | |
| 179 | + | read -rp "Enter choices separated by spaces (e.g., 1 7 12), 0 for ALL, -1 to exit: " choices </dev/tty || { | |
| 180 | + | echo; log "Input closed; exiting." | |
| 181 | + | break | |
| 182 | + | } | |
| 183 | + | ||
| 184 | + | # Trim whitespace | |
| 185 | + | choices="${choices##[[:space:]]}" | |
| 186 | + | choices="${choices%%[[:space:]]}" | |
| 187 | + | ||
| 188 | + | if [[ -z "$choices" ]]; then | |
| 189 | + | continue | |
| 190 | + | fi | |
| 191 | + | ||
| 192 | + | if [[ "$choices" == "-1" ]]; then | |
| 193 | + | log "Exiting." | |
| 194 | + | break | |
| 195 | + | fi | |
| 196 | + | ||
| 197 | + | # Expand "0" to all options | |
| 198 | + | if [[ "$choices" == "0" ]]; then | |
| 199 | + | choices="${ALL_NUMS[*]}" | |
| 200 | + | fi | |
| 201 | + | ||
| 202 | + | # Validate every token first; reject the whole batch if any token is bogus, | |
| 203 | + | # so the user sees the problem before any work starts. | |
| 204 | + | bad="" | |
| 205 | + | for c in $choices; do | |
| 206 | + | if ! [[ "$c" =~ ^-?[0-9]+$ ]] || ! resolve_choice "$c" >/dev/null; then | |
| 207 | + | bad+=" $c" | |
| 208 | + | fi | |
| 209 | + | done | |
| 210 | + | if [[ -n "$bad" ]]; then | |
| 211 | + | err "Invalid option(s):$bad" | |
| 212 | + | sleep 1 | |
| 213 | + | continue | |
| 214 | + | fi | |
| 215 | + | ||
| 216 | + | failures=() | |
| 217 | + | for c in $choices; do | |
| 218 | + | spec="$(resolve_choice "$c")" | |
| 219 | + | name="${spec%|*}" | |
| 220 | + | mode="${spec##*|}" | |
| 221 | + | hr | |
| 222 | + | log "[$c] Running $name ($mode)..." | |
| 223 | + | if ! run_script "$name" "$mode"; then | |
| 224 | + | failures+=("$c:$name") | |
| 225 | + | fi | |
| 226 | + | done | |
| 227 | + | ||
| 228 | + | hr | |
| 229 | + | if (( ${#failures[@]} == 0 )); then | |
| 230 | + | log "All selected tasks completed." | |
| 231 | + | else | |
| 232 | + | warn "Completed with ${#failures[@]} failure(s): ${failures[*]}" | |
| 233 | + | fi | |
| 234 | + | echo | |
| 235 | + | done | |
weehong revidoval tento gist 4 days ago. Přejít na revizi
2 files changed, 154 insertions, 226 deletions
README.md
| @@ -34,10 +34,11 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 34 | 34 | | 10 | [`install-jetbrains-toolbox.sh`](install-jetbrains-toolbox.sh) | **user** | Per-user install into `~/.local/share/JetBrains/Toolbox`. SHA-256 verified against JetBrains' release feed. x86_64 + aarch64. Drops a `.desktop` launcher. | | |
| 35 | 35 | | 11 | [`install-bruno.sh`](install-bruno.sh) | sudo | Bruno API client from the official APT repo. Keyserver fetch is wrapped in a 5-attempt retry/backoff because `keyserver.ubuntu.com` is occasionally flaky. | | |
| 36 | 36 | | 12 | [`install-ipatool.sh`](install-ipatool.sh) | sudo | Installs the latest release of `majd/ipatool` from GitHub. SHA-256 verified against the release `checksums.txt`. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 37 | - | | 13 | [`install-network_drive.sh`](install-network_drive.sh) | sudo | Discovers SMB shares on a Synology NAS and adds them to `/etc/fstab` under `/mnt/Synology` with `x-systemd.automount`. fstab block is managed via begin/end markers so re-runs replace rather than duplicate. Credentials file is `0600`. Best-effort GNOME Dock pin. | | |
| 38 | - | | 14 | [`install-ibus-pinyin.sh`](install-ibus-pinyin.sh) | **user** | Installs `ibus-libpinyin` and Simplified Chinese language packs, restarts the IBus daemon, and idempotently adds `('ibus', 'libpinyin')` to GNOME's input sources via `gsettings`. | | |
| 39 | - | | 15 | [`install-font.sh`](install-font.sh) | **user** | Installs the latest Ubuntu Sans Nerd Font and JetBrains Mono Nerd Font to `~/.local/share/fonts`, then refreshes the font cache. No sudo needed. | | |
| 40 | - | | 16 | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. | | |
| 37 | + | | 13 | [`install-qbittorrent.sh`](install-qbittorrent.sh) | sudo | Installs the latest official qBittorrent x86_64 AppImage from `qbittorrent/qBittorrent`. SHA-256 verified against the GitHub release asset digest when available. Installs launcher, icons, and torrent/magnet handlers. | | |
| 38 | + | | 14 | [`install-network_drive.sh`](install-network_drive.sh) | sudo | Discovers SMB shares on a Synology NAS and adds them to `/etc/fstab` under `/mnt/Synology` with `x-systemd.automount`. fstab block is managed via begin/end markers so re-runs replace rather than duplicate. Credentials file is `0600`. Best-effort GNOME Dock pin. | | |
| 39 | + | | 15 | [`install-ibus-pinyin.sh`](install-ibus-pinyin.sh) | **user** | Installs `ibus-libpinyin` and Simplified Chinese language packs, restarts the IBus daemon, and idempotently adds `('ibus', 'libpinyin')` to GNOME's input sources via `gsettings`. | | |
| 40 | + | | 16 | [`install-font.sh`](install-font.sh) | **user** | Installs the latest Ubuntu Sans Nerd Font and JetBrains Mono Nerd Font to `~/.local/share/fonts`, then refreshes the font cache. No sudo needed. | | |
| 41 | + | | 17 | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. | | |
| 41 | 42 | ||
| 42 | 43 | "Runs as **user**" entries must be invoked as your normal desktop user, not via `sudo`. The other entries elevate via `sudo` internally and the menu primes `sudo -v` up-front, so you'll only be prompted once. | |
| 43 | 44 | ||
| @@ -46,10 +47,10 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 46 | 47 | If you'd rather skip the menu, each script can be run on its own. Use the right invocation pattern for that script's privilege mode: | |
| 47 | 48 | ||
| 48 | 49 | ```bash | |
| 49 | - | # sudo scripts (1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 16) — pipe through sudo bash | |
| 50 | + | # sudo scripts (1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 17) — pipe through sudo bash | |
| 50 | 51 | curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 51 | 52 | ||
| 52 | - | # user scripts (10, 14, 15) — DO NOT use sudo; they install per-user | |
| 53 | + | # user scripts (10, 15, 16) — DO NOT use sudo; they install per-user | |
| 53 | 54 | bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)" | |
| 54 | 55 | ``` | |
| 55 | 56 | ||
| @@ -69,7 +70,7 @@ The hardened installers generally share this robustness baseline: | |||
| 69 | 70 | - `dpkg --print-architecture` / `uname -m` for architecture (amd64 / arm64 / armhf / x86_64 / aarch64 as applicable to each upstream) | |
| 70 | 71 | - Idempotent — re-running a script does not duplicate APT sources, fstab entries, gsettings entries, or `.desktop` files | |
| 71 | 72 | - `signed-by` keyrings in `/etc/apt/keyrings` (no deprecated `apt-key add`) | |
| 72 | - | - Checksum verification where the upstream publishes one (JetBrains SHA-256, LibreOffice MD5, Obsidian SHA-256, ipatool SHA-256, draw.io SHA-256) | |
| 73 | + | - Checksum verification where the upstream publishes one (JetBrains SHA-256, LibreOffice MD5, Obsidian SHA-256, ipatool SHA-256, draw.io SHA-256, qBittorrent SHA-256) | |
| 73 | 74 | - Per-user installers refuse to run as root; system installers refuse to run as non-root | |
| 74 | 75 | ||
| 75 | 76 | ## Supported distros | |
menu.sh
| @@ -1,233 +1,160 @@ | |||
| 1 | 1 | #!/usr/bin/env bash | |
| 2 | - | # menu.sh — Ubuntu/Debian Setup Manager | |
| 3 | - | # | |
| 4 | - | # Interactive menu that fetches and runs the hardened install scripts in this | |
| 5 | - | # Opengist. Each script is downloaded to a temp file (not blindly piped to bash) | |
| 6 | - | # and executed with the appropriate privilege level for that script: | |
| 7 | - | # | |
| 8 | - | # - "sudo" mode for system-wide installers (apt, /etc, /usr/local/bin) | |
| 9 | - | # - "user" mode for per-user installers that must NOT run as root | |
| 10 | - | # (gsettings, ~/.local, JetBrains Toolbox, fonts) | |
| 11 | - | # | |
| 12 | - | # Usage: | |
| 13 | - | # bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/menu.sh)" | |
| 14 | - | # | |
| 15 | - | # Or save and run locally: | |
| 16 | - | # curl -fsSLO https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/menu.sh | |
| 17 | - | # bash menu.sh | |
| 18 | - | ||
| 19 | - | # Note: NOT using `set -e` because we want the menu loop to survive a failed | |
| 20 | - | # sub-script. We do use -u and pipefail to catch real bugs in this file. | |
| 21 | - | set -uo pipefail | |
| 2 | + | # install-qbittorrent.sh — Install qBittorrent from the latest official GitHub release. | |
| 3 | + | # Hardened: arch-aware, GitHub API token support, SHA-256 verification from the | |
| 4 | + | # release asset digest, idempotent, --dry-run. | |
| 5 | + | ||
| 6 | + | set -euo pipefail | |
| 22 | 7 | IFS=$'\n\t' | |
| 23 | 8 | ||
| 24 | 9 | readonly SCRIPT_NAME="${0##*/}" | |
| 25 | - | readonly BASE_URL='https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD' | |
| 26 | - | ||
| 27 | - | log() { printf '\033[1;34m[menu]\033[0m %s\n' "$*"; } | |
| 28 | - | warn() { printf '\033[1;33m[menu] WARN:\033[0m %s\n' "$*" >&2; } | |
| 29 | - | err() { printf '\033[1;31m[menu] ERROR:\033[0m %s\n' "$*" >&2; } | |
| 30 | - | hr() { printf '%s\n' "------------------------------------------------------"; } | |
| 31 | - | ||
| 32 | - | # Refuse to run as root: per-user scripts (jetbrains, fonts, ibus) need a real | |
| 33 | - | # desktop user. Sub-scripts elevate via sudo on their own. | |
| 34 | - | if (( EUID == 0 )); then | |
| 35 | - | err "Don't run $SCRIPT_NAME as root. Run as your normal user — it will call sudo for installers that need it." | |
| 36 | - | exit 1 | |
| 37 | - | fi | |
| 10 | + | DRY_RUN=0 | |
| 11 | + | REPO="qbittorrent/qBittorrent" | |
| 38 | 12 | ||
| 39 | - | # Sanity-check tools we depend on. | |
| 40 | - | for tool in curl bash mktemp; do | |
| 41 | - | command -v "$tool" >/dev/null 2>&1 || { err "Missing required tool: $tool"; exit 1; } | |
| 42 | - | done | |
| 13 | + | usage() { | |
| 14 | + | cat <<EOF | |
| 15 | + | Usage: sudo $SCRIPT_NAME [--dry-run] [--help] | |
| 43 | 16 | ||
| 44 | - | # Distro check (warn-only; sub-scripts enforce strictly). | |
| 45 | - | if [[ -r /etc/os-release ]]; then | |
| 46 | - | # shellcheck disable=SC1091 | |
| 47 | - | . /etc/os-release | |
| 48 | - | case "${ID:-}:${ID_LIKE:-}" in | |
| 49 | - | *ubuntu*|*debian*) : ;; | |
| 50 | - | *) warn "Detected ${PRETTY_NAME:-unknown}. These installers target Debian/Ubuntu; some will refuse to run." ;; | |
| 51 | - | esac | |
| 52 | - | fi | |
| 17 | + | Resolves the latest release of qbittorrent/qBittorrent, downloads the official | |
| 18 | + | Linux x86_64 AppImage, verifies its SHA-256 against the GitHub release asset | |
| 19 | + | digest when available, and installs it system-wide. | |
| 53 | 20 | ||
| 54 | - | # Cache sudo credentials up-front so sub-scripts that elevate don't keep | |
| 55 | - | # re-prompting in the middle of a multi-task run. | |
| 56 | - | prime_sudo() { | |
| 57 | - | if ! sudo -n true 2>/dev/null; then | |
| 58 | - | log "Caching sudo credentials (you may be prompted)..." | |
| 59 | - | sudo -v || { err "sudo authentication failed."; return 1; } | |
| 60 | - | fi | |
| 61 | - | # Keep sudo timestamp refreshed in the background while the menu runs. | |
| 62 | - | ( while true; do sudo -n true 2>/dev/null; sleep 60; done ) & | |
| 63 | - | SUDO_KEEPALIVE_PID=$! | |
| 64 | - | trap 'kill "${SUDO_KEEPALIVE_PID:-}" 2>/dev/null || true' EXIT | |
| 65 | - | } | |
| 21 | + | If \$GITHUB_TOKEN is set in the environment, it is used to authenticate the | |
| 22 | + | GitHub API request (avoids rate limits). | |
| 66 | 23 | ||
| 67 | - | # run_script <filename> <mode> | |
| 68 | - | # mode: "sudo" (run as root via sudo) or "user" (run as current user) | |
| 69 | - | # Returns: 0 on success, non-zero on failure (does NOT exit the menu). | |
| 70 | - | run_script() { | |
| 71 | - | local name="$1" mode="$2" | |
| 72 | - | local url="$BASE_URL/$name" | |
| 73 | - | local tmp | |
| 74 | - | tmp="$(mktemp -t "${name%.sh}.XXXXXX.sh")" || { err "mktemp failed"; return 1; } | |
| 75 | - | ||
| 76 | - | log "Fetching $name..." | |
| 77 | - | if ! curl -fsSL --max-time 60 --retry 2 "$url" -o "$tmp"; then | |
| 78 | - | err "Download failed: $url" | |
| 79 | - | rm -f "$tmp" | |
| 80 | - | return 1 | |
| 81 | - | fi | |
| 82 | - | if [[ ! -s "$tmp" ]]; then | |
| 83 | - | err "Downloaded $name is empty." | |
| 84 | - | rm -f "$tmp" | |
| 85 | - | return 1 | |
| 86 | - | fi | |
| 87 | - | # Cheap sanity: confirm it looks like a shell script. | |
| 88 | - | if ! head -n1 "$tmp" | grep -qE '^#!.*sh'; then | |
| 89 | - | warn "$name doesn't start with a shebang; proceeding anyway." | |
| 90 | - | fi | |
| 91 | - | chmod +x "$tmp" | |
| 92 | - | ||
| 93 | - | local rc=0 | |
| 94 | - | if [[ "$mode" == "sudo" ]]; then | |
| 95 | - | sudo bash "$tmp" || rc=$? | |
| 96 | - | else | |
| 97 | - | bash "$tmp" || rc=$? | |
| 98 | - | fi | |
| 99 | - | rm -f "$tmp" | |
| 100 | - | if (( rc != 0 )); then | |
| 101 | - | err "$name exited with status $rc" | |
| 102 | - | fi | |
| 103 | - | return "$rc" | |
| 24 | + | Options: | |
| 25 | + | --dry-run Print actions without executing. | |
| 26 | + | --help, -h Show this help. | |
| 27 | + | EOF | |
| 104 | 28 | } | |
| 105 | 29 | ||
| 106 | - | # Catalog: number | label | script-filename | mode | |
| 107 | - | # (Edit here to add/remove options — the menu loop is data-driven.) | |
| 108 | - | OPTIONS=( | |
| 109 | - | "1|Install Google Chrome|install-chrome.sh|sudo" | |
| 110 | - | "2|Install Firefox (Mozilla APT)|install-firefox.sh|sudo" | |
| 111 | - | "3|Install Thunderbird|install-thunderbird.sh|sudo" | |
| 112 | - | "4|Install 1Password|install-1password.sh|sudo" | |
| 113 | - | "5|Install Espanso (text expander)|install-espanso.sh|sudo" | |
| 114 | - | "6|Install LibreOffice (latest stable .deb)|install-libreoffice.sh|sudo" | |
| 115 | - | "7|Install Obsidian|install-obsidian.sh|sudo" | |
| 116 | - | "8|Install draw.io Desktop|install-drawio.sh|sudo" | |
| 117 | - | "9|Install Visual Studio Code|install-vscode.sh|sudo" | |
| 118 | - | "10|Install JetBrains Toolbox (per-user)|install-jetbrains-toolbox.sh|user" | |
| 119 | - | "11|Install Bruno (API client)|install-bruno.sh|sudo" | |
| 120 | - | "12|Install IPATool|install-ipatool.sh|sudo" | |
| 121 | - | "13|Mount Synology Network Drive|install-network_drive.sh|sudo" | |
| 122 | - | "14|Install IBus Intelligent Pinyin (per-user)|install-ibus-pinyin.sh|user" | |
| 123 | - | "15|Install Nerd Fonts (per-user)|install-font.sh|user" | |
| 124 | - | "16|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 125 | - | ) | |
| 30 | + | log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; } | |
| 31 | + | warn() { printf '\033[1;33m[%s] WARN:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; } | |
| 32 | + | die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } | |
| 33 | + | run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } | |
| 34 | + | trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR | |
| 35 | + | ||
| 36 | + | while (( $# )); do | |
| 37 | + | case "$1" in | |
| 38 | + | --dry-run) DRY_RUN=1 ;; | |
| 39 | + | -h|--help) usage; exit 0 ;; | |
| 40 | + | *) die "Unknown argument: $1 (try --help)" ;; | |
| 41 | + | esac | |
| 42 | + | shift | |
| 43 | + | done | |
| 126 | 44 | ||
| 127 | - | print_menu() { | |
| 128 | - | hr | |
| 129 | - | echo " Ubuntu / Debian Setup Manager" | |
| 130 | - | hr | |
| 131 | - | echo "--- [ Browsers & Mail ] ---" | |
| 132 | - | echo " 1) Install Google Chrome" | |
| 133 | - | echo " 2) Install Firefox" | |
| 134 | - | echo " 3) Install Thunderbird" | |
| 135 | - | echo "--- [ Productivity & Security ] ---" | |
| 136 | - | echo " 4) Install 1Password" | |
| 137 | - | echo " 5) Install Espanso" | |
| 138 | - | echo " 6) Install LibreOffice" | |
| 139 | - | echo " 7) Install Obsidian" | |
| 140 | - | echo " 8) Install draw.io Desktop" | |
| 141 | - | echo "--- [ Development Tools ] ---" | |
| 142 | - | echo " 9) Install Visual Studio Code" | |
| 143 | - | echo " 10) Install JetBrains Toolbox (runs as you, not root)" | |
| 144 | - | echo " 11) Install Bruno" | |
| 145 | - | echo " 12) Install IPATool" | |
| 146 | - | echo "--- [ System ] ---" | |
| 147 | - | echo " 13) Mount Synology Network Drive" | |
| 148 | - | echo " 14) Install IBus Intelligent Pinyin (runs as you, not root)" | |
| 149 | - | echo " 15) Install Nerd Fonts (runs as you, not root)" | |
| 150 | - | echo " 16) Fix Dual-Boot Time (RTC to UTC)" | |
| 151 | - | hr | |
| 152 | - | echo " 0) Run ALL options (1-16)" | |
| 153 | - | echo " -1) Exit" | |
| 154 | - | hr | |
| 155 | - | } | |
| 45 | + | (( EUID == 0 )) || die "Must run as root. Try: sudo $SCRIPT_NAME" | |
| 46 | + | ||
| 47 | + | [[ -r /etc/os-release ]] || die "/etc/os-release not found." | |
| 48 | + | # shellcheck disable=SC1091 | |
| 49 | + | . /etc/os-release | |
| 50 | + | case "${ID:-}:${ID_LIKE:-}" in | |
| 51 | + | *ubuntu*|*debian*) : ;; | |
| 52 | + | *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; | |
| 53 | + | esac | |
| 54 | + | ||
| 55 | + | ARCH="$(dpkg --print-architecture)" | |
| 56 | + | [[ "$ARCH" == "amd64" ]] || die "qBittorrent publishes official Linux AppImages for x86_64/amd64 only (detected: $ARCH)." | |
| 57 | + | log "Detected: ${PRETTY_NAME:-unknown}, arch: $ARCH" | |
| 58 | + | ||
| 59 | + | export DEBIAN_FRONTEND=noninteractive | |
| 60 | + | log "Installing prerequisites..." | |
| 61 | + | run "apt-get update -qq" | |
| 62 | + | run "apt-get install -y curl jq ca-certificates desktop-file-utils shared-mime-info hicolor-icon-theme" | |
| 63 | + | ||
| 64 | + | GH_HDRS=(-H "Accept: application/vnd.github+json") | |
| 65 | + | [[ -n "${GITHUB_TOKEN:-}" ]] && GH_HDRS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") | |
| 66 | + | ||
| 67 | + | log "Querying GitHub API for latest release of $REPO..." | |
| 68 | + | RELEASE_JSON="$(curl -fsSL "${GH_HDRS[@]}" "https://api.github.com/repos/${REPO}/releases/latest")" | |
| 69 | + | TAG="$(jq -r '.tag_name // empty' <<<"$RELEASE_JSON")" | |
| 70 | + | [[ -n "$TAG" ]] || die "Could not parse latest release tag (rate limited? set GITHUB_TOKEN)." | |
| 71 | + | VERSION="${TAG#release-}" | |
| 72 | + | log "Latest release: $TAG" | |
| 73 | + | ||
| 74 | + | ASSET_REGEX='^qbittorrent-[0-9][^/]*_x86_64\.AppImage$' | |
| 75 | + | DOWNLOAD_URL="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | select(.name | test("_lt20_") | not) | .browser_download_url' <<<"$RELEASE_JSON" | head -n1)" | |
| 76 | + | ASSET_NAME="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | select(.name | test("_lt20_") | not) | .name' <<<"$RELEASE_JSON" | head -n1)" | |
| 77 | + | EXPECTED_SHA="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | select(.name | test("_lt20_") | not) | (.digest // "") | sub("^sha256:"; "")' <<<"$RELEASE_JSON" | head -n1)" | |
| 78 | + | [[ "$DOWNLOAD_URL" =~ ^https:// ]] || die "No standard x86_64 AppImage asset found in latest qBittorrent release." | |
| 79 | + | ||
| 80 | + | STAGE="$(mktemp -d -t qbittorrent.XXXXXX)" | |
| 81 | + | trap 'rm -rf "$STAGE"' EXIT | |
| 82 | + | APPIMAGE="$STAGE/$ASSET_NAME" | |
| 83 | + | ||
| 84 | + | log "Downloading $ASSET_NAME..." | |
| 85 | + | run "curl -fL -o '$APPIMAGE' '$DOWNLOAD_URL'" | |
| 86 | + | ||
| 87 | + | if (( DRY_RUN )); then | |
| 88 | + | [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]] \ | |
| 89 | + | && printf ' DRY-RUN: verify SHA-256 %s\n' "$EXPECTED_SHA" \ | |
| 90 | + | || printf ' DRY-RUN: skip SHA-256 verification; no GitHub asset digest found\n' | |
| 91 | + | printf ' DRY-RUN: install AppImage to /opt/qbittorrent/qbittorrent\n' | |
| 92 | + | printf ' DRY-RUN: create /usr/local/bin/qbittorrent symlink\n' | |
| 93 | + | printf ' DRY-RUN: install desktop launcher, icons, and MIME handlers\n' | |
| 94 | + | log "Done." | |
| 95 | + | exit 0 | |
| 96 | + | fi | |
| 156 | 97 | ||
| 157 | - | # Resolve a numeric choice to its catalog entry; print "name|mode" to stdout. | |
| 158 | - | resolve_choice() { | |
| 159 | - | local want="$1" entry num | |
| 160 | - | for entry in "${OPTIONS[@]}"; do | |
| 161 | - | num="${entry%%|*}" | |
| 162 | - | if [[ "$num" == "$want" ]]; then | |
| 163 | - | # Strip the leading "N|label|"; what remains is "filename|mode" | |
| 164 | - | printf '%s' "${entry#*|*|}" | |
| 165 | - | return 0 | |
| 166 | - | fi | |
| 167 | - | done | |
| 168 | - | return 1 | |
| 169 | - | } | |
| 98 | + | if [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]]; then | |
| 99 | + | log "Verifying SHA-256..." | |
| 100 | + | ACTUAL_SHA="$(sha256sum "$APPIMAGE" | awk '{print $1}')" | |
| 101 | + | [[ "$EXPECTED_SHA" == "$ACTUAL_SHA" ]] || die "SHA-256 mismatch: expected=$EXPECTED_SHA actual=$ACTUAL_SHA" | |
| 102 | + | log "SHA-256 ok." | |
| 103 | + | else | |
| 104 | + | warn "No SHA-256 asset digest in GitHub release metadata; skipping verification." | |
| 105 | + | fi | |
| 106 | + | ||
| 107 | + | log "Installing qBittorrent AppImage..." | |
| 108 | + | run "install -d -m 0755 /opt/qbittorrent" | |
| 109 | + | run "install -m 0755 '$APPIMAGE' /opt/qbittorrent/qbittorrent" | |
| 110 | + | run "ln -sfn /opt/qbittorrent/qbittorrent /usr/local/bin/qbittorrent" | |
| 111 | + | ||
| 112 | + | log "Extracting desktop metadata and icons..." | |
| 113 | + | EXTRACT_DIR="$STAGE/extract" | |
| 114 | + | run "install -d -m 0755 '$EXTRACT_DIR'" | |
| 115 | + | ( | |
| 116 | + | cd "$EXTRACT_DIR" | |
| 117 | + | /opt/qbittorrent/qbittorrent --appimage-extract >/dev/null | |
| 118 | + | ) | |
| 170 | 119 | ||
| 171 | - | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16) | |
| 172 | - | ||
| 173 | - | prime_sudo || exit 1 | |
| 174 | - | ||
| 175 | - | while true; do | |
| 176 | - | print_menu | |
| 177 | - | read -rp "Enter choices separated by spaces (e.g., 1 7 12), 0 for ALL, -1 to exit: " choices </dev/tty || { | |
| 178 | - | echo; log "Input closed; exiting." | |
| 179 | - | break | |
| 180 | - | } | |
| 181 | - | ||
| 182 | - | # Trim whitespace | |
| 183 | - | choices="${choices##[[:space:]]}" | |
| 184 | - | choices="${choices%%[[:space:]]}" | |
| 185 | - | ||
| 186 | - | if [[ -z "$choices" ]]; then | |
| 187 | - | continue | |
| 188 | - | fi | |
| 189 | - | ||
| 190 | - | if [[ "$choices" == "-1" ]]; then | |
| 191 | - | log "Exiting." | |
| 192 | - | break | |
| 193 | - | fi | |
| 194 | - | ||
| 195 | - | # Expand "0" to all options | |
| 196 | - | if [[ "$choices" == "0" ]]; then | |
| 197 | - | choices="${ALL_NUMS[*]}" | |
| 198 | - | fi | |
| 199 | - | ||
| 200 | - | # Validate every token first; reject the whole batch if any token is bogus, | |
| 201 | - | # so the user sees the problem before any work starts. | |
| 202 | - | bad="" | |
| 203 | - | for c in $choices; do | |
| 204 | - | if ! [[ "$c" =~ ^-?[0-9]+$ ]] || ! resolve_choice "$c" >/dev/null; then | |
| 205 | - | bad+=" $c" | |
| 206 | - | fi | |
| 207 | - | done | |
| 208 | - | if [[ -n "$bad" ]]; then | |
| 209 | - | err "Invalid option(s):$bad" | |
| 210 | - | sleep 1 | |
| 211 | - | continue | |
| 212 | - | fi | |
| 213 | - | ||
| 214 | - | failures=() | |
| 215 | - | for c in $choices; do | |
| 216 | - | spec="$(resolve_choice "$c")" | |
| 217 | - | name="${spec%|*}" | |
| 218 | - | mode="${spec##*|}" | |
| 219 | - | hr | |
| 220 | - | log "[$c] Running $name ($mode)..." | |
| 221 | - | if ! run_script "$name" "$mode"; then | |
| 222 | - | failures+=("$c:$name") | |
| 223 | - | fi | |
| 224 | - | done | |
| 225 | - | ||
| 226 | - | hr | |
| 227 | - | if (( ${#failures[@]} == 0 )); then | |
| 228 | - | log "All selected tasks completed." | |
| 229 | - | else | |
| 230 | - | warn "Completed with ${#failures[@]} failure(s): ${failures[*]}" | |
| 231 | - | fi | |
| 232 | - | echo | |
| 120 | + | run "install -d -m 0755 /usr/share/icons/hicolor/scalable/apps" | |
| 121 | + | run "install -m 0644 '$EXTRACT_DIR/squashfs-root/usr/share/icons/hicolor/scalable/apps/qbittorrent.svg' /usr/share/icons/hicolor/scalable/apps/qbittorrent.svg" | |
| 122 | + | for size in 16 22 24 32 36 48 64 72 96 128 192; do | |
| 123 | + | src="$EXTRACT_DIR/squashfs-root/usr/share/icons/hicolor/${size}x${size}/apps/qbittorrent.png" | |
| 124 | + | [[ -f "$src" ]] || continue | |
| 125 | + | run "install -d -m 0755 '/usr/share/icons/hicolor/${size}x${size}/apps'" | |
| 126 | + | run "install -m 0644 '$src' '/usr/share/icons/hicolor/${size}x${size}/apps/qbittorrent.png'" | |
| 233 | 127 | done | |
| 128 | + | ||
| 129 | + | log "Installing desktop launcher..." | |
| 130 | + | cat >/usr/share/applications/org.qbittorrent.qBittorrent.desktop <<EOF | |
| 131 | + | [Desktop Entry] | |
| 132 | + | Type=Application | |
| 133 | + | Name=qBittorrent | |
| 134 | + | GenericName=BitTorrent client | |
| 135 | + | Comment=Download and share files over BitTorrent | |
| 136 | + | Exec=/opt/qbittorrent/qbittorrent %U | |
| 137 | + | Icon=qbittorrent | |
| 138 | + | Terminal=false | |
| 139 | + | Categories=Network;FileTransfer;P2P;Qt; | |
| 140 | + | MimeType=application/x-bittorrent;x-scheme-handler/magnet; | |
| 141 | + | StartupNotify=false | |
| 142 | + | StartupWMClass=qbittorrent | |
| 143 | + | Keywords=bittorrent;torrent;magnet;download;p2p; | |
| 144 | + | SingleMainWindow=true | |
| 145 | + | EOF | |
| 146 | + | ||
| 147 | + | run "desktop-file-validate /usr/share/applications/org.qbittorrent.qBittorrent.desktop" | |
| 148 | + | run "update-desktop-database /usr/share/applications" | |
| 149 | + | run "update-mime-database /usr/share/mime" | |
| 150 | + | run "gtk-update-icon-cache -f -t /usr/share/icons/hicolor >/dev/null 2>&1 || true" | |
| 151 | + | ||
| 152 | + | if command -v xdg-mime >/dev/null 2>&1; then | |
| 153 | + | run "xdg-mime default org.qbittorrent.qBittorrent.desktop application/x-bittorrent || true" | |
| 154 | + | run "xdg-mime default org.qbittorrent.qBittorrent.desktop x-scheme-handler/magnet || true" | |
| 155 | + | fi | |
| 156 | + | ||
| 157 | + | if command -v qbittorrent >/dev/null 2>&1; then | |
| 158 | + | log "Installed: $(qbittorrent --version 2>/dev/null || echo "$VERSION")" | |
| 159 | + | fi | |
| 160 | + | log "Done." | |
weehong revidoval tento gist 4 days ago. Přejít na revizi
Žádné změny
weehong revidoval tento gist 1 week ago. Přejít na revizi
2 files changed, 29 insertions, 29 deletions
README.md
| @@ -29,15 +29,15 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 29 | 29 | | 5 | [`install-espanso.sh`](install-espanso.sh) | sudo | Installs Espanso. Auto-detects Wayland vs X11 from `$XDG_SESSION_TYPE` and registers the systemd-user service as the invoking desktop user (not root). | | |
| 30 | 30 | | 6 | [`install-libreoffice.sh`](install-libreoffice.sh) | sudo | Detects the latest stable release on documentfoundation.org, downloads the matching `.deb` tarball, verifies the published MD5, and installs. Purges the distro's `libreoffice*` first to avoid library conflicts (opt-out with `--keep-distro-libreoffice`). | | |
| 31 | 31 | | 7 | [`install-obsidian.sh`](install-obsidian.sh) | sudo | Installs the latest official Obsidian amd64 `.deb` from `obsidianmd/obsidian-releases`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 32 | - | | 8 | [`install-vscode.sh`](install-vscode.sh) | sudo | Microsoft's official `code` APT repo, signed-by keyring. `--insiders` flag installs `code-insiders` instead. | | |
| 33 | - | | 9 | [`install-jetbrains-toolbox.sh`](install-jetbrains-toolbox.sh) | **user** | Per-user install into `~/.local/share/JetBrains/Toolbox`. SHA-256 verified against JetBrains' release feed. x86_64 + aarch64. Drops a `.desktop` launcher. | | |
| 34 | - | | 10 | [`install-bruno.sh`](install-bruno.sh) | sudo | Bruno API client from the official APT repo. Keyserver fetch is wrapped in a 5-attempt retry/backoff because `keyserver.ubuntu.com` is occasionally flaky. | | |
| 35 | - | | 11 | [`install-ipatool.sh`](install-ipatool.sh) | sudo | Installs the latest release of `majd/ipatool` from GitHub. SHA-256 verified against the release `checksums.txt`. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 36 | - | | 12 | [`install-network_drive.sh`](install-network_drive.sh) | sudo | Discovers SMB shares on a Synology NAS and adds them to `/etc/fstab` under `/mnt/Synology` with `x-systemd.automount`. fstab block is managed via begin/end markers so re-runs replace rather than duplicate. Credentials file is `0600`. Best-effort GNOME Dock pin. | | |
| 37 | - | | 13 | [`install-ibus-pinyin.sh`](install-ibus-pinyin.sh) | **user** | Installs `ibus-libpinyin` and Simplified Chinese language packs, restarts the IBus daemon, and idempotently adds `('ibus', 'libpinyin')` to GNOME's input sources via `gsettings`. | | |
| 38 | - | | 14 | [`install-font.sh`](install-font.sh) | **user** | Installs the latest Ubuntu Sans Nerd Font and JetBrains Mono Nerd Font to `~/.local/share/fonts`, then refreshes the font cache. No sudo needed. | | |
| 39 | - | | 15 | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. | | |
| 40 | - | | 16 | [`install-drawio.sh`](install-drawio.sh) | sudo | Installs the latest official draw.io Desktop `.deb` from `jgraph/drawio-desktop`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 32 | + | | 8 | [`install-drawio.sh`](install-drawio.sh) | sudo | Installs the latest official draw.io Desktop `.deb` from `jgraph/drawio-desktop`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 33 | + | | 9 | [`install-vscode.sh`](install-vscode.sh) | sudo | Microsoft's official `code` APT repo, signed-by keyring. `--insiders` flag installs `code-insiders` instead. | | |
| 34 | + | | 10 | [`install-jetbrains-toolbox.sh`](install-jetbrains-toolbox.sh) | **user** | Per-user install into `~/.local/share/JetBrains/Toolbox`. SHA-256 verified against JetBrains' release feed. x86_64 + aarch64. Drops a `.desktop` launcher. | | |
| 35 | + | | 11 | [`install-bruno.sh`](install-bruno.sh) | sudo | Bruno API client from the official APT repo. Keyserver fetch is wrapped in a 5-attempt retry/backoff because `keyserver.ubuntu.com` is occasionally flaky. | | |
| 36 | + | | 12 | [`install-ipatool.sh`](install-ipatool.sh) | sudo | Installs the latest release of `majd/ipatool` from GitHub. SHA-256 verified against the release `checksums.txt`. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 37 | + | | 13 | [`install-network_drive.sh`](install-network_drive.sh) | sudo | Discovers SMB shares on a Synology NAS and adds them to `/etc/fstab` under `/mnt/Synology` with `x-systemd.automount`. fstab block is managed via begin/end markers so re-runs replace rather than duplicate. Credentials file is `0600`. Best-effort GNOME Dock pin. | | |
| 38 | + | | 14 | [`install-ibus-pinyin.sh`](install-ibus-pinyin.sh) | **user** | Installs `ibus-libpinyin` and Simplified Chinese language packs, restarts the IBus daemon, and idempotently adds `('ibus', 'libpinyin')` to GNOME's input sources via `gsettings`. | | |
| 39 | + | | 15 | [`install-font.sh`](install-font.sh) | **user** | Installs the latest Ubuntu Sans Nerd Font and JetBrains Mono Nerd Font to `~/.local/share/fonts`, then refreshes the font cache. No sudo needed. | | |
| 40 | + | | 16 | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. | | |
| 41 | 41 | ||
| 42 | 42 | "Runs as **user**" entries must be invoked as your normal desktop user, not via `sudo`. The other entries elevate via `sudo` internally and the menu primes `sudo -v` up-front, so you'll only be prompted once. | |
| 43 | 43 | ||
| @@ -46,10 +46,10 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 46 | 46 | If you'd rather skip the menu, each script can be run on its own. Use the right invocation pattern for that script's privilege mode: | |
| 47 | 47 | ||
| 48 | 48 | ```bash | |
| 49 | - | # sudo scripts (1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 15, 16) — pipe through sudo bash | |
| 49 | + | # sudo scripts (1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 16) — pipe through sudo bash | |
| 50 | 50 | curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 51 | 51 | ||
| 52 | - | # user scripts (9, 13, 14) — DO NOT use sudo; they install per-user | |
| 52 | + | # user scripts (10, 14, 15) — DO NOT use sudo; they install per-user | |
| 53 | 53 | bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)" | |
| 54 | 54 | ``` | |
| 55 | 55 | ||
menu.sh
| @@ -113,15 +113,15 @@ OPTIONS=( | |||
| 113 | 113 | "5|Install Espanso (text expander)|install-espanso.sh|sudo" | |
| 114 | 114 | "6|Install LibreOffice (latest stable .deb)|install-libreoffice.sh|sudo" | |
| 115 | 115 | "7|Install Obsidian|install-obsidian.sh|sudo" | |
| 116 | - | "8|Install Visual Studio Code|install-vscode.sh|sudo" | |
| 117 | - | "9|Install JetBrains Toolbox (per-user)|install-jetbrains-toolbox.sh|user" | |
| 118 | - | "10|Install Bruno (API client)|install-bruno.sh|sudo" | |
| 119 | - | "11|Install IPATool|install-ipatool.sh|sudo" | |
| 120 | - | "12|Mount Synology Network Drive|install-network_drive.sh|sudo" | |
| 121 | - | "13|Install IBus Intelligent Pinyin (per-user)|install-ibus-pinyin.sh|user" | |
| 122 | - | "14|Install Nerd Fonts (per-user)|install-font.sh|user" | |
| 123 | - | "15|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 124 | - | "16|Install draw.io Desktop|install-drawio.sh|sudo" | |
| 116 | + | "8|Install draw.io Desktop|install-drawio.sh|sudo" | |
| 117 | + | "9|Install Visual Studio Code|install-vscode.sh|sudo" | |
| 118 | + | "10|Install JetBrains Toolbox (per-user)|install-jetbrains-toolbox.sh|user" | |
| 119 | + | "11|Install Bruno (API client)|install-bruno.sh|sudo" | |
| 120 | + | "12|Install IPATool|install-ipatool.sh|sudo" | |
| 121 | + | "13|Mount Synology Network Drive|install-network_drive.sh|sudo" | |
| 122 | + | "14|Install IBus Intelligent Pinyin (per-user)|install-ibus-pinyin.sh|user" | |
| 123 | + | "15|Install Nerd Fonts (per-user)|install-font.sh|user" | |
| 124 | + | "16|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 125 | 125 | ) | |
| 126 | 126 | ||
| 127 | 127 | print_menu() { | |
| @@ -137,17 +137,17 @@ print_menu() { | |||
| 137 | 137 | echo " 5) Install Espanso" | |
| 138 | 138 | echo " 6) Install LibreOffice" | |
| 139 | 139 | echo " 7) Install Obsidian" | |
| 140 | - | echo " 16) Install draw.io Desktop" | |
| 140 | + | echo " 8) Install draw.io Desktop" | |
| 141 | 141 | echo "--- [ Development Tools ] ---" | |
| 142 | - | echo " 8) Install Visual Studio Code" | |
| 143 | - | echo " 9) Install JetBrains Toolbox (runs as you, not root)" | |
| 144 | - | echo " 10) Install Bruno" | |
| 145 | - | echo " 11) Install IPATool" | |
| 142 | + | echo " 9) Install Visual Studio Code" | |
| 143 | + | echo " 10) Install JetBrains Toolbox (runs as you, not root)" | |
| 144 | + | echo " 11) Install Bruno" | |
| 145 | + | echo " 12) Install IPATool" | |
| 146 | 146 | echo "--- [ System ] ---" | |
| 147 | - | echo " 12) Mount Synology Network Drive" | |
| 148 | - | echo " 13) Install IBus Intelligent Pinyin (runs as you, not root)" | |
| 149 | - | echo " 14) Install Nerd Fonts (runs as you, not root)" | |
| 150 | - | echo " 15) Fix Dual-Boot Time (RTC to UTC)" | |
| 147 | + | echo " 13) Mount Synology Network Drive" | |
| 148 | + | echo " 14) Install IBus Intelligent Pinyin (runs as you, not root)" | |
| 149 | + | echo " 15) Install Nerd Fonts (runs as you, not root)" | |
| 150 | + | echo " 16) Fix Dual-Boot Time (RTC to UTC)" | |
| 151 | 151 | hr | |
| 152 | 152 | echo " 0) Run ALL options (1-16)" | |
| 153 | 153 | echo " -1) Exit" | |
weehong revidoval tento gist 1 week ago. Přejít na revizi
3 files changed, 120 insertions, 4 deletions
README.md
| @@ -37,6 +37,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 37 | 37 | | 13 | [`install-ibus-pinyin.sh`](install-ibus-pinyin.sh) | **user** | Installs `ibus-libpinyin` and Simplified Chinese language packs, restarts the IBus daemon, and idempotently adds `('ibus', 'libpinyin')` to GNOME's input sources via `gsettings`. | | |
| 38 | 38 | | 14 | [`install-font.sh`](install-font.sh) | **user** | Installs the latest Ubuntu Sans Nerd Font and JetBrains Mono Nerd Font to `~/.local/share/fonts`, then refreshes the font cache. No sudo needed. | | |
| 39 | 39 | | 15 | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. | | |
| 40 | + | | 16 | [`install-drawio.sh`](install-drawio.sh) | sudo | Installs the latest official draw.io Desktop `.deb` from `jgraph/drawio-desktop`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 40 | 41 | ||
| 41 | 42 | "Runs as **user**" entries must be invoked as your normal desktop user, not via `sudo`. The other entries elevate via `sudo` internally and the menu primes `sudo -v` up-front, so you'll only be prompted once. | |
| 42 | 43 | ||
| @@ -45,7 +46,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 45 | 46 | If you'd rather skip the menu, each script can be run on its own. Use the right invocation pattern for that script's privilege mode: | |
| 46 | 47 | ||
| 47 | 48 | ```bash | |
| 48 | - | # sudo scripts (1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 15) — pipe through sudo bash | |
| 49 | + | # sudo scripts (1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 15, 16) — pipe through sudo bash | |
| 49 | 50 | curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 50 | 51 | ||
| 51 | 52 | # user scripts (9, 13, 14) — DO NOT use sudo; they install per-user | |
| @@ -68,7 +69,7 @@ The hardened installers generally share this robustness baseline: | |||
| 68 | 69 | - `dpkg --print-architecture` / `uname -m` for architecture (amd64 / arm64 / armhf / x86_64 / aarch64 as applicable to each upstream) | |
| 69 | 70 | - Idempotent — re-running a script does not duplicate APT sources, fstab entries, gsettings entries, or `.desktop` files | |
| 70 | 71 | - `signed-by` keyrings in `/etc/apt/keyrings` (no deprecated `apt-key add`) | |
| 71 | - | - Checksum verification where the upstream publishes one (JetBrains SHA-256, LibreOffice MD5, Obsidian SHA-256, ipatool SHA-256) | |
| 72 | + | - Checksum verification where the upstream publishes one (JetBrains SHA-256, LibreOffice MD5, Obsidian SHA-256, ipatool SHA-256, draw.io SHA-256) | |
| 72 | 73 | - Per-user installers refuse to run as root; system installers refuse to run as non-root | |
| 73 | 74 | ||
| 74 | 75 | ## Supported distros | |
install-drawio.sh(vytvořil soubor)
| @@ -0,0 +1,113 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # install-drawio.sh — Install draw.io Desktop from the latest official GitHub release. | |
| 3 | + | # Hardened: arch-aware, GitHub API token support, SHA-256 verification from the | |
| 4 | + | # release asset digest, idempotent, --dry-run. | |
| 5 | + | ||
| 6 | + | set -euo pipefail | |
| 7 | + | IFS=$'\n\t' | |
| 8 | + | ||
| 9 | + | readonly SCRIPT_NAME="${0##*/}" | |
| 10 | + | DRY_RUN=0 | |
| 11 | + | REPO="jgraph/drawio-desktop" | |
| 12 | + | ||
| 13 | + | usage() { | |
| 14 | + | cat <<EOF | |
| 15 | + | Usage: sudo $SCRIPT_NAME [--dry-run] [--help] | |
| 16 | + | ||
| 17 | + | Resolves the latest release of jgraph/drawio-desktop, downloads the official | |
| 18 | + | Linux .deb package for your architecture, verifies its SHA-256 against the | |
| 19 | + | GitHub release asset digest when available, and installs it with apt-get. | |
| 20 | + | ||
| 21 | + | If \$GITHUB_TOKEN is set in the environment, it is used to authenticate the | |
| 22 | + | GitHub API request (avoids rate limits). | |
| 23 | + | ||
| 24 | + | Options: | |
| 25 | + | --dry-run Print actions without executing. | |
| 26 | + | --help, -h Show this help. | |
| 27 | + | EOF | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; } | |
| 31 | + | warn() { printf '\033[1;33m[%s] WARN:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; } | |
| 32 | + | die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } | |
| 33 | + | run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } | |
| 34 | + | trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR | |
| 35 | + | ||
| 36 | + | while (( $# )); do | |
| 37 | + | case "$1" in | |
| 38 | + | --dry-run) DRY_RUN=1 ;; | |
| 39 | + | -h|--help) usage; exit 0 ;; | |
| 40 | + | *) die "Unknown argument: $1 (try --help)" ;; | |
| 41 | + | esac | |
| 42 | + | shift | |
| 43 | + | done | |
| 44 | + | ||
| 45 | + | (( EUID == 0 )) || die "Must run as root. Try: sudo $SCRIPT_NAME" | |
| 46 | + | ||
| 47 | + | [[ -r /etc/os-release ]] || die "/etc/os-release not found." | |
| 48 | + | # shellcheck disable=SC1091 | |
| 49 | + | . /etc/os-release | |
| 50 | + | case "${ID:-}:${ID_LIKE:-}" in | |
| 51 | + | *ubuntu*|*debian*) : ;; | |
| 52 | + | *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; | |
| 53 | + | esac | |
| 54 | + | ||
| 55 | + | ARCH="$(dpkg --print-architecture)" | |
| 56 | + | case "$ARCH" in | |
| 57 | + | amd64|arm64) : ;; | |
| 58 | + | *) die "draw.io Desktop publishes Linux .deb assets for amd64 and arm64 only (detected: $ARCH)." ;; | |
| 59 | + | esac | |
| 60 | + | log "Detected: ${PRETTY_NAME:-unknown}, arch: $ARCH" | |
| 61 | + | ||
| 62 | + | export DEBIAN_FRONTEND=noninteractive | |
| 63 | + | log "Installing prerequisites..." | |
| 64 | + | run "apt-get update -qq" | |
| 65 | + | run "apt-get install -y curl jq ca-certificates" | |
| 66 | + | ||
| 67 | + | GH_HDRS=(-H "Accept: application/vnd.github+json") | |
| 68 | + | [[ -n "${GITHUB_TOKEN:-}" ]] && GH_HDRS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") | |
| 69 | + | ||
| 70 | + | log "Querying GitHub API for latest release of $REPO..." | |
| 71 | + | RELEASE_JSON="$(curl -fsSL "${GH_HDRS[@]}" "https://api.github.com/repos/${REPO}/releases/latest")" | |
| 72 | + | TAG="$(jq -r '.tag_name // empty' <<<"$RELEASE_JSON")" | |
| 73 | + | [[ -n "$TAG" ]] || die "Could not parse latest release tag (rate limited? set GITHUB_TOKEN)." | |
| 74 | + | log "Latest release: $TAG" | |
| 75 | + | ||
| 76 | + | ASSET_REGEX="^drawio-${ARCH}-[^/]+\\.deb$" | |
| 77 | + | DOWNLOAD_URL="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | .browser_download_url' <<<"$RELEASE_JSON" | head -n1)" | |
| 78 | + | ASSET_NAME="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | .name' <<<"$RELEASE_JSON" | head -n1)" | |
| 79 | + | EXPECTED_SHA="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | (.digest // "") | sub("^sha256:"; "")' <<<"$RELEASE_JSON" | head -n1)" | |
| 80 | + | [[ "$DOWNLOAD_URL" =~ ^https:// ]] || die "No $ARCH .deb asset found in latest draw.io Desktop release." | |
| 81 | + | ||
| 82 | + | STAGE="$(mktemp -d -t drawio.XXXXXX)" | |
| 83 | + | trap 'rm -rf "$STAGE"' EXIT | |
| 84 | + | DEB="$STAGE/$ASSET_NAME" | |
| 85 | + | ||
| 86 | + | log "Downloading $ASSET_NAME..." | |
| 87 | + | run "curl -fsSL -o '$DEB' '$DOWNLOAD_URL'" | |
| 88 | + | ||
| 89 | + | if (( DRY_RUN )); then | |
| 90 | + | [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]] \ | |
| 91 | + | && printf ' DRY-RUN: verify SHA-256 %s\n' "$EXPECTED_SHA" \ | |
| 92 | + | || printf ' DRY-RUN: skip SHA-256 verification; no GitHub asset digest found\n' | |
| 93 | + | printf ' DRY-RUN: apt-get install -y %s\n' "$DEB" | |
| 94 | + | log "Done." | |
| 95 | + | exit 0 | |
| 96 | + | fi | |
| 97 | + | ||
| 98 | + | if [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]]; then | |
| 99 | + | log "Verifying SHA-256..." | |
| 100 | + | ACTUAL_SHA="$(sha256sum "$DEB" | awk '{print $1}')" | |
| 101 | + | [[ "$EXPECTED_SHA" == "$ACTUAL_SHA" ]] || die "SHA-256 mismatch: expected=$EXPECTED_SHA actual=$ACTUAL_SHA" | |
| 102 | + | log "SHA-256 ok." | |
| 103 | + | else | |
| 104 | + | warn "No SHA-256 asset digest in GitHub release metadata; skipping verification." | |
| 105 | + | fi | |
| 106 | + | ||
| 107 | + | log "Installing draw.io Desktop..." | |
| 108 | + | run "apt-get install -y '$DEB'" | |
| 109 | + | ||
| 110 | + | if (( ! DRY_RUN )) && command -v drawio >/dev/null 2>&1; then | |
| 111 | + | log "Installed: $(drawio --version 2>/dev/null || echo "$TAG")" | |
| 112 | + | fi | |
| 113 | + | log "Done." | |
menu.sh
| @@ -121,6 +121,7 @@ OPTIONS=( | |||
| 121 | 121 | "13|Install IBus Intelligent Pinyin (per-user)|install-ibus-pinyin.sh|user" | |
| 122 | 122 | "14|Install Nerd Fonts (per-user)|install-font.sh|user" | |
| 123 | 123 | "15|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 124 | + | "16|Install draw.io Desktop|install-drawio.sh|sudo" | |
| 124 | 125 | ) | |
| 125 | 126 | ||
| 126 | 127 | print_menu() { | |
| @@ -136,6 +137,7 @@ print_menu() { | |||
| 136 | 137 | echo " 5) Install Espanso" | |
| 137 | 138 | echo " 6) Install LibreOffice" | |
| 138 | 139 | echo " 7) Install Obsidian" | |
| 140 | + | echo " 16) Install draw.io Desktop" | |
| 139 | 141 | echo "--- [ Development Tools ] ---" | |
| 140 | 142 | echo " 8) Install Visual Studio Code" | |
| 141 | 143 | echo " 9) Install JetBrains Toolbox (runs as you, not root)" | |
| @@ -147,7 +149,7 @@ print_menu() { | |||
| 147 | 149 | echo " 14) Install Nerd Fonts (runs as you, not root)" | |
| 148 | 150 | echo " 15) Fix Dual-Boot Time (RTC to UTC)" | |
| 149 | 151 | hr | |
| 150 | - | echo " 0) Run ALL options (1-15)" | |
| 152 | + | echo " 0) Run ALL options (1-16)" | |
| 151 | 153 | echo " -1) Exit" | |
| 152 | 154 | hr | |
| 153 | 155 | } | |
| @@ -166,7 +168,7 @@ resolve_choice() { | |||
| 166 | 168 | return 1 | |
| 167 | 169 | } | |
| 168 | 170 | ||
| 169 | - | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) | |
| 171 | + | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16) | |
| 170 | 172 | ||
| 171 | 173 | prime_sudo || exit 1 | |
| 172 | 174 | ||
weehong revidoval tento gist 2 weeks ago. Přejít na revizi
3 files changed, 151 insertions, 36 deletions
README.md
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | # Ubuntu / Debian Setup Manager | |
| 2 | 2 | ||
| 3 | - | A collection of hardened install scripts for a fresh Ubuntu / Debian desktop, driven by an interactive `menu.sh`. Each script auto-detects the distro, uses `signed-by` APT keyrings (no `apt-key`), is idempotent (safe to re-run), and supports `--help` and `--dry-run`. | |
| 3 | + | A collection of hardened install scripts for a fresh Ubuntu / Debian desktop, driven by an interactive `menu.sh`. Each script auto-detects the distro, uses `signed-by` APT keyrings where applicable (no `apt-key`), and is idempotent (safe to re-run). | |
| 4 | 4 | ||
| 5 | 5 | ## Quick start — run the menu | |
| 6 | 6 | ||
| @@ -22,18 +22,21 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 22 | 22 | ||
| 23 | 23 | | # | Script | Runs as | What it does | | |
| 24 | 24 | |---|---|---|---| | |
| 25 | - | | 1 | [`install-firefox.sh`](install-firefox.sh) | sudo | Removes the Firefox Snap and installs Firefox from Mozilla's official APT repo, with APT pinning so it stays on the Mozilla build. Verifies the Mozilla signing-key fingerprint. | | |
| 26 | - | | 2 | [`install-thunderbird.sh`](install-thunderbird.sh) | sudo | Ubuntu: removes the Snap, adds the Mozilla Team PPA, and pins it. Debian: installs from the standard repos. | | |
| 27 | - | | 3 | [`install-1password.sh`](install-1password.sh) | sudo | Configures the 1Password APT repo with `debsig` signature policy. Arch-aware (amd64 / arm64). | | |
| 28 | - | | 4 | [`install-espanso.sh`](install-espanso.sh) | sudo | Installs Espanso. Auto-detects Wayland vs X11 from `$XDG_SESSION_TYPE` and registers the systemd-user service as the invoking desktop user (not root). | | |
| 29 | - | | 5 | [`install-libreoffice.sh`](install-libreoffice.sh) | sudo | Detects the latest stable release on documentfoundation.org, downloads the matching `.deb` tarball, verifies the published MD5, and installs. Purges the distro's `libreoffice*` first to avoid library conflicts (opt-out with `--keep-distro-libreoffice`). | | |
| 30 | - | | 6 | [`install-vscode.sh`](install-vscode.sh) | sudo | Microsoft's official `code` APT repo, signed-by keyring. `--insiders` flag installs `code-insiders` instead. | | |
| 31 | - | | 7 | [`install-jetbrains-toolbox.sh`](install-jetbrains-toolbox.sh) | **user** | Per-user install into `~/.local/share/JetBrains/Toolbox`. SHA-256 verified against JetBrains' release feed. x86_64 + aarch64. Drops a `.desktop` launcher. | | |
| 32 | - | | 8 | [`install-bruno.sh`](install-bruno.sh) | sudo | Bruno API client from the official APT repo. Keyserver fetch is wrapped in a 5-attempt retry/backoff because `keyserver.ubuntu.com` is occasionally flaky. | | |
| 33 | - | | 9 | [`install-ipatool.sh`](install-ipatool.sh) | sudo | Installs the latest release of `majd/ipatool` from GitHub. SHA-256 verified against the release `checksums.txt`. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 34 | - | | 10 | [`install-network_drive.sh`](install-network_drive.sh) | sudo | Discovers SMB shares on a Synology NAS and adds them to `/etc/fstab` under `/mnt/Synology` with `x-systemd.automount`. fstab block is managed via begin/end markers so re-runs replace rather than duplicate. Credentials file is `0600`. Best-effort GNOME Dock pin. | | |
| 35 | - | | 11 | [`install-ibus-pinyin.sh`](install-ibus-pinyin.sh) | **user** | Installs `ibus-libpinyin` and Simplified Chinese language packs, restarts the IBus daemon, and idempotently adds `('ibus', 'libpinyin')` to GNOME's input sources via `gsettings`. | | |
| 36 | - | | 12 | [`install-font.sh`](install-font.sh) | **user** | Installs the latest Ubuntu Sans Nerd Font and JetBrains Mono Nerd Font to `~/.local/share/fonts`, then refreshes the font cache. No sudo needed. | | |
| 25 | + | | 1 | [`install-chrome.sh`](install-chrome.sh) | sudo | Installs Google Chrome Stable from Google's official APT repo using a `signed-by` keyring. | | |
| 26 | + | | 2 | [`install-firefox.sh`](install-firefox.sh) | sudo | Removes the Firefox Snap and installs Firefox from Mozilla's official APT repo, with APT pinning so it stays on the Mozilla build. Verifies the Mozilla signing-key fingerprint. | | |
| 27 | + | | 3 | [`install-thunderbird.sh`](install-thunderbird.sh) | sudo | Ubuntu: removes the Snap, adds the Mozilla Team PPA, and pins it. Debian: installs from the standard repos. | | |
| 28 | + | | 4 | [`install-1password.sh`](install-1password.sh) | sudo | Configures the 1Password APT repo with `debsig` signature policy. Arch-aware (amd64 / arm64). | | |
| 29 | + | | 5 | [`install-espanso.sh`](install-espanso.sh) | sudo | Installs Espanso. Auto-detects Wayland vs X11 from `$XDG_SESSION_TYPE` and registers the systemd-user service as the invoking desktop user (not root). | | |
| 30 | + | | 6 | [`install-libreoffice.sh`](install-libreoffice.sh) | sudo | Detects the latest stable release on documentfoundation.org, downloads the matching `.deb` tarball, verifies the published MD5, and installs. Purges the distro's `libreoffice*` first to avoid library conflicts (opt-out with `--keep-distro-libreoffice`). | | |
| 31 | + | | 7 | [`install-obsidian.sh`](install-obsidian.sh) | sudo | Installs the latest official Obsidian amd64 `.deb` from `obsidianmd/obsidian-releases`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 32 | + | | 8 | [`install-vscode.sh`](install-vscode.sh) | sudo | Microsoft's official `code` APT repo, signed-by keyring. `--insiders` flag installs `code-insiders` instead. | | |
| 33 | + | | 9 | [`install-jetbrains-toolbox.sh`](install-jetbrains-toolbox.sh) | **user** | Per-user install into `~/.local/share/JetBrains/Toolbox`. SHA-256 verified against JetBrains' release feed. x86_64 + aarch64. Drops a `.desktop` launcher. | | |
| 34 | + | | 10 | [`install-bruno.sh`](install-bruno.sh) | sudo | Bruno API client from the official APT repo. Keyserver fetch is wrapped in a 5-attempt retry/backoff because `keyserver.ubuntu.com` is occasionally flaky. | | |
| 35 | + | | 11 | [`install-ipatool.sh`](install-ipatool.sh) | sudo | Installs the latest release of `majd/ipatool` from GitHub. SHA-256 verified against the release `checksums.txt`. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 36 | + | | 12 | [`install-network_drive.sh`](install-network_drive.sh) | sudo | Discovers SMB shares on a Synology NAS and adds them to `/etc/fstab` under `/mnt/Synology` with `x-systemd.automount`. fstab block is managed via begin/end markers so re-runs replace rather than duplicate. Credentials file is `0600`. Best-effort GNOME Dock pin. | | |
| 37 | + | | 13 | [`install-ibus-pinyin.sh`](install-ibus-pinyin.sh) | **user** | Installs `ibus-libpinyin` and Simplified Chinese language packs, restarts the IBus daemon, and idempotently adds `('ibus', 'libpinyin')` to GNOME's input sources via `gsettings`. | | |
| 38 | + | | 14 | [`install-font.sh`](install-font.sh) | **user** | Installs the latest Ubuntu Sans Nerd Font and JetBrains Mono Nerd Font to `~/.local/share/fonts`, then refreshes the font cache. No sudo needed. | | |
| 39 | + | | 15 | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. | | |
| 37 | 40 | ||
| 38 | 41 | "Runs as **user**" entries must be invoked as your normal desktop user, not via `sudo`. The other entries elevate via `sudo` internally and the menu primes `sudo -v` up-front, so you'll only be prompted once. | |
| 39 | 42 | ||
| @@ -42,14 +45,14 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 42 | 45 | If you'd rather skip the menu, each script can be run on its own. Use the right invocation pattern for that script's privilege mode: | |
| 43 | 46 | ||
| 44 | 47 | ```bash | |
| 45 | - | # sudo scripts (1, 2, 3, 4, 5, 6, 8, 9, 10) — pipe through sudo bash | |
| 48 | + | # sudo scripts (1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 15) — pipe through sudo bash | |
| 46 | 49 | curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 47 | 50 | ||
| 48 | - | # user scripts (7, 11, 12) — DO NOT use sudo; they install per-user | |
| 51 | + | # user scripts (9, 13, 14) — DO NOT use sudo; they install per-user | |
| 49 | 52 | bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)" | |
| 50 | 53 | ``` | |
| 51 | 54 | ||
| 52 | - | Every script accepts `--help` and `--dry-run` (the latter prints what would happen without executing). For example: | |
| 55 | + | Most installers accept `--help` and `--dry-run` (the latter prints what would happen without executing). For example: | |
| 53 | 56 | ||
| 54 | 57 | ```bash | |
| 55 | 58 | curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-libreoffice.sh \ | |
| @@ -58,14 +61,14 @@ curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b | |||
| 58 | 61 | ||
| 59 | 62 | ## What "hardened" means here | |
| 60 | 63 | ||
| 61 | - | All scripts share the same robustness baseline: | |
| 64 | + | The hardened installers generally share this robustness baseline: | |
| 62 | 65 | ||
| 63 | 66 | - `set -euo pipefail` + IFS hygiene + `ERR` trap reporting the failing line number | |
| 64 | 67 | - `/etc/os-release` distro auto-detection (no hardcoded codenames) | |
| 65 | 68 | - `dpkg --print-architecture` / `uname -m` for architecture (amd64 / arm64 / armhf / x86_64 / aarch64 as applicable to each upstream) | |
| 66 | 69 | - Idempotent — re-running a script does not duplicate APT sources, fstab entries, gsettings entries, or `.desktop` files | |
| 67 | 70 | - `signed-by` keyrings in `/etc/apt/keyrings` (no deprecated `apt-key add`) | |
| 68 | - | - Checksum verification where the upstream publishes one (JetBrains SHA-256, LibreOffice MD5, ipatool SHA-256) | |
| 71 | + | - Checksum verification where the upstream publishes one (JetBrains SHA-256, LibreOffice MD5, Obsidian SHA-256, ipatool SHA-256) | |
| 69 | 72 | - Per-user installers refuse to run as root; system installers refuse to run as non-root | |
| 70 | 73 | ||
| 71 | 74 | ## Supported distros | |
install-obsidian.sh(vytvořil soubor)
| @@ -0,0 +1,110 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # install-obsidian.sh — Install Obsidian from the latest official GitHub release. | |
| 3 | + | # Hardened: arch-aware, GitHub API token support, SHA-256 verification from the | |
| 4 | + | # release asset digest, idempotent, --dry-run. | |
| 5 | + | ||
| 6 | + | set -euo pipefail | |
| 7 | + | IFS=$'\n\t' | |
| 8 | + | ||
| 9 | + | readonly SCRIPT_NAME="${0##*/}" | |
| 10 | + | DRY_RUN=0 | |
| 11 | + | REPO="obsidianmd/obsidian-releases" | |
| 12 | + | ||
| 13 | + | usage() { | |
| 14 | + | cat <<EOF | |
| 15 | + | Usage: sudo $SCRIPT_NAME [--dry-run] [--help] | |
| 16 | + | ||
| 17 | + | Resolves the latest release of obsidianmd/obsidian-releases, downloads the | |
| 18 | + | official amd64 .deb package, verifies its SHA-256 against the GitHub release | |
| 19 | + | asset digest when available, and installs it with apt-get. | |
| 20 | + | ||
| 21 | + | If \$GITHUB_TOKEN is set in the environment, it is used to authenticate the | |
| 22 | + | GitHub API request (avoids rate limits). | |
| 23 | + | ||
| 24 | + | Options: | |
| 25 | + | --dry-run Print actions without executing. | |
| 26 | + | --help, -h Show this help. | |
| 27 | + | EOF | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; } | |
| 31 | + | warn() { printf '\033[1;33m[%s] WARN:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; } | |
| 32 | + | die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } | |
| 33 | + | run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } | |
| 34 | + | trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR | |
| 35 | + | ||
| 36 | + | while (( $# )); do | |
| 37 | + | case "$1" in | |
| 38 | + | --dry-run) DRY_RUN=1 ;; | |
| 39 | + | -h|--help) usage; exit 0 ;; | |
| 40 | + | *) die "Unknown argument: $1 (try --help)" ;; | |
| 41 | + | esac | |
| 42 | + | shift | |
| 43 | + | done | |
| 44 | + | ||
| 45 | + | (( EUID == 0 )) || die "Must run as root. Try: sudo $SCRIPT_NAME" | |
| 46 | + | ||
| 47 | + | [[ -r /etc/os-release ]] || die "/etc/os-release not found." | |
| 48 | + | # shellcheck disable=SC1091 | |
| 49 | + | . /etc/os-release | |
| 50 | + | case "${ID:-}:${ID_LIKE:-}" in | |
| 51 | + | *ubuntu*|*debian*) : ;; | |
| 52 | + | *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; | |
| 53 | + | esac | |
| 54 | + | ||
| 55 | + | ARCH="$(dpkg --print-architecture)" | |
| 56 | + | [[ "$ARCH" == "amd64" ]] || die "Obsidian publishes a Linux .deb for amd64 only (detected: $ARCH)." | |
| 57 | + | log "Detected: ${PRETTY_NAME:-unknown}, arch: $ARCH" | |
| 58 | + | ||
| 59 | + | export DEBIAN_FRONTEND=noninteractive | |
| 60 | + | log "Installing prerequisites..." | |
| 61 | + | run "apt-get update -qq" | |
| 62 | + | run "apt-get install -y curl jq ca-certificates" | |
| 63 | + | ||
| 64 | + | GH_HDRS=(-H "Accept: application/vnd.github+json") | |
| 65 | + | [[ -n "${GITHUB_TOKEN:-}" ]] && GH_HDRS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") | |
| 66 | + | ||
| 67 | + | log "Querying GitHub API for latest release of $REPO..." | |
| 68 | + | RELEASE_JSON="$(curl -fsSL "${GH_HDRS[@]}" "https://api.github.com/repos/${REPO}/releases/latest")" | |
| 69 | + | TAG="$(jq -r '.tag_name // empty' <<<"$RELEASE_JSON")" | |
| 70 | + | [[ -n "$TAG" ]] || die "Could not parse latest release tag (rate limited? set GITHUB_TOKEN)." | |
| 71 | + | log "Latest release: $TAG" | |
| 72 | + | ||
| 73 | + | ASSET_REGEX='^obsidian_[^/]+_amd64\.deb$' | |
| 74 | + | DOWNLOAD_URL="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | .browser_download_url' <<<"$RELEASE_JSON" | head -n1)" | |
| 75 | + | ASSET_NAME="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | .name' <<<"$RELEASE_JSON" | head -n1)" | |
| 76 | + | EXPECTED_SHA="$(jq -r --arg re "$ASSET_REGEX" '.assets[] | select(.name | test($re)) | (.digest // "") | sub("^sha256:"; "")' <<<"$RELEASE_JSON" | head -n1)" | |
| 77 | + | [[ "$DOWNLOAD_URL" =~ ^https:// ]] || die "No amd64 .deb asset found in latest Obsidian release." | |
| 78 | + | ||
| 79 | + | STAGE="$(mktemp -d -t obsidian.XXXXXX)" | |
| 80 | + | trap 'rm -rf "$STAGE"' EXIT | |
| 81 | + | DEB="$STAGE/$ASSET_NAME" | |
| 82 | + | ||
| 83 | + | log "Downloading $ASSET_NAME..." | |
| 84 | + | run "curl -fsSL -o '$DEB' '$DOWNLOAD_URL'" | |
| 85 | + | ||
| 86 | + | if (( DRY_RUN )); then | |
| 87 | + | [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]] \ | |
| 88 | + | && printf ' DRY-RUN: verify SHA-256 %s\n' "$EXPECTED_SHA" \ | |
| 89 | + | || printf ' DRY-RUN: skip SHA-256 verification; no GitHub asset digest found\n' | |
| 90 | + | printf ' DRY-RUN: apt-get install -y %s\n' "$DEB" | |
| 91 | + | log "Done." | |
| 92 | + | exit 0 | |
| 93 | + | fi | |
| 94 | + | ||
| 95 | + | if [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" ]]; then | |
| 96 | + | log "Verifying SHA-256..." | |
| 97 | + | ACTUAL_SHA="$(sha256sum "$DEB" | awk '{print $1}')" | |
| 98 | + | [[ "$EXPECTED_SHA" == "$ACTUAL_SHA" ]] || die "SHA-256 mismatch: expected=$EXPECTED_SHA actual=$ACTUAL_SHA" | |
| 99 | + | log "SHA-256 ok." | |
| 100 | + | else | |
| 101 | + | warn "No SHA-256 asset digest in GitHub release metadata; skipping verification." | |
| 102 | + | fi | |
| 103 | + | ||
| 104 | + | log "Installing Obsidian..." | |
| 105 | + | run "apt-get install -y '$DEB'" | |
| 106 | + | ||
| 107 | + | if (( ! DRY_RUN )) && command -v obsidian >/dev/null 2>&1; then | |
| 108 | + | log "Installed: $(obsidian --version 2>/dev/null || echo "$TAG")" | |
| 109 | + | fi | |
| 110 | + | log "Done." | |
menu.sh
| @@ -112,14 +112,15 @@ OPTIONS=( | |||
| 112 | 112 | "4|Install 1Password|install-1password.sh|sudo" | |
| 113 | 113 | "5|Install Espanso (text expander)|install-espanso.sh|sudo" | |
| 114 | 114 | "6|Install LibreOffice (latest stable .deb)|install-libreoffice.sh|sudo" | |
| 115 | - | "7|Install Visual Studio Code|install-vscode.sh|sudo" | |
| 116 | - | "8|Install JetBrains Toolbox (per-user)|install-jetbrains-toolbox.sh|user" | |
| 117 | - | "9|Install Bruno (API client)|install-bruno.sh|sudo" | |
| 118 | - | "10|Install IPATool|install-ipatool.sh|sudo" | |
| 119 | - | "11|Mount Synology Network Drive|install-network_drive.sh|sudo" | |
| 120 | - | "12|Install IBus Intelligent Pinyin (per-user)|install-ibus-pinyin.sh|user" | |
| 121 | - | "13|Install Nerd Fonts (per-user)|install-font.sh|user" | |
| 122 | - | "14|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 115 | + | "7|Install Obsidian|install-obsidian.sh|sudo" | |
| 116 | + | "8|Install Visual Studio Code|install-vscode.sh|sudo" | |
| 117 | + | "9|Install JetBrains Toolbox (per-user)|install-jetbrains-toolbox.sh|user" | |
| 118 | + | "10|Install Bruno (API client)|install-bruno.sh|sudo" | |
| 119 | + | "11|Install IPATool|install-ipatool.sh|sudo" | |
| 120 | + | "12|Mount Synology Network Drive|install-network_drive.sh|sudo" | |
| 121 | + | "13|Install IBus Intelligent Pinyin (per-user)|install-ibus-pinyin.sh|user" | |
| 122 | + | "14|Install Nerd Fonts (per-user)|install-font.sh|user" | |
| 123 | + | "15|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 123 | 124 | ) | |
| 124 | 125 | ||
| 125 | 126 | print_menu() { | |
| @@ -134,18 +135,19 @@ print_menu() { | |||
| 134 | 135 | echo " 4) Install 1Password" | |
| 135 | 136 | echo " 5) Install Espanso" | |
| 136 | 137 | echo " 6) Install LibreOffice" | |
| 138 | + | echo " 7) Install Obsidian" | |
| 137 | 139 | echo "--- [ Development Tools ] ---" | |
| 138 | - | echo " 7) Install Visual Studio Code" | |
| 139 | - | echo " 8) Install JetBrains Toolbox (runs as you, not root)" | |
| 140 | - | echo " 9) Install Bruno" | |
| 141 | - | echo " 10) Install IPATool" | |
| 140 | + | echo " 8) Install Visual Studio Code" | |
| 141 | + | echo " 9) Install JetBrains Toolbox (runs as you, not root)" | |
| 142 | + | echo " 10) Install Bruno" | |
| 143 | + | echo " 11) Install IPATool" | |
| 142 | 144 | echo "--- [ System ] ---" | |
| 143 | - | echo " 11) Mount Synology Network Drive" | |
| 144 | - | echo " 12) Install IBus Intelligent Pinyin (runs as you, not root)" | |
| 145 | - | echo " 13) Install Nerd Fonts (runs as you, not root)" | |
| 146 | - | echo " 14) Fix Dual-Boot Time (RTC to UTC)" | |
| 145 | + | echo " 12) Mount Synology Network Drive" | |
| 146 | + | echo " 13) Install IBus Intelligent Pinyin (runs as you, not root)" | |
| 147 | + | echo " 14) Install Nerd Fonts (runs as you, not root)" | |
| 148 | + | echo " 15) Fix Dual-Boot Time (RTC to UTC)" | |
| 147 | 149 | hr | |
| 148 | - | echo " 0) Run ALL options (1-14)" | |
| 150 | + | echo " 0) Run ALL options (1-15)" | |
| 149 | 151 | echo " -1) Exit" | |
| 150 | 152 | hr | |
| 151 | 153 | } | |
| @@ -164,7 +166,7 @@ resolve_choice() { | |||
| 164 | 166 | return 1 | |
| 165 | 167 | } | |
| 166 | 168 | ||
| 167 | - | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14) | |
| 169 | + | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) | |
| 168 | 170 | ||
| 169 | 171 | prime_sudo || exit 1 | |
| 170 | 172 | ||
weehong revidoval tento gist 2 weeks ago. Přejít na revizi
1 file changed, 52 insertions, 20 deletions
install-ibus-pinyin.sh
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | #!/usr/bin/env bash | |
| 2 | - | # install-ibus-pinyin.sh — Install IBus + libpinyin and add Intelligent Pinyin | |
| 3 | - | # to GNOME's Input Sources. Tuned for Ubuntu 25.10 (GNOME 49 / Wayland) but | |
| 2 | + | # install-ibus-rime.sh — Install IBus + Rime and add it | |
| 3 | + | # to GNOME's Input Sources. Tuned for Ubuntu 26.04 (GNOME 50 / Wayland) but | |
| 4 | 4 | # works on any modern Ubuntu/Debian GNOME desktop. | |
| 5 | 5 | # | |
| 6 | 6 | # Hardened: distro-detect, runs as the desktop user (not root) for gsettings, | |
| @@ -17,8 +17,9 @@ usage() { | |||
| 17 | 17 | cat <<EOF | |
| 18 | 18 | Usage: $SCRIPT_NAME [--dry-run] [--skip-gsettings] [--help] | |
| 19 | 19 | ||
| 20 | - | Installs ibus-libpinyin (plus Simplified Chinese language packs) and registers | |
| 21 | - | 'Intelligent Pinyin' as a GNOME input source. Idempotent. | |
| 20 | + | Purges old ibus-libpinyin, installs ibus-rime (plus Chinese language packs), | |
| 21 | + | configures Rime to default to Simplified Pinyin, and registers 'Rime' | |
| 22 | + | as a GNOME input source. Idempotent. | |
| 22 | 23 | ||
| 23 | 24 | Do NOT run with sudo: gsettings is per-user and must run as the desktop user. | |
| 24 | 25 | The script invokes sudo internally only for apt-get steps. | |
| @@ -58,9 +59,10 @@ else | |||
| 58 | 59 | DESKTOP_USER="$USER" | |
| 59 | 60 | fi | |
| 60 | 61 | ||
| 61 | - | # Look up the user's UID for the DBus session bus. | |
| 62 | + | # Look up the user's UID and Home Directory for configs and DBus. | |
| 62 | 63 | USER_ENTRY="$(getent passwd "$DESKTOP_USER")" || die "User '$DESKTOP_USER' not found in passwd." | |
| 63 | 64 | USER_ID="$(awk -F: '{print $3}' <<<"$USER_ENTRY")" | |
| 65 | + | USER_HOME="$(awk -F: '{print $6}' <<<"$USER_ENTRY")" | |
| 64 | 66 | ||
| 65 | 67 | [[ -r /etc/os-release ]] || die "/etc/os-release not found." | |
| 66 | 68 | # shellcheck disable=SC1091 | |
| @@ -99,15 +101,40 @@ sudo_run() { | |||
| 99 | 101 | ||
| 100 | 102 | export DEBIAN_FRONTEND=noninteractive | |
| 101 | 103 | ||
| 102 | - | log "Installing IBus Pinyin + Simplified Chinese language packs..." | |
| 104 | + | log "Removing old ibus-libpinyin if present..." | |
| 105 | + | sudo_run apt-get remove --purge -y ibus-libpinyin || true | |
| 106 | + | ||
| 107 | + | log "Installing IBus Rime + Chinese language packs..." | |
| 103 | 108 | sudo_run apt-get update -qq | |
| 104 | 109 | sudo_run apt-get install -y \ | |
| 105 | 110 | ibus \ | |
| 106 | - | ibus-libpinyin \ | |
| 111 | + | ibus-rime \ | |
| 107 | 112 | language-pack-zh-hans \ | |
| 108 | 113 | language-pack-gnome-zh-hans | |
| 109 | 114 | ||
| 110 | - | # Make sure ibus-daemon picks up the new engines for the user. | |
| 115 | + | log "Configuring Rime to permanently default to Simplified Pinyin..." | |
| 116 | + | RIME_DIR="$USER_HOME/.config/ibus/rime" | |
| 117 | + | if (( DRY_RUN )); then | |
| 118 | + | printf ' DRY-RUN (as %s): Create %s/default.custom.yaml\n' "$DESKTOP_USER" "$RIME_DIR" | |
| 119 | + | else | |
| 120 | + | if (( EUID == 0 )); then | |
| 121 | + | sudo -u "$DESKTOP_USER" mkdir -p "$RIME_DIR" | |
| 122 | + | sudo -u "$DESKTOP_USER" tee "$RIME_DIR/default.custom.yaml" > /dev/null <<'EOF' | |
| 123 | + | patch: | |
| 124 | + | schema_list: | |
| 125 | + | - schema: luna_pinyin_simp | |
| 126 | + | EOF | |
| 127 | + | else | |
| 128 | + | mkdir -p "$RIME_DIR" | |
| 129 | + | tee "$RIME_DIR/default.custom.yaml" > /dev/null <<'EOF' | |
| 130 | + | patch: | |
| 131 | + | schema_list: | |
| 132 | + | - schema: luna_pinyin_simp | |
| 133 | + | EOF | |
| 134 | + | fi | |
| 135 | + | fi | |
| 136 | + | ||
| 137 | + | # Make sure ibus-daemon picks up the new engines and config for the user. | |
| 111 | 138 | if command -v ibus >/dev/null 2>&1; then | |
| 112 | 139 | log "Restarting ibus-daemon for $DESKTOP_USER..." | |
| 113 | 140 | # `ibus exit` will fail if no daemon is running — treat as non-fatal. | |
| @@ -125,35 +152,40 @@ fi | |||
| 125 | 152 | ||
| 126 | 153 | if (( SKIP_GSETTINGS )); then | |
| 127 | 154 | log "Skipping GNOME input-sources update (--skip-gsettings)." | |
| 128 | - | log "Done. Add 'Chinese (Intelligent Pinyin)' manually under Settings → Keyboard → Input Sources." | |
| 155 | + | log "Done. Add 'Chinese (Rime)' manually under Settings → Keyboard → Input Sources." | |
| 129 | 156 | exit 0 | |
| 130 | 157 | fi | |
| 131 | 158 | ||
| 132 | 159 | # Only manage gsettings if GNOME schemas are present. | |
| 133 | 160 | if ! run_as_user gsettings list-schemas 2>/dev/null | grep -q '^org.gnome.desktop.input-sources$'; then | |
| 134 | 161 | warn "GNOME schema org.gnome.desktop.input-sources not found; skipping gsettings update." | |
| 135 | - | log "Done. Add 'Chinese (Intelligent Pinyin)' manually in your DE's input settings." | |
| 162 | + | log "Done. Add 'Chinese (Rime)' manually in your DE's input settings." | |
| 136 | 163 | exit 0 | |
| 137 | 164 | fi | |
| 138 | 165 | ||
| 139 | - | log "Adding 'Intelligent Pinyin' to GNOME Input Sources (idempotent)..." | |
| 166 | + | log "Adding 'Rime' to GNOME Input Sources (idempotent)..." | |
| 140 | 167 | ||
| 141 | 168 | CURRENT_SOURCES="$(run_as_user gsettings get org.gnome.desktop.input-sources sources 2>/dev/null || echo '[]')" | |
| 142 | 169 | # Strip the optional "@as " type annotation gvariant sometimes prepends. | |
| 143 | 170 | CLEAN_SOURCES="${CURRENT_SOURCES#@as }" | |
| 144 | 171 | ||
| 145 | - | if [[ "$CLEAN_SOURCES" == *"'ibus', 'libpinyin'"* ]]; then | |
| 146 | - | log "Intelligent Pinyin already present in input sources — nothing to do." | |
| 172 | + | if [[ "$CLEAN_SOURCES" == *"'ibus', 'rime'"* ]]; then | |
| 173 | + | log "Rime already present in input sources — nothing to do." | |
| 147 | 174 | else | |
| 148 | - | if [[ -z "$CLEAN_SOURCES" || "$CLEAN_SOURCES" == "[]" || "$CLEAN_SOURCES" == "@as []" ]]; then | |
| 149 | - | NEW_SOURCES="[('xkb', 'us'), ('ibus', 'libpinyin')]" | |
| 175 | + | if [[ "$CLEAN_SOURCES" == *"'ibus', 'libpinyin'"* ]]; then | |
| 176 | + | # Dynamically replace libpinyin with rime | |
| 177 | + | NEW_SOURCES="${CLEAN_SOURCES//\'libpinyin\'/\'rime\'}" | |
| 178 | + | elif [[ -z "$CLEAN_SOURCES" || "$CLEAN_SOURCES" == "[]" || "$CLEAN_SOURCES" == "@as []" ]]; then | |
| 179 | + | NEW_SOURCES="[('xkb', 'us'), ('ibus', 'rime')]" | |
| 150 | 180 | else | |
| 151 | - | # Insert the libpinyin tuple before the closing bracket of the existing list. | |
| 152 | - | NEW_SOURCES="${CLEAN_SOURCES%]*}, ('ibus', 'libpinyin')]" | |
| 181 | + | # Insert the rime tuple before the closing bracket of the existing list. | |
| 182 | + | NEW_SOURCES="${CLEAN_SOURCES%]*}, ('ibus', 'rime')]" | |
| 153 | 183 | fi | |
| 154 | 184 | run_as_user gsettings set org.gnome.desktop.input-sources sources "$NEW_SOURCES" | |
| 155 | - | log "Added: ('ibus', 'libpinyin')" | |
| 185 | + | log "Input sources updated to include: ('ibus', 'rime')" | |
| 156 | 186 | fi | |
| 157 | 187 | ||
| 158 | - | log "Done. Switch input methods with: Super + Space" | |
| 159 | - | log "If 'Chinese (Intelligent Pinyin)' doesn't appear in the top bar, log out and back in." | |
| 188 | + | log "---" | |
| 189 | + | log "Done! Rime is installed and configured for Simplified Pinyin." | |
| 190 | + | log "Switch input methods with: Super + Space" | |
| 191 | + | log "If 'Chinese (Rime)' doesn't appear in the top bar right away, log out and back in." | |