Vernon Wee Hong KOH ha revisionato questo gist 1 month ago. Vai alla revisione
2 files changed, 85 insertions, 87 deletions
README.md
| @@ -20,32 +20,32 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 20 | 20 | ||
| 21 | 21 | ## Available scripts | |
| 22 | 22 | ||
| 23 | - | | # | Script | Runs as | What it does | | |
| 24 | - | |---|---|---|---| | |
| 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-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 | - | | 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. | | |
| 46 | - | | 22 | [`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. | | |
| 47 | - | | 23 | [`install-marktext.sh`](install-marktext.sh) | sudo | Installs the latest official MarkText amd64 `.deb` from `marktext/marktext`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 48 | - | | 24 | [`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. | | |
| 23 | + | | # | Category | Script | Runs as | What it does | | |
| 24 | + | |---|---|---|---|---| | |
| 25 | + | | 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. | | |
| 26 | + | | 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 | + | | 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. | | |
| 28 | + | | 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. | | |
| 29 | + | | 5 | Communication | [`install-discord.sh`](install-discord.sh) | sudo | Installs the latest official Discord Linux `.deb` from Discord's latest download endpoint. | | |
| 30 | + | | 6 | Files, Downloads & Storage | [`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. | | |
| 31 | + | | 7 | Files, Downloads & Storage | [`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. | | |
| 32 | + | | 8 | Files, Downloads & Storage | [`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. | | |
| 33 | + | | 9 | Productivity & Security | [`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 | Productivity & Security | [`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 | Productivity & Security | [`install-marktext.sh`](install-marktext.sh) | sudo | Installs the latest official MarkText amd64 `.deb` from `marktext/marktext`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 36 | + | | 12 | Productivity & Security | [`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. | | |
| 37 | + | | 13 | Productivity & Security | [`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). | | |
| 38 | + | | 14 | Productivity & Security | [`install-1password.sh`](install-1password.sh) | sudo | Configures the 1Password APT repo with `debsig` signature policy. Arch-aware (amd64 / arm64). | | |
| 39 | + | | 15 | Development Tools | [`install-vscode.sh`](install-vscode.sh) | sudo | Microsoft's official `code` APT repo, signed-by keyring. `--insiders` flag installs `code-insiders` instead. | | |
| 40 | + | | 16 | Development Tools | [`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. | | |
| 41 | + | | 17 | Development Tools | [`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. | | |
| 42 | + | | 18 | Development Tools | [`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. | | |
| 43 | + | | 19 | Desktop & Localization | [`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`. | | |
| 44 | + | | 20 | Desktop & Localization | [`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. | | |
| 45 | + | | 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. | | |
| 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 | + | | 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 | + | | 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 | 49 | ||
| 50 | 50 | "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 | 51 | ||
| @@ -54,10 +54,10 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 54 | 54 | 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: | |
| 55 | 55 | ||
| 56 | 56 | ```bash | |
| 57 | - | # sudo scripts (1-12, 14-17, 20, 22-23) — pipe through sudo bash | |
| 57 | + | # sudo scripts (1-15, 17-18, 22-23) — pipe through sudo bash | |
| 58 | 58 | curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 59 | 59 | ||
| 60 | - | # user scripts (13, 18, 19, 21, 24) — DO NOT use sudo; they install or run per-user | |
| 60 | + | # user scripts (16, 19-21, 24) — DO NOT use sudo; they install or run per-user | |
| 61 | 61 | bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)" | |
| 62 | 62 | ``` | |
| 63 | 63 | ||
menu.sh
| @@ -103,68 +103,63 @@ run_script() { | |||
| 103 | 103 | return "$rc" | |
| 104 | 104 | } | |
| 105 | 105 | ||
| 106 | - | # Catalog: number | label | script-filename | mode | |
| 106 | + | # Catalog: number | category | label | script-filename | mode | include-in-all | |
| 107 | 107 | # (Edit here to add/remove options — the menu loop is data-driven.) | |
| 108 | 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 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 | - | "21|Install Screenshots Cleanup Cron (per-user)|install-screenshot-cleanup-cron.sh|user" | |
| 130 | - | "22|Disable GRUB Countdown Timer|disable-grub-countdown.sh|sudo" | |
| 131 | - | "23|Install MarkText (Markdown editor)|install-marktext.sh|sudo" | |
| 132 | - | "24|Run Thunderbird Synology Backup (per-user)|backup-thunderbird-to-synology.sh|user" | |
| 109 | + | "1|Browsers & Mail|Install Google Chrome|install-chrome.sh|sudo|yes" | |
| 110 | + | "2|Browsers & Mail|Install Firefox (Mozilla APT)|install-firefox.sh|sudo|yes" | |
| 111 | + | "3|Browsers & Mail|Install Thunderbird|install-thunderbird.sh|sudo|yes" | |
| 112 | + | "4|Communication|Install Telegram Desktop|install-telegram.sh|sudo|yes" | |
| 113 | + | "5|Communication|Install Discord|install-discord.sh|sudo|yes" | |
| 114 | + | "6|Files, Downloads & Storage|Install LocalSend|install-localsend.sh|sudo|yes" | |
| 115 | + | "7|Files, Downloads & Storage|Install qBittorrent (latest AppImage)|install-qbittorrent.sh|sudo|yes" | |
| 116 | + | "8|Files, Downloads & Storage|Mount Synology Network Drive|install-network_drive.sh|sudo|yes" | |
| 117 | + | "9|Productivity & Security|Install LibreOffice (latest stable .deb)|install-libreoffice.sh|sudo|yes" | |
| 118 | + | "10|Productivity & Security|Install Obsidian|install-obsidian.sh|sudo|yes" | |
| 119 | + | "11|Productivity & Security|Install MarkText (Markdown editor)|install-marktext.sh|sudo|yes" | |
| 120 | + | "12|Productivity & Security|Install draw.io Desktop|install-drawio.sh|sudo|yes" | |
| 121 | + | "13|Productivity & Security|Install Espanso (text expander)|install-espanso.sh|sudo|yes" | |
| 122 | + | "14|Productivity & Security|Install 1Password|install-1password.sh|sudo|yes" | |
| 123 | + | "15|Development Tools|Install Visual Studio Code|install-vscode.sh|sudo|yes" | |
| 124 | + | "16|Development Tools|Install JetBrains Toolbox|install-jetbrains-toolbox.sh|user|yes" | |
| 125 | + | "17|Development Tools|Install Bruno (API client)|install-bruno.sh|sudo|yes" | |
| 126 | + | "18|Development Tools|Install IPATool|install-ipatool.sh|sudo|yes" | |
| 127 | + | "19|Desktop & Localization|Install IBus Intelligent Pinyin|install-ibus-pinyin.sh|user|yes" | |
| 128 | + | "20|Desktop & Localization|Install Nerd Fonts|install-font.sh|user|yes" | |
| 129 | + | "21|System & Maintenance|Install Screenshots Cleanup Cron|install-screenshot-cleanup-cron.sh|user|yes" | |
| 130 | + | "22|System & Maintenance|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo|yes" | |
| 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" | |
| 133 | 133 | ) | |
| 134 | 134 | ||
| 135 | 135 | print_menu() { | |
| 136 | + | local entry num category label filename mode include_in_all | |
| 137 | + | local last_category="" note display_label | |
| 138 | + | ||
| 136 | 139 | hr | |
| 137 | 140 | echo " Ubuntu / Debian Setup Manager" | |
| 138 | 141 | hr | |
| 139 | - | echo "--- [ Browsers & Mail ] ---" | |
| 140 | - | echo " 1) Install Google Chrome" | |
| 141 | - | echo " 2) Install Firefox" | |
| 142 | - | echo " 3) Install Thunderbird" | |
| 143 | - | echo "--- [ Communication ] ---" | |
| 144 | - | echo " 4) Install LocalSend" | |
| 145 | - | echo " 5) Install Telegram Desktop" | |
| 146 | - | echo " 6) Install Discord" | |
| 147 | - | echo "--- [ Productivity & Security ] ---" | |
| 148 | - | echo " 7) Install 1Password" | |
| 149 | - | echo " 8) Install Espanso" | |
| 150 | - | echo " 9) Install LibreOffice" | |
| 151 | - | echo " 10) Install Obsidian" | |
| 152 | - | echo " 11) Install draw.io Desktop" | |
| 153 | - | echo "--- [ Development Tools ] ---" | |
| 154 | - | echo " 12) Install Visual Studio Code" | |
| 155 | - | echo " 13) Install JetBrains Toolbox (runs as you, not root)" | |
| 156 | - | echo " 14) Install Bruno" | |
| 157 | - | echo " 15) Install IPATool" | |
| 158 | - | echo " 16) Install qBittorrent" | |
| 159 | - | echo "--- [ System ] ---" | |
| 160 | - | echo " 17) Mount Synology Network Drive" | |
| 161 | - | echo " 18) Install IBus Intelligent Pinyin (runs as you, not root)" | |
| 162 | - | echo " 19) Install Nerd Fonts (runs as you, not root)" | |
| 163 | - | echo " 20) Fix Dual-Boot Time (RTC to UTC)" | |
| 164 | - | echo " 21) Install Screenshots Cleanup Cron (runs as you, not root)" | |
| 165 | - | echo " 22) Disable GRUB Countdown Timer" | |
| 166 | - | echo " 23) Install MarkText" | |
| 167 | - | echo " 24) Run Thunderbird Synology Backup (runs as you, not root; not in ALL)" | |
| 142 | + | for entry in "${OPTIONS[@]}"; do | |
| 143 | + | IFS='|' read -r num category label filename mode include_in_all <<< "$entry" | |
| 144 | + | ||
| 145 | + | if [[ "$category" != "$last_category" ]]; then | |
| 146 | + | printf '%s\n' "--- [ $category ] ---" | |
| 147 | + | last_category="$category" | |
| 148 | + | fi | |
| 149 | + | ||
| 150 | + | note="" | |
| 151 | + | if [[ "$mode" == "user" ]]; then | |
| 152 | + | note="runs as you, not root" | |
| 153 | + | fi | |
| 154 | + | if [[ "$include_in_all" != "yes" ]]; then | |
| 155 | + | [[ -n "$note" ]] && note+="; " | |
| 156 | + | note+="not in ALL" | |
| 157 | + | fi | |
| 158 | + | ||
| 159 | + | display_label="$label" | |
| 160 | + | [[ -n "$note" ]] && display_label+=" ($note)" | |
| 161 | + | printf '%4s) %s\n' "$num" "$display_label" | |
| 162 | + | done | |
| 168 | 163 | hr | |
| 169 | 164 | echo " 0) Run ALL setup options (1-23)" | |
| 170 | 165 | echo " -1) Exit" | |
| @@ -173,19 +168,22 @@ print_menu() { | |||
| 173 | 168 | ||
| 174 | 169 | # Resolve a numeric choice to its catalog entry; print "name|mode" to stdout. | |
| 175 | 170 | resolve_choice() { | |
| 176 | - | local want="$1" entry num | |
| 171 | + | local want="$1" entry num category label filename mode include_in_all | |
| 177 | 172 | for entry in "${OPTIONS[@]}"; do | |
| 178 | - | num="${entry%%|*}" | |
| 173 | + | IFS='|' read -r num category label filename mode include_in_all <<< "$entry" | |
| 179 | 174 | if [[ "$num" == "$want" ]]; then | |
| 180 | - | # Strip the leading "N|label|"; what remains is "filename|mode" | |
| 181 | - | printf '%s' "${entry#*|*|}" | |
| 175 | + | printf '%s|%s' "$filename" "$mode" | |
| 182 | 176 | return 0 | |
| 183 | 177 | fi | |
| 184 | 178 | done | |
| 185 | 179 | return 1 | |
| 186 | 180 | } | |
| 187 | 181 | ||
| 188 | - | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23) | |
| 182 | + | ALL_NUMS=() | |
| 183 | + | for entry in "${OPTIONS[@]}"; do | |
| 184 | + | IFS='|' read -r num category label filename mode include_in_all <<< "$entry" | |
| 185 | + | [[ "$include_in_all" == "yes" ]] && ALL_NUMS+=("$num") | |
| 186 | + | done | |
| 189 | 187 | ||
| 190 | 188 | prime_sudo || exit 1 | |
| 191 | 189 | ||
Vernon Wee Hong KOH ha revisionato questo gist 1 month ago. Vai alla revisione
3 files changed, 124 insertions, 9 deletions
README.md
| @@ -13,7 +13,7 @@ bash -c "$(curl -fsSL https://tinyurl.com/bdddmxc4)" | |||
| 13 | 13 | How to use the picker: | |
| 14 | 14 | ||
| 15 | 15 | - Enter one or more numbers separated by spaces, e.g. `1 6 11` | |
| 16 | - | - `0` runs setup options 1-22 in order | |
| 16 | + | - `0` runs setup options 1-23 in order | |
| 17 | 17 | - `-1` exits | |
| 18 | 18 | ||
| 19 | 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). | |
| @@ -44,7 +44,8 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 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 | 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. | | |
| 46 | 46 | | 22 | [`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. | | |
| 47 | - | | 23 | [`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. | | |
| 47 | + | | 23 | [`install-marktext.sh`](install-marktext.sh) | sudo | Installs the latest official MarkText amd64 `.deb` from `marktext/marktext`. SHA-256 verified against the GitHub release asset digest when available. Honors `$GITHUB_TOKEN` to avoid API rate limits. | | |
| 48 | + | | 24 | [`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. | | |
| 48 | 49 | ||
| 49 | 50 | "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. | |
| 50 | 51 | ||
| @@ -53,10 +54,10 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 53 | 54 | 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: | |
| 54 | 55 | ||
| 55 | 56 | ```bash | |
| 56 | - | # sudo scripts (1-12, 14-17, 20, 22) — pipe through sudo bash | |
| 57 | + | # sudo scripts (1-12, 14-17, 20, 22-23) — pipe through sudo bash | |
| 57 | 58 | curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 58 | 59 | ||
| 59 | - | # user scripts (13, 18, 19, 21, 23) — DO NOT use sudo; they install or run per-user | |
| 60 | + | # user scripts (13, 18, 19, 21, 24) — DO NOT use sudo; they install or run per-user | |
| 60 | 61 | bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)" | |
| 61 | 62 | ``` | |
| 62 | 63 | ||
| @@ -112,7 +113,7 @@ The hardened installers generally share this robustness baseline: | |||
| 112 | 113 | - `dpkg --print-architecture` / `uname -m` for architecture (amd64 / arm64 / armhf / x86_64 / aarch64 as applicable to each upstream) | |
| 113 | 114 | - Idempotent — re-running a script does not duplicate APT sources, fstab entries, gsettings entries, or `.desktop` files | |
| 114 | 115 | - `signed-by` keyrings in `/etc/apt/keyrings` (no deprecated `apt-key add`) | |
| 115 | - | - 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) | |
| 116 | + | - Checksum verification where the upstream publishes one (JetBrains SHA-256, LibreOffice MD5, Obsidian SHA-256, MarkText SHA-256, ipatool SHA-256, draw.io SHA-256, qBittorrent SHA-256, LocalSend SHA-256) | |
| 116 | 117 | - Per-user installers refuse to run as root; system installers refuse to run as non-root | |
| 117 | 118 | ||
| 118 | 119 | ## Hide Drive from the Dock | |
install-marktext.sh(file creato)
| @@ -0,0 +1,112 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # install-marktext.sh — Install MarkText from the latest official GitHub release. | |
| 3 | + | # Hardened: architecture check, GitHub API token support, SHA-256 verification | |
| 4 | + | # from the 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="marktext/marktext" | |
| 12 | + | ||
| 13 | + | usage() { | |
| 14 | + | cat <<EOF | |
| 15 | + | Usage: sudo $SCRIPT_NAME [--dry-run] [--help] | |
| 16 | + | ||
| 17 | + | Resolves the latest stable release of marktext/marktext, downloads the official | |
| 18 | + | Linux amd64 .deb package, verifies its SHA-256 against the GitHub release asset | |
| 19 | + | digest, 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 "MarkText publishes its 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='^marktext-linux-[^/]+\.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 MarkText release." | |
| 78 | + | ||
| 79 | + | STAGE="$(mktemp -d -t marktext.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 MarkText..." | |
| 105 | + | run "apt-get install -y '$DEB'" | |
| 106 | + | ||
| 107 | + | if command -v marktext >/dev/null 2>&1; then | |
| 108 | + | log "Installed: $(marktext --version 2>/dev/null || echo "$TAG")" | |
| 109 | + | else | |
| 110 | + | log "Installed: $TAG" | |
| 111 | + | fi | |
| 112 | + | log "Done." | |
menu.sh
| @@ -128,7 +128,8 @@ OPTIONS=( | |||
| 128 | 128 | "20|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 129 | 129 | "21|Install Screenshots Cleanup Cron (per-user)|install-screenshot-cleanup-cron.sh|user" | |
| 130 | 130 | "22|Disable GRUB Countdown Timer|disable-grub-countdown.sh|sudo" | |
| 131 | - | "23|Run Thunderbird Synology Backup (per-user)|backup-thunderbird-to-synology.sh|user" | |
| 131 | + | "23|Install MarkText (Markdown editor)|install-marktext.sh|sudo" | |
| 132 | + | "24|Run Thunderbird Synology Backup (per-user)|backup-thunderbird-to-synology.sh|user" | |
| 132 | 133 | ) | |
| 133 | 134 | ||
| 134 | 135 | print_menu() { | |
| @@ -162,9 +163,10 @@ print_menu() { | |||
| 162 | 163 | echo " 20) Fix Dual-Boot Time (RTC to UTC)" | |
| 163 | 164 | echo " 21) Install Screenshots Cleanup Cron (runs as you, not root)" | |
| 164 | 165 | echo " 22) Disable GRUB Countdown Timer" | |
| 165 | - | echo " 23) Run Thunderbird Synology Backup (runs as you, not root; not in ALL)" | |
| 166 | + | echo " 23) Install MarkText" | |
| 167 | + | echo " 24) Run Thunderbird Synology Backup (runs as you, not root; not in ALL)" | |
| 166 | 168 | hr | |
| 167 | - | echo " 0) Run ALL setup options (1-22)" | |
| 169 | + | echo " 0) Run ALL setup options (1-23)" | |
| 168 | 170 | echo " -1) Exit" | |
| 169 | 171 | hr | |
| 170 | 172 | } | |
| @@ -183,7 +185,7 @@ resolve_choice() { | |||
| 183 | 185 | return 1 | |
| 184 | 186 | } | |
| 185 | 187 | ||
| 186 | - | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22) | |
| 188 | + | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23) | |
| 187 | 189 | ||
| 188 | 190 | prime_sudo || exit 1 | |
| 189 | 191 | ||
Vernon Wee Hong KOH ha revisionato questo gist 1 month ago. Vai alla revisione
5 files changed, 322 insertions, 6 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 where applicable (no `apt-key`), and is idempotent (safe to re-run). | |
| 3 | + | A collection of hardened install and maintenance scripts for a fresh Ubuntu / Debian desktop, driven by an interactive `menu.sh`. Each installer 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 | ||
| @@ -13,10 +13,10 @@ bash -c "$(curl -fsSL https://tinyurl.com/bdddmxc4)" | |||
| 13 | 13 | How to use the picker: | |
| 14 | 14 | ||
| 15 | 15 | - Enter one or more numbers separated by spaces, e.g. `1 6 11` | |
| 16 | - | - `0` runs every option in order | |
| 16 | + | - `0` runs setup options 1-22 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 the three scripts that must not run as root (JetBrains Toolbox, IBus Pinyin, Nerd Fonts). | |
| 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). | |
| 20 | 20 | ||
| 21 | 21 | ## Available scripts | |
| 22 | 22 | ||
| @@ -44,6 +44,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 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 | 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. | | |
| 46 | 46 | | 22 | [`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. | | |
| 47 | + | | 23 | [`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. | | |
| 47 | 48 | ||
| 48 | 49 | "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. | |
| 49 | 50 | ||
| @@ -55,17 +56,53 @@ If you'd rather skip the menu, each script can be run on its own. Use the right | |||
| 55 | 56 | # sudo scripts (1-12, 14-17, 20, 22) — pipe through sudo bash | |
| 56 | 57 | curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 57 | 58 | ||
| 58 | - | # user scripts (13, 18, 19, 21) — DO NOT use sudo; they install per-user | |
| 59 | + | # user scripts (13, 18, 19, 21, 23) — DO NOT use sudo; they install or run per-user | |
| 59 | 60 | bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)" | |
| 60 | 61 | ``` | |
| 61 | 62 | ||
| 62 | - | Most installers accept `--help` and `--dry-run` (the latter prints what would happen without executing). For example: | |
| 63 | + | Most installers and utilities accept `--help` and `--dry-run` (the latter prints what would happen without executing). For example: | |
| 63 | 64 | ||
| 64 | 65 | ```bash | |
| 65 | 66 | curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-libreoffice.sh \ | |
| 66 | 67 | | bash -s -- --help | |
| 67 | 68 | ``` | |
| 68 | 69 | ||
| 70 | + | ## Thunderbird to Synology Backup | |
| 71 | + | ||
| 72 | + | The Thunderbird backup utility reads its default config from this gist: | |
| 73 | + | ||
| 74 | + | ```bash | |
| 75 | + | https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/thunderbird-backup.conf | |
| 76 | + | ``` | |
| 77 | + | ||
| 78 | + | Run it manually: | |
| 79 | + | ||
| 80 | + | ```bash | |
| 81 | + | bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/backup-thunderbird-to-synology.sh)" | |
| 82 | + | ``` | |
| 83 | + | ||
| 84 | + | Preview changes without writing files: | |
| 85 | + | ||
| 86 | + | ```bash | |
| 87 | + | curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/backup-thunderbird-to-synology.sh \ | |
| 88 | + | | bash -s -- --dry-run | |
| 89 | + | ``` | |
| 90 | + | ||
| 91 | + | 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. | |
| 92 | + | ||
| 93 | + | For hourly backups, install the script locally and add it to the current user's crontab: | |
| 94 | + | ||
| 95 | + | ```bash | |
| 96 | + | mkdir -p /home/vernon/bin | |
| 97 | + | curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/backup-thunderbird-to-synology.sh \ | |
| 98 | + | -o /home/vernon/bin/backup-thunderbird-to-synology.sh | |
| 99 | + | chmod 755 /home/vernon/bin/backup-thunderbird-to-synology.sh | |
| 100 | + | ``` | |
| 101 | + | ||
| 102 | + | ```cron | |
| 103 | + | 0 * * * * /home/vernon/bin/backup-thunderbird-to-synology.sh | |
| 104 | + | ``` | |
| 105 | + | ||
| 69 | 106 | ## What "hardened" means here | |
| 70 | 107 | ||
| 71 | 108 | The hardened installers generally share this robustness baseline: | |
backup-thunderbird-to-synology.sh(file creato)
| @@ -0,0 +1,258 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | set -euo pipefail | |
| 3 | + | ||
| 4 | + | CONFIG_URL_DEFAULT="https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/thunderbird-backup.conf" | |
| 5 | + | CONFIG_FILE="${THUNDERBIRD_BACKUP_CONFIG:-}" | |
| 6 | + | CONFIG_URL="${THUNDERBIRD_BACKUP_CONFIG_URL:-$CONFIG_URL_DEFAULT}" | |
| 7 | + | SHOW_HELP=false | |
| 8 | + | ||
| 9 | + | args=("$@") | |
| 10 | + | for ((i = 0; i < ${#args[@]}; i++)); do | |
| 11 | + | case "${args[$i]}" in | |
| 12 | + | --config) | |
| 13 | + | CONFIG_FILE="${args[$((i + 1))]:?Missing value for --config}" | |
| 14 | + | CONFIG_URL="" | |
| 15 | + | ;; | |
| 16 | + | --config-url) | |
| 17 | + | CONFIG_URL="${args[$((i + 1))]:?Missing value for --config-url}" | |
| 18 | + | CONFIG_FILE="" | |
| 19 | + | ;; | |
| 20 | + | -h|--help) | |
| 21 | + | SHOW_HELP=true | |
| 22 | + | ;; | |
| 23 | + | esac | |
| 24 | + | done | |
| 25 | + | ||
| 26 | + | SOURCE_DEFAULT="/home/vernon/.thunderbird/" | |
| 27 | + | DESTINATION_DEFAULT="/mnt/Synology/Backup/Applications/Thunderbird/" | |
| 28 | + | MOUNTPOINT_DEFAULT="/mnt/Synology/Backup" | |
| 29 | + | LOG_FILE_DEFAULT="/home/vernon/.local/state/thunderbird-backup.log" | |
| 30 | + | LOCK_FILE_DEFAULT="/home/vernon/.cache/thunderbird-backup.lock" | |
| 31 | + | DRY_RUN=false | |
| 32 | + | ||
| 33 | + | SOURCE="$SOURCE_DEFAULT" | |
| 34 | + | DESTINATION="$DESTINATION_DEFAULT" | |
| 35 | + | MOUNTPOINT="$MOUNTPOINT_DEFAULT" | |
| 36 | + | LOG_FILE="$LOG_FILE_DEFAULT" | |
| 37 | + | LOCK_FILE="$LOCK_FILE_DEFAULT" | |
| 38 | + | ||
| 39 | + | trim() { | |
| 40 | + | local value="$1" | |
| 41 | + | value="${value#"${value%%[![:space:]]*}"}" | |
| 42 | + | value="${value%"${value##*[![:space:]]}"}" | |
| 43 | + | printf '%s' "$value" | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | parse_config_content() { | |
| 47 | + | local line key raw value | |
| 48 | + | ||
| 49 | + | while IFS= read -r line || [[ -n "$line" ]]; do | |
| 50 | + | line="$(trim "$line")" | |
| 51 | + | [[ -z "$line" || "$line" == \#* ]] && continue | |
| 52 | + | ||
| 53 | + | if [[ ! "$line" =~ ^([A-Z_]+)[[:space:]]*=(.*)$ ]]; then | |
| 54 | + | echo "ERROR: Invalid config line: $line" >&2 | |
| 55 | + | exit 2 | |
| 56 | + | fi | |
| 57 | + | ||
| 58 | + | key="${BASH_REMATCH[1]}" | |
| 59 | + | raw="$(trim "${BASH_REMATCH[2]}")" | |
| 60 | + | ||
| 61 | + | if [[ "$raw" == \"*\" && "$raw" == *\" ]]; then | |
| 62 | + | value="${raw:1:${#raw}-2}" | |
| 63 | + | elif [[ "$raw" == \'*\' && "$raw" == *\' ]]; then | |
| 64 | + | value="${raw:1:${#raw}-2}" | |
| 65 | + | else | |
| 66 | + | value="$raw" | |
| 67 | + | fi | |
| 68 | + | ||
| 69 | + | case "$key" in | |
| 70 | + | SOURCE) SOURCE="$value" ;; | |
| 71 | + | DESTINATION) DESTINATION="$value" ;; | |
| 72 | + | MOUNTPOINT) MOUNTPOINT="$value" ;; | |
| 73 | + | LOG_FILE) LOG_FILE="$value" ;; | |
| 74 | + | LOCK_FILE) LOCK_FILE="$value" ;; | |
| 75 | + | *) | |
| 76 | + | echo "ERROR: Unsupported config key: $key" >&2 | |
| 77 | + | exit 2 | |
| 78 | + | ;; | |
| 79 | + | esac | |
| 80 | + | done | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | usage() { | |
| 84 | + | cat <<'USAGE' | |
| 85 | + | Usage: backup-thunderbird-to-synology.sh [options] | |
| 86 | + | ||
| 87 | + | Options: | |
| 88 | + | --source PATH Thunderbird profile directory to back up | |
| 89 | + | --destination PATH Backup destination directory | |
| 90 | + | --mountpoint PATH Required mounted path to check before backup | |
| 91 | + | --no-mount-check Disable the mountpoint safety check | |
| 92 | + | --log-file PATH Log file path | |
| 93 | + | --config PATH Local config file path; disables remote config | |
| 94 | + | --config-url URL Remote config URL | |
| 95 | + | --dry-run Show what would change without writing files | |
| 96 | + | -h, --help Show this help | |
| 97 | + | ||
| 98 | + | Config defaults to: | |
| 99 | + | https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/thunderbird-backup.conf | |
| 100 | + | ||
| 101 | + | Supported config variables: SOURCE, DESTINATION, MOUNTPOINT, LOG_FILE, LOCK_FILE. | |
| 102 | + | Environment overrides: THUNDERBIRD_BACKUP_SOURCE, THUNDERBIRD_BACKUP_DESTINATION, | |
| 103 | + | THUNDERBIRD_BACKUP_MOUNTPOINT, THUNDERBIRD_BACKUP_LOG_FILE, THUNDERBIRD_BACKUP_LOCK_FILE, | |
| 104 | + | THUNDERBIRD_BACKUP_CONFIG, THUNDERBIRD_BACKUP_CONFIG_URL. | |
| 105 | + | USAGE | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | if [[ "$SHOW_HELP" == true ]]; then | |
| 109 | + | usage | |
| 110 | + | exit 0 | |
| 111 | + | fi | |
| 112 | + | ||
| 113 | + | if [[ -n "$CONFIG_FILE" ]]; then | |
| 114 | + | if [[ ! -f "$CONFIG_FILE" ]]; then | |
| 115 | + | mkdir -p "$(dirname "$LOG_FILE")" | |
| 116 | + | echo "$(date -Is) ERROR: Config file not found: $CONFIG_FILE" >> "$LOG_FILE" | |
| 117 | + | exit 1 | |
| 118 | + | fi | |
| 119 | + | parse_config_content < "$CONFIG_FILE" | |
| 120 | + | elif [[ -n "$CONFIG_URL" ]]; then | |
| 121 | + | config_content="$(curl -fsSL --max-time 20 "$CONFIG_URL")" || { | |
| 122 | + | mkdir -p "$(dirname "$LOG_FILE")" | |
| 123 | + | echo "$(date -Is) ERROR: Unable to fetch config from $CONFIG_URL" >> "$LOG_FILE" | |
| 124 | + | exit 1 | |
| 125 | + | } | |
| 126 | + | parse_config_content <<< "$config_content" | |
| 127 | + | fi | |
| 128 | + | ||
| 129 | + | SOURCE="${THUNDERBIRD_BACKUP_SOURCE:-$SOURCE}" | |
| 130 | + | DESTINATION="${THUNDERBIRD_BACKUP_DESTINATION:-$DESTINATION}" | |
| 131 | + | MOUNTPOINT="${THUNDERBIRD_BACKUP_MOUNTPOINT:-$MOUNTPOINT}" | |
| 132 | + | LOG_FILE="${THUNDERBIRD_BACKUP_LOG_FILE:-$LOG_FILE}" | |
| 133 | + | LOCK_FILE="${THUNDERBIRD_BACKUP_LOCK_FILE:-$LOCK_FILE}" | |
| 134 | + | ||
| 135 | + | while [[ $# -gt 0 ]]; do | |
| 136 | + | case "$1" in | |
| 137 | + | --source) | |
| 138 | + | SOURCE="${2:?Missing value for --source}" | |
| 139 | + | shift 2 | |
| 140 | + | ;; | |
| 141 | + | --destination) | |
| 142 | + | DESTINATION="${2:?Missing value for --destination}" | |
| 143 | + | shift 2 | |
| 144 | + | ;; | |
| 145 | + | --mountpoint) | |
| 146 | + | MOUNTPOINT="${2:?Missing value for --mountpoint}" | |
| 147 | + | shift 2 | |
| 148 | + | ;; | |
| 149 | + | --no-mount-check) | |
| 150 | + | MOUNTPOINT="" | |
| 151 | + | shift | |
| 152 | + | ;; | |
| 153 | + | --log-file) | |
| 154 | + | LOG_FILE="${2:?Missing value for --log-file}" | |
| 155 | + | shift 2 | |
| 156 | + | ;; | |
| 157 | + | --dry-run) | |
| 158 | + | DRY_RUN=true | |
| 159 | + | shift | |
| 160 | + | ;; | |
| 161 | + | --config) | |
| 162 | + | shift 2 | |
| 163 | + | ;; | |
| 164 | + | --config-url) | |
| 165 | + | shift 2 | |
| 166 | + | ;; | |
| 167 | + | -h|--help) | |
| 168 | + | usage | |
| 169 | + | exit 0 | |
| 170 | + | ;; | |
| 171 | + | *) | |
| 172 | + | echo "ERROR: Unknown option: $1" >&2 | |
| 173 | + | usage >&2 | |
| 174 | + | exit 2 | |
| 175 | + | ;; | |
| 176 | + | esac | |
| 177 | + | done | |
| 178 | + | ||
| 179 | + | mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$LOCK_FILE")" | |
| 180 | + | ||
| 181 | + | timestamp() { | |
| 182 | + | date -Is | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | normalize_path() { | |
| 186 | + | realpath -m "$1" | |
| 187 | + | } | |
| 188 | + | ||
| 189 | + | SOURCE_REAL="$(normalize_path "$SOURCE")" | |
| 190 | + | DESTINATION_REAL="$(normalize_path "$DESTINATION")" | |
| 191 | + | ||
| 192 | + | if [[ "$SOURCE" != /* || "$DESTINATION" != /* || "$LOG_FILE" != /* || "$LOCK_FILE" != /* ]]; then | |
| 193 | + | echo "$(timestamp) ERROR: SOURCE, DESTINATION, LOG_FILE, and LOCK_FILE must be absolute paths." >> "$LOG_FILE" | |
| 194 | + | exit 1 | |
| 195 | + | fi | |
| 196 | + | ||
| 197 | + | if [[ -n "$MOUNTPOINT" && "$MOUNTPOINT" != /* ]]; then | |
| 198 | + | echo "$(timestamp) ERROR: MOUNTPOINT must be an absolute path." >> "$LOG_FILE" | |
| 199 | + | exit 1 | |
| 200 | + | fi | |
| 201 | + | ||
| 202 | + | if [[ "$SOURCE_REAL" == "$DESTINATION_REAL" ]]; then | |
| 203 | + | echo "$(timestamp) ERROR: SOURCE and DESTINATION cannot be the same path: $SOURCE_REAL" >> "$LOG_FILE" | |
| 204 | + | exit 1 | |
| 205 | + | fi | |
| 206 | + | ||
| 207 | + | if [[ "$DESTINATION_REAL" == "$SOURCE_REAL"/* ]]; then | |
| 208 | + | echo "$(timestamp) ERROR: DESTINATION cannot be inside SOURCE: $DESTINATION_REAL" >> "$LOG_FILE" | |
| 209 | + | exit 1 | |
| 210 | + | fi | |
| 211 | + | ||
| 212 | + | case "$DESTINATION_REAL" in | |
| 213 | + | /|/home|/home/vernon|/mnt|/mnt/Synology|/mnt/Synology/Backup) | |
| 214 | + | echo "$(timestamp) ERROR: Refusing unsafe backup destination: $DESTINATION_REAL" >> "$LOG_FILE" | |
| 215 | + | exit 1 | |
| 216 | + | ;; | |
| 217 | + | esac | |
| 218 | + | ||
| 219 | + | if [[ -n "$MOUNTPOINT" ]] && ! mountpoint -q "$MOUNTPOINT"; then | |
| 220 | + | echo "$(timestamp) ERROR: $MOUNTPOINT is not mounted; backup skipped." >> "$LOG_FILE" | |
| 221 | + | exit 1 | |
| 222 | + | fi | |
| 223 | + | ||
| 224 | + | if [[ ! -d "$SOURCE" ]]; then | |
| 225 | + | echo "$(timestamp) ERROR: Thunderbird profile directory not found: $SOURCE" >> "$LOG_FILE" | |
| 226 | + | exit 1 | |
| 227 | + | fi | |
| 228 | + | ||
| 229 | + | mkdir -p "$DESTINATION" | |
| 230 | + | ||
| 231 | + | exec 9>"$LOCK_FILE" | |
| 232 | + | if ! flock -n 9; then | |
| 233 | + | echo "$(timestamp) INFO: Previous Thunderbird backup still running; backup skipped." >> "$LOG_FILE" | |
| 234 | + | exit 0 | |
| 235 | + | fi | |
| 236 | + | ||
| 237 | + | { | |
| 238 | + | if [[ "$DRY_RUN" == true ]]; then | |
| 239 | + | echo "$(timestamp) INFO: Starting Thunderbird backup dry run: $SOURCE_REAL -> $DESTINATION_REAL" | |
| 240 | + | else | |
| 241 | + | echo "$(timestamp) INFO: Starting Thunderbird backup: $SOURCE_REAL -> $DESTINATION_REAL" | |
| 242 | + | fi | |
| 243 | + | ||
| 244 | + | rsync_args=(-a --delete) | |
| 245 | + | if [[ "$DRY_RUN" == true ]]; then | |
| 246 | + | rsync_args+=(-n --itemize-changes) | |
| 247 | + | fi | |
| 248 | + | ||
| 249 | + | rsync "${rsync_args[@]}" \ | |
| 250 | + | --exclude='*.parentlock' \ | |
| 251 | + | --exclude='lock' \ | |
| 252 | + | "$SOURCE" "$DESTINATION" | |
| 253 | + | if [[ "$DRY_RUN" == true ]]; then | |
| 254 | + | echo "$(timestamp) INFO: Thunderbird backup dry run completed." | |
| 255 | + | else | |
| 256 | + | echo "$(timestamp) INFO: Thunderbird backup completed." | |
| 257 | + | fi | |
| 258 | + | } >> "$LOG_FILE" 2>&1 | |
menu.sh
| @@ -128,6 +128,7 @@ OPTIONS=( | |||
| 128 | 128 | "20|Fix Dual-Boot Time (RTC to UTC)|timedatectl-fix.sh|sudo" | |
| 129 | 129 | "21|Install Screenshots Cleanup Cron (per-user)|install-screenshot-cleanup-cron.sh|user" | |
| 130 | 130 | "22|Disable GRUB Countdown Timer|disable-grub-countdown.sh|sudo" | |
| 131 | + | "23|Run Thunderbird Synology Backup (per-user)|backup-thunderbird-to-synology.sh|user" | |
| 131 | 132 | ) | |
| 132 | 133 | ||
| 133 | 134 | print_menu() { | |
| @@ -161,8 +162,9 @@ print_menu() { | |||
| 161 | 162 | echo " 20) Fix Dual-Boot Time (RTC to UTC)" | |
| 162 | 163 | echo " 21) Install Screenshots Cleanup Cron (runs as you, not root)" | |
| 163 | 164 | echo " 22) Disable GRUB Countdown Timer" | |
| 165 | + | echo " 23) Run Thunderbird Synology Backup (runs as you, not root; not in ALL)" | |
| 164 | 166 | hr | |
| 165 | - | echo " 0) Run ALL options (1-22)" | |
| 167 | + | echo " 0) Run ALL setup options (1-22)" | |
| 166 | 168 | echo " -1) Exit" | |
| 167 | 169 | hr | |
| 168 | 170 | } | |
thunderbird-backup.conf(file creato)
| @@ -0,0 +1,9 @@ | |||
| 1 | + | # Thunderbird backup settings. | |
| 2 | + | # This file is fetched by backup-thunderbird-to-synology.sh from OpenGist. | |
| 3 | + | # All paths should be absolute. | |
| 4 | + | ||
| 5 | + | SOURCE="/home/vernon/.thunderbird/" | |
| 6 | + | DESTINATION="/mnt/Synology/Backup/Applications/Thunderbird/" | |
| 7 | + | MOUNTPOINT="/mnt/Synology/Backup" | |
| 8 | + | LOG_FILE="/home/vernon/.local/state/thunderbird-backup.log" | |
| 9 | + | LOCK_FILE="/home/vernon/.cache/thunderbird-backup.lock" | |
thunderbird-backup.conf.example(file creato)
| @@ -0,0 +1,10 @@ | |||
| 1 | + | # Thunderbird backup settings. | |
| 2 | + | # Use this as a local override with: | |
| 3 | + | # backup-thunderbird-to-synology.sh --config /path/to/thunderbird-backup.conf | |
| 4 | + | # All paths should be absolute. | |
| 5 | + | ||
| 6 | + | SOURCE="/home/vernon/.thunderbird/" | |
| 7 | + | DESTINATION="/mnt/Synology/Backup/Applications/Thunderbird/" | |
| 8 | + | MOUNTPOINT="/mnt/Synology/Backup" | |
| 9 | + | LOG_FILE="/home/vernon/.local/state/thunderbird-backup.log" | |
| 10 | + | LOCK_FILE="/home/vernon/.cache/thunderbird-backup.lock" | |
Vernon Wee Hong KOH ha revisionato questo gist 1 month ago. Vai alla revisione
3 files changed, 99 insertions, 3 deletions
README.md
| @@ -43,6 +43,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 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 | 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. | | |
| 46 | + | | 22 | [`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. | | |
| 46 | 47 | ||
| 47 | 48 | "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. | |
| 48 | 49 | ||
| @@ -51,7 +52,7 @@ The menu caches `sudo` credentials up-front so multi-task runs don't keep re-pro | |||
| 51 | 52 | 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: | |
| 52 | 53 | ||
| 53 | 54 | ```bash | |
| 54 | - | # sudo scripts (1-12, 14-17, 20) — pipe through sudo bash | |
| 55 | + | # sudo scripts (1-12, 14-17, 20, 22) — pipe through sudo bash | |
| 55 | 56 | curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 56 | 57 | ||
| 57 | 58 | # user scripts (13, 18, 19, 21) — DO NOT use sudo; they install per-user | |
disable-grub-countdown.sh(file creato)
| @@ -0,0 +1,93 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # disable-grub-countdown.sh — Show the GRUB dual-boot menu without a countdown. | |
| 3 | + | ||
| 4 | + | set -euo pipefail | |
| 5 | + | IFS=$'\n\t' | |
| 6 | + | ||
| 7 | + | readonly SCRIPT_NAME="${0##*/}" | |
| 8 | + | readonly GRUB_DEFAULTS="/etc/default/grub" | |
| 9 | + | DRY_RUN=0 | |
| 10 | + | ||
| 11 | + | usage() { | |
| 12 | + | cat <<EOF | |
| 13 | + | Usage: $SCRIPT_NAME [--dry-run] [--help] | |
| 14 | + | ||
| 15 | + | Configures GRUB to show the boot menu and wait indefinitely: | |
| 16 | + | ||
| 17 | + | GRUB_TIMEOUT_STYLE=menu | |
| 18 | + | GRUB_TIMEOUT=-1 | |
| 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 | + | ||
| 29 | + | while (( $# )); do | |
| 30 | + | case "$1" in | |
| 31 | + | --dry-run) DRY_RUN=1 ;; | |
| 32 | + | -h|--help) usage; exit 0 ;; | |
| 33 | + | *) die "Unknown argument: $1 (try --help)" ;; | |
| 34 | + | esac | |
| 35 | + | shift | |
| 36 | + | done | |
| 37 | + | ||
| 38 | + | (( EUID == 0 )) || die "Please run as root: sudo bash $SCRIPT_NAME" | |
| 39 | + | [[ -f "$GRUB_DEFAULTS" ]] || die "Missing $GRUB_DEFAULTS. This system does not look like a GRUB-based Ubuntu/Debian install." | |
| 40 | + | ||
| 41 | + | for tool in cp date grep mktemp sed; do | |
| 42 | + | command -v "$tool" >/dev/null 2>&1 || die "Missing required tool: $tool" | |
| 43 | + | done | |
| 44 | + | ||
| 45 | + | if command -v update-grub >/dev/null 2>&1; then | |
| 46 | + | GRUB_UPDATE_CMD=(update-grub) | |
| 47 | + | elif command -v grub-mkconfig >/dev/null 2>&1; then | |
| 48 | + | GRUB_UPDATE_CMD=(grub-mkconfig -o /boot/grub/grub.cfg) | |
| 49 | + | elif command -v grub2-mkconfig >/dev/null 2>&1; then | |
| 50 | + | if [[ -d /boot/grub2 ]]; then | |
| 51 | + | GRUB_UPDATE_CMD=(grub2-mkconfig -o /boot/grub2/grub.cfg) | |
| 52 | + | else | |
| 53 | + | GRUB_UPDATE_CMD=(grub2-mkconfig -o /boot/grub/grub.cfg) | |
| 54 | + | fi | |
| 55 | + | else | |
| 56 | + | die "Could not find update-grub, grub-mkconfig, or grub2-mkconfig." | |
| 57 | + | fi | |
| 58 | + | ||
| 59 | + | BACKUP="${GRUB_DEFAULTS}.backup.$(date +%Y%m%d-%H%M%S)" | |
| 60 | + | STAGE="$(mktemp -t grub-defaults.XXXXXX)" | |
| 61 | + | trap 'rm -f "$STAGE"' EXIT | |
| 62 | + | ||
| 63 | + | cp "$GRUB_DEFAULTS" "$STAGE" | |
| 64 | + | ||
| 65 | + | if grep -q '^GRUB_TIMEOUT_STYLE=' "$STAGE"; then | |
| 66 | + | sed -i 's/^GRUB_TIMEOUT_STYLE=.*/GRUB_TIMEOUT_STYLE=menu/' "$STAGE" | |
| 67 | + | else | |
| 68 | + | printf '\nGRUB_TIMEOUT_STYLE=menu\n' >>"$STAGE" | |
| 69 | + | fi | |
| 70 | + | ||
| 71 | + | if grep -q '^GRUB_TIMEOUT=' "$STAGE"; then | |
| 72 | + | sed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=-1/' "$STAGE" | |
| 73 | + | else | |
| 74 | + | printf 'GRUB_TIMEOUT=-1\n' >>"$STAGE" | |
| 75 | + | fi | |
| 76 | + | ||
| 77 | + | if (( DRY_RUN )); then | |
| 78 | + | log "Would back up $GRUB_DEFAULTS to $BACKUP" | |
| 79 | + | log "Would write these GRUB timeout settings:" | |
| 80 | + | grep -E '^GRUB_TIMEOUT_STYLE=|^GRUB_TIMEOUT=' "$STAGE" | sed 's/^/ /' | |
| 81 | + | log "Would run: ${GRUB_UPDATE_CMD[*]}" | |
| 82 | + | exit 0 | |
| 83 | + | fi | |
| 84 | + | ||
| 85 | + | cp "$GRUB_DEFAULTS" "$BACKUP" | |
| 86 | + | cp "$STAGE" "$GRUB_DEFAULTS" | |
| 87 | + | ||
| 88 | + | log "Updated $GRUB_DEFAULTS" | |
| 89 | + | log "Backup saved to $BACKUP" | |
| 90 | + | log "Regenerating GRUB configuration..." | |
| 91 | + | "${GRUB_UPDATE_CMD[@]}" | |
| 92 | + | ||
| 93 | + | log "Done. On next boot, the GRUB menu will wait until you choose an entry." | |
menu.sh
| @@ -127,6 +127,7 @@ OPTIONS=( | |||
| 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 | 129 | "21|Install Screenshots Cleanup Cron (per-user)|install-screenshot-cleanup-cron.sh|user" | |
| 130 | + | "22|Disable GRUB Countdown Timer|disable-grub-countdown.sh|sudo" | |
| 130 | 131 | ) | |
| 131 | 132 | ||
| 132 | 133 | print_menu() { | |
| @@ -159,8 +160,9 @@ print_menu() { | |||
| 159 | 160 | echo " 19) Install Nerd Fonts (runs as you, not root)" | |
| 160 | 161 | echo " 20) Fix Dual-Boot Time (RTC to UTC)" | |
| 161 | 162 | echo " 21) Install Screenshots Cleanup Cron (runs as you, not root)" | |
| 163 | + | echo " 22) Disable GRUB Countdown Timer" | |
| 162 | 164 | hr | |
| 163 | - | echo " 0) Run ALL options (1-21)" | |
| 165 | + | echo " 0) Run ALL options (1-22)" | |
| 164 | 166 | echo " -1) Exit" | |
| 165 | 167 | hr | |
| 166 | 168 | } | |
| @@ -179,7 +181,7 @@ resolve_choice() { | |||
| 179 | 181 | return 1 | |
| 180 | 182 | } | |
| 181 | 183 | ||
| 182 | - | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21) | |
| 184 | + | ALL_NUMS=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22) | |
| 183 | 185 | ||
| 184 | 186 | prime_sudo || exit 1 | |
| 185 | 187 | ||
Vernon Wee Hong KOH ha revisionato questo gist 1 month ago. Vai alla revisione
1 file changed, 7 insertions, 3 deletions
menu.sh
| @@ -203,15 +203,19 @@ while true; do | |||
| 203 | 203 | break | |
| 204 | 204 | fi | |
| 205 | 205 | ||
| 206 | + | selected_choices=() | |
| 207 | + | ||
| 206 | 208 | # Expand "0" to all options | |
| 207 | 209 | if [[ "$choices" == "0" ]]; then | |
| 208 | - | choices="${ALL_NUMS[*]}" | |
| 210 | + | selected_choices=("${ALL_NUMS[@]}") | |
| 211 | + | else | |
| 212 | + | IFS=$' \t\n' read -r -a selected_choices <<< "$choices" | |
| 209 | 213 | fi | |
| 210 | 214 | ||
| 211 | 215 | # Validate every token first; reject the whole batch if any token is bogus, | |
| 212 | 216 | # so the user sees the problem before any work starts. | |
| 213 | 217 | bad="" | |
| 214 | - | for c in $choices; do | |
| 218 | + | for c in "${selected_choices[@]}"; do | |
| 215 | 219 | if ! [[ "$c" =~ ^-?[0-9]+$ ]] || ! resolve_choice "$c" >/dev/null; then | |
| 216 | 220 | bad+=" $c" | |
| 217 | 221 | fi | |
| @@ -223,7 +227,7 @@ while true; do | |||
| 223 | 227 | fi | |
| 224 | 228 | ||
| 225 | 229 | failures=() | |
| 226 | - | for c in $choices; do | |
| 230 | + | for c in "${selected_choices[@]}"; do | |
| 227 | 231 | spec="$(resolve_choice "$c")" | |
| 228 | 232 | name="${spec%|*}" | |
| 229 | 233 | mode="${spec##*|}" | |
weehong ha revisionato questo gist 1 month ago. Vai alla revisione
1 file changed, 1 insertion, 1 deletion
README.md
| @@ -7,7 +7,7 @@ A collection of hardened install scripts for a fresh Ubuntu / Debian desktop, dr | |||
| 7 | 7 | One-liner — fetches `menu.sh` and runs it. You'll see a numeric picker and can select one option, several, or all: | |
| 8 | 8 | ||
| 9 | 9 | ```bash | |
| 10 | - | bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/menu.sh)" | |
| 10 | + | bash -c "$(curl -fsSL https://tinyurl.com/bdddmxc4)" | |
| 11 | 11 | ``` | |
| 12 | 12 | ||
| 13 | 13 | How to use the picker: | |
weehong ha revisionato questo gist 1 month ago. Vai alla revisione
1 file changed, 33 insertions, 15 deletions
install-espanso.sh
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | #!/usr/bin/env bash | |
| 2 | 2 | # install-espanso.sh — Install Espanso (text expander) with Wayland or X11 build. | |
| 3 | - | # Auto-detects session type; falls back to X11 if Wayland is not active. | |
| 3 | + | # Auto-detects session type; falls back to Wayland if unknown. | |
| 4 | 4 | # Hardened: handles apt temp file permissions, package collisions, and ghost processes. | |
| 5 | 5 | ||
| 6 | 6 | set -euo pipefail | |
| @@ -15,8 +15,8 @@ usage() { | |||
| 15 | 15 | Usage: sudo $SCRIPT_NAME [--dry-run] [--wayland|--x11] [--help] | |
| 16 | 16 | ||
| 17 | 17 | Installs Espanso from the latest GitHub release. Auto-detects whether to use | |
| 18 | - | the Wayland or X11 build based on \$XDG_SESSION_TYPE of the invoking user | |
| 19 | - | (SUDO_USER). The systemd-user service is registered for that user, not root. | |
| 18 | + | the Wayland or X11 build based on the invoking user (SUDO_USER). | |
| 19 | + | The systemd-user service is registered for that user, not root. | |
| 20 | 20 | ||
| 21 | 21 | Options: | |
| 22 | 22 | --wayland Force the Wayland build. | |
| @@ -60,25 +60,44 @@ ACTUAL_USER="${SUDO_USER:-${USER:-}}" | |||
| 60 | 60 | [[ -n "$ACTUAL_USER" && "$ACTUAL_USER" != "root" ]] || die "Run via sudo as a regular user; cannot register the service as root." | |
| 61 | 61 | USER_ID="$(id -u "$ACTUAL_USER")" | |
| 62 | 62 | ||
| 63 | - | # Detect session type from the invoking user's environment, fallback to env, fallback to wayland | |
| 63 | + | # --- FIXED: Better session detection logic using loginctl --- | |
| 64 | 64 | if [[ -n "$FORCE_VARIANT" ]]; then | |
| 65 | 65 | VARIANT="$FORCE_VARIANT" | |
| 66 | 66 | else | |
| 67 | - | SESSION_TYPE="$(sudo -u "$ACTUAL_USER" -i printenv XDG_SESSION_TYPE 2>/dev/null || true)" | |
| 68 | - | [[ -z "$SESSION_TYPE" ]] && SESSION_TYPE="${XDG_SESSION_TYPE:-wayland}" | |
| 67 | + | # 1. Try systemd loginctl (most reliable) | |
| 68 | + | SESSION_ID=$(loginctl list-sessions 2>/dev/null | grep " $ACTUAL_USER " | awk '{print $1}' | head -n 1 || true) | |
| 69 | + | if [[ -n "$SESSION_ID" ]]; then | |
| 70 | + | SESSION_TYPE=$(loginctl show-session "$SESSION_ID" -p Type --value 2>/dev/null || true) | |
| 71 | + | else | |
| 72 | + | SESSION_TYPE="" | |
| 73 | + | fi | |
| 74 | + | ||
| 75 | + | # 2. If loginctl fails, check for active Wayland/X11 socket files | |
| 76 | + | if [[ -z "$SESSION_TYPE" || "$SESSION_TYPE" == "tty" ]]; then | |
| 77 | + | if sudo -u "$ACTUAL_USER" test -S "/run/user/$USER_ID/wayland-0"; then | |
| 78 | + | SESSION_TYPE="wayland" | |
| 79 | + | elif sudo -u "$ACTUAL_USER" test -S "/tmp/.X11-unix/X0"; then | |
| 80 | + | SESSION_TYPE="x11" | |
| 81 | + | else | |
| 82 | + | SESSION_TYPE="wayland" # Default fallback for modern Ubuntu | |
| 83 | + | fi | |
| 84 | + | fi | |
| 85 | + | ||
| 69 | 86 | case "$SESSION_TYPE" in | |
| 70 | 87 | wayland) VARIANT=wayland ;; | |
| 71 | - | x11|tty) VARIANT=x11 ;; | |
| 72 | - | *) warn "Unknown XDG_SESSION_TYPE='$SESSION_TYPE'; defaulting to wayland."; VARIANT=wayland ;; | |
| 88 | + | x11) VARIANT=x11 ;; | |
| 89 | + | *) warn "Unknown session type='$SESSION_TYPE'; defaulting to wayland."; VARIANT=wayland ;; | |
| 73 | 90 | esac | |
| 74 | 91 | fi | |
| 75 | 92 | log "Detected: ${PRETTY_NAME:-unknown}, user: $ACTUAL_USER, session: $VARIANT" | |
| 76 | 93 | ||
| 77 | 94 | export DEBIAN_FRONTEND=noninteractive | |
| 78 | - | TEMP_DEB="$(mktemp -t espanso.XXXXXX.deb)" | |
| 79 | - | # Ensure the _apt user can read the temporary file to prevent permission denied errors | |
| 80 | - | chmod 644 "$TEMP_DEB" | |
| 81 | - | trap 'rm -f "$TEMP_DEB"' EXIT | |
| 95 | + | ||
| 96 | + | # --- FIXED: Use a subdirectory to avoid apt "unsandboxed" permission warnings --- | |
| 97 | + | WORK_DIR="$(mktemp -d /tmp/espanso-install.XXXXXX)" | |
| 98 | + | chmod 755 "$WORK_DIR" | |
| 99 | + | TEMP_DEB="$WORK_DIR/espanso.deb" | |
| 100 | + | trap 'rm -rf "$WORK_DIR"' EXIT | |
| 82 | 101 | ||
| 83 | 102 | DEB_NAME="espanso-debian-${VARIANT}-amd64.deb" | |
| 84 | 103 | URL="https://github.com/espanso/espanso/releases/latest/download/${DEB_NAME}" | |
| @@ -87,11 +106,11 @@ log "Installing prerequisites..." | |||
| 87 | 106 | run "apt-get update -qq" | |
| 88 | 107 | run "apt-get install -y wget libcap2-bin psmisc" | |
| 89 | 108 | ||
| 109 | + | # --- FIXED: Use wget with progress bar instead of silent mode --- | |
| 90 | 110 | log "Downloading ${DEB_NAME}..." | |
| 91 | - | run "wget -qO '$TEMP_DEB' '$URL'" | |
| 111 | + | run "wget -q --show-progress -O '$TEMP_DEB' '$URL'" | |
| 92 | 112 | ||
| 93 | 113 | log "Removing conflicting Espanso packages (if any)..." | |
| 94 | - | # Suppress output and errors if packages don't exist | |
| 95 | 114 | run "apt-get remove -y espanso espanso-wayland >/dev/null 2>&1 || true" | |
| 96 | 115 | ||
| 97 | 116 | log "Installing package..." | |
| @@ -109,7 +128,6 @@ log "Clearing ghost processes to prevent start timeouts..." | |||
| 109 | 128 | run "sudo -u '$ACTUAL_USER' killall espanso 2>/dev/null || true" | |
| 110 | 129 | ||
| 111 | 130 | log "Registering & starting espanso service for $ACTUAL_USER..." | |
| 112 | - | # Register may fail if already registered — treat that as success. | |
| 113 | 131 | if (( DRY_RUN )); then | |
| 114 | 132 | printf ' DRY-RUN: sudo -u %s XDG_RUNTIME_DIR=/run/user/%s espanso service register || true\n' "$ACTUAL_USER" "$USER_ID" | |
| 115 | 133 | printf ' DRY-RUN: sudo -u %s XDG_RUNTIME_DIR=/run/user/%s espanso start || true\n' "$ACTUAL_USER" "$USER_ID" | |
weehong ha revisionato questo gist 1 month ago. Vai alla revisione
1 file changed, 5 insertions
README.md
| @@ -77,6 +77,11 @@ The hardened installers generally share this robustness baseline: | |||
| 77 | 77 | - 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) | |
| 78 | 78 | - Per-user installers refuse to run as root; system installers refuse to run as non-root | |
| 79 | 79 | ||
| 80 | + | ## Hide Drive from the Dock | |
| 81 | + | ```bash | |
| 82 | + | gsettings set org.gnome.shell.extensions.dash-to-dock show-mounts false | |
| 83 | + | ``` | |
| 84 | + | ||
| 80 | 85 | ## Supported distros | |
| 81 | 86 | ||
| 82 | 87 | - Ubuntu (any modern release; some scripts target Ubuntu 25.10 specifically but work elsewhere) | |
weehong ha revisionato questo gist 1 month ago. Vai alla revisione
2 files changed, 7 insertions, 7 deletions
README.md
| @@ -7,7 +7,7 @@ A collection of hardened install scripts for a fresh Ubuntu / Debian desktop, dr | |||
| 7 | 7 | One-liner — fetches `menu.sh` and runs it. You'll see a numeric picker and can select one option, several, or all: | |
| 8 | 8 | ||
| 9 | 9 | ```bash | |
| 10 | - | bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/menu.sh)" | |
| 10 | + | bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/menu.sh)" | |
| 11 | 11 | ``` | |
| 12 | 12 | ||
| 13 | 13 | How to use the picker: | |
| @@ -52,16 +52,16 @@ If you'd rather skip the menu, each script can be run on its own. Use the right | |||
| 52 | 52 | ||
| 53 | 53 | ```bash | |
| 54 | 54 | # sudo scripts (1-12, 14-17, 20) — pipe through sudo bash | |
| 55 | - | curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 55 | + | curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-firefox.sh | sudo bash | |
| 56 | 56 | ||
| 57 | 57 | # user scripts (13, 18, 19, 21) — DO NOT use sudo; they install per-user | |
| 58 | - | bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)" | |
| 58 | + | bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-font.sh)" | |
| 59 | 59 | ``` | |
| 60 | 60 | ||
| 61 | 61 | Most installers accept `--help` and `--dry-run` (the latter prints what would happen without executing). For example: | |
| 62 | 62 | ||
| 63 | 63 | ```bash | |
| 64 | - | curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-libreoffice.sh \ | |
| 64 | + | curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/install-libreoffice.sh \ | |
| 65 | 65 | | bash -s -- --help | |
| 66 | 66 | ``` | |
| 67 | 67 | ||
menu.sh
| @@ -10,10 +10,10 @@ | |||
| 10 | 10 | # (gsettings, ~/.local, JetBrains Toolbox, fonts) | |
| 11 | 11 | # | |
| 12 | 12 | # Usage: | |
| 13 | - | # bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/menu.sh)" | |
| 13 | + | # bash -c "$(curl -fsSL https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/menu.sh)" | |
| 14 | 14 | # | |
| 15 | 15 | # Or save and run locally: | |
| 16 | - | # curl -fsSLO https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/menu.sh | |
| 16 | + | # curl -fsSLO https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD/menu.sh | |
| 17 | 17 | # bash menu.sh | |
| 18 | 18 | ||
| 19 | 19 | # Note: NOT using `set -e` because we want the menu loop to survive a failed | |
| @@ -22,7 +22,7 @@ set -uo pipefail | |||
| 22 | 22 | IFS=$'\n\t' | |
| 23 | 23 | ||
| 24 | 24 | readonly SCRIPT_NAME="${0##*/}" | |
| 25 | - | readonly BASE_URL='https://opengist.rmrf.online/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD' | |
| 25 | + | readonly BASE_URL='https://opengist.resetrix.work/weehong/2de15ba0106a475fa41215159203a63b/raw/HEAD' | |
| 26 | 26 | ||
| 27 | 27 | log() { printf '\033[1;34m[menu]\033[0m %s\n' "$*"; } | |
| 28 | 28 | warn() { printf '\033[1;33m[menu] WARN:\033[0m %s\n' "$*" >&2; } | |
weehong ha revisionato questo gist 2 months ago. Vai alla revisione
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(file creato)
| @@ -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 | ||