~cytrogen/gstack

ref: bb46ca6b217e5732f8c0b9458ebecb4c90c382ad gstack/bin/gstack-update-check -rwxr-xr-x 5.4 KiB
bb46ca6b — Garry Tan feat: smart update check with auto-upgrade, snooze backoff, config CLI (v0.3.9) (#62) a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/env bash
# gstack-update-check — periodic version check for all skills.
#
# Output (one line, or nothing):
#   JUST_UPGRADED <old> <new>       — marker found from recent upgrade
#   UPGRADE_AVAILABLE <old> <new>   — remote VERSION differs from local
#   (nothing)                       — up to date, snoozed, disabled, or check skipped
#
# Env overrides (for testing):
#   GSTACK_DIR          — override auto-detected gstack root
#   GSTACK_REMOTE_URL   — override remote VERSION URL
#   GSTACK_STATE_DIR    — override ~/.gstack state directory
set -euo pipefail

GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}"
CACHE_FILE="$STATE_DIR/last-update-check"
MARKER_FILE="$STATE_DIR/just-upgraded-from"
SNOOZE_FILE="$STATE_DIR/update-snoozed"
VERSION_FILE="$GSTACK_DIR/VERSION"
REMOTE_URL="${GSTACK_REMOTE_URL:-https://raw.githubusercontent.com/garrytan/gstack/main/VERSION}"

# ─── Step 0: Check if updates are disabled ────────────────────
_UC=$("$GSTACK_DIR/bin/gstack-config" get update_check 2>/dev/null || true)
if [ "$_UC" = "false" ]; then
  exit 0
fi

# ─── Snooze helper ──────────────────────────────────────────
# check_snooze <remote_version>
#   Returns 0 if snoozed (should stay quiet), 1 if not snoozed (should output).
#
#   Snooze file format: <version> <level> <epoch>
#   Level durations: 1=24h, 2=48h, 3+=7d
#   New version (version mismatch) resets snooze.
check_snooze() {
  local remote_ver="$1"
  if [ ! -f "$SNOOZE_FILE" ]; then
    return 1  # no snooze file → not snoozed
  fi
  local snoozed_ver snoozed_level snoozed_epoch
  snoozed_ver="$(awk '{print $1}' "$SNOOZE_FILE" 2>/dev/null || true)"
  snoozed_level="$(awk '{print $2}' "$SNOOZE_FILE" 2>/dev/null || true)"
  snoozed_epoch="$(awk '{print $3}' "$SNOOZE_FILE" 2>/dev/null || true)"

  # Validate: all three fields must be non-empty
  if [ -z "$snoozed_ver" ] || [ -z "$snoozed_level" ] || [ -z "$snoozed_epoch" ]; then
    return 1  # corrupt file → not snoozed
  fi

  # Validate: level and epoch must be integers
  case "$snoozed_level" in *[!0-9]*) return 1 ;; esac
  case "$snoozed_epoch" in *[!0-9]*) return 1 ;; esac

  # New version dropped? Ignore snooze.
  if [ "$snoozed_ver" != "$remote_ver" ]; then
    return 1
  fi

  # Compute snooze duration based on level
  local duration
  case "$snoozed_level" in
    1) duration=86400 ;;   # 24 hours
    2) duration=172800 ;;  # 48 hours
    *) duration=604800 ;;  # 7 days (level 3+)
  esac

  local now
  now="$(date +%s)"
  local expires=$(( snoozed_epoch + duration ))
  if [ "$now" -lt "$expires" ]; then
    return 0  # still snoozed
  fi

  return 1  # snooze expired
}

# ─── Step 1: Read local version ──────────────────────────────
LOCAL=""
if [ -f "$VERSION_FILE" ]; then
  LOCAL="$(cat "$VERSION_FILE" 2>/dev/null | tr -d '[:space:]')"
fi
if [ -z "$LOCAL" ]; then
  exit 0  # No VERSION file → skip check
fi

# ─── Step 2: Check "just upgraded" marker ─────────────────────
if [ -f "$MARKER_FILE" ]; then
  OLD="$(cat "$MARKER_FILE" 2>/dev/null | tr -d '[:space:]')"
  rm -f "$MARKER_FILE"
  rm -f "$SNOOZE_FILE"
  mkdir -p "$STATE_DIR"
  echo "UP_TO_DATE $LOCAL" > "$CACHE_FILE"
  if [ -n "$OLD" ]; then
    echo "JUST_UPGRADED $OLD $LOCAL"
  fi
  exit 0
fi

# ─── Step 3: Check cache freshness (12h = 720 min) ──────────
if [ -f "$CACHE_FILE" ]; then
  # Cache is fresh if modified within 720 minutes
  STALE=$(find "$CACHE_FILE" -mmin +720 2>/dev/null || true)
  if [ -z "$STALE" ]; then
    # Cache is fresh — read it
    CACHED="$(cat "$CACHE_FILE" 2>/dev/null || true)"
    case "$CACHED" in
      UP_TO_DATE*)
        # Verify local version still matches cached version
        CACHED_VER="$(echo "$CACHED" | awk '{print $2}')"
        if [ "$CACHED_VER" = "$LOCAL" ]; then
          exit 0
        fi
        # Local version changed — fall through to re-check
        ;;
      UPGRADE_AVAILABLE*)
        # Verify local version still matches cached old version
        CACHED_OLD="$(echo "$CACHED" | awk '{print $2}')"
        if [ "$CACHED_OLD" = "$LOCAL" ]; then
          CACHED_NEW="$(echo "$CACHED" | awk '{print $3}')"
          if check_snooze "$CACHED_NEW"; then
            exit 0  # snoozed — stay quiet
          fi
          echo "$CACHED"
          exit 0
        fi
        # Local version changed (manual upgrade?) — fall through to re-check
        ;;
    esac
  fi
fi

# ─── Step 4: Slow path — fetch remote version ────────────────
mkdir -p "$STATE_DIR"

REMOTE=""
REMOTE="$(curl -sf --max-time 5 "$REMOTE_URL" 2>/dev/null || true)"
REMOTE="$(echo "$REMOTE" | tr -d '[:space:]')"

# Validate: must look like a version number (reject HTML error pages)
if ! echo "$REMOTE" | grep -qE '^[0-9]+\.[0-9.]+$'; then
  # Invalid or empty response — assume up to date
  echo "UP_TO_DATE $LOCAL" > "$CACHE_FILE"
  exit 0
fi

if [ "$LOCAL" = "$REMOTE" ]; then
  echo "UP_TO_DATE $LOCAL" > "$CACHE_FILE"
  exit 0
fi

# Versions differ — upgrade available
echo "UPGRADE_AVAILABLE $LOCAL $REMOTE" > "$CACHE_FILE"
if check_snooze "$REMOTE"; then
  exit 0  # snoozed — stay quiet
fi
echo "UPGRADE_AVAILABLE $LOCAL $REMOTE"