b548dfed3e
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.
257 lines
11 KiB
Bash
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
|