Last active
July 12, 2026 21:29
-
-
Save monun/b8b81fe3e8c05cc87633867d3ff875f3 to your computer and use it in GitHub Desktop.
docker-gc.service
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -Eeuo pipefail | |
| UNIT_DIR="/etc/systemd/system" | |
| SERVICE_UNIT="docker-gc.service" | |
| TIMER_UNIT="docker-gc.timer" | |
| TMP_DIR="" | |
| cleanup() { | |
| if [[ -n "${TMP_DIR:-}" && -d "$TMP_DIR" ]]; then | |
| rm -r -- "$TMP_DIR" | |
| fi | |
| } | |
| trap cleanup EXIT | |
| usage() { | |
| printf '%s\n' \ | |
| 'Usage:' \ | |
| ' sudo ./svc.sh install' \ | |
| ' sudo ./svc.sh uninstall' \ | |
| ' sudo ./svc.sh status' \ | |
| '' \ | |
| 'Remote usage:' \ | |
| ' curl -fsSL https://gist.githubusercontent.com/monun/b8b81fe3e8c05cc87633867d3ff875f3/raw/svc.sh | sudo bash -s install' \ | |
| ' curl -fsSL https://gist.githubusercontent.com/monun/b8b81fe3e8c05cc87633867d3ff875f3/raw/svc.sh | sudo bash -s uninstall' \ | |
| ' curl -fsSL https://gist.githubusercontent.com/monun/b8b81fe3e8c05cc87633867d3ff875f3/raw/svc.sh | sudo bash -s status' | |
| } | |
| die() { | |
| echo "error: $*" >&2 | |
| exit 1 | |
| } | |
| require_root() { | |
| if (( EUID != 0 )); then | |
| die "this command must be run with sudo" | |
| fi | |
| } | |
| require_command() { | |
| local command="$1" | |
| if ! command -v "$command" >/dev/null 2>&1; then | |
| die "required command not found: $command" | |
| fi | |
| } | |
| require_systemd() { | |
| require_command systemctl | |
| if [[ ! -d /run/systemd/system ]]; then | |
| die "systemd is not running" | |
| fi | |
| } | |
| open_terminal() { | |
| # Read prompts from the terminal even when invoked through curl | sudo bash. | |
| if ! { exec 3<>/dev/tty; } 2>/dev/null; then | |
| die "unable to open an interactive terminal" | |
| fi | |
| } | |
| prompt() { | |
| local message="$1" | |
| local default="$2" | |
| printf '%s [%s]: ' "$message" "$default" >&3 | |
| IFS= read -r REPLY <&3 || REPLY="" | |
| if [[ -z "$REPLY" ]]; then | |
| REPLY="$default" | |
| fi | |
| } | |
| prompt_integer() { | |
| local message="$1" | |
| local default="$2" | |
| local minimum="$3" | |
| local maximum="$4" | |
| local value | |
| while true; do | |
| prompt "$message" "$default" | |
| if [[ "$REPLY" =~ ^[0-9]{1,9}$ ]]; then | |
| value=$((10#$REPLY)) | |
| if (( value >= minimum && value <= maximum )); then | |
| REPLY="$value" | |
| return | |
| fi | |
| fi | |
| printf \ | |
| 'Invalid value. Enter a whole number from %d to %d.\n' \ | |
| "$minimum" \ | |
| "$maximum" >&3 | |
| done | |
| } | |
| prompt_time() { | |
| local message="$1" | |
| local default="$2" | |
| while true; do | |
| prompt "$message" "$default" | |
| if [[ "$REPLY" =~ ^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$ ]]; then | |
| return | |
| fi | |
| printf \ | |
| 'Invalid time. Enter a 24-hour time in HH:MM:SS format, for example 01:00:00.\n' >&3 | |
| done | |
| } | |
| install_service() { | |
| require_root | |
| require_systemd | |
| require_command docker | |
| require_command install | |
| require_command systemd-analyze | |
| require_command mktemp | |
| require_command rm | |
| if [[ ! -x /usr/bin/docker ]]; then | |
| die "/usr/bin/docker not found" | |
| fi | |
| if ! /usr/bin/docker buildx version >/dev/null 2>&1; then | |
| die "Docker Buildx is not available" | |
| fi | |
| open_terminal | |
| echo | |
| echo "Docker GC configuration" | |
| echo | |
| prompt_integer "Minimum free disk space to maintain (GB)" "40" 1 999999 | |
| local min_free_gb="$REPLY" | |
| prompt_integer "Minimum build cache space to preserve (GB)" "10" 1 999999 | |
| local reserved_gb="$REPLY" | |
| prompt_integer "Minimum resource age before pruning (hours)" "168" 1 999999 | |
| local prune_hours="$REPLY" | |
| prompt_time "Daily cleanup time (HH:MM:SS)" "03:30:00" | |
| local cleanup_time="$REPLY" | |
| prompt_integer "Randomized execution delay (minutes)" "30" 0 1440 | |
| local randomized_delay_minutes="$REPLY" | |
| local min_free_space="${min_free_gb}gb" | |
| local reserved_space="${reserved_gb}gb" | |
| local prune_until="${prune_hours}h" | |
| local randomized_delay="${randomized_delay_minutes}m" | |
| local on_calendar="*-*-* ${cleanup_time}" | |
| echo | |
| echo "Configuration summary" | |
| echo " Minimum free space: $min_free_gb GB" | |
| echo " Reserved cache: $reserved_gb GB" | |
| echo " Prune resources: older than $prune_hours hours" | |
| echo " Cleanup time: $cleanup_time" | |
| echo " Randomized delay: up to $randomized_delay_minutes minutes" | |
| echo | |
| echo "Generated systemd values" | |
| echo " OnCalendar=$on_calendar" | |
| echo " RandomizedDelaySec=$randomized_delay" | |
| echo | |
| prompt "Install with this configuration? (Y/n)" "Y" | |
| case "$REPLY" in | |
| Y|y|YES|Yes|yes) | |
| ;; | |
| *) | |
| echo "Installation cancelled." | |
| return | |
| ;; | |
| esac | |
| TMP_DIR="$(mktemp -d)" | |
| { | |
| printf '%s\n' \ | |
| '[Unit]' \ | |
| 'Description=Prune Docker data on GitHub Actions runner' \ | |
| 'After=docker.service' \ | |
| 'Requires=docker.service' \ | |
| '' \ | |
| '[Service]' \ | |
| 'Type=oneshot' | |
| printf \ | |
| 'ExecStart=/usr/bin/docker buildx prune -af --min-free-space %s --reserved-space %s\n' \ | |
| "$min_free_space" \ | |
| "$reserved_space" | |
| printf \ | |
| 'ExecStart=/usr/bin/docker system prune -af --filter until=%s\n' \ | |
| "$prune_until" | |
| } >"${TMP_DIR}/${SERVICE_UNIT}" | |
| { | |
| printf '%s\n' \ | |
| '[Unit]' \ | |
| 'Description=Daily Docker cleanup' \ | |
| '' \ | |
| '[Timer]' | |
| printf 'OnCalendar=%s\n' "$on_calendar" | |
| printf 'RandomizedDelaySec=%s\n' "$randomized_delay" | |
| printf '%s\n' \ | |
| 'Persistent=true' \ | |
| '' \ | |
| '[Install]' \ | |
| 'WantedBy=timers.target' | |
| } >"${TMP_DIR}/${TIMER_UNIT}" | |
| echo | |
| echo "Validating systemd units..." | |
| systemd-analyze verify \ | |
| "${TMP_DIR}/${SERVICE_UNIT}" \ | |
| "${TMP_DIR}/${TIMER_UNIT}" | |
| echo "Installing systemd units..." | |
| install -o root -g root -m 0644 \ | |
| "${TMP_DIR}/${SERVICE_UNIT}" \ | |
| "${UNIT_DIR}/${SERVICE_UNIT}" | |
| install -o root -g root -m 0644 \ | |
| "${TMP_DIR}/${TIMER_UNIT}" \ | |
| "${UNIT_DIR}/${TIMER_UNIT}" | |
| systemctl daemon-reload | |
| systemctl enable "$TIMER_UNIT" >/dev/null | |
| systemctl restart "$TIMER_UNIT" | |
| if ! systemctl is-active --quiet "$TIMER_UNIT"; then | |
| echo "error: $TIMER_UNIT failed to start" >&2 | |
| systemctl status "$TIMER_UNIT" --no-pager || true | |
| exit 1 | |
| fi | |
| echo | |
| echo "Installation completed successfully." | |
| echo | |
| systemctl list-timers --all "$TIMER_UNIT" | |
| } | |
| uninstall_service() { | |
| require_root | |
| require_systemd | |
| require_command rm | |
| if [[ ! -e "${UNIT_DIR}/${SERVICE_UNIT}" && ! -e "${UNIT_DIR}/${TIMER_UNIT}" ]]; then | |
| echo "Docker GC is not installed." | |
| return | |
| fi | |
| systemctl disable --now "$TIMER_UNIT" >/dev/null 2>&1 || true | |
| systemctl stop "$SERVICE_UNIT" >/dev/null 2>&1 || true | |
| rm -f \ | |
| "${UNIT_DIR}/${SERVICE_UNIT}" \ | |
| "${UNIT_DIR}/${TIMER_UNIT}" | |
| systemctl daemon-reload | |
| systemctl reset-failed "$SERVICE_UNIT" "$TIMER_UNIT" >/dev/null 2>&1 || true | |
| echo "Docker GC uninstalled successfully." | |
| } | |
| show_status() { | |
| require_systemd | |
| if [[ ! -e "${UNIT_DIR}/${SERVICE_UNIT}" && ! -e "${UNIT_DIR}/${TIMER_UNIT}" ]]; then | |
| echo "Docker GC is not installed." | |
| return | |
| fi | |
| local enabled | |
| local active | |
| enabled="$(systemctl is-enabled "$TIMER_UNIT" 2>/dev/null || true)" | |
| active="$(systemctl is-active "$TIMER_UNIT" 2>/dev/null || true)" | |
| echo "Docker GC status" | |
| echo " Installed: yes" | |
| echo " Enabled: ${enabled:-unknown}" | |
| echo " Active: ${active:-unknown}" | |
| echo | |
| systemctl list-timers --all "$TIMER_UNIT" || true | |
| echo | |
| systemctl status "$TIMER_UNIT" "$SERVICE_UNIT" --no-pager || true | |
| } | |
| if (( $# != 1 )); then | |
| usage >&2 | |
| exit 2 | |
| fi | |
| case "$1" in | |
| install) | |
| install_service | |
| ;; | |
| uninstall) | |
| uninstall_service | |
| ;; | |
| status) | |
| show_status | |
| ;; | |
| -h|--help|help) | |
| usage | |
| ;; | |
| *) | |
| echo "error: unknown command: $1" >&2 | |
| echo >&2 | |
| usage >&2 | |
| exit 2 | |
| ;; | |
| esac |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Download and run
Run directly
Install:
curl -fsSL https://gist.githubusercontent.com/monun/b8b81fe3e8c05cc87633867d3ff875f3/raw/svc.sh | sudo bash -s installStatus:
curl -fsSL https://gist.githubusercontent.com/monun/b8b81fe3e8c05cc87633867d3ff875f3/raw/svc.sh | sudo bash -s statusUninstall:
curl -fsSL https://gist.githubusercontent.com/monun/b8b81fe3e8c05cc87633867d3ff875f3/raw/svc.sh | sudo bash -s uninstall