Dernière activité 2 weeks ago

Hardened interactive Ubuntu/Debian setup scripts, including CCUsage multi-device sync for Claude Code, Codex CLI, and OpenCode.

Vernon Wee Hong KOH a révisé ce gist 2 weeks ago. Aller à la révision

2 files changed, 54 insertions, 7 deletions

README.md

@@ -14,7 +14,7 @@ How to use the picker:
14 14
15 15 - Enter one or more numbers separated by spaces, e.g. `1 6 11`
16 16 - `0` runs setup options 1-23 in order
17 - - `26` updates all software by re-running installers 1-7 and 9-20
17 + - `26` updates installed software from options 1-7 and 9-20; software that is not installed is skipped
18 18 - `-1` exits
19 19
20 20 The menu caches `sudo` credentials up-front so multi-task runs don't keep re-prompting, and falls back to per-user execution for scripts that must not run as root (JetBrains Toolbox, IBus Pinyin, Nerd Fonts, screenshots cleanup, and Thunderbird backup).
@@ -47,7 +47,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro
47 47 | 22 | System & Maintenance | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. |
48 48 | 23 | System & Maintenance | [`disable-grub-countdown.sh`](disable-grub-countdown.sh) | sudo | Configures GRUB to show the dual-boot menu with no countdown by setting `GRUB_TIMEOUT_STYLE=menu` and `GRUB_TIMEOUT=-1`, then regenerates the GRUB config. |
49 49 | 24 | Backup | [`install-thunderbird-backup-cron.sh`](install-thunderbird-backup-cron.sh) | **user** | Installs an hourly cron job that fetches and runs the hosted Thunderbird-to-Synology backup script without storing it locally. Not included in menu option `0`; select it explicitly. |
50 - | 26 | System & Maintenance | Built into [`menu.sh`](menu.sh) | mixed | Updates all software by re-running the idempotent installers for options 1-7 and 9-20. Excludes network-drive, cron, system configuration, backup, and sync tasks. |
50 + | 26 | System & Maintenance | Built into [`menu.sh`](menu.sh) | mixed | Detects and updates installed software from options 1-7 and 9-20. Software that is not installed is logged and skipped. Excludes network-drive, cron, system configuration, backup, and sync tasks. |
51 51
52 52 "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.
53 53

menu.sh

