diff --git a/README.md b/README.md index 14c542c..3b0ee51 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,19 @@ finishes it - and offers to install + log into: - **GitHub CLI** (`gh`, via the official apt repo, then `gh auth login`) - **Gitea CLI** (`tea`, official binary release downloaded to `~/.local/bin`, then `tea login add`) +`git` and `gnupg` themselves aren't part of this opt-in flow - every +template's `coder_agent` startup script installs them unconditionally as +base packages (a no-op where they're already present, e.g. baked into +`templates/web`'s image). If the wizard just authenticated GitHub and/or +Gitea above (skipped entirely for "local git only" - neither set up), it +asks once more whether to auto-generate an ed25519 SSH key and an ed25519 +GPG signing key and register them with whichever host(s) got set up: `gh +ssh-key add` / `gh gpg-key add` for GitHub, a direct call against Gitea's +`/api/v1/user/keys` and `/api/v1/user/gpg_keys` (using the token `tea +login add` already stored) for Gitea. Either upload failing just prints +the manual command/URL to finish it yourself - never blocks the rest of +the wizard. + It does **not** ask about VS Code extensions, since those are handled by Terraform (see above). Once the user confirms completion it writes a sentinel file (`~/.cache/coder-cli-wizard/done`) and stops prompting. It can diff --git a/templates/3d-printing/cli-setup-wizard.sh b/templates/3d-printing/cli-setup-wizard.sh index 14aca7e..6cb3804 100644 --- a/templates/3d-printing/cli-setup-wizard.sh +++ b/templates/3d-printing/cli-setup-wizard.sh @@ -19,6 +19,12 @@ 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}" @@ -112,6 +118,7 @@ else 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 @@ -136,6 +143,101 @@ else 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 +fi echo "" if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then diff --git a/templates/3d-printing/main.tf b/templates/3d-printing/main.tf index 04a38e7..74af2c9 100644 --- a/templates/3d-printing/main.tf +++ b/templates/3d-printing/main.tf @@ -53,6 +53,14 @@ resource "coder_agent" "main" { touch ~/.init_done fi + # Ensure git and gnupg (commit signing) are present as base packages - + # not every base image ships gnupg by default. No-op once both are + # present (e.g. templates/web already bakes them into its image). + if ! command -v git >/dev/null 2>&1 || ! command -v gpg >/dev/null 2>&1; then + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends git gnupg + fi + # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here EOT diff --git a/templates/cobol/cli-setup-wizard.sh b/templates/cobol/cli-setup-wizard.sh index 14aca7e..6cb3804 100644 --- a/templates/cobol/cli-setup-wizard.sh +++ b/templates/cobol/cli-setup-wizard.sh @@ -19,6 +19,12 @@ 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}" @@ -112,6 +118,7 @@ else 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 @@ -136,6 +143,101 @@ else 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 +fi echo "" if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then diff --git a/templates/cobol/main.tf b/templates/cobol/main.tf index 4419fd8..9194ab1 100644 --- a/templates/cobol/main.tf +++ b/templates/cobol/main.tf @@ -53,6 +53,14 @@ resource "coder_agent" "main" { touch ~/.init_done fi + # Ensure git and gnupg (commit signing) are present as base packages - + # not every base image ships gnupg by default. No-op once both are + # present (e.g. templates/web already bakes them into its image). + if ! command -v git >/dev/null 2>&1 || ! command -v gpg >/dev/null 2>&1; then + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends git gnupg + fi + # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here EOT diff --git a/templates/default/cli-setup-wizard.sh b/templates/default/cli-setup-wizard.sh index 14aca7e..6cb3804 100644 --- a/templates/default/cli-setup-wizard.sh +++ b/templates/default/cli-setup-wizard.sh @@ -19,6 +19,12 @@ 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}" @@ -112,6 +118,7 @@ else 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 @@ -136,6 +143,101 @@ else 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 +fi echo "" if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then diff --git a/templates/default/main.tf b/templates/default/main.tf index 76caa0b..9e4a74e 100644 --- a/templates/default/main.tf +++ b/templates/default/main.tf @@ -53,6 +53,14 @@ resource "coder_agent" "main" { touch ~/.init_done fi + # Ensure git and gnupg (commit signing) are present as base packages - + # not every base image ships gnupg by default. No-op once both are + # present (e.g. templates/web already bakes them into its image). + if ! command -v git >/dev/null 2>&1 || ! command -v gpg >/dev/null 2>&1; then + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends git gnupg + fi + # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here EOT diff --git a/templates/python/cli-setup-wizard.sh b/templates/python/cli-setup-wizard.sh index 14aca7e..6cb3804 100644 --- a/templates/python/cli-setup-wizard.sh +++ b/templates/python/cli-setup-wizard.sh @@ -19,6 +19,12 @@ 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}" @@ -112,6 +118,7 @@ else 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 @@ -136,6 +143,101 @@ else 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 +fi echo "" if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then diff --git a/templates/python/main.tf b/templates/python/main.tf index 958822d..c3c4327 100644 --- a/templates/python/main.tf +++ b/templates/python/main.tf @@ -53,6 +53,14 @@ resource "coder_agent" "main" { touch ~/.init_done fi + # Ensure git and gnupg (commit signing) are present as base packages - + # not every base image ships gnupg by default. No-op once both are + # present (e.g. templates/web already bakes them into its image). + if ! command -v git >/dev/null 2>&1 || ! command -v gpg >/dev/null 2>&1; then + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends git gnupg + fi + # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here EOT diff --git a/templates/ttrpg/cli-setup-wizard.sh b/templates/ttrpg/cli-setup-wizard.sh index 14aca7e..6cb3804 100644 --- a/templates/ttrpg/cli-setup-wizard.sh +++ b/templates/ttrpg/cli-setup-wizard.sh @@ -19,6 +19,12 @@ 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}" @@ -112,6 +118,7 @@ else 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 @@ -136,6 +143,101 @@ else 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 +fi echo "" if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then diff --git a/templates/ttrpg/main.tf b/templates/ttrpg/main.tf index d041c28..dc49ae8 100644 --- a/templates/ttrpg/main.tf +++ b/templates/ttrpg/main.tf @@ -53,6 +53,14 @@ resource "coder_agent" "main" { touch ~/.init_done fi + # Ensure git and gnupg (commit signing) are present as base packages - + # not every base image ships gnupg by default. No-op once both are + # present (e.g. templates/web already bakes them into its image). + if ! command -v git >/dev/null 2>&1 || ! command -v gpg >/dev/null 2>&1; then + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends git gnupg + fi + # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here EOT diff --git a/templates/web/cli-setup-wizard.sh b/templates/web/cli-setup-wizard.sh index 14aca7e..6cb3804 100644 --- a/templates/web/cli-setup-wizard.sh +++ b/templates/web/cli-setup-wizard.sh @@ -19,6 +19,12 @@ 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}" @@ -112,6 +118,7 @@ else 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 @@ -136,6 +143,101 @@ else 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 +fi echo "" if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then diff --git a/templates/web/main.tf b/templates/web/main.tf index 2d66b75..9c23b5e 100644 --- a/templates/web/main.tf +++ b/templates/web/main.tf @@ -53,6 +53,14 @@ resource "coder_agent" "main" { touch ~/.init_done fi + # Ensure git and gnupg (commit signing) are present as base packages - + # not every base image ships gnupg by default. No-op once both are + # present (e.g. templates/web already bakes them into its image). + if ! command -v git >/dev/null 2>&1 || ! command -v gpg >/dev/null 2>&1; then + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends git gnupg + fi + # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here EOT