Files
Profiles-for-Coder/templates/python/cli-setup-wizard.sh
octoturge d60e44508a
Provision Coder Templates / provision (push) Successful in 2m7s
cli-setup-wizard: explain why SSH/GPG key setup got skipped
The SSH/GPG key-generation question only fires when gh/tea are actually
logged in (DID_GITHUB/DID_GITEA), which is correct - but if login was
declined, failed, or never completed, the section was skipped with zero
explanation. From the user's side that looked like a missing feature
rather than an unfinished login.

Confirmed on a live workspace: gh was never installed, and tea was
installed but `tea login add` never actually completed (no
~/.config/tea/config.yml), so the gate correctly stayed closed - the
user just had no way to know why. Now prints a one-line hint (only when
gh or tea is installed at all) pointing at the login command and the
--force re-run.
2026-08-27 00:14:11 +02:00

257 lines
11 KiB
Bash

#!/usr/bin/env bash
# Coder workspace first-run CLI setup wizard.
#
# Meant to be `source`d from a new interactive shell (e.g. via .bashrc). It asks,
# once per user per workspace, whether to install and log into a few optional
# AI coding CLIs. It re-runs on every new terminal until the user lets it finish
# (or explicitly skips it for good), then gets out of the way.
#
# VS Code / code-server extensions are intentionally NOT asked about here -
# they're installed declaratively by the Coder template itself (the
# `code-server` module's `extensions` input, populated from the matching
# profile-templates/*.code-profile file at template-push time).
#
# Manual re-run: bash /opt/coder/cli-setup-wizard.sh --force
set -u
WIZARD_DONE_FILE="${HOME}/.cache/coder-cli-wizard/done"
FORCE=0
[ "${1:-}" = "--force" ] && FORCE=1
# Tracks whether the user actually ended up authenticated against GitHub
# and/or Gitea below, so the SSH/GPG key step can ask about exactly the
# host(s) in play (and stay silent - "local git only" - if neither).
DID_GITHUB=0
DID_GITEA=0
export BUN_INSTALL="${HOME}/.bun"
export PATH="${BUN_INSTALL}/bin:${HOME}/.local/bin:${PATH}"
# Only bother interactive shells with a real terminal attached, and only until
# the user marks the wizard as done.
if [ "$FORCE" -ne 1 ]; then
case "$-" in
*i*) : ;;
*) return 0 2>/dev/null || exit 0 ;;
esac
[ -t 0 ] || { return 0 2>/dev/null || exit 0; }
[ -f "$WIZARD_DONE_FILE" ] && { return 0 2>/dev/null || exit 0; }
fi
mkdir -p "$(dirname "$WIZARD_DONE_FILE")"
ask_yes_no() {
local prompt="$1" reply
read -r -p "$prompt [y/N] " reply
case "$reply" in
[Yy]*) return 0 ;;
*) return 1 ;;
esac
}
echo ""
echo "==================================================================="
echo " Coder workspace setup wizard"
echo " Runs once per new terminal until you finish it. Ctrl+C any time"
echo " to skip for now - it'll ask again next terminal."
echo "==================================================================="
# --- GitHub Copilot CLI ---
if command -v copilot >/dev/null 2>&1; then
echo "GitHub Copilot CLI already installed, skipping."
elif command -v bun >/dev/null 2>&1; then
if ask_yes_no "Install GitHub Copilot CLI and log in?"; then
if bun install -g @github/copilot; then
copilot login || echo "Install succeeded but login didn't complete. Retry any time with: copilot login"
else
echo "Copilot CLI install failed. Retry later with: bun install -g @github/copilot && copilot login"
fi
fi
else
echo "Skipping GitHub Copilot CLI: bun not found on this workspace image."
fi
# --- Google Antigravity CLI (agy) ---
if command -v agy >/dev/null 2>&1; then
echo "Antigravity CLI already installed, skipping."
else
if ask_yes_no "Install Google Antigravity CLI (agy) and log in?"; then
if curl -fsSL https://antigravity.google/cli/install.sh | bash; then
echo "Launching 'agy' once to complete sign-in (exit with /logout or Ctrl+D when done)..."
agy || echo "Sign-in didn't complete. Retry any time by running: agy"
else
echo "Antigravity CLI install failed. Retry later with: curl -fsSL https://antigravity.google/cli/install.sh | bash"
fi
fi
fi
# --- Claude Code CLI ---
if command -v claude >/dev/null 2>&1; then
echo "Claude Code CLI already installed, skipping."
elif command -v bun >/dev/null 2>&1; then
if ask_yes_no "Install Claude Code CLI and log in?"; then
if bun install -g @anthropic-ai/claude-code; then
echo "Launching 'claude' once to complete sign-in (use /login if not prompted; Ctrl+C to exit when done)..."
claude || echo "Sign-in didn't complete. Retry any time by running: claude"
else
echo "Claude Code CLI install failed. Retry later with: bun install -g @anthropic-ai/claude-code"
fi
fi
else
echo "Skipping Claude Code CLI: bun not found on this workspace image."
fi
# --- GitHub CLI (gh) ---
if command -v gh >/dev/null 2>&1; then
echo "GitHub CLI already installed, skipping."
else
if ask_yes_no "Install GitHub CLI (gh) and log in?"; then
if (sudo mkdir -p -m 755 /etc/apt/keyrings \
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg >/dev/null \
&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list >/dev/null \
&& sudo apt-get update -qq && sudo apt-get install -y gh); then
gh auth login || echo "Install succeeded but login didn't complete. Retry any time with: gh auth login"
else
echo "GitHub CLI install failed. Retry later with: gh auth login (once gh is installed)"
fi
fi
fi
command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1 && DID_GITHUB=1
# --- Gitea CLI (tea) ---
if command -v tea >/dev/null 2>&1; then
echo "Gitea CLI already installed, skipping."
else
if ask_yes_no "Install Gitea CLI (tea) and log in?"; then
TEA_ARCH="$(uname -m)"
case "$TEA_ARCH" in
x86_64) TEA_ARCH="amd64" ;;
aarch64) TEA_ARCH="arm64" ;;
esac
TEA_VERSION="$(curl -fsSL https://gitea.com/api/v1/repos/gitea/tea/releases/latest | grep -o '"tag_name":[^,]*' | grep -o 'v[0-9][^"]*')"
mkdir -p "$HOME/.local/bin"
if [ -n "$TEA_VERSION" ] \
&& curl -fsSL "https://gitea.com/gitea/tea/releases/download/${TEA_VERSION}/tea-${TEA_VERSION#v}-linux-${TEA_ARCH}" -o "$HOME/.local/bin/tea" \
&& chmod +x "$HOME/.local/bin/tea"; then
echo "Add this Gitea instance now (e.g. https://git.octoturge.com)..."
tea login add || echo "Login didn't complete. Retry any time with: tea login add"
else
echo "Gitea CLI install failed. Retry later from: https://gitea.com/gitea/tea/releases"
rm -f "$HOME/.local/bin/tea"
fi
fi
fi
TEA_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/tea/config.yml"
if command -v tea >/dev/null 2>&1 && [ -f "$TEA_CONFIG" ]; then
TEA_URL="$(awk '/^logins:/{f=1} f && /^ url:/{print $2; exit}' "$TEA_CONFIG")"
TEA_TOKEN="$(awk '/^logins:/{f=1} f && /^ token:/{print $2; exit}' "$TEA_CONFIG")"
[ -n "$TEA_URL" ] && [ -n "$TEA_TOKEN" ] && DID_GITEA=1
fi
# --- SSH + GPG keys for the external git host(s) selected above ---
# Only asks if the user actually set up GitHub and/or Gitea just now -
# stays silent for "local git only" (neither was set up).
if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
if [ "$DID_GITHUB" -eq 1 ] && [ "$DID_GITEA" -eq 1 ]; then
KEY_HOSTS_DESC="GitHub and Gitea"
elif [ "$DID_GITHUB" -eq 1 ]; then
KEY_HOSTS_DESC="GitHub"
else
KEY_HOSTS_DESC="Gitea"
fi
if ask_yes_no "Auto-generate an SSH key and a GPG signing key, and register them with $KEY_HOSTS_DESC?"; then
KEY_NAME="${GIT_AUTHOR_NAME:-$(whoami)}"
KEY_EMAIL="${GIT_AUTHOR_EMAIL:-$(whoami)@$(hostname)}"
# SSH key: ed25519, no passphrase (disposable dev workspace convenience;
# add one manually afterwards with `ssh-keygen -p` if you want one).
SSH_KEY="$HOME/.ssh/id_ed25519"
if [ ! -f "$SSH_KEY" ]; then
mkdir -p "$HOME/.ssh" && chmod 700 "$HOME/.ssh"
ssh-keygen -t ed25519 -N "" -C "$KEY_EMAIL" -f "$SSH_KEY" -q
echo "Generated SSH key: ${SSH_KEY}.pub"
else
echo "SSH key already exists at ${SSH_KEY}.pub, reusing it."
fi
if [ "$DID_GITHUB" -eq 1 ]; then
if gh ssh-key add "${SSH_KEY}.pub" --title "coder-$(hostname)" 2>/dev/null; then
echo "SSH key added to GitHub."
else
echo "Couldn't add the SSH key to GitHub automatically (may already be added). Add manually: gh ssh-key add ${SSH_KEY}.pub"
fi
fi
if [ "$DID_GITEA" -eq 1 ]; then
SSH_PUB_JSON="$(sed 's/\\/\\\\/g; s/"/\\"/g' "${SSH_KEY}.pub")"
if curl -fsS -X POST "${TEA_URL%/}/api/v1/user/keys" \
-H "Authorization: token ${TEA_TOKEN}" -H "Content-Type: application/json" \
-d "{\"title\":\"coder-$(hostname)\",\"key\":\"${SSH_PUB_JSON}\"}" >/dev/null 2>&1; then
echo "SSH key added to Gitea."
else
echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually at: ${TEA_URL%/}/user/settings/keys"
fi
fi
# GPG key: ed25519 signing key, no passphrase, no expiry.
if gpg --list-secret-keys --with-colons "$KEY_EMAIL" 2>/dev/null | grep -q '^sec'; then
echo "GPG key for $KEY_EMAIL already exists, reusing it."
else
mkdir -p "$HOME/.gnupg" && chmod 700 "$HOME/.gnupg"
grep -qF "allow-loopback-pinentry" "$HOME/.gnupg/gpg-agent.conf" 2>/dev/null \
|| echo "allow-loopback-pinentry" >> "$HOME/.gnupg/gpg-agent.conf"
gpgconf --kill gpg-agent 2>/dev/null
if gpg --batch --pinentry-mode loopback --passphrase '' --quick-gen-key "$KEY_NAME <$KEY_EMAIL>" ed25519 sign 0 2>/dev/null; then
echo "Generated GPG signing key for $KEY_EMAIL."
else
echo "GPG key generation failed. Generate manually with: gpg --quick-gen-key \"$KEY_NAME <$KEY_EMAIL>\" ed25519 sign 0"
fi
fi
GPG_KEY_ID="$(gpg --list-secret-keys --with-colons "$KEY_EMAIL" 2>/dev/null | awk -F: '/^sec/{print $5; exit}')"
if [ -n "$GPG_KEY_ID" ]; then
git config --global user.signingkey "$GPG_KEY_ID"
git config --global commit.gpgsign true
echo "Configured git to sign commits with this key."
if [ "$DID_GITHUB" -eq 1 ]; then
if gpg --armor --export "$GPG_KEY_ID" | gh gpg-key add - 2>/dev/null; then
echo "GPG key added to GitHub."
else
echo "Couldn't add the GPG key to GitHub automatically (may already be added). Add manually: gpg --armor --export $GPG_KEY_ID | gh gpg-key add -"
fi
fi
if [ "$DID_GITEA" -eq 1 ]; then
GPG_ARMORED_JSON="$(gpg --armor --export "$GPG_KEY_ID" | awk '{printf "%s\\n", $0}')"
if curl -fsS -X POST "${TEA_URL%/}/api/v1/user/gpg_keys" \
-H "Authorization: token ${TEA_TOKEN}" -H "Content-Type: application/json" \
-d "{\"armored_public_key\":\"${GPG_ARMORED_JSON}\"}" >/dev/null 2>&1; then
echo "GPG key added to Gitea."
else
echo "Couldn't add the GPG key to Gitea automatically (may already be added). Add manually at: ${TEA_URL%/}/user/settings/keys"
fi
fi
fi
fi
elif command -v gh >/dev/null 2>&1 || command -v tea >/dev/null 2>&1; then
# gh and/or tea are installed but neither is actually logged in yet (login
# was declined, failed, or never completed) - say why the key-generation
# question above got skipped instead of just silently not asking.
echo "Skipping SSH/GPG key setup: not logged in to GitHub or Gitea yet."
echo "Log in (gh auth login / tea login add) then re-run: bash /opt/coder/cli-setup-wizard.sh --force"
fi
echo ""
if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then
touch "$WIZARD_DONE_FILE"
echo "Done. Re-run any time with: bash /opt/coder/cli-setup-wizard.sh --force"
else
echo "OK, this'll ask again next time you open a terminal."
fi
return 0 2>/dev/null || exit 0