CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
install.sh
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 46d6165 | 1 | #!/usr/bin/env bash |
| 2 | # ============================================================================= | |
| 3 | # Gluecron one-command install. | |
| 4 | # | |
| 5 | # curl -sSL https://gluecron.com/install | bash | |
| 6 | # | |
| 7 | # What it does (every step is idempotent + verbose): | |
| 8 | # 1. Verify git, curl, jq are on PATH | |
| 9 | # 2. Resolve Gluecron host (default https://gluecron.com, env override) | |
| 10 | # 3. Locate Claude Desktop config (macOS / Linux / WSL) | |
| 11 | # 4. Sign in (env vars or interactive prompt) and capture session cookie | |
| 12 | # 5. Mint a fresh PAT via POST /api/v2/auth/install-token | |
| 13 | # 6. Merge a 'gluecron' MCP server entry into claude_desktop_config.json | |
| 14 | # 7. If we're inside a GitHub-origin repo, offer to import it | |
| 15 | # 8. Print a "you're done" success banner | |
| 16 | # | |
| 17 | # No third-party tools beyond plain curl, jq, git. Idempotent. Safe to re-run. | |
| 18 | # ============================================================================= | |
| 19 | ||
| 20 | set -euo pipefail | |
| 21 | ||
| 22 | # ── pretty printers (match scripts/bootstrap-hetzner.sh style) ─────────────── | |
| 23 | say() { printf "\n\033[1;34m> %s\033[0m\n" "$*"; } | |
| 24 | ok() { printf " \033[32mv\033[0m %s\n" "$*"; } | |
| 25 | warn() { printf " \033[33m!\033[0m %s\n" "$*"; } | |
| 26 | fail() { printf " \033[31mx\033[0m %s\n" "$*"; exit 1; } | |
| 27 | ||
| 28 | # ── 1. Prerequisites ──────────────────────────────────────────────────────── | |
| 29 | say "[1/7] Checking prerequisites" | |
| 30 | MISSING=() | |
| 31 | for tool in git curl jq; do | |
| 32 | if ! command -v "$tool" >/dev/null 2>&1; then | |
| 33 | MISSING+=("$tool") | |
| 34 | fi | |
| 35 | done | |
| 36 | if [ "${#MISSING[@]}" -gt 0 ]; then | |
| 37 | warn "Missing tools: ${MISSING[*]}" | |
| 38 | echo " Install them and re-run, e.g.:" | |
| 39 | for tool in "${MISSING[@]}"; do | |
| 40 | case "$tool" in | |
| 41 | jq) echo " macOS: brew install jq | Debian/Ubuntu: sudo apt-get install -y jq" ;; | |
| 42 | git) echo " macOS: xcode-select --install | Debian/Ubuntu: sudo apt-get install -y git" ;; | |
| 43 | curl) echo " macOS: (preinstalled) | Debian/Ubuntu: sudo apt-get install -y curl" ;; | |
| 44 | esac | |
| 45 | done | |
| 46 | fail "Please install the missing tools above and re-run." | |
| 47 | fi | |
| 48 | ok "git, curl, jq present" | |
| 49 | ||
| 50 | # ── 2. Host ───────────────────────────────────────────────────────────────── | |
| 51 | say "[2/7] Resolving Gluecron host" | |
| 52 | HOST="${GLUECRON_HOST:-https://gluecron.com}" | |
| 53 | HOST="${HOST%/}" # strip trailing slash | |
| 54 | ok "Using host: $HOST" | |
| 55 | ||
| 56 | # ── 3. Locate Claude Desktop config ──────────────────────────────────────── | |
| 57 | say "[3/7] Locating Claude Desktop config" | |
| 58 | UNAME_S="$(uname -s 2>/dev/null || echo unknown)" | |
| 59 | MAC_CONF="$HOME/Library/Application Support/Claude/claude_desktop_config.json" | |
| 60 | LINUX_CONF="$HOME/.config/Claude/claude_desktop_config.json" | |
| 61 | CLAUDE_CONF="" | |
| 62 | ||
| 63 | case "$UNAME_S" in | |
| 64 | Darwin*) | |
| 65 | CLAUDE_CONF="$MAC_CONF" | |
| 66 | ;; | |
| 67 | Linux*) | |
| 68 | # WSL detection | |
| 69 | if grep -qi microsoft /proc/version 2>/dev/null; then | |
| 70 | if [ -d "$HOME/.config/Claude" ]; then | |
| 71 | CLAUDE_CONF="$LINUX_CONF" | |
| 72 | else | |
| 73 | CLAUDE_CONF="$LINUX_CONF" | |
| 74 | warn "WSL detected but no existing Claude config — using $CLAUDE_CONF" | |
| 75 | fi | |
| 76 | else | |
| 77 | CLAUDE_CONF="$LINUX_CONF" | |
| 78 | fi | |
| 79 | ;; | |
| 80 | *) | |
| 81 | warn "Unknown platform '$UNAME_S' — defaulting to macOS path." | |
| 82 | CLAUDE_CONF="$MAC_CONF" | |
| 83 | ;; | |
| 84 | esac | |
| 85 | ||
| 86 | mkdir -p "$(dirname "$CLAUDE_CONF")" | |
| 87 | if [ ! -f "$CLAUDE_CONF" ]; then | |
| 88 | echo '{}' > "$CLAUDE_CONF" | |
| 89 | ok "Created empty $CLAUDE_CONF" | |
| 90 | else | |
| 91 | ok "Found existing $CLAUDE_CONF" | |
| 92 | fi | |
| 93 | ||
| 94 | # ── 4. Sign in ────────────────────────────────────────────────────────────── | |
| 95 | say "[4/7] Signing in to $HOST" | |
| 96 | USERNAME="${GLUECRON_USERNAME:-}" | |
| 97 | PASSWORD="${GLUECRON_PASSWORD:-}" | |
| 98 | if [ -z "$USERNAME" ]; then | |
| 99 | if [ ! -t 0 ]; then | |
| 100 | fail "GLUECRON_USERNAME is unset and stdin is not a TTY. Re-run as: GLUECRON_USERNAME=you GLUECRON_PASSWORD=*** curl -sSL $HOST/install | bash" | |
| 101 | fi | |
| 102 | printf " Gluecron username: " | |
| 103 | read -r USERNAME | |
| 104 | fi | |
| 105 | if [ -z "$PASSWORD" ]; then | |
| 106 | if [ ! -t 0 ]; then | |
| 107 | fail "GLUECRON_PASSWORD is unset and stdin is not a TTY." | |
| 108 | fi | |
| 109 | printf " Gluecron password: " | |
| 110 | stty -echo | |
| 111 | read -r PASSWORD | |
| 112 | stty echo | |
| 113 | printf "\n" | |
| 114 | fi | |
| 115 | ||
| 116 | COOKIE_JAR="$(mktemp -t gluecron-cookies.XXXXXX)" | |
| 117 | trap 'rm -f "$COOKIE_JAR"' EXIT | |
| 118 | ||
| 119 | LOGIN_BODY="username=$(printf %s "$USERNAME" | jq -sRr @uri)&password=$(printf %s "$PASSWORD" | jq -sRr @uri)" | |
| 120 | LOGIN_STATUS=$( | |
| 121 | curl -sS -o /dev/null -w "%{http_code}" \ | |
| 122 | -X POST \ | |
| 123 | -H "Content-Type: application/x-www-form-urlencoded" \ | |
| 124 | -c "$COOKIE_JAR" \ | |
| 125 | --data "$LOGIN_BODY" \ | |
| 126 | "$HOST/login" || echo 000 | |
| 127 | ) | |
| 128 | if ! grep -q "session" "$COOKIE_JAR" 2>/dev/null; then | |
| 129 | fail "Login failed (HTTP $LOGIN_STATUS). Check username/password." | |
| 130 | fi | |
| 131 | ok "Signed in as $USERNAME" | |
| 132 | ||
| 133 | # ── 5. Mint PAT ───────────────────────────────────────────────────────────── | |
| 134 | say "[5/7] Minting a fresh PAT" | |
| 135 | SHORT_TS=$(date +%s | tail -c 7) | |
| 136 | MINT_BODY=$(jq -nc --arg name "gluecron-install-$SHORT_TS" --arg scope "admin" \ | |
| 137 | '{name: $name, scope: $scope}') | |
| 138 | MINT_RESPONSE=$( | |
| 139 | curl -sS \ | |
| 140 | -X POST \ | |
| 141 | -H "Content-Type: application/json" \ | |
| 142 | -b "$COOKIE_JAR" \ | |
| 143 | --data "$MINT_BODY" \ | |
| 144 | "$HOST/api/v2/auth/install-token" | |
| 145 | ) | |
| 146 | PAT=$(printf %s "$MINT_RESPONSE" | jq -r '.token // empty') | |
| 147 | if [ -z "$PAT" ]; then | |
| 148 | ERR=$(printf %s "$MINT_RESPONSE" | jq -r '.error // .hint // .') | |
| 149 | fail "PAT mint failed: $ERR" | |
| 150 | fi | |
| 151 | PAT_PREFIX=$(printf %s "$PAT" | cut -c1-12) | |
| 152 | ok "Created PAT $PAT_PREFIX..." | |
| 153 | ||
| 154 | # ── 6. Merge MCP server entry into Claude Desktop config ─────────────────── | |
| 155 | say "[6/7] Wiring Claude Desktop -> Gluecron MCP" | |
| 156 | TMP_CONF="$(mktemp -t gluecron-conf.XXXXXX)" | |
| 157 | jq --arg url "$HOST/mcp" --arg auth "Bearer $PAT" ' | |
| 158 | . as $root | |
| 159 | | (.mcpServers // {}) as $servers | |
| 160 | | $root | |
| 161 | | .mcpServers = ($servers + { | |
| 162 | "gluecron": { | |
| 163 | "transport": "http", | |
| 164 | "url": $url, | |
| 165 | "headers": { "Authorization": $auth } | |
| 166 | } | |
| 167 | }) | |
| 168 | ' "$CLAUDE_CONF" > "$TMP_CONF" | |
| 169 | mv "$TMP_CONF" "$CLAUDE_CONF" | |
| 170 | ok "Updated $CLAUDE_CONF" | |
| 171 | ||
| 172 | # ── 7. Optional repo import ───────────────────────────────────────────────── | |
| 173 | say "[7/7] Repo import (optional)" | |
| 174 | if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then | |
| 175 | ORIGIN_URL=$(git config --get remote.origin.url || true) | |
| 176 | if [ -n "$ORIGIN_URL" ] && printf %s "$ORIGIN_URL" | grep -q "github.com"; then | |
| 177 | if [ -t 0 ]; then | |
| 178 | printf " Import this repo (%s) to Gluecron? [y/N] " "$ORIGIN_URL" | |
| 179 | read -r ANSWER | |
| 180 | else | |
| 181 | ANSWER="${GLUECRON_IMPORT:-n}" | |
| 182 | fi | |
| 183 | case "$ANSWER" in | |
| 184 | y|Y|yes|YES) | |
| 185 | IMPORT_BODY="repo_url=$(printf %s "$ORIGIN_URL" | jq -sRr @uri)" | |
| 186 | IMPORT_STATUS=$( | |
| 187 | curl -sS -o /dev/null -w "%{http_code}" \ | |
| 188 | -X POST \ | |
| 189 | -H "Authorization: Bearer $PAT" \ | |
| 190 | -H "Content-Type: application/x-www-form-urlencoded" \ | |
| 191 | --data "$IMPORT_BODY" \ | |
| 192 | "$HOST/import/github/repo" || echo 000 | |
| 193 | ) | |
| 194 | case "$IMPORT_STATUS" in | |
| 195 | 2*|3*) ok "Import dispatched (HTTP $IMPORT_STATUS)" ;; | |
| 196 | *) warn "Import returned HTTP $IMPORT_STATUS — you can re-run from $HOST/import" ;; | |
| 197 | esac | |
| 198 | ;; | |
| 199 | *) | |
| 200 | ok "Skipped import" | |
| 201 | ;; | |
| 202 | esac | |
| 203 | else | |
| 204 | ok "No GitHub origin detected — skipping import" | |
| 205 | fi | |
| 206 | else | |
| 207 | ok "Not inside a git repo — skipping import" | |
| 208 | fi | |
| 209 | ||
| 210 | # ── Done ──────────────────────────────────────────────────────────────────── | |
| 211 | printf "\n" | |
| 212 | printf "\033[1;32m============================================================\033[0m\n" | |
| 213 | printf "\033[1;32m GLUECRON INSTALL COMPLETE\033[0m\n" | |
| 214 | printf "\033[1;32m============================================================\033[0m\n" | |
| 215 | printf " Host: %s\n" "$HOST" | |
| 216 | printf " PAT prefix: %s...\n" "$PAT_PREFIX" | |
| 217 | printf " MCP config: %s\n" "$CLAUDE_CONF" | |
| 218 | printf "\n" | |
| 219 | printf " v Done. Restart Claude Desktop and try:\n" | |
| 220 | printf " \"Open a PR on this repo with a one-line README fix\"\n" | |
| 221 | printf "\n" |