Última actividad 4 days ago

Hardened install scripts with an interactive menu — Chrome, Firefox, Thunderbird, 1Password, Espanso, LibreOffice, Obsidian, draw.io Desktop, VS Code, JetBrains Toolbox, Bruno, IPATool, Synology NAS auto-mount, IBus Pinyin, Nerd Fonts, qBittorrent LocalSend Telegram Discord.

weehong revisó este gist 4 days ago. Ir a la revisión

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(archivo creado)

@@ -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 revisó este gist 4 days ago. Ir a la revisión

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 revisó este gist 4 days ago. Ir a la revisión

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(archivo creado)

@@ -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(archivo creado)

@@ -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(archivo creado)

@@ -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 revisó este gist 4 days ago. Ir a la revisión

2 files changed, 381 insertions, 146 deletions

install-qbittorrent.sh(archivo creado)

@@ -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 revisó este gist 4 days ago. Ir a la revisión

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 revisó este gist 4 days ago. Ir a la revisión

Sin cambios

weehong revisó este gist 1 week ago. Ir a la revisión

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 revisó este gist 1 week ago. Ir a la revisión

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(archivo creado)

@@ -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 revisó este gist 2 weeks ago. Ir a la revisión

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(archivo creado)

@@ -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 revisó este gist 2 weeks ago. Ir a la revisión

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."
Siguiente Anterior