@@ -130,13 +130,56 @@ OPTIONS=(
130 130 "22|System & Maintenance|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo|yes"
131 131 "23|System & Maintenance|Disable GRUB Countdown Timer|disable-grub-countdown.sh|sudo|yes"
132 132 "24|Backup|Install Hourly Thunderbird Synology Backup|install-thunderbird-backup-cron.sh|user|no"
133 - "26|System & Maintenance|Update All Software|update-all|group|no"
133 + "26|System & Maintenance|Update Installed Software|update-all|group|no"
134 134 )
135 135
136 - # Software installers that are safe to re-run to fetch current packages and
137 - # releases. Configuration, maintenance, backup, and sync tasks are excluded.
136 + # Software installers that are safe to re-run when their software is already
137 + # installed. Configuration, maintenance, backup, and sync tasks are excluded.
138 138 SOFTWARE_NUMS=(1 2 3 4 5 6 7 9 10 11 12 13 14 15 16 17 18 19 20)
139 139
140 + package_is_installed() {
141 + [[ "$(dpkg-query -W -f='${db:Status-Abbrev}' "$1" 2>/dev/null || true)" == "ii " ]]
142 + }
143 +
144 + package_pattern_is_installed() {
145 + local status
146 +
147 + while IFS= read -r status; do
148 + [[ "$status" == "ii " ]] && return 0
149 + done < <(dpkg-query -W -f='${db:Status-Abbrev}\n' "$1" 2>/dev/null || true)
150 + return 1
151 + }
152 +
153 + software_is_installed() {
154 + local choice="$1"
155 +
156 + case "$choice" in
157 + 1) package_is_installed google-chrome-stable ;;
158 + 2) package_is_installed firefox ;;
159 + 3) package_is_installed thunderbird ;;
160 + 4) [[ -x /opt/Telegram/Telegram ]] ;;
161 + 5) package_is_installed discord ;;
162 + 6) command -v localsend_app >/dev/null 2>&1 ;;
163 + 7) [[ -x /opt/qbittorrent/qbittorrent ]] ;;
164 + 9) package_pattern_is_installed 'libreoffice*' ;;
165 + 10) command -v obsidian >/dev/null 2>&1 ;;
166 + 11) command -v marktext >/dev/null 2>&1 ;;
167 + 12) command -v drawio >/dev/null 2>&1 ;;
168 + 13) command -v espanso >/dev/null 2>&1 ;;
169 + 14) package_is_installed 1password ;;
170 + 15) package_is_installed code ;;
171 + 16) [[ -x "$HOME/.local/share/JetBrains/Toolbox/bin/jetbrains-toolbox" ]] ;;
172 + 17) package_is_installed bruno ;;
173 + 18) [[ -x /usr/local/bin/ipatool ]] ;;
174 + 19) package_is_installed ibus-libpinyin ;;
175 + 20)
176 + compgen -G "$HOME/.local/share/fonts/UbuntuSans/*.[ot]tf" >/dev/null &&
177 + compgen -G "$HOME/.local/share/fonts/JetBrainsMonoNerdFont/*.[ot]tf" >/dev/null
178 + ;;
179 + *) return 1 ;;
180 + esac
181 + }
182 +
140 183 print_menu() {
141 184 local entry num category label filename mode include_in_all
142 185 local last_category="" note display_label
@@ -154,7 +197,7 @@ print_menu() {
154 197
155 198 note=""
156 199 if [[ "$mode" == "group" ]]; then
157 - note="runs software options 1-7 and 9-20"
200 + note="updates installed software from options 1-7 and 9-20"
158 201 elif [[ "$mode" == "user" ]]; then
159 202 note="runs as you, not root"
160 203 fi
@@ -241,11 +284,15 @@ while true; do
241 284 for c in "${selected_choices[@]}"; do
242 285 if [[ "$c" == "26" ]]; then
243 286 hr
244 - log "[26] Updating all software..."
287 + log "[26] Updating installed software..."
245 288 for software_choice in "${SOFTWARE_NUMS[@]}"; do
246 289 spec="$(resolve_choice "$software_choice")"
247 290 name="${spec%|*}"
248 291 mode="${spec##*|}"
292 + if ! software_is_installed "$software_choice"; then
293 + log "[$software_choice] Skipping $name (not installed)."
294 + continue
295 + fi
249 296 hr
250 297 log "[$software_choice] Running $name ($mode)..."
251 298 if ! run_script "$name" "$mode"; then

Vernon Wee Hong KOH a révisé ce gist 2 weeks ago. Aller à la révision

2 files changed, 59 insertions, 2 deletions

README.md

@@ -24,7 +24,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro
24 24 | # | Category | Script | Runs as | What it does |
25 25 |---|---|---|---|---|
26 26 | 1 | Browsers & Mail | [`install-chrome.sh`](install-chrome.sh) | sudo | Installs Google Chrome Stable from Google's official APT repo using a `signed-by` keyring. |
27 - | 2 | Browsers & Mail | [`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 + | 2 | Browsers & Mail | [`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 and best-effort pins Firefox at GNOME Dock position 2. |
28 28 | 3 | Browsers & Mail | [`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. |
29 29 | 4 | Communication | [`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 30 | 5 | Communication | [`install-discord.sh`](install-discord.sh) | sudo | Installs the latest official Discord Linux `.deb` from Discord's latest download endpoint. |

install-firefox.sh

@@ -1,6 +1,7 @@
1 1 #!/usr/bin/env bash
2 2 # install-firefox.sh — Install Firefox from Mozilla's official APT repository.
3 3 # Removes the Snap transition package and pins Mozilla as the source of truth.
4 + # Best-effort: pins the APT launcher at GNOME Dock position 2.
4 5 # Hardened: distro-detect, idempotent, signed-by keyring, ERR trap, --dry-run.
5 6
6 7 set -euo pipefail
@@ -16,7 +17,8 @@ Usage: sudo $SCRIPT_NAME [--dry-run] [--yes] [--help]
16 17
17 18 Removes Firefox Snap (and Ubuntu's transitional wrapper), configures the
18 19 official Mozilla APT repository (signed-by keyring + APT pin), then installs
19 - Firefox so it auto-updates from Mozilla.
20 + Firefox so it auto-updates from Mozilla. On GNOME, Firefox is pinned at Dock
21 + position 2 when a desktop user session is available.
20 22
21 23 Options:
22 24 --dry-run Print the actions without executing them.
@@ -118,4 +120,59 @@ if (( ! DRY_RUN )) && command -v firefox >/dev/null 2>&1; then
118 120 log "Installed: $(firefox --version 2>/dev/null || echo 'firefox')"
119 121 fi
120 122
123 + # Replace the stale Snap launcher, remove duplicates, and place the Mozilla APT
124 + # launcher after the first existing favorite. Dock changes are per-user, so run
125 + # gsettings against the desktop user's active D-Bus session rather than root's.
126 + DESKTOP_USER="${SUDO_USER:-}"
127 + if (( DRY_RUN )); then
128 + printf ' DRY-RUN: pin firefox.desktop at GNOME Dock position 2 when a desktop session is available\n'
129 + elif [[ -z "$DESKTOP_USER" || "$DESKTOP_USER" == "root" ]]; then
130 + warn "No sudo desktop user detected; skipping GNOME Dock pin."
131 + else
132 + DESKTOP_UID="$(id -u "$DESKTOP_USER" 2>/dev/null || true)"
133 + RUNTIME_DIR="/run/user/$DESKTOP_UID"
134 + DBUS_ADDRESS="unix:path=$RUNTIME_DIR/bus"
135 +
136 + if [[ -n "$DESKTOP_UID" && -S "$RUNTIME_DIR/bus" ]] &&
137 + command -v gsettings >/dev/null 2>&1 &&
138 + sudo -u "$DESKTOP_USER" XDG_RUNTIME_DIR="$RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$DBUS_ADDRESS" \
139 + gsettings list-keys org.gnome.shell >/dev/null 2>&1; then
140 + log "Pinning Firefox at GNOME Dock position 2..."
141 + CURRENT_FAVS="$(sudo -u "$DESKTOP_USER" XDG_RUNTIME_DIR="$RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$DBUS_ADDRESS" \
142 + gsettings get org.gnome.shell favorite-apps 2>/dev/null || echo '[]')"
143 +
144 + FAVORITES=()
145 + while IFS= read -r favorite; do
146 + favorite="${favorite#\'}"
147 + favorite="${favorite%\'}"
148 + case "$favorite" in
149 + firefox.desktop|firefox_firefox.desktop) ;;
150 + *) FAVORITES+=("$favorite") ;;
151 + esac
152 + done < <(printf '%s\n' "$CURRENT_FAVS" | grep -o "'[^']*'" || true)
153 +
154 + ORDERED_FAVORITES=()
155 + if (( ${#FAVORITES[@]} == 0 )); then
156 + ORDERED_FAVORITES=(firefox.desktop)
157 + else
158 + ORDERED_FAVORITES=("${FAVORITES[0]}" firefox.desktop "${FAVORITES[@]:1}")
159 + fi
160 +
161 + NEW_FAVS="["
162 + separator=""
163 + for favorite in "${ORDERED_FAVORITES[@]}"; do
164 + favorite="${favorite//\\/\\\\}"
165 + favorite="${favorite//\'/\\\'}"
166 + NEW_FAVS+="${separator}'${favorite}'"
167 + separator=", "
168 + done
169 + NEW_FAVS+="]"
170 +
171 + sudo -u "$DESKTOP_USER" XDG_RUNTIME_DIR="$RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$DBUS_ADDRESS" \
172 + gsettings set org.gnome.shell favorite-apps "$NEW_FAVS" || warn "GNOME Dock pin failed (non-fatal)."
173 + else
174 + log "GNOME not detected (or no active desktop D-Bus session); skipping Dock pin."
175 + fi
176 + fi
177 +
121 178 log "Done. Firefox installed from Mozilla APT and will auto-update."

Vernon Wee Hong KOH a révisé ce gist 3 weeks ago. Aller à la révision

3 files changed, 71 insertions, 12 deletions

README.md

@@ -46,7 +46,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro
46 46 | 21 | System & Maintenance | [`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. |
47 47 | 22 | System & Maintenance | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. |
48 48 | 23 | System & Maintenance | [`disable-grub-countdown.sh`](disable-grub-countdown.sh) | sudo | Configures GRUB to show the dual-boot menu with no countdown by setting `GRUB_TIMEOUT_STYLE=menu` and `GRUB_TIMEOUT=-1`, then regenerates the GRUB config. |
49 - | 24 | Backup | [`backup-thunderbird-to-synology.sh`](backup-thunderbird-to-synology.sh) | **user** | Backs up `/home/vernon/.thunderbird/` to a configurable Synology NAS destination using `rsync -a --delete`, mountpoint checks, and `flock`. Not included in menu option `0`; run it explicitly. |
49 + | 24 | Backup | [`install-thunderbird-backup-cron.sh`](install-thunderbird-backup-cron.sh) | **user** | Installs an hourly cron job that fetches and runs the hosted Thunderbird-to-Synology backup script without storing it locally. Not included in menu option `0`; select it explicitly. |
50 50 | 26 | System & Maintenance | Built into [`menu.sh`](menu.sh) | mixed | Updates all software by re-running the idempotent installers for options 1-7 and 9-20. Excludes network-drive, cron, system configuration, backup, and sync tasks. |
51 51
52 52 "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.
@@ -78,7 +78,7 @@ The Thunderbird backup utility reads its default config from this gist:
78 78 https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/thunderbird-backup.conf
79 79 ```
80 80
81 - Run it manually:
81 + Run a backup manually:
82 82
83 83 ```bash
84 84 bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/backup-thunderbird-to-synology.sh)"
@@ -93,19 +93,14 @@ curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a6
93 93
94 94 To change the backup location for every machine using the hosted config, edit `thunderbird-backup.conf` in this gist and update `DESTINATION`; if the backup location is on a different mounted drive or NAS share, update `MOUNTPOINT` too. The script parses only `SOURCE`, `DESTINATION`, `MOUNTPOINT`, `LOG_FILE`, and `LOCK_FILE`; unsupported keys or invalid lines stop the run.
95 95
96 - For hourly backups, install the script locally and add it to the current user's crontab:
97 -
98 - ```bash
99 - mkdir -p /home/vernon/bin
100 - curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/backup-thunderbird-to-synology.sh \
101 - -o /home/vernon/bin/backup-thunderbird-to-synology.sh
102 - chmod 755 /home/vernon/bin/backup-thunderbird-to-synology.sh
103 - ```
96 + Menu option `24` installs this hourly user cron job idempotently and removes legacy entries that invoke a locally stored backup script:
104 97
105 98 ```cron
106 - 0 * * * * /home/vernon/bin/backup-thunderbird-to-synology.sh
99 + 0 * * * * /usr/bin/bash -o pipefail -c '/usr/bin/curl --proto "=https" --tlsv1.2 -fsSL --max-time 60 "https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/backup-thunderbird-to-synology.sh" | /usr/bin/bash' >> "$HOME/.local/state/thunderbird-cron.log" 2>&1 # thunderbird-synology-backup
107 100 ```
108 101
102 + The current hosted script is downloaded on every run and executes locally because the Thunderbird profile and Synology mount are local. Changes pushed to the hosted script therefore take effect on the next hourly run.
103 +
109 104 ## What "hardened" means here
110 105
111 106 The hardened installers generally share this robustness baseline:

install-thunderbird-backup-cron.sh(fichier créé)

@@ -0,0 +1,64 @@
1 + #!/usr/bin/env bash
2 + set -euo pipefail
3 +
4 + readonly SCRIPT_NAME="${0##*/}"
5 + readonly BACKUP_URL='https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/backup-thunderbird-to-synology.sh'
6 + readonly CRON_MARKER='# thunderbird-synology-backup'
7 + readonly CRON_ENTRY="0 * * * * /usr/bin/bash -o pipefail -c '/usr/bin/curl --proto \"=https\" --tlsv1.2 -fsSL --max-time 60 \"$BACKUP_URL\" | /usr/bin/bash' >> \"\$HOME/.local/state/thunderbird-cron.log\" 2>&1 $CRON_MARKER"
8 + DRY_RUN=0
9 +
10 + usage() {
11 + cat <<EOF
12 + Usage: $SCRIPT_NAME [--dry-run] [--help]
13 +
14 + Installs an hourly user cron job that fetches and executes the hosted
15 + Thunderbird-to-Synology backup script without storing the script locally.
16 +
17 + Options:
18 + --dry-run Print the cron entry without installing it.
19 + --help, -h Show this help.
20 + EOF
21 + }
22 +
23 + die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
24 +
25 + while (( $# )); do
26 + case "$1" in
27 + --dry-run) DRY_RUN=1 ;;
28 + -h|--help) usage; exit 0 ;;
29 + *) die "Unknown argument: $1 (try --help)" ;;
30 + esac
31 + shift
32 + done
33 +
34 + (( EUID != 0 )) || die "Do not run as root. Run as your normal user."
35 +
36 + for tool in /usr/bin/bash /usr/bin/curl crontab grep; do
37 + if [[ "$tool" == /* ]]; then
38 + [[ -x "$tool" ]] || die "Missing required tool: $tool"
39 + else
40 + command -v "$tool" >/dev/null 2>&1 || die "Missing required tool: $tool"
41 + fi
42 + done
43 +
44 + if (( DRY_RUN )); then
45 + printf '%s\n' "$CRON_ENTRY"
46 + exit 0
47 + fi
48 +
49 + mkdir -p "$HOME/.local/state"
50 +
51 + current_crontab="$(crontab -l 2>/dev/null || true)"
52 + filtered_crontab="$(printf '%s\n' "$current_crontab" | grep -Fv -e "$CRON_MARKER" -e 'backup-thunderbird-to-synology.sh' || true)"
53 +
54 + if [[ -n "$filtered_crontab" ]]; then
55 + printf '%s\n%s\n' "$filtered_crontab" "$CRON_ENTRY" | crontab -
56 + else
57 + printf '%s\n' "$CRON_ENTRY" | crontab -
58 + fi
59 +
60 + installed_count="$(crontab -l | grep -Fxc "$CRON_ENTRY" || true)"
61 + [[ "$installed_count" == 1 ]] || die "Unable to verify the hourly cron entry."
62 +
63 + printf 'Installed hourly Thunderbird backup cron job.\n'
64 + printf '%s\n' "$CRON_ENTRY"

menu.sh

@@ -129,7 +129,7 @@ OPTIONS=(
129 129 "21|System & Maintenance|Install Screenshots Cleanup Cron|install-screenshot-cleanup-cron.sh|user|yes"
130 130 "22|System & Maintenance|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo|yes"
131 131 "23|System & Maintenance|Disable GRUB Countdown Timer|disable-grub-countdown.sh|sudo|yes"
132 - "24|Backup|Run Thunderbird Synology Backup|backup-thunderbird-to-synology.sh|user|no"
132 + "24|Backup|Install Hourly Thunderbird Synology Backup|install-thunderbird-backup-cron.sh|user|no"
133 133 "26|System & Maintenance|Update All Software|update-all|group|no"
134 134 )
135 135

Vernon Wee Hong KOH a révisé ce gist 3 weeks ago. Aller à la révision

1 file changed, 1 insertion, 1 deletion

README.md

@@ -59,7 +59,7 @@ If you'd rather skip the menu, each script can be run on its own. Use the right
59 59 # sudo scripts (1-15, 17-18, 22-23) — pipe through sudo bash
60 60 curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash
61 61
62 - # user scripts (16, 19-21, 24-25) — DO NOT use sudo; they install or run per-user
62 + # user scripts (16, 19-21, 24) — DO NOT use sudo; they install or run per-user
63 63 bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)"
64 64 ```
65 65

Vernon Wee Hong KOH a révisé ce gist 3 weeks ago. Aller à la révision

3 files changed, 1 insertion, 133 deletions

README.md

@@ -17,7 +17,7 @@ How to use the picker:
17 17 - `26` updates all software by re-running installers 1-7 and 9-20
18 18 - `-1` exits
19 19
20 - The menu caches `sudo` credentials up-front so multi-task runs don't keep re-prompting, and falls back to per-user execution for scripts that must not run as root (JetBrains Toolbox, IBus Pinyin, Nerd Fonts, screenshots cleanup, Thunderbird backup, CCUsage sync).
20 + The menu caches `sudo` credentials up-front so multi-task runs don't keep re-prompting, and falls back to per-user execution for scripts that must not run as root (JetBrains Toolbox, IBus Pinyin, Nerd Fonts, screenshots cleanup, and Thunderbird backup).
21 21
22 22 ## Available scripts
23 23
@@ -47,7 +47,6 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro
47 47 | 22 | System & Maintenance | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. |
48 48 | 23 | System & Maintenance | [`disable-grub-countdown.sh`](disable-grub-countdown.sh) | sudo | Configures GRUB to show the dual-boot menu with no countdown by setting `GRUB_TIMEOUT_STYLE=menu` and `GRUB_TIMEOUT=-1`, then regenerates the GRUB config. |
49 49 | 24 | Backup | [`backup-thunderbird-to-synology.sh`](backup-thunderbird-to-synology.sh) | **user** | Backs up `/home/vernon/.thunderbird/` to a configurable Synology NAS destination using `rsync -a --delete`, mountpoint checks, and `flock`. Not included in menu option `0`; run it explicitly. |
50 - | 25 | Backup | [`install-ccusage-sync.sh`](install-ccusage-sync.sh) | **user** | Installs and verifies five-minute multi-device usage synchronization for Claude Code, Codex CLI, and OpenCode through a preconfigured Hetzner Storage Box. Not included in menu option `0`; select it explicitly. |
51 50 | 26 | System & Maintenance | Built into [`menu.sh`](menu.sh) | mixed | Updates all software by re-running the idempotent installers for options 1-7 and 9-20. Excludes network-drive, cron, system configuration, backup, and sync tasks. |
52 51
53 52 "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.
@@ -71,31 +70,6 @@ curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a6
71 70 | bash -s -- --help
72 71 ```
73 72
74 - ## CCUsage Multi-Device Sync
75 -
76 - Menu option `25` downloads and validates the canonical public installer from:
77 -
78 - ```text
79 - https://opengist.resetrix.work/weehong/ai-coding-agent
80 - ```
81 -
82 - It installs dependencies, deploys `~/.local/bin/sync-ccusage`, runs an initial push and pull, and installs a five-minute user cron job. The sync includes Claude Code, Codex CLI, and OpenCode data. The complete cloud tree is pulled into `~/.ccusage-synced-logs/`.
83 -
84 - Before selecting option `25`, configure passwordless SSH for the alias `hetzner-ccusage-storage-box`. The installer does not create, copy, or replace SSH private keys. If SSH or the initial rsync fails, it does not install the cron entry.
85 -
86 - Run option `25` directly without the menu:
87 -
88 - ```bash
89 - bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-ccusage-sync.sh)"
90 - ```
91 -
92 - Validate the wrapper without installing:
93 -
94 - ```bash
95 - curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-ccusage-sync.sh \
96 - | bash -s -- --dry-run
97 - ```
98 -
99 73 ## Thunderbird to Synology Backup
100 74
101 75 The Thunderbird backup utility reads its default config from this gist:

install-ccusage-sync.sh (fichier supprimé)

@@ -1,105 +0,0 @@
1 - #!/usr/bin/env bash
2 - # install-ccusage-sync.sh - Install multi-device AI coding usage synchronization.
3 -
4 - set -euo pipefail
5 - IFS=$'\n\t'
6 -
7 - readonly SCRIPT_NAME="${0##*/}"
8 - readonly INSTALLER_URL_DEFAULT='https://opengist.resetrix.work/weehong/ai-coding-agent/raw/HEAD/sync-ccusage.sh'
9 - readonly INSTALL_PATH="$HOME/.local/bin/sync-ccusage"
10 - readonly CRON_ENTRY='*/5 * * * * "$HOME/.local/bin/sync-ccusage" >/dev/null 2>&1'
11 - INSTALLER_URL="${CCUSAGE_INSTALLER_URL:-$INSTALLER_URL_DEFAULT}"
12 - DRY_RUN=0
13 -
14 - usage() {
15 - cat <<EOF
16 - Usage: $SCRIPT_NAME [--dry-run] [--help]
17 -
18 - Downloads and validates the public CCUsage sync installer, then installs:
19 -
20 - $INSTALL_PATH
21 -
22 - The installed script synchronizes Claude Code, Codex CLI, and OpenCode data
23 - with a preconfigured Hetzner Storage Box every five minutes.
24 -
25 - Prerequisite:
26 - The SSH alias hetzner-ccusage-storage-box and its authorized identity key
27 - must already be configured for this user.
28 -
29 - Options:
30 - --dry-run Download and validate the installer without executing it.
31 - --help, -h Show this help.
32 -
33 - Environment:
34 - CCUSAGE_INSTALLER_URL Override the canonical installer URL.
35 - EOF
36 - }
37 -
38 - log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; }
39 - die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; }
40 -
41 - trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR
42 -
43 - while (( $# )); do
44 - case "$1" in
45 - --dry-run) DRY_RUN=1 ;;
46 - -h|--help) usage; exit 0 ;;
47 - *) die "Unknown argument: $1 (try --help)" ;;
48 - esac
49 - shift
50 - done
51 -
52 - (( EUID != 0 )) || die "Do not run as root. Run as your normal user."
53 -
54 - for tool in bash curl grep head mktemp; do
55 - command -v "$tool" >/dev/null 2>&1 || die "Missing required tool: $tool"
56 - done
57 -
58 - if [[ -r /etc/os-release ]]; then
59 - # shellcheck disable=SC1091
60 - . /etc/os-release
61 - case "${ID:-}:${ID_LIKE:-}" in
62 - *ubuntu*|*debian*) : ;;
63 - *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;;
64 - esac
65 - else
66 - die "/etc/os-release not found."
67 - fi
68 -
69 - STAGE="$(mktemp -d -t ccusage-sync.XXXXXX)"
70 - trap 'rm -rf "$STAGE"' EXIT
71 - DOWNLOADED_INSTALLER="$STAGE/sync-ccusage.sh"
72 -
73 - log "Downloading canonical installer..."
74 - curl --proto '=https' --tlsv1.2 -fsSL --max-time 60 --retry 2 \
75 - "$INSTALLER_URL" -o "$DOWNLOADED_INSTALLER"
76 -
77 - [[ -s "$DOWNLOADED_INSTALLER" ]] || die "Downloaded installer is empty."
78 - head -n1 "$DOWNLOADED_INSTALLER" | grep -qE '^#!/usr/bin/env bash$' \
79 - || die "Downloaded installer has an unexpected shebang."
80 - bash -n "$DOWNLOADED_INSTALLER" \
81 - || die "Downloaded installer failed Bash syntax validation."
82 - grep -qF 'hetzner-ccusage-storage-box' "$DOWNLOADED_INSTALLER" \
83 - || die "Downloaded installer does not contain the expected SSH target."
84 -
85 - if (( DRY_RUN )); then
86 - log "Validated installer: $INSTALLER_URL"
87 - log "Would run the installer as the current user."
88 - log "Would install: $INSTALL_PATH"
89 - log "Would install cron: $CRON_ENTRY"
90 - exit 0
91 - fi
92 -
93 - log "Running the CCUsage installer..."
94 - bash "$DOWNLOADED_INSTALLER" install
95 -
96 - [[ -x "$INSTALL_PATH" ]] \
97 - || die "Installed script is missing or not executable: $INSTALL_PATH"
98 - bash -n "$INSTALL_PATH" \
99 - || die "Installed script failed Bash syntax validation."
100 - crontab -l 2>/dev/null | grep -Fqx "$CRON_ENTRY" \
101 - || die "The five-minute CCUsage cron entry was not installed."
102 -
103 - log "CCUsage sync installation verified."
104 - log "Synced logs are available under: $HOME/.ccusage-synced-logs"
105 - log "Cron schedule: $CRON_ENTRY"

menu.sh

@@ -130,7 +130,6 @@ OPTIONS=(
130 130 "22|System & Maintenance|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo|yes"
131 131 "23|System & Maintenance|Disable GRUB Countdown Timer|disable-grub-countdown.sh|sudo|yes"
132 132 "24|Backup|Run Thunderbird Synology Backup|backup-thunderbird-to-synology.sh|user|no"
133 - "25|Backup|Install CCUsage Multi-Device Sync|install-ccusage-sync.sh|user|no"
134 133 "26|System & Maintenance|Update All Software|update-all|group|no"
135 134 )
136 135

Vernon Wee Hong KOH a révisé ce gist 3 weeks ago. Aller à la révision

2 files changed, 8 insertions, 7 deletions

README.md

@@ -79,7 +79,7 @@ Menu option `25` downloads and validates the canonical public installer from:
79 79 https://opengist.resetrix.work/weehong/ai-coding-agent
80 80 ```
81 81
82 - It installs dependencies, deploys `~/sync-ccusage.sh`, runs an initial push and pull, and installs a five-minute user cron job. The sync includes Claude Code, Codex CLI, and OpenCode data. The complete cloud tree is pulled into `~/.ccusage-synced-logs/`.
82 + It installs dependencies, deploys `~/.local/bin/sync-ccusage`, runs an initial push and pull, and installs a five-minute user cron job. The sync includes Claude Code, Codex CLI, and OpenCode data. The complete cloud tree is pulled into `~/.ccusage-synced-logs/`.
83 83
84 84 Before selecting option `25`, configure passwordless SSH for the alias `hetzner-ccusage-storage-box`. The installer does not create, copy, or replace SSH private keys. If SSH or the initial rsync fails, it does not install the cron entry.
85 85

install-ccusage-sync.sh

@@ -6,7 +6,8 @@ IFS=$'\n\t'
6 6
7 7 readonly SCRIPT_NAME="${0##*/}"
8 8 readonly INSTALLER_URL_DEFAULT='https://opengist.resetrix.work/weehong/ai-coding-agent/raw/HEAD/sync-ccusage.sh'
9 - readonly CRON_ENTRY='*/5 * * * * /bin/bash $HOME/sync-ccusage.sh >/dev/null 2>&1'
9 + readonly INSTALL_PATH="$HOME/.local/bin/sync-ccusage"
10 + readonly CRON_ENTRY='*/5 * * * * "$HOME/.local/bin/sync-ccusage" >/dev/null 2>&1'
10 11 INSTALLER_URL="${CCUSAGE_INSTALLER_URL:-$INSTALLER_URL_DEFAULT}"
11 12 DRY_RUN=0
12 13
@@ -16,7 +17,7 @@ Usage: $SCRIPT_NAME [--dry-run] [--help]
16 17
17 18 Downloads and validates the public CCUsage sync installer, then installs:
18 19
19 - $HOME/sync-ccusage.sh
20 + $INSTALL_PATH
20 21
21 22 The installed script synchronizes Claude Code, Codex CLI, and OpenCode data
22 23 with a preconfigured Hetzner Storage Box every five minutes.
@@ -84,7 +85,7 @@ grep -qF 'hetzner-ccusage-storage-box' "$DOWNLOADED_INSTALLER" \
84 85 if (( DRY_RUN )); then
85 86 log "Validated installer: $INSTALLER_URL"
86 87 log "Would run the installer as the current user."
87 - log "Would install: $HOME/sync-ccusage.sh"
88 + log "Would install: $INSTALL_PATH"
88 89 log "Would install cron: $CRON_ENTRY"
89 90 exit 0
90 91 fi
@@ -92,9 +93,9 @@ fi
92 93 log "Running the CCUsage installer..."
93 94 bash "$DOWNLOADED_INSTALLER" install
94 95
95 - [[ -x "$HOME/sync-ccusage.sh" ]] \
96 - || die "Installed script is missing or not executable: $HOME/sync-ccusage.sh"
97 - bash -n "$HOME/sync-ccusage.sh" \
96 + [[ -x "$INSTALL_PATH" ]] \
97 + || die "Installed script is missing or not executable: $INSTALL_PATH"
98 + bash -n "$INSTALL_PATH" \
98 99 || die "Installed script failed Bash syntax validation."
99 100 crontab -l 2>/dev/null | grep -Fqx "$CRON_ENTRY" \
100 101 || die "The five-minute CCUsage cron entry was not installed."

weehong a révisé ce gist 3 weeks ago. Aller à la révision

2 files changed, 27 insertions, 2 deletions

README.md

@@ -14,6 +14,7 @@ How to use the picker:
14 14
15 15 - Enter one or more numbers separated by spaces, e.g. `1 6 11`
16 16 - `0` runs setup options 1-23 in order
17 + - `26` updates all software by re-running installers 1-7 and 9-20
17 18 - `-1` exits
18 19
19 20 The menu caches `sudo` credentials up-front so multi-task runs don't keep re-prompting, and falls back to per-user execution for scripts that must not run as root (JetBrains Toolbox, IBus Pinyin, Nerd Fonts, screenshots cleanup, Thunderbird backup, CCUsage sync).
@@ -47,6 +48,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro
47 48 | 23 | System & Maintenance | [`disable-grub-countdown.sh`](disable-grub-countdown.sh) | sudo | Configures GRUB to show the dual-boot menu with no countdown by setting `GRUB_TIMEOUT_STYLE=menu` and `GRUB_TIMEOUT=-1`, then regenerates the GRUB config. |
48 49 | 24 | Backup | [`backup-thunderbird-to-synology.sh`](backup-thunderbird-to-synology.sh) | **user** | Backs up `/home/vernon/.thunderbird/` to a configurable Synology NAS destination using `rsync -a --delete`, mountpoint checks, and `flock`. Not included in menu option `0`; run it explicitly. |
49 50 | 25 | Backup | [`install-ccusage-sync.sh`](install-ccusage-sync.sh) | **user** | Installs and verifies five-minute multi-device usage synchronization for Claude Code, Codex CLI, and OpenCode through a preconfigured Hetzner Storage Box. Not included in menu option `0`; select it explicitly. |
51 + | 26 | System & Maintenance | Built into [`menu.sh`](menu.sh) | mixed | Updates all software by re-running the idempotent installers for options 1-7 and 9-20. Excludes network-drive, cron, system configuration, backup, and sync tasks. |
50 52
51 53 "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.
52 54

menu.sh

@@ -131,8 +131,13 @@ OPTIONS=(
131 131 "23|System & Maintenance|Disable GRUB Countdown Timer|disable-grub-countdown.sh|sudo|yes"
132 132 "24|Backup|Run Thunderbird Synology Backup|backup-thunderbird-to-synology.sh|user|no"
133 133 "25|Backup|Install CCUsage Multi-Device Sync|install-ccusage-sync.sh|user|no"
134 + "26|System & Maintenance|Update All Software|update-all|group|no"
134 135 )
135 136
137 + # Software installers that are safe to re-run to fetch current packages and
138 + # releases. Configuration, maintenance, backup, and sync tasks are excluded.
139 + SOFTWARE_NUMS=(1 2 3 4 5 6 7 9 10 11 12 13 14 15 16 17 18 19 20)
140 +
136 141 print_menu() {
137 142 local entry num category label filename mode include_in_all
138 143 local last_category="" note display_label
@@ -149,10 +154,12 @@ print_menu() {
149 154 fi
150 155
151 156 note=""
152 - if [[ "$mode" == "user" ]]; then
157 + if [[ "$mode" == "group" ]]; then
158 + note="runs software options 1-7 and 9-20"
159 + elif [[ "$mode" == "user" ]]; then
153 160 note="runs as you, not root"
154 161 fi
155 - if [[ "$include_in_all" != "yes" ]]; then
162 + if [[ "$include_in_all" != "yes" && "$mode" != "group" ]]; then
156 163 [[ -n "$note" ]] && note+="; "
157 164 note+="not in ALL"
158 165 fi
@@ -233,6 +240,22 @@ while true; do
233 240
234 241 failures=()
235 242 for c in "${selected_choices[@]}"; do
243 + if [[ "$c" == "26" ]]; then
244 + hr
245 + log "[26] Updating all software..."
246 + for software_choice in "${SOFTWARE_NUMS[@]}"; do
247 + spec="$(resolve_choice "$software_choice")"
248 + name="${spec%|*}"
249 + mode="${spec##*|}"
250 + hr
251 + log "[$software_choice] Running $name ($mode)..."
252 + if ! run_script "$name" "$mode"; then
253 + failures+=("$software_choice:$name")
254 + fi
255 + done
256 + continue
257 + fi
258 +
236 259 spec="$(resolve_choice "$c")"
237 260 name="${spec%|*}"
238 261 mode="${spec##*|}"

weehong a révisé ce gist 3 weeks ago. Aller à la révision

3 files changed, 133 insertions, 2 deletions

README.md

@@ -16,7 +16,7 @@ How to use the picker:
16 16 - `0` runs setup options 1-23 in order
17 17 - `-1` exits
18 18
19 - The menu caches `sudo` credentials up-front so multi-task runs don't keep re-prompting, and falls back to per-user execution for scripts that must not run as root (JetBrains Toolbox, IBus Pinyin, Nerd Fonts, screenshots cleanup, Thunderbird backup).
19 + The menu caches `sudo` credentials up-front so multi-task runs don't keep re-prompting, and falls back to per-user execution for scripts that must not run as root (JetBrains Toolbox, IBus Pinyin, Nerd Fonts, screenshots cleanup, Thunderbird backup, CCUsage sync).
20 20
21 21 ## Available scripts
22 22
@@ -46,6 +46,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro
46 46 | 22 | System & Maintenance | [`timedatectl-fix.sh`](timedatectl-fix.sh) | sudo | Sets the hardware clock to UTC to avoid time drift when dual-booting Linux and Windows. |
47 47 | 23 | System & Maintenance | [`disable-grub-countdown.sh`](disable-grub-countdown.sh) | sudo | Configures GRUB to show the dual-boot menu with no countdown by setting `GRUB_TIMEOUT_STYLE=menu` and `GRUB_TIMEOUT=-1`, then regenerates the GRUB config. |
48 48 | 24 | Backup | [`backup-thunderbird-to-synology.sh`](backup-thunderbird-to-synology.sh) | **user** | Backs up `/home/vernon/.thunderbird/` to a configurable Synology NAS destination using `rsync -a --delete`, mountpoint checks, and `flock`. Not included in menu option `0`; run it explicitly. |
49 + | 25 | Backup | [`install-ccusage-sync.sh`](install-ccusage-sync.sh) | **user** | Installs and verifies five-minute multi-device usage synchronization for Claude Code, Codex CLI, and OpenCode through a preconfigured Hetzner Storage Box. Not included in menu option `0`; select it explicitly. |
49 50
50 51 "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.
51 52
@@ -57,7 +58,7 @@ If you'd rather skip the menu, each script can be run on its own. Use the right
57 58 # sudo scripts (1-15, 17-18, 22-23) — pipe through sudo bash
58 59 curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash
59 60
60 - # user scripts (16, 19-21, 24) — DO NOT use sudo; they install or run per-user
61 + # user scripts (16, 19-21, 24-25) — DO NOT use sudo; they install or run per-user
61 62 bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)"
62 63 ```
63 64
@@ -68,6 +69,31 @@ curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a6
68 69 | bash -s -- --help
69 70 ```
70 71
72 + ## CCUsage Multi-Device Sync
73 +
74 + Menu option `25` downloads and validates the canonical public installer from:
75 +
76 + ```text
77 + https://opengist.resetrix.work/weehong/ai-coding-agent
78 + ```
79 +
80 + It installs dependencies, deploys `~/sync-ccusage.sh`, runs an initial push and pull, and installs a five-minute user cron job. The sync includes Claude Code, Codex CLI, and OpenCode data. The complete cloud tree is pulled into `~/.ccusage-synced-logs/`.
81 +
82 + Before selecting option `25`, configure passwordless SSH for the alias `hetzner-ccusage-storage-box`. The installer does not create, copy, or replace SSH private keys. If SSH or the initial rsync fails, it does not install the cron entry.
83 +
84 + Run option `25` directly without the menu:
85 +
86 + ```bash
87 + bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-ccusage-sync.sh)"
88 + ```
89 +
90 + Validate the wrapper without installing:
91 +
92 + ```bash
93 + curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-ccusage-sync.sh \
94 + | bash -s -- --dry-run
95 + ```
96 +
71 97 ## Thunderbird to Synology Backup
72 98
73 99 The Thunderbird backup utility reads its default config from this gist:

install-ccusage-sync.sh(fichier créé)

@@ -0,0 +1,104 @@
1 + #!/usr/bin/env bash
2 + # install-ccusage-sync.sh - Install multi-device AI coding usage synchronization.
3 +
4 + set -euo pipefail
5 + IFS=$'\n\t'
6 +
7 + readonly SCRIPT_NAME="${0##*/}"
8 + readonly INSTALLER_URL_DEFAULT='https://opengist.resetrix.work/weehong/ai-coding-agent/raw/HEAD/sync-ccusage.sh'
9 + readonly CRON_ENTRY='*/5 * * * * /bin/bash $HOME/sync-ccusage.sh >/dev/null 2>&1'
10 + INSTALLER_URL="${CCUSAGE_INSTALLER_URL:-$INSTALLER_URL_DEFAULT}"
11 + DRY_RUN=0
12 +
13 + usage() {
14 + cat <<EOF
15 + Usage: $SCRIPT_NAME [--dry-run] [--help]
16 +
17 + Downloads and validates the public CCUsage sync installer, then installs:
18 +
19 + $HOME/sync-ccusage.sh
20 +
21 + The installed script synchronizes Claude Code, Codex CLI, and OpenCode data
22 + with a preconfigured Hetzner Storage Box every five minutes.
23 +
24 + Prerequisite:
25 + The SSH alias hetzner-ccusage-storage-box and its authorized identity key
26 + must already be configured for this user.
27 +
28 + Options:
29 + --dry-run Download and validate the installer without executing it.
30 + --help, -h Show this help.
31 +
32 + Environment:
33 + CCUSAGE_INSTALLER_URL Override the canonical installer URL.
34 + EOF
35 + }
36 +
37 + log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; }
38 + die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; }
39 +
40 + trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR
41 +
42 + while (( $# )); do
43 + case "$1" in
44 + --dry-run) DRY_RUN=1 ;;
45 + -h|--help) usage; exit 0 ;;
46 + *) die "Unknown argument: $1 (try --help)" ;;
47 + esac
48 + shift
49 + done
50 +
51 + (( EUID != 0 )) || die "Do not run as root. Run as your normal user."
52 +
53 + for tool in bash curl grep head mktemp; do
54 + command -v "$tool" >/dev/null 2>&1 || die "Missing required tool: $tool"
55 + done
56 +
57 + if [[ -r /etc/os-release ]]; then
58 + # shellcheck disable=SC1091
59 + . /etc/os-release
60 + case "${ID:-}:${ID_LIKE:-}" in
61 + *ubuntu*|*debian*) : ;;
62 + *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;;
63 + esac
64 + else
65 + die "/etc/os-release not found."
66 + fi
67 +
68 + STAGE="$(mktemp -d -t ccusage-sync.XXXXXX)"
69 + trap 'rm -rf "$STAGE"' EXIT
70 + DOWNLOADED_INSTALLER="$STAGE/sync-ccusage.sh"
71 +
72 + log "Downloading canonical installer..."
73 + curl --proto '=https' --tlsv1.2 -fsSL --max-time 60 --retry 2 \
74 + "$INSTALLER_URL" -o "$DOWNLOADED_INSTALLER"
75 +
76 + [[ -s "$DOWNLOADED_INSTALLER" ]] || die "Downloaded installer is empty."
77 + head -n1 "$DOWNLOADED_INSTALLER" | grep -qE '^#!/usr/bin/env bash$' \
78 + || die "Downloaded installer has an unexpected shebang."
79 + bash -n "$DOWNLOADED_INSTALLER" \
80 + || die "Downloaded installer failed Bash syntax validation."
81 + grep -qF 'hetzner-ccusage-storage-box' "$DOWNLOADED_INSTALLER" \
82 + || die "Downloaded installer does not contain the expected SSH target."
83 +
84 + if (( DRY_RUN )); then
85 + log "Validated installer: $INSTALLER_URL"
86 + log "Would run the installer as the current user."
87 + log "Would install: $HOME/sync-ccusage.sh"
88 + log "Would install cron: $CRON_ENTRY"
89 + exit 0
90 + fi
91 +
92 + log "Running the CCUsage installer..."
93 + bash "$DOWNLOADED_INSTALLER" install
94 +
95 + [[ -x "$HOME/sync-ccusage.sh" ]] \
96 + || die "Installed script is missing or not executable: $HOME/sync-ccusage.sh"
97 + bash -n "$HOME/sync-ccusage.sh" \
98 + || die "Installed script failed Bash syntax validation."
99 + crontab -l 2>/dev/null | grep -Fqx "$CRON_ENTRY" \
100 + || die "The five-minute CCUsage cron entry was not installed."
101 +
102 + log "CCUsage sync installation verified."
103 + log "Synced logs are available under: $HOME/.ccusage-synced-logs"
104 + log "Cron schedule: $CRON_ENTRY"

menu.sh

@@ -130,6 +130,7 @@ OPTIONS=(
130 130 "22|System & Maintenance|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo|yes"
131 131 "23|System & Maintenance|Disable GRUB Countdown Timer|disable-grub-countdown.sh|sudo|yes"
132 132 "24|Backup|Run Thunderbird Synology Backup|backup-thunderbird-to-synology.sh|user|no"
133 + "25|Backup|Install CCUsage Multi-Device Sync|install-ccusage-sync.sh|user|no"
133 134 )
134 135
135 136 print_menu() {

Vernon Wee Hong KOH a révisé ce gist 1 month ago. Aller à la révision

1 file changed, 4 insertions, 2 deletions

install-ibus-pinyin.sh

@@ -133,8 +133,10 @@ if (( SKIP_GSETTINGS )); then
133 133 exit 0
134 134 fi
135 135
136 - # Only manage gsettings if GNOME schemas are present.
137 - if ! query_as_user gsettings list-schemas 2>/dev/null | grep -q '^org.gnome.desktop.input-sources$'; then
136 + # Query the key directly. Avoid a `list-schemas | grep -q` pipeline here:
137 + # with `pipefail`, grep's early exit can make gsettings' SIGPIPE look like a
138 + # missing schema and silently skip input-source registration.
139 + if ! query_as_user gsettings get org.gnome.desktop.input-sources sources >/dev/null 2>&1; then
138 140 warn "GNOME schema org.gnome.desktop.input-sources not found; skipping gsettings update."
139 141 log "Done. Add 'Chinese (Intelligent Pinyin)' manually in your DE's input settings."
140 142 exit 0

Vernon Wee Hong KOH a révisé ce gist 1 month ago. Aller à la révision

1 file changed, 55 insertions, 77 deletions

install-ibus-pinyin.sh

@@ -1,5 +1,5 @@
1 1 #!/usr/bin/env bash
2 - # install-ibus-rime.sh — Install IBus + Rime and add it
2 + # install-ibus-pinyin.sh — Install IBus Intelligent Pinyin and add it
3 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 #
@@ -17,9 +17,8 @@ usage() {
17 17 cat <<EOF
18 18 Usage: $SCRIPT_NAME [--dry-run] [--skip-gsettings] [--help]
19 19
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.
20 + Installs ibus-libpinyin (plus Chinese language packs), refreshes IBus, and
21 + registers 'Intelligent Pinyin' as a GNOME input source. Idempotent.
23 22
24 23 Do NOT run with sudo: gsettings is per-user and must run as the desktop user.
25 24 The script invokes sudo internally only for apt-get steps.
@@ -37,7 +36,6 @@ EOF
37 36 log() { printf '\033[1;34m[%s]\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*"; }
38 37 warn() { printf '\033[1;33m[%s] WARN:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; }
39 38 die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; }
40 - run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; }
41 39 trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR
42 40
43 41 while (( $# )); do
@@ -59,10 +57,9 @@ else
59 57 DESKTOP_USER="$USER"
60 58 fi
61 59
62 - # Look up the user's UID and Home Directory for configs and DBus.
60 + # Look up the user's UID for DBus access when invoked through sudo.
63 61 USER_ENTRY="$(getent passwd "$DESKTOP_USER")" || die "User '$DESKTOP_USER' not found in passwd."
64 62 USER_ID="$(awk -F: '{print $3}' <<<"$USER_ENTRY")"
65 - USER_HOME="$(awk -F: '{print $6}' <<<"$USER_ENTRY")"
66 63
67 64 [[ -r /etc/os-release ]] || die "/etc/os-release not found."
68 65 # shellcheck disable=SC1091
@@ -73,27 +70,34 @@ case "${ID:-}:${ID_LIKE:-}" in
73 70 esac
74 71 log "Detected: ${PRETTY_NAME:-unknown}, desktop user: $DESKTOP_USER"
75 72
76 - # Helper: invoke a command as $DESKTOP_USER with a working DBus address.
77 - run_as_user() {
78 - local cmd=("$@")
79 - if (( DRY_RUN )); then
80 - printf ' DRY-RUN (as %s): %s\n' "$DESKTOP_USER" "${cmd[*]}"
81 - return 0
82 - fi
73 + # Helpers: invoke a command as $DESKTOP_USER with a working DBus address.
74 + query_as_user() {
83 75 if (( EUID == 0 )); then
84 76 sudo -u "$DESKTOP_USER" \
85 77 DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus" \
86 78 XDG_RUNTIME_DIR="/run/user/$USER_ID" \
87 - "${cmd[@]}"
79 + "$@"
80 + else
81 + "$@"
82 + fi
83 + }
84 +
85 + run_as_user() {
86 + if (( DRY_RUN )); then
87 + printf ' DRY-RUN (as %s):' "$DESKTOP_USER"
88 + printf ' %q' "$@"
89 + printf '\n'
88 90 else
89 - "${cmd[@]}"
91 + query_as_user "$@"
90 92 fi
91 93 }
92 94
93 95 # Helper: invoke a command with sudo when the caller is non-root.
94 96 sudo_run() {
95 97 if (( DRY_RUN )); then
96 - printf ' DRY-RUN (sudo): %s\n' "$*"
98 + printf ' DRY-RUN (sudo):'
99 + printf ' %q' "$@"
100 + printf '\n'
97 101 return 0
98 102 fi
99 103 if (( EUID == 0 )); then "$@"; else sudo "$@"; fi
@@ -101,91 +105,65 @@ sudo_run() {
101 105
102 106 export DEBIAN_FRONTEND=noninteractive
103 107
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..."
108 + log "Installing IBus Intelligent Pinyin + Chinese language packs..."
108 109 sudo_run apt-get update -qq
109 110 sudo_run apt-get install -y \
110 111 ibus \
111 - ibus-rime \
112 + ibus-libpinyin \
112 113 language-pack-zh-hans \
113 114 language-pack-gnome-zh-hans
114 115
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.
138 - if command -v ibus >/dev/null 2>&1; then
139 - log "Restarting ibus-daemon for $DESKTOP_USER..."
140 - # `ibus exit` will fail if no daemon is running — treat as non-fatal.
141 - run_as_user ibus exit >/dev/null 2>&1 || true
142 - # Start fresh in the background; -drx replaces a running daemon.
143 - if (( ! DRY_RUN )); then
144 - sudo -u "$DESKTOP_USER" \
145 - DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus" \
146 - XDG_RUNTIME_DIR="/run/user/$USER_ID" \
147 - sh -c 'nohup ibus-daemon -drx >/dev/null 2>&1 &' || \
148 - warn "ibus-daemon restart returned non-zero (non-fatal)."
149 - sleep 1
150 - fi
116 + # Make the running daemon rebuild its engine registry. GNOME owns IBus through
117 + # a systemd user service, so starting a competing daemon manually breaks the
118 + # portal on Wayland. Other desktops can use the regular IBus restart command.
119 + if command -v systemctl >/dev/null 2>&1 && \
120 + query_as_user systemctl --user is-active --quiet org.freedesktop.IBus.session.GNOME.service; then
121 + log "Restarting GNOME's system-managed IBus service for $DESKTOP_USER..."
122 + run_as_user systemctl --user restart org.freedesktop.IBus.session.GNOME.service || \
123 + warn "Could not restart GNOME's IBus service; log out and back in after installation."
124 + elif command -v ibus >/dev/null 2>&1; then
125 + log "Refreshing ibus-daemon for $DESKTOP_USER..."
126 + run_as_user ibus restart || \
127 + warn "Could not restart ibus-daemon; log out and back in after installation."
151 128 fi
152 129
153 130 if (( SKIP_GSETTINGS )); then
154 131 log "Skipping GNOME input-sources update (--skip-gsettings)."
155 - log "Done. Add 'Chinese (Rime)' manually under Settings → Keyboard → Input Sources."
132 + log "Done. Add 'Chinese (Intelligent Pinyin)' manually under Settings → Keyboard → Input Sources."
156 133 exit 0
157 134 fi
158 135
159 136 # Only manage gsettings if GNOME schemas are present.
160 - if ! run_as_user gsettings list-schemas 2>/dev/null | grep -q '^org.gnome.desktop.input-sources$'; then
137 + if ! query_as_user gsettings list-schemas 2>/dev/null | grep -q '^org.gnome.desktop.input-sources$'; then
161 138 warn "GNOME schema org.gnome.desktop.input-sources not found; skipping gsettings update."
162 - log "Done. Add 'Chinese (Rime)' manually in your DE's input settings."
139 + log "Done. Add 'Chinese (Intelligent Pinyin)' manually in your DE's input settings."
163 140 exit 0
164 141 fi
165 142
166 - log "Adding 'Rime' to GNOME Input Sources (idempotent)..."
143 + log "Adding 'Intelligent Pinyin' to GNOME Input Sources (idempotent)..."
167 144
168 - CURRENT_SOURCES="$(run_as_user gsettings get org.gnome.desktop.input-sources sources 2>/dev/null || echo '[]')"
169 - # Strip the optional "@as " type annotation gvariant sometimes prepends.
170 - CLEAN_SOURCES="${CURRENT_SOURCES#@as }"
145 + CURRENT_SOURCES="$(query_as_user gsettings get org.gnome.desktop.input-sources sources 2>/dev/null || echo '[]')"
146 + # Strip the optional GVariant type annotation used for an empty tuple array.
147 + CLEAN_SOURCES="${CURRENT_SOURCES#@a(ss) }"
148 + CLEAN_SOURCES="${CLEAN_SOURCES#@as }"
171 149
172 - if [[ "$CLEAN_SOURCES" == *"'ibus', 'rime'"* ]]; then
173 - log "Rime already present in input sources — nothing to do."
150 + if [[ "$CLEAN_SOURCES" == *"'ibus', 'libpinyin'"* ]]; then
151 + log "Intelligent Pinyin already present in input sources — nothing to do."
174 152 else
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')]"
153 + if [[ "$CLEAN_SOURCES" == *"'ibus', 'rime'"* ]]; then
154 + # Migrate users of the briefly shipped Rime-based version of this script.
155 + NEW_SOURCES="${CLEAN_SOURCES//\'rime\'/\'libpinyin\'}"
156 + elif [[ -z "$CLEAN_SOURCES" || "$CLEAN_SOURCES" == "[]" ]]; then
157 + NEW_SOURCES="[('xkb', 'us'), ('ibus', 'libpinyin')]"
180 158 else
181 - # Insert the rime tuple before the closing bracket of the existing list.
182 - NEW_SOURCES="${CLEAN_SOURCES%]*}, ('ibus', 'rime')]"
159 + # Insert the libpinyin tuple before the closing bracket of the existing list.
160 + NEW_SOURCES="${CLEAN_SOURCES%]*}, ('ibus', 'libpinyin')]"
183 161 fi
184 162 run_as_user gsettings set org.gnome.desktop.input-sources sources "$NEW_SOURCES"
185 - log "Input sources updated to include: ('ibus', 'rime')"
163 + log "Input sources updated to include: ('ibus', 'libpinyin')"
186 164 fi
187 165
188 166 log "---"
189 - log "Done! Rime is installed and configured for Simplified Pinyin."
167 + log "Done! Intelligent Pinyin is installed and configured."
190 168 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."
169 + log "If 'Chinese (Intelligent Pinyin)' doesn't appear in the top bar right away, log out and back in."
Plus récent Plus ancien