1 Commits

Author SHA1 Message Date
octoturge b548dfed3e 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-26 23:58:46 +02:00
21 changed files with 126 additions and 692 deletions
+9 -156
View File
@@ -2,52 +2,17 @@ name: Provision Coder Templates
# Keeps Coder templates in sync with templates/*/ in this repo: # Keeps Coder templates in sync with templates/*/ in this repo:
# - every push to main pushes a new version of each templates/<env>/ dir # - every push to main pushes a new version of each templates/<env>/ dir
# whose VERSION file names a version not already pushed (coder templates # (coder templates push creates it if it doesn't exist yet, so adding a
# push creates the template if it doesn't exist yet, so adding a new # new templates/<env>/ directory is enough to provision a new one)
# templates/<env>/ directory - with a VERSION file - is enough to
# provision a new one). This workflow triggers on any change under
# templates/**, not just a specific template's own directory, so VERSION
# is what keeps an edit to one template from generating a no-op new
# version for every other, unchanged template.
# - if a templates/<env>/ directory is removed on main, its template is # - if a templates/<env>/ directory is removed on main, its template is
# deleted from Coder. `coder templates delete` refuses to delete a # deleted from Coder. `coder templates delete` refuses to delete a
# template that still has active workspaces, so this can't silently # template that still has active workspaces, so this can't silently
# orphan running workspaces - it just fails loudly and needs a human. # orphan running workspaces - it just fails loudly and needs a human.
# #
# Requires three repo/org secrets (Settings > Actions > Secrets): # Requires two repo/org secrets (Settings > Actions > Secrets):
# CODER_URL e.g. https://code.octoturge.com # CODER_URL e.g. https://code.octoturge.com
# CODER_SESSION_TOKEN a token from `coder tokens create`, ideally under a # CODER_SESSION_TOKEN a token from `coder tokens create`, ideally under a
# dedicated service account rather than a personal one # dedicated service account rather than a personal one
# PACKAGE_REGISTRY_TOKEN a Gitea access token (user Settings > Applications)
# with write:package scope, for pushing each
# Dockerfile-having template's image to this
# instance's container registry.
#
# Not secrets.GITEA_TOKEN (Gitea Actions' built-in token): as of this
# writing it cannot authenticate to the container registry in any shipped
# Gitea version - `permissions: packages: write` is a no-op because the
# Actions token's package scope isn't wired up server-side yet (open since
# Gitea 1.19: https://github.com/go-gitea/gitea/issues/23642; fix in
# https://github.com/go-gitea/gitea/pull/39070, not yet merged). Every
# attempt fails at docker login with a plain "unauthorized", regardless of
# the permissions: block or which account triggered the workflow. A
# manually-created PAT is the only thing that currently works. Named
# without a GITEA_ prefix because Gitea Actions reserves that prefix for
# its own built-in secrets and rejects creating one with that name.
#
# Any templates/<env>/ that has its own Dockerfile gets its image built and
# pushed here (build-images, on the dedicated "docker-build" runner - see
# templates/web/main.tf for why: that runner is scoped to this repo only and
# has host Docker socket access that the shared runner-1 deliberately
# doesn't). provision then just pulls the tag build-images produced, instead
# of building it itself at `terraform apply` time - keeps a slow toolchain
# compile off of "someone is waiting to create a workspace".
#
# Each template's image is built independently - one Dockerfile failing to
# build doesn't stop the others from building, and provision skips pushing
# only the specific template(s) whose image build failed this run (leaving
# their previous, already-working Coder template version in place) rather
# than skipping every template or pushing one with no matching image.
on: on:
push: push:
@@ -58,83 +23,11 @@ on:
workflow_dispatch: {} workflow_dispatch: {}
jobs: jobs:
build-images:
# The docker-build runner's docker_host: "" setting already auto-injects
# /var/run/docker.sock into job containers - an explicit
# container.volumes mount for the same path here fails at container
# creation with "Duplicate mount point: /var/run/docker.sock".
runs-on: docker-build
# Currently a no-op (see the PACKAGE_REGISTRY_TOKEN note above) but
# harmless to declare now - once go-gitea/gitea#39070 ships, GITEA_TOKEN
# will need this to get package write access, so this is one less thing
# to remember when PACKAGE_REGISTRY_TOKEN can eventually be retired.
permissions:
packages: write
outputs:
failed_templates: ${{ steps.build.outputs.failed_templates }}
# docker:27-cli (Alpine) has no bash - only the POSIX /bin/sh (busybox
# ash) - but run: steps default to bash, which fails with "exec: bash:
# executable file not found in $PATH". Both run: steps below are plain
# POSIX shell already, so just run them under sh.
defaults:
run:
shell: sh
steps:
# actions/checkout is a JS action and needs Node in the job container -
# this job runs in docker:27-cli (Alpine, just the Docker CLI) so it has
# no Node, and actions/checkout fails immediately with "exec: node:
# executable file not found in $PATH". Alpine does have apk/git though,
# so clone directly instead.
- name: Checkout
run: |
set -e
apk add --no-cache git
git clone --depth 1 --branch "${{ github.ref_name }}" "${{ github.server_url }}/${{ github.repository }}.git" .
- name: Build and push every template's image (skips a tag that's already in the registry)
id: build
run: |
set -e
echo "${{ secrets.PACKAGE_REGISTRY_TOKEN }}" | docker login git.octoturge.com -u octoturge --password-stdin
FAILED=""
for dockerfile in templates/*/Dockerfile; do
[ -e "$dockerfile" ] || continue
dir="$(dirname "$dockerfile")"
name="$(basename "$dir")"
TAG="$(sha1sum "$dockerfile" | cut -d' ' -f1)"
IMAGE="git.octoturge.com/octo-tech/profiles-${name}:${TAG}"
echo "::group::${name}"
if docker manifest inspect "$IMAGE" >/dev/null 2>&1; then
echo "$IMAGE already in the registry (Dockerfile unchanged), skipping build."
elif docker build -t "$IMAGE" "$dir" && docker push "$IMAGE"; then
echo "Built and pushed $IMAGE"
else
echo "::warning::Failed to build/push $IMAGE - templates/$name will be skipped this run."
FAILED="$FAILED $name"
fi
echo "::endgroup::"
done
echo "failed_templates=${FAILED# }" >> "$GITHUB_OUTPUT"
provision: provision:
# needs: build-images orders this after the image builds (so a fresh
# template push never points at a tag that isn't in the registry yet)
# without making every template's reprovisioning depend on ALL builds
# succeeding - if:always() overrides the default "skip if a dependency
# failed" behavior, since build-images only fails outright on an
# infra-level problem (e.g. registry login); a single template's build
# failure is reported via failed_templates instead and only skips that
# one template below.
needs: build-images
if: always()
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
CODER_URL: ${{ secrets.CODER_URL }} CODER_URL: ${{ secrets.CODER_URL }}
CODER_SESSION_TOKEN: ${{ secrets.CODER_SESSION_TOKEN }} CODER_SESSION_TOKEN: ${{ secrets.CODER_SESSION_TOKEN }}
FAILED_TEMPLATES: ${{ needs.build-images.outputs.failed_templates }}
steps: steps:
- name: Checkout (full history, needed to detect removed templates) - name: Checkout (full history, needed to detect removed templates)
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -151,50 +44,10 @@ jobs:
run: | run: |
set -e set -e
for dir in templates/*/; do for dir in templates/*/; do
name="$(basename "$dir")" name="profiles-$(basename "$dir")"
full="profiles-$name" echo "::group::Pushing $name from $dir"
case " $FAILED_TEMPLATES " in coder templates push "$name" -d "$dir" --yes \
*" $name "*) -m "auto-provisioned from ${GITHUB_SHA:0:12}"
echo "::warning::Skipping $full - its Docker image failed to build this run (see build-images), leaving the previous template version in place."
continue
;;
esac
# templates/<name>/VERSION lets a template opt out of being
# reprovisioned on every push: bump it and coder templates push
# names the new version "v<N>". This is a manual contract, not a
# content hash - editing a template without bumping its VERSION
# means the change won't go out until someone does. paths: on
# this workflow's trigger is templates/** as a whole, so without
# this every template gets a new (identical) version on any push
# under templates/, even ones whose own directory didn't change.
#
# Rather than pre-checking `coder templates versions list` for
# whether v<N> already exists (fragile - depends on its exact
# JSON shape matching what we expect), just attempt the push and
# treat its specific "version already exists" failure as the
# skip signal instead.
version=""
if [ -f "$dir/VERSION" ]; then
version="$(tr -d '[:space:]' < "$dir/VERSION")"
fi
args=(-d "$dir" --yes -m "auto-provisioned from ${GITHUB_SHA:0:12}")
[ -n "$version" ] && args+=(--name "v$version")
echo "::group::Pushing $full from $dir"
if push_output="$(coder templates push "$full" "${args[@]}" 2>&1)"; then
echo "$push_output"
else
push_status=$?
echo "$push_output"
if [ -n "$version" ] && printf '%s' "$push_output" | grep -qF "A template version with name \"v$version\" already exists"; then
echo "Version v$version (templates/$name/VERSION) is already pushed - nothing to do. Bump the VERSION file to push a new one."
else
echo "::endgroup::"
exit "$push_status"
fi
fi
echo "::endgroup::" echo "::endgroup::"
done done
-1
View File
@@ -1 +0,0 @@
3
+15 -64
View File
@@ -143,66 +143,11 @@ else
fi fi
fi fi
fi fi
# `tea whoami` succeeds regardless of how the login was done (personal TEA_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/tea/config.yml"
# access token or OAuth) - more reliable than parsing tea's own if command -v tea >/dev/null 2>&1 && [ -f "$TEA_CONFIG" ]; then
# config.yml, whose indentation and fields (no plaintext `token:` at all TEA_URL="$(awk '/^logins:/{f=1} f && /^ url:/{print $2; exit}' "$TEA_CONFIG")"
# for an OAuth login) vary by auth method. TEA_TOKEN="$(awk '/^logins:/{f=1} f && /^ token:/{print $2; exit}' "$TEA_CONFIG")"
command -v tea >/dev/null 2>&1 && tea whoami >/dev/null 2>&1 && DID_GITEA=1 [ -n "$TEA_URL" ] && [ -n "$TEA_TOKEN" ] && DID_GITEA=1
# --- Git credential helper for Gitea, backed by tea's own login ---
# Coder injects GIT_ASKPASS to answer git's own credential prompts, but it
# only recognizes external auth providers it knows about (github.com is
# configured on the Coder deployment itself) - for any other host it falls
# through to a broken interactive prompt that just hangs a non-interactive
# git subprocess. A credential helper that successfully answers
# `git credential fill` runs *before* GIT_ASKPASS is ever consulted, so
# registering one for git.octoturge.com sidesteps that broken path
# entirely, without touching how Coder handles github.com.
#
# The token itself lives in tea's own store
# (~/.config/tea/credentials.json), which tea keeps fresh (via its
# refresh_token) as a side effect of any authenticated call - this helper
# never keeps its own copy, just re-reads tea's live value every time git
# asks.
if [ "$DID_GITEA" -eq 1 ]; then
GITEA_LOGIN_NAME="$(awk '
/^[[:space:]]*- name:/ { name = $NF }
/url: https:\/\/git\.octoturge\.com/ { print name; exit }
' "$HOME/.config/tea/config.yml" 2>/dev/null)"
if [ -n "$GITEA_LOGIN_NAME" ]; then
mkdir -p "$HOME/.local/bin"
HELPER="$HOME/.local/bin/git-credential-gitea-tea"
cat > "$HELPER" <<'HELPER_EOT'
#!/bin/sh
# Auto-generated by cli-setup-wizard.sh - re-run the wizard with --force to
# regenerate this after logging into a different Gitea account.
LOGIN_NAME="__GITEA_LOGIN_NAME__"
CRED_FILE="$HOME/.config/tea/credentials.json"
case "$1" in
get)
# Authenticated no-op call: gives tea a chance to refresh and persist
# an expired access_token before we read it below.
tea whoami >/dev/null 2>&1
token="$(grep -oP "(?<=\"$LOGIN_NAME\":).*" "$CRED_FILE" 2>/dev/null \
| grep -oP '(?<=access_token\\":\\")[^\\"]*' | head -n1)"
if [ -n "$token" ]; then
echo "username=oauth2"
echo "password=$token"
fi
;;
store|erase)
# No-op: credentials.json (managed by tea) is the single source of
# truth, nothing for git to persist or clear on this end.
;;
esac
HELPER_EOT
sed -i "s/__GITEA_LOGIN_NAME__/$GITEA_LOGIN_NAME/" "$HELPER"
chmod +x "$HELPER"
git config --global credential."https://git.octoturge.com".helper "$HELPER"
echo "Configured git to push/pull https://git.octoturge.com using your tea login."
fi
fi fi
# --- SSH + GPG keys for the external git host(s) selected above --- # --- SSH + GPG keys for the external git host(s) selected above ---
@@ -241,10 +186,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if tea ssh-keys add "${SSH_KEY}.pub" --title "coder-$(hostname)" >/dev/null 2>&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." echo "SSH key added to Gitea."
else else
echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually with: tea ssh-keys add ${SSH_KEY}.pub" echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually at: ${TEA_URL%/}/user/settings/keys"
fi fi
fi fi
@@ -278,10 +226,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if gpg --armor --export "$GPG_KEY_ID" | tea api -X POST /user/gpg_keys -F armored_public_key=@- >/dev/null 2>&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." echo "GPG key added to Gitea."
else else
echo "Couldn't add the GPG key to Gitea automatically (may already be added). Add manually: gpg --armor --export $GPG_KEY_ID | tea api -X POST /user/gpg_keys -F armored_public_key=@-" 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 fi
-7
View File
@@ -204,13 +204,6 @@ resource "docker_container" "workspace" {
host = "code.octoturge.com" host = "code.octoturge.com"
ip = "host-gateway" ip = "host-gateway"
} }
# install-skills.sh curls this at startup to pull the repo's Agent Skills;
# without this the workspace's public DNS answer for git.octoturge.com
# NAT-hairpins back to the LAN and times out (curl: (28)).
host {
host = "git.octoturge.com"
ip = "host-gateway"
}
volumes { volumes {
container_path = "/home/coder" container_path = "/home/coder"
volume_name = docker_volume.home_volume.name volume_name = docker_volume.home_volume.name
-1
View File
@@ -1 +0,0 @@
3
+15 -64
View File
@@ -143,66 +143,11 @@ else
fi fi
fi fi
fi fi
# `tea whoami` succeeds regardless of how the login was done (personal TEA_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/tea/config.yml"
# access token or OAuth) - more reliable than parsing tea's own if command -v tea >/dev/null 2>&1 && [ -f "$TEA_CONFIG" ]; then
# config.yml, whose indentation and fields (no plaintext `token:` at all TEA_URL="$(awk '/^logins:/{f=1} f && /^ url:/{print $2; exit}' "$TEA_CONFIG")"
# for an OAuth login) vary by auth method. TEA_TOKEN="$(awk '/^logins:/{f=1} f && /^ token:/{print $2; exit}' "$TEA_CONFIG")"
command -v tea >/dev/null 2>&1 && tea whoami >/dev/null 2>&1 && DID_GITEA=1 [ -n "$TEA_URL" ] && [ -n "$TEA_TOKEN" ] && DID_GITEA=1
# --- Git credential helper for Gitea, backed by tea's own login ---
# Coder injects GIT_ASKPASS to answer git's own credential prompts, but it
# only recognizes external auth providers it knows about (github.com is
# configured on the Coder deployment itself) - for any other host it falls
# through to a broken interactive prompt that just hangs a non-interactive
# git subprocess. A credential helper that successfully answers
# `git credential fill` runs *before* GIT_ASKPASS is ever consulted, so
# registering one for git.octoturge.com sidesteps that broken path
# entirely, without touching how Coder handles github.com.
#
# The token itself lives in tea's own store
# (~/.config/tea/credentials.json), which tea keeps fresh (via its
# refresh_token) as a side effect of any authenticated call - this helper
# never keeps its own copy, just re-reads tea's live value every time git
# asks.
if [ "$DID_GITEA" -eq 1 ]; then
GITEA_LOGIN_NAME="$(awk '
/^[[:space:]]*- name:/ { name = $NF }
/url: https:\/\/git\.octoturge\.com/ { print name; exit }
' "$HOME/.config/tea/config.yml" 2>/dev/null)"
if [ -n "$GITEA_LOGIN_NAME" ]; then
mkdir -p "$HOME/.local/bin"
HELPER="$HOME/.local/bin/git-credential-gitea-tea"
cat > "$HELPER" <<'HELPER_EOT'
#!/bin/sh
# Auto-generated by cli-setup-wizard.sh - re-run the wizard with --force to
# regenerate this after logging into a different Gitea account.
LOGIN_NAME="__GITEA_LOGIN_NAME__"
CRED_FILE="$HOME/.config/tea/credentials.json"
case "$1" in
get)
# Authenticated no-op call: gives tea a chance to refresh and persist
# an expired access_token before we read it below.
tea whoami >/dev/null 2>&1
token="$(grep -oP "(?<=\"$LOGIN_NAME\":).*" "$CRED_FILE" 2>/dev/null \
| grep -oP '(?<=access_token\\":\\")[^\\"]*' | head -n1)"
if [ -n "$token" ]; then
echo "username=oauth2"
echo "password=$token"
fi
;;
store|erase)
# No-op: credentials.json (managed by tea) is the single source of
# truth, nothing for git to persist or clear on this end.
;;
esac
HELPER_EOT
sed -i "s/__GITEA_LOGIN_NAME__/$GITEA_LOGIN_NAME/" "$HELPER"
chmod +x "$HELPER"
git config --global credential."https://git.octoturge.com".helper "$HELPER"
echo "Configured git to push/pull https://git.octoturge.com using your tea login."
fi
fi fi
# --- SSH + GPG keys for the external git host(s) selected above --- # --- SSH + GPG keys for the external git host(s) selected above ---
@@ -241,10 +186,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if tea ssh-keys add "${SSH_KEY}.pub" --title "coder-$(hostname)" >/dev/null 2>&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." echo "SSH key added to Gitea."
else else
echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually with: tea ssh-keys add ${SSH_KEY}.pub" echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually at: ${TEA_URL%/}/user/settings/keys"
fi fi
fi fi
@@ -278,10 +226,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if gpg --armor --export "$GPG_KEY_ID" | tea api -X POST /user/gpg_keys -F armored_public_key=@- >/dev/null 2>&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." echo "GPG key added to Gitea."
else else
echo "Couldn't add the GPG key to Gitea automatically (may already be added). Add manually: gpg --armor --export $GPG_KEY_ID | tea api -X POST /user/gpg_keys -F armored_public_key=@-" 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 fi
-7
View File
@@ -204,13 +204,6 @@ resource "docker_container" "workspace" {
host = "code.octoturge.com" host = "code.octoturge.com"
ip = "host-gateway" ip = "host-gateway"
} }
# install-skills.sh curls this at startup to pull the repo's Agent Skills;
# without this the workspace's public DNS answer for git.octoturge.com
# NAT-hairpins back to the LAN and times out (curl: (28)).
host {
host = "git.octoturge.com"
ip = "host-gateway"
}
volumes { volumes {
container_path = "/home/coder" container_path = "/home/coder"
volume_name = docker_volume.home_volume.name volume_name = docker_volume.home_volume.name
-1
View File
@@ -1 +0,0 @@
3
+15 -64
View File
@@ -143,66 +143,11 @@ else
fi fi
fi fi
fi fi
# `tea whoami` succeeds regardless of how the login was done (personal TEA_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/tea/config.yml"
# access token or OAuth) - more reliable than parsing tea's own if command -v tea >/dev/null 2>&1 && [ -f "$TEA_CONFIG" ]; then
# config.yml, whose indentation and fields (no plaintext `token:` at all TEA_URL="$(awk '/^logins:/{f=1} f && /^ url:/{print $2; exit}' "$TEA_CONFIG")"
# for an OAuth login) vary by auth method. TEA_TOKEN="$(awk '/^logins:/{f=1} f && /^ token:/{print $2; exit}' "$TEA_CONFIG")"
command -v tea >/dev/null 2>&1 && tea whoami >/dev/null 2>&1 && DID_GITEA=1 [ -n "$TEA_URL" ] && [ -n "$TEA_TOKEN" ] && DID_GITEA=1
# --- Git credential helper for Gitea, backed by tea's own login ---
# Coder injects GIT_ASKPASS to answer git's own credential prompts, but it
# only recognizes external auth providers it knows about (github.com is
# configured on the Coder deployment itself) - for any other host it falls
# through to a broken interactive prompt that just hangs a non-interactive
# git subprocess. A credential helper that successfully answers
# `git credential fill` runs *before* GIT_ASKPASS is ever consulted, so
# registering one for git.octoturge.com sidesteps that broken path
# entirely, without touching how Coder handles github.com.
#
# The token itself lives in tea's own store
# (~/.config/tea/credentials.json), which tea keeps fresh (via its
# refresh_token) as a side effect of any authenticated call - this helper
# never keeps its own copy, just re-reads tea's live value every time git
# asks.
if [ "$DID_GITEA" -eq 1 ]; then
GITEA_LOGIN_NAME="$(awk '
/^[[:space:]]*- name:/ { name = $NF }
/url: https:\/\/git\.octoturge\.com/ { print name; exit }
' "$HOME/.config/tea/config.yml" 2>/dev/null)"
if [ -n "$GITEA_LOGIN_NAME" ]; then
mkdir -p "$HOME/.local/bin"
HELPER="$HOME/.local/bin/git-credential-gitea-tea"
cat > "$HELPER" <<'HELPER_EOT'
#!/bin/sh
# Auto-generated by cli-setup-wizard.sh - re-run the wizard with --force to
# regenerate this after logging into a different Gitea account.
LOGIN_NAME="__GITEA_LOGIN_NAME__"
CRED_FILE="$HOME/.config/tea/credentials.json"
case "$1" in
get)
# Authenticated no-op call: gives tea a chance to refresh and persist
# an expired access_token before we read it below.
tea whoami >/dev/null 2>&1
token="$(grep -oP "(?<=\"$LOGIN_NAME\":).*" "$CRED_FILE" 2>/dev/null \
| grep -oP '(?<=access_token\\":\\")[^\\"]*' | head -n1)"
if [ -n "$token" ]; then
echo "username=oauth2"
echo "password=$token"
fi
;;
store|erase)
# No-op: credentials.json (managed by tea) is the single source of
# truth, nothing for git to persist or clear on this end.
;;
esac
HELPER_EOT
sed -i "s/__GITEA_LOGIN_NAME__/$GITEA_LOGIN_NAME/" "$HELPER"
chmod +x "$HELPER"
git config --global credential."https://git.octoturge.com".helper "$HELPER"
echo "Configured git to push/pull https://git.octoturge.com using your tea login."
fi
fi fi
# --- SSH + GPG keys for the external git host(s) selected above --- # --- SSH + GPG keys for the external git host(s) selected above ---
@@ -241,10 +186,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if tea ssh-keys add "${SSH_KEY}.pub" --title "coder-$(hostname)" >/dev/null 2>&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." echo "SSH key added to Gitea."
else else
echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually with: tea ssh-keys add ${SSH_KEY}.pub" echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually at: ${TEA_URL%/}/user/settings/keys"
fi fi
fi fi
@@ -278,10 +226,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if gpg --armor --export "$GPG_KEY_ID" | tea api -X POST /user/gpg_keys -F armored_public_key=@- >/dev/null 2>&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." echo "GPG key added to Gitea."
else else
echo "Couldn't add the GPG key to Gitea automatically (may already be added). Add manually: gpg --armor --export $GPG_KEY_ID | tea api -X POST /user/gpg_keys -F armored_public_key=@-" 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 fi
-7
View File
@@ -204,13 +204,6 @@ resource "docker_container" "workspace" {
host = "code.octoturge.com" host = "code.octoturge.com"
ip = "host-gateway" ip = "host-gateway"
} }
# install-skills.sh curls this at startup to pull the repo's Agent Skills;
# without this the workspace's public DNS answer for git.octoturge.com
# NAT-hairpins back to the LAN and times out (curl: (28)).
host {
host = "git.octoturge.com"
ip = "host-gateway"
}
volumes { volumes {
container_path = "/home/coder" container_path = "/home/coder"
volume_name = docker_volume.home_volume.name volume_name = docker_volume.home_volume.name
-1
View File
@@ -1 +0,0 @@
3
+15 -64
View File
@@ -143,66 +143,11 @@ else
fi fi
fi fi
fi fi
# `tea whoami` succeeds regardless of how the login was done (personal TEA_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/tea/config.yml"
# access token or OAuth) - more reliable than parsing tea's own if command -v tea >/dev/null 2>&1 && [ -f "$TEA_CONFIG" ]; then
# config.yml, whose indentation and fields (no plaintext `token:` at all TEA_URL="$(awk '/^logins:/{f=1} f && /^ url:/{print $2; exit}' "$TEA_CONFIG")"
# for an OAuth login) vary by auth method. TEA_TOKEN="$(awk '/^logins:/{f=1} f && /^ token:/{print $2; exit}' "$TEA_CONFIG")"
command -v tea >/dev/null 2>&1 && tea whoami >/dev/null 2>&1 && DID_GITEA=1 [ -n "$TEA_URL" ] && [ -n "$TEA_TOKEN" ] && DID_GITEA=1
# --- Git credential helper for Gitea, backed by tea's own login ---
# Coder injects GIT_ASKPASS to answer git's own credential prompts, but it
# only recognizes external auth providers it knows about (github.com is
# configured on the Coder deployment itself) - for any other host it falls
# through to a broken interactive prompt that just hangs a non-interactive
# git subprocess. A credential helper that successfully answers
# `git credential fill` runs *before* GIT_ASKPASS is ever consulted, so
# registering one for git.octoturge.com sidesteps that broken path
# entirely, without touching how Coder handles github.com.
#
# The token itself lives in tea's own store
# (~/.config/tea/credentials.json), which tea keeps fresh (via its
# refresh_token) as a side effect of any authenticated call - this helper
# never keeps its own copy, just re-reads tea's live value every time git
# asks.
if [ "$DID_GITEA" -eq 1 ]; then
GITEA_LOGIN_NAME="$(awk '
/^[[:space:]]*- name:/ { name = $NF }
/url: https:\/\/git\.octoturge\.com/ { print name; exit }
' "$HOME/.config/tea/config.yml" 2>/dev/null)"
if [ -n "$GITEA_LOGIN_NAME" ]; then
mkdir -p "$HOME/.local/bin"
HELPER="$HOME/.local/bin/git-credential-gitea-tea"
cat > "$HELPER" <<'HELPER_EOT'
#!/bin/sh
# Auto-generated by cli-setup-wizard.sh - re-run the wizard with --force to
# regenerate this after logging into a different Gitea account.
LOGIN_NAME="__GITEA_LOGIN_NAME__"
CRED_FILE="$HOME/.config/tea/credentials.json"
case "$1" in
get)
# Authenticated no-op call: gives tea a chance to refresh and persist
# an expired access_token before we read it below.
tea whoami >/dev/null 2>&1
token="$(grep -oP "(?<=\"$LOGIN_NAME\":).*" "$CRED_FILE" 2>/dev/null \
| grep -oP '(?<=access_token\\":\\")[^\\"]*' | head -n1)"
if [ -n "$token" ]; then
echo "username=oauth2"
echo "password=$token"
fi
;;
store|erase)
# No-op: credentials.json (managed by tea) is the single source of
# truth, nothing for git to persist or clear on this end.
;;
esac
HELPER_EOT
sed -i "s/__GITEA_LOGIN_NAME__/$GITEA_LOGIN_NAME/" "$HELPER"
chmod +x "$HELPER"
git config --global credential."https://git.octoturge.com".helper "$HELPER"
echo "Configured git to push/pull https://git.octoturge.com using your tea login."
fi
fi fi
# --- SSH + GPG keys for the external git host(s) selected above --- # --- SSH + GPG keys for the external git host(s) selected above ---
@@ -241,10 +186,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if tea ssh-keys add "${SSH_KEY}.pub" --title "coder-$(hostname)" >/dev/null 2>&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." echo "SSH key added to Gitea."
else else
echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually with: tea ssh-keys add ${SSH_KEY}.pub" echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually at: ${TEA_URL%/}/user/settings/keys"
fi fi
fi fi
@@ -278,10 +226,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if gpg --armor --export "$GPG_KEY_ID" | tea api -X POST /user/gpg_keys -F armored_public_key=@- >/dev/null 2>&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." echo "GPG key added to Gitea."
else else
echo "Couldn't add the GPG key to Gitea automatically (may already be added). Add manually: gpg --armor --export $GPG_KEY_ID | tea api -X POST /user/gpg_keys -F armored_public_key=@-" 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 fi
-7
View File
@@ -204,13 +204,6 @@ resource "docker_container" "workspace" {
host = "code.octoturge.com" host = "code.octoturge.com"
ip = "host-gateway" ip = "host-gateway"
} }
# install-skills.sh curls this at startup to pull the repo's Agent Skills;
# without this the workspace's public DNS answer for git.octoturge.com
# NAT-hairpins back to the LAN and times out (curl: (28)).
host {
host = "git.octoturge.com"
ip = "host-gateway"
}
volumes { volumes {
container_path = "/home/coder" container_path = "/home/coder"
volume_name = docker_volume.home_volume.name volume_name = docker_volume.home_volume.name
-1
View File
@@ -1 +0,0 @@
3
+15 -64
View File
@@ -143,66 +143,11 @@ else
fi fi
fi fi
fi fi
# `tea whoami` succeeds regardless of how the login was done (personal TEA_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/tea/config.yml"
# access token or OAuth) - more reliable than parsing tea's own if command -v tea >/dev/null 2>&1 && [ -f "$TEA_CONFIG" ]; then
# config.yml, whose indentation and fields (no plaintext `token:` at all TEA_URL="$(awk '/^logins:/{f=1} f && /^ url:/{print $2; exit}' "$TEA_CONFIG")"
# for an OAuth login) vary by auth method. TEA_TOKEN="$(awk '/^logins:/{f=1} f && /^ token:/{print $2; exit}' "$TEA_CONFIG")"
command -v tea >/dev/null 2>&1 && tea whoami >/dev/null 2>&1 && DID_GITEA=1 [ -n "$TEA_URL" ] && [ -n "$TEA_TOKEN" ] && DID_GITEA=1
# --- Git credential helper for Gitea, backed by tea's own login ---
# Coder injects GIT_ASKPASS to answer git's own credential prompts, but it
# only recognizes external auth providers it knows about (github.com is
# configured on the Coder deployment itself) - for any other host it falls
# through to a broken interactive prompt that just hangs a non-interactive
# git subprocess. A credential helper that successfully answers
# `git credential fill` runs *before* GIT_ASKPASS is ever consulted, so
# registering one for git.octoturge.com sidesteps that broken path
# entirely, without touching how Coder handles github.com.
#
# The token itself lives in tea's own store
# (~/.config/tea/credentials.json), which tea keeps fresh (via its
# refresh_token) as a side effect of any authenticated call - this helper
# never keeps its own copy, just re-reads tea's live value every time git
# asks.
if [ "$DID_GITEA" -eq 1 ]; then
GITEA_LOGIN_NAME="$(awk '
/^[[:space:]]*- name:/ { name = $NF }
/url: https:\/\/git\.octoturge\.com/ { print name; exit }
' "$HOME/.config/tea/config.yml" 2>/dev/null)"
if [ -n "$GITEA_LOGIN_NAME" ]; then
mkdir -p "$HOME/.local/bin"
HELPER="$HOME/.local/bin/git-credential-gitea-tea"
cat > "$HELPER" <<'HELPER_EOT'
#!/bin/sh
# Auto-generated by cli-setup-wizard.sh - re-run the wizard with --force to
# regenerate this after logging into a different Gitea account.
LOGIN_NAME="__GITEA_LOGIN_NAME__"
CRED_FILE="$HOME/.config/tea/credentials.json"
case "$1" in
get)
# Authenticated no-op call: gives tea a chance to refresh and persist
# an expired access_token before we read it below.
tea whoami >/dev/null 2>&1
token="$(grep -oP "(?<=\"$LOGIN_NAME\":).*" "$CRED_FILE" 2>/dev/null \
| grep -oP '(?<=access_token\\":\\")[^\\"]*' | head -n1)"
if [ -n "$token" ]; then
echo "username=oauth2"
echo "password=$token"
fi
;;
store|erase)
# No-op: credentials.json (managed by tea) is the single source of
# truth, nothing for git to persist or clear on this end.
;;
esac
HELPER_EOT
sed -i "s/__GITEA_LOGIN_NAME__/$GITEA_LOGIN_NAME/" "$HELPER"
chmod +x "$HELPER"
git config --global credential."https://git.octoturge.com".helper "$HELPER"
echo "Configured git to push/pull https://git.octoturge.com using your tea login."
fi
fi fi
# --- SSH + GPG keys for the external git host(s) selected above --- # --- SSH + GPG keys for the external git host(s) selected above ---
@@ -241,10 +186,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if tea ssh-keys add "${SSH_KEY}.pub" --title "coder-$(hostname)" >/dev/null 2>&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." echo "SSH key added to Gitea."
else else
echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually with: tea ssh-keys add ${SSH_KEY}.pub" echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually at: ${TEA_URL%/}/user/settings/keys"
fi fi
fi fi
@@ -278,10 +226,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if gpg --armor --export "$GPG_KEY_ID" | tea api -X POST /user/gpg_keys -F armored_public_key=@- >/dev/null 2>&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." echo "GPG key added to Gitea."
else else
echo "Couldn't add the GPG key to Gitea automatically (may already be added). Add manually: gpg --armor --export $GPG_KEY_ID | tea api -X POST /user/gpg_keys -F armored_public_key=@-" 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 fi
-7
View File
@@ -204,13 +204,6 @@ resource "docker_container" "workspace" {
host = "code.octoturge.com" host = "code.octoturge.com"
ip = "host-gateway" ip = "host-gateway"
} }
# install-skills.sh curls this at startup to pull the repo's Agent Skills;
# without this the workspace's public DNS answer for git.octoturge.com
# NAT-hairpins back to the LAN and times out (curl: (28)).
host {
host = "git.octoturge.com"
ip = "host-gateway"
}
volumes { volumes {
container_path = "/home/coder" container_path = "/home/coder"
volume_name = docker_volume.home_volume.name volume_name = docker_volume.home_volume.name
+17 -92
View File
@@ -11,87 +11,34 @@ ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
locales sudo ca-certificates gnupg curl wget \ locales sudo ca-certificates gnupg curl wget \
&& locale-gen en_US.UTF-8 \ && locale-gen en_US.UTF-8 \
&& apt-get clean \ && rm -rf /var/lib/apt/lists/*
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
ENV LANG=en_US.UTF-8 \ ENV LANG=en_US.UTF-8 \
LANGUAGE=en_US:en \ LANGUAGE=en_US:en \
LC_ALL=en_US.UTF-8 LC_ALL=en_US.UTF-8
# Google Chrome (stable), for the Browse Lite VS Code extension's embedded
# browser preview. Ubuntu's own `chromium-browser` apt package is just a
# snap wrapper and doesn't work in a container (no snapd) - Google's own
# .deb is the reliable way to get a real Chrome binary here. amd64 only
# (Google doesn't publish a Chrome .deb for arm64), which matches this
# repo's single x86_64 Docker host.
RUN curl -fsSL https://dl.google.com/linux/linux_signing_key.pub \
| gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg \
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" \
> /etc/apt/sources.list.d/google-chrome.list \
&& apt-get update && apt-get install -y google-chrome-stable \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
# Core build toolchain, crypto/DB headers, Tauri 2 / WebKit GUI prerequisites, # Core build toolchain, crypto/DB headers, Tauri 2 / WebKit GUI prerequisites,
# X11 dev libs, DB CLI clients, Python + OpenCV, protobuf compiler. # X11 dev libs, DB CLI clients, Python + OpenCV, protobuf compiler.
#
# Split into several RUN steps (rather than one big apt-get install) so no
# single resulting layer is too large to push to the registry - it sits
# behind a reverse proxy with a request body size cap, and a couple of these
# packages (llvm, libopencv-dev, libwebkit2gtk-4.1-dev) are individually
# large enough to blow past it if lumped together with everything else.
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential pkg-config cmake \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
RUN apt-get update && apt-get install -y --no-install-recommends \
clang llvm \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential pkg-config cmake clang llvm \
git git-lfs jq unzip tar file htop tree tmux zsh openssh-client \ git git-lfs jq unzip tar file htop tree tmux zsh openssh-client \
&& git lfs install --system \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
RUN apt-get update && apt-get install -y --no-install-recommends \
libssl-dev libpq-dev libsqlite3-dev \ libssl-dev libpq-dev libsqlite3-dev \
postgresql-client redis-tools sqlite3 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
RUN apt-get update && apt-get install -y --no-install-recommends \
libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev libxdo-dev \ libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev libxdo-dev \
libgtk-3-dev libsoup-3.0-dev \ libgtk-3-dev libsoup-3.0-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
RUN apt-get update && apt-get install -y --no-install-recommends \
libx11-dev libxext-dev libxrender-dev libxtst-dev libxi-dev \ libx11-dev libxext-dev libxrender-dev libxtst-dev libxi-dev \
&& apt-get clean \ postgresql-client redis-tools sqlite3 \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip python3-venv python3-dev \ python3 python3-pip python3-venv python3-dev \
protobuf-compiler \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
RUN apt-get update && apt-get install -y --no-install-recommends \
libopencv-dev \ libopencv-dev \
&& apt-get clean \ protobuf-compiler \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* && git lfs install --system \
&& rm -rf /var/lib/apt/lists/*
# Node.js LTS (22.x) plus npm/pnpm/yarn as root so global bins land on the # Node.js LTS (22.x) plus npm/pnpm/yarn as root so global bins land on the
# system PATH for every user. # system PATH for every user.
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \ && apt-get install -y --no-install-recommends nodejs \
&& apt-get clean \ && npm install -g pnpm yarn \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* && rm -rf /var/lib/apt/lists/*
RUN npm install -g pnpm yarn
# Global Python prototyping packages: CV, ONNX runtime, CPU-only torch wheel. # Global Python prototyping packages: CV, ONNX runtime, CPU-only torch wheel.
# Ubuntu 24.04's system Python is PEP 668 externally-managed; this is a # Ubuntu 24.04's system Python is PEP 668 externally-managed; this is a
@@ -101,14 +48,9 @@ RUN npm install -g pnpm yarn
# file (dpkg-installed, not pip-installed), so `pip install --upgrade pip` # file (dpkg-installed, not pip-installed), so `pip install --upgrade pip`
# fails trying to uninstall it in place - and it's unneeded anyway, the # fails trying to uninstall it in place - and it's unneeded anyway, the
# packages below install fine under the stock version. # packages below install fine under the stock version.
#
# Each package gets its own RUN/layer for the same reverse-proxy body-size
# reason as the apt-get split above - torch's CPU wheel and opencv-python's
# wheel are each large enough on their own to be worth isolating.
RUN python3 -m pip install --break-system-packages --no-cache-dir numpy
RUN python3 -m pip install --break-system-packages --no-cache-dir opencv-python-headless
RUN python3 -m pip install --break-system-packages --no-cache-dir onnxruntime
RUN python3 -m pip install --break-system-packages --no-cache-dir \ RUN python3 -m pip install --break-system-packages --no-cache-dir \
numpy opencv-python-headless onnxruntime \
&& python3 -m pip install --break-system-packages --no-cache-dir \
torch --index-url https://download.pytorch.org/whl/cpu torch --index-url https://download.pytorch.org/whl/cpu
# Standard non-root dev user with passwordless sudo. Ubuntu 24.04's base # Standard non-root dev user with passwordless sudo. Ubuntu 24.04's base
@@ -133,32 +75,15 @@ WORKDIR /home/coder
# Rust via rustup: stable toolchain, rust-analyzer/clippy/rustfmt/rust-src, # Rust via rustup: stable toolchain, rust-analyzer/clippy/rustfmt/rust-src,
# native + musl targets for x86_64/aarch64, and cargo helper utilities. # native + musl targets for x86_64/aarch64, and cargo helper utilities.
#
# Split into one RUN per target/tool (rather than one big chained command) so
# no single layer is too large to push to the registry, for the same
# reverse-proxy body-size reason as the apt-get split above - the base
# toolchain and each additional target's std library are each sizeable, and
# `cargo install` leaves a build/registry cache behind that needs clearing
# inside its own RUN, or it would just bloat that same layer instead.
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
--default-toolchain stable --profile default \ --default-toolchain stable --profile default \
&& rm -rf "$RUSTUP_HOME"/tmp "$RUSTUP_HOME"/downloads && rustup component add rustfmt clippy rust-analyzer rust-src \
&& rustup target add \
RUN rustup component add rustfmt clippy rust-analyzer rust-src x86_64-unknown-linux-gnu \
x86_64-unknown-linux-musl \
RUN rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu \
RUN rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl \
RUN rustup target add aarch64-unknown-linux-gnu && cargo install --locked cargo-watch cargo-edit cross bacon
RUN rustup target add aarch64-unknown-linux-musl
RUN cargo install --locked cargo-watch \
&& rm -rf "$CARGO_HOME"/registry "$CARGO_HOME"/git
RUN cargo install --locked cargo-edit \
&& rm -rf "$CARGO_HOME"/registry "$CARGO_HOME"/git
RUN cargo install --locked cross \
&& rm -rf "$CARGO_HOME"/registry "$CARGO_HOME"/git
RUN cargo install --locked bacon \
&& rm -rf "$CARGO_HOME"/registry "$CARGO_HOME"/git
# Bun: global runtime for the ElysiaJS backend and fast scripting. # Bun: global runtime for the ElysiaJS backend and fast scripting.
RUN curl -fsSL https://bun.sh/install | bash RUN curl -fsSL https://bun.sh/install | bash
-1
View File
@@ -1 +0,0 @@
3
+15 -64
View File
@@ -143,66 +143,11 @@ else
fi fi
fi fi
fi fi
# `tea whoami` succeeds regardless of how the login was done (personal TEA_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/tea/config.yml"
# access token or OAuth) - more reliable than parsing tea's own if command -v tea >/dev/null 2>&1 && [ -f "$TEA_CONFIG" ]; then
# config.yml, whose indentation and fields (no plaintext `token:` at all TEA_URL="$(awk '/^logins:/{f=1} f && /^ url:/{print $2; exit}' "$TEA_CONFIG")"
# for an OAuth login) vary by auth method. TEA_TOKEN="$(awk '/^logins:/{f=1} f && /^ token:/{print $2; exit}' "$TEA_CONFIG")"
command -v tea >/dev/null 2>&1 && tea whoami >/dev/null 2>&1 && DID_GITEA=1 [ -n "$TEA_URL" ] && [ -n "$TEA_TOKEN" ] && DID_GITEA=1
# --- Git credential helper for Gitea, backed by tea's own login ---
# Coder injects GIT_ASKPASS to answer git's own credential prompts, but it
# only recognizes external auth providers it knows about (github.com is
# configured on the Coder deployment itself) - for any other host it falls
# through to a broken interactive prompt that just hangs a non-interactive
# git subprocess. A credential helper that successfully answers
# `git credential fill` runs *before* GIT_ASKPASS is ever consulted, so
# registering one for git.octoturge.com sidesteps that broken path
# entirely, without touching how Coder handles github.com.
#
# The token itself lives in tea's own store
# (~/.config/tea/credentials.json), which tea keeps fresh (via its
# refresh_token) as a side effect of any authenticated call - this helper
# never keeps its own copy, just re-reads tea's live value every time git
# asks.
if [ "$DID_GITEA" -eq 1 ]; then
GITEA_LOGIN_NAME="$(awk '
/^[[:space:]]*- name:/ { name = $NF }
/url: https:\/\/git\.octoturge\.com/ { print name; exit }
' "$HOME/.config/tea/config.yml" 2>/dev/null)"
if [ -n "$GITEA_LOGIN_NAME" ]; then
mkdir -p "$HOME/.local/bin"
HELPER="$HOME/.local/bin/git-credential-gitea-tea"
cat > "$HELPER" <<'HELPER_EOT'
#!/bin/sh
# Auto-generated by cli-setup-wizard.sh - re-run the wizard with --force to
# regenerate this after logging into a different Gitea account.
LOGIN_NAME="__GITEA_LOGIN_NAME__"
CRED_FILE="$HOME/.config/tea/credentials.json"
case "$1" in
get)
# Authenticated no-op call: gives tea a chance to refresh and persist
# an expired access_token before we read it below.
tea whoami >/dev/null 2>&1
token="$(grep -oP "(?<=\"$LOGIN_NAME\":).*" "$CRED_FILE" 2>/dev/null \
| grep -oP '(?<=access_token\\":\\")[^\\"]*' | head -n1)"
if [ -n "$token" ]; then
echo "username=oauth2"
echo "password=$token"
fi
;;
store|erase)
# No-op: credentials.json (managed by tea) is the single source of
# truth, nothing for git to persist or clear on this end.
;;
esac
HELPER_EOT
sed -i "s/__GITEA_LOGIN_NAME__/$GITEA_LOGIN_NAME/" "$HELPER"
chmod +x "$HELPER"
git config --global credential."https://git.octoturge.com".helper "$HELPER"
echo "Configured git to push/pull https://git.octoturge.com using your tea login."
fi
fi fi
# --- SSH + GPG keys for the external git host(s) selected above --- # --- SSH + GPG keys for the external git host(s) selected above ---
@@ -241,10 +186,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if tea ssh-keys add "${SSH_KEY}.pub" --title "coder-$(hostname)" >/dev/null 2>&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." echo "SSH key added to Gitea."
else else
echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually with: tea ssh-keys add ${SSH_KEY}.pub" echo "Couldn't add the SSH key to Gitea automatically (may already be added). Add manually at: ${TEA_URL%/}/user/settings/keys"
fi fi
fi fi
@@ -278,10 +226,13 @@ if [ "$DID_GITHUB" -eq 1 ] || [ "$DID_GITEA" -eq 1 ]; then
fi fi
if [ "$DID_GITEA" -eq 1 ]; then if [ "$DID_GITEA" -eq 1 ]; then
if gpg --armor --export "$GPG_KEY_ID" | tea api -X POST /user/gpg_keys -F armored_public_key=@- >/dev/null 2>&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." echo "GPG key added to Gitea."
else else
echo "Couldn't add the GPG key to Gitea automatically (may already be added). Add manually: gpg --armor --export $GPG_KEY_ID | tea api -X POST /user/gpg_keys -F armored_public_key=@-" 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 fi
+9 -18
View File
@@ -193,18 +193,16 @@ resource "docker_volume" "home_volume" {
} }
} }
# Pulls the full Web Applications toolchain (Rust/Tauri 2, Bun/Node/pnpm, # Builds the full Web Applications toolchain (Rust/Tauri 2, Bun/Node/pnpm,
# Python CV/ONNX, DB clients - see ./Dockerfile) instead of building it here. # Python CV/ONNX, DB clients - see ./Dockerfile) from this template's own
# The image is built and pushed by .gitea/workflows/coder-templates.yml's # directory, so no external registry push is required. The tag embeds the
# build-web-image job, tagged with the same Dockerfile hash this resource # Dockerfile's hash so a Dockerfile edit forces a rebuild on next apply/push,
# computes - so a Dockerfile edit always resolves to the matching image, and # while an unchanged Dockerfile reuses the cached image.
# an unchanged Dockerfile resolves to one already built (CI skips rebuilding
# it, and this pull is normally a same-host cache hit rather than a real
# network pull, since CI and this Coder deployment share one Docker daemon).
# Moves the ~15-20min Rust toolchain compile off of "someone is waiting to
# create a workspace" and onto CI, where it runs once per Dockerfile change.
resource "docker_image" "web" { resource "docker_image" "web" {
name = "git.octoturge.com/octo-tech/profiles-web:${filesha1("${path.module}/Dockerfile")}" name = "coder-profiles-web:${filesha1("${path.module}/Dockerfile")}"
build {
context = path.module
}
keep_locally = true keep_locally = true
} }
@@ -226,13 +224,6 @@ resource "docker_container" "workspace" {
host = "code.octoturge.com" host = "code.octoturge.com"
ip = "host-gateway" ip = "host-gateway"
} }
# install-skills.sh curls this at startup to pull the repo's Agent Skills;
# without this the workspace's public DNS answer for git.octoturge.com
# NAT-hairpins back to the LAN and times out (curl: (28)).
host {
host = "git.octoturge.com"
ip = "host-gateway"
}
volumes { volumes {
container_path = "/home/coder" container_path = "/home/coder"
volume_name = docker_volume.home_volume.name volume_name = docker_volume.home_volume.name
File diff suppressed because one or more lines are too long