Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e8a8f93f9d | |||
| 557d6a3d84 | |||
| 354536dad5 | |||
| 1be7c00b84 | |||
| 52cc84650a | |||
| 0c53bb6554 | |||
| a302e21609 | |||
| 60e63edd61 | |||
| 22429aa4e3 | |||
| 5b32539d65 |
@@ -19,8 +19,6 @@ on:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "templates/**"
|
||||
- "scripts/**"
|
||||
- "profile-templates/**"
|
||||
- ".gitea/workflows/coder-templates.yml"
|
||||
workflow_dispatch: {}
|
||||
|
||||
@@ -63,7 +61,7 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
|
||||
OLD_DIRS="$(git ls-tree -d --name-only "$PREV_SHA" -- templates 2>/dev/null | xargs -n1 basename 2>/dev/null || true)"
|
||||
OLD_DIRS="$(git ls-tree -d --name-only "$PREV_SHA" -- 'templates/*' 2>/dev/null | xargs -n1 basename 2>/dev/null || true)"
|
||||
if [ -z "$OLD_DIRS" ]; then
|
||||
echo "No templates/ directory at $PREV_SHA, nothing to diff. Skipping."
|
||||
exit 0
|
||||
|
||||
@@ -8,9 +8,11 @@ name: Rotate Coder API Token
|
||||
# it replaced.
|
||||
#
|
||||
# One-time bootstrap (see README "Auto-provisioning" section): a
|
||||
# GITEA_ROTATION_TOKEN secret holding a Gitea personal access token
|
||||
# (write:repository scope, no expiration) with permission to write this
|
||||
# repo's Actions secrets. Nothing else needs to touch this ever again.
|
||||
# ROTATION_PAT secret holding a Gitea personal access token (write:repository
|
||||
# scope, no expiration) with permission to write this repo's Actions secrets.
|
||||
# Not named GITEA_ROTATION_TOKEN because Gitea reserves the GITEA_ prefix for
|
||||
# its own automatic tokens/variables and rejects secrets with that prefix.
|
||||
# Nothing else needs to touch this ever again.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -23,7 +25,7 @@ jobs:
|
||||
env:
|
||||
CODER_URL: ${{ secrets.CODER_URL }}
|
||||
CODER_SESSION_TOKEN: ${{ secrets.CODER_SESSION_TOKEN }}
|
||||
GITEA_ROTATION_TOKEN: ${{ secrets.GITEA_ROTATION_TOKEN }}
|
||||
ROTATION_PAT: ${{ secrets.ROTATION_PAT }}
|
||||
GITEA_API_URL: ${{ github.server_url }}/api/v1
|
||||
GITEA_REPO_PATH: ${{ github.repository }}
|
||||
steps:
|
||||
@@ -56,7 +58,7 @@ jobs:
|
||||
BODY="$(jq -n --arg data "$NEW_TOKEN" '{data:$data}')"
|
||||
HTTP_STATUS="$(curl -s -o /tmp/put-secret.out -w '%{http_code}' \
|
||||
-X PUT \
|
||||
-H "Authorization: token ${GITEA_ROTATION_TOKEN}" \
|
||||
-H "Authorization: token ${ROTATION_PAT}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$BODY" \
|
||||
"${GITEA_API_URL}/repos/${GITEA_REPO_PATH}/actions/secrets/CODER_SESSION_TOKEN")"
|
||||
|
||||
@@ -9,25 +9,58 @@ do and which didn't actually work.
|
||||
|
||||
```
|
||||
templates/
|
||||
default/main.tf # Standard Default Dev
|
||||
3d-printing/main.tf # 3D Printing & Engineering
|
||||
cobol/main.tf # COBOL Modern Mainframe
|
||||
python/main.tf # Python Engineering
|
||||
ttrpg/main.tf # TTRPG & Lore Building
|
||||
web/main.tf # Web Applications
|
||||
scripts/
|
||||
cli-setup-wizard.sh # shared first-run wizard, see below
|
||||
install-skills.sh # shared Agent Skills installer, see below
|
||||
profile-templates/ # VS Code .code-profile exports, one per env
|
||||
default/
|
||||
main.tf # Standard Default Dev
|
||||
profile.code-profile # VS Code .code-profile export for this env
|
||||
cli-setup-wizard.sh # first-run wizard, see below
|
||||
install-skills.sh # Agent Skills installer, see below
|
||||
3d-printing/ (same 4 files) # 3D Printing & Engineering
|
||||
cobol/ (same 4 files) # COBOL Modern Mainframe
|
||||
python/ (same 4 files) # Python Engineering
|
||||
ttrpg/ (same 4 files) # TTRPG & Lore Building
|
||||
web/ (same 4 files, plus Dockerfile) # Web Applications
|
||||
extensions/ # Agent Skills bundles, installed per env (see below)
|
||||
```
|
||||
|
||||
Each `templates/<env>/main.tf` is a complete, independent Coder template
|
||||
(agent, docker container, code-server, JetBrains). They're deliberately not
|
||||
built from a shared Terraform module - only their `locals` block differs
|
||||
(which profile file to read). The naming convention is `profiles-<dir>`
|
||||
(matches what `.gitea/workflows/coder-templates.yml` does automatically -
|
||||
see below). To push by hand:
|
||||
### Web Applications: baked-in toolchain image
|
||||
|
||||
Every other template pulls `codercom/enterprise-base:ubuntu` straight from
|
||||
Docker Hub and installs what little it needs (just Bun) via a `coder_script`
|
||||
at workspace start. `templates/web/` doesn't do that: it needs a large,
|
||||
slow-to-install native toolchain (Rust/rustup with cross targets, the Tauri 2
|
||||
/ WebKit GUI dev libraries, Node + Bun + pnpm/yarn, Python with OpenCV/ONNX/
|
||||
CPU-torch, Postgres/Redis/SQLite CLI clients, protoc, clang/llvm) that would
|
||||
make every workspace start take many minutes if installed on the fly.
|
||||
|
||||
Instead `templates/web/main.tf` builds `templates/web/Dockerfile` at
|
||||
apply/push time via the `docker` provider's `docker_image` resource (`build
|
||||
{ context = path.module }`, same directory as `main.tf` so no `file()`
|
||||
reaches outside the template per the constraint above) and runs the
|
||||
container from that image instead of the enterprise-base one. The image tag
|
||||
embeds `filesha1(Dockerfile)`, so editing the Dockerfile forces a rebuild on
|
||||
the next apply/push while an unchanged Dockerfile reuses Docker's build
|
||||
cache. Because the toolchain lives under `/home/coder` (rustup, cargo, bun),
|
||||
and that path is a fresh *named* Docker volume on a workspace's first boot,
|
||||
Docker's own "populate an empty volume from the image's directory contents"
|
||||
behavior copies all of it into the persistent volume automatically - no
|
||||
extra `coder_script` needed, matching how the `/etc/skel` copy in every
|
||||
template's `startup_script` already relies on that same mechanism.
|
||||
|
||||
Each `templates/<env>/` is a complete, independent Coder template (agent,
|
||||
docker container, code-server) with its own copy of everything `main.tf`
|
||||
needs. They're deliberately not built from a shared Terraform
|
||||
module or shared files elsewhere in the repo: `coder templates push -d
|
||||
templates/<env>` only uploads that one directory to the Coder server, so any
|
||||
`file()` reference reaching outside it (e.g. the old shared
|
||||
`scripts/`/`profile-templates/` layout) fails at push/apply time with
|
||||
`Invalid function argument: ... this function works only with files that
|
||||
are distributed as part of the configuration source code` - discovered the
|
||||
hard way once `coder-templates.yml` actually ran end-to-end. `main.tf`,
|
||||
`profile.code-profile`, `cli-setup-wizard.sh`, and `install-skills.sh` are
|
||||
duplicated per template rather than shared for this reason; only their
|
||||
`locals` block and profile file differ meaningfully. The naming convention
|
||||
is `profiles-<dir>` (matches what `.gitea/workflows/coder-templates.yml`
|
||||
does automatically - see below). To push by hand:
|
||||
|
||||
```sh
|
||||
coder templates push profiles-default -d templates/default
|
||||
@@ -40,7 +73,7 @@ coder templates push profiles-web -d templates/web
|
||||
|
||||
### How the VS Code profile gets applied
|
||||
|
||||
`profile-templates/*.code-profile` is a real VS Code Profile export: a JSON
|
||||
`templates/<env>/profile.code-profile` is a real VS Code Profile export: a JSON
|
||||
file whose `settings` and `extensions` fields are themselves JSON-encoded
|
||||
strings (double/triple-nested). Each template's `main.tf` reads and decodes
|
||||
its matching file **at `terraform apply`/push time** (via `file()` +
|
||||
@@ -62,22 +95,27 @@ never really worked.
|
||||
|
||||
### Bun
|
||||
|
||||
Every template installs [Bun](https://bun.sh) via a `coder_script`
|
||||
(`curl -fsSL https://bun.sh/install | bash`) and hooks `~/.bun/bin` onto
|
||||
`PATH` in `~/.bashrc` (the installer doesn't reliably do this itself in a
|
||||
non-interactive/scripted shell). The CLI setup wizard below uses
|
||||
`bun install -g <pkg>` instead of `npm install -g <pkg>` for everything it
|
||||
installs.
|
||||
Every template except `web` installs [Bun](https://bun.sh) via a
|
||||
`coder_script` (`curl -fsSL https://bun.sh/install | bash`) and hooks
|
||||
`~/.bun/bin` onto `PATH` in `~/.bashrc` (the installer doesn't reliably do
|
||||
this itself in a non-interactive/scripted shell). `web` instead bakes Bun
|
||||
into its Dockerfile with `BUN_INSTALL`/`PATH` set as image `ENV` - see "Web
|
||||
Applications: baked-in toolchain image" above. Either way, the CLI setup
|
||||
wizard below uses `bun install -g <pkg>` instead of `npm install -g <pkg>`
|
||||
for everything it installs.
|
||||
|
||||
### CLI setup wizard
|
||||
|
||||
`scripts/cli-setup-wizard.sh` is dropped onto every workspace and hooked
|
||||
`templates/<env>/cli-setup-wizard.sh` (identical across envs) is dropped
|
||||
onto every workspace and hooked
|
||||
into `~/.bashrc`. It runs in every new interactive terminal - until the user
|
||||
finishes it - and offers to install + log into:
|
||||
|
||||
- **GitHub Copilot CLI** (`bun install -g @github/copilot`, then `copilot login`)
|
||||
- **Google Antigravity CLI** (`curl -fsSL https://antigravity.google/cli/install.sh | bash`, binary `agy`)
|
||||
- **Claude Code CLI** (`bun install -g @anthropic-ai/claude-code`, then `claude`)
|
||||
- **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`)
|
||||
|
||||
It does **not** ask about VS Code extensions, since those are handled by
|
||||
Terraform (see above). Once the user confirms completion it writes a
|
||||
@@ -92,7 +130,7 @@ root `main.tf` copied this folder straight into code-server's VS Code
|
||||
extensions directory, which never worked since these aren't VS Code
|
||||
extension packages.
|
||||
|
||||
Each template now runs `scripts/install-skills.sh` (via a `coder_script`,
|
||||
Each template now runs its own copy of `install-skills.sh` (via a `coder_script`,
|
||||
pulling a fresh zip of this repo from Gitea rather than embedding ~2.5MB
|
||||
into Terraform state) to install skills into all three AI CLIs' personal
|
||||
skills directories:
|
||||
@@ -117,13 +155,13 @@ the `SPECIALTY_SKILLS` env var passed to the script from each template's
|
||||
### Auto-provisioning (Gitea Actions)
|
||||
|
||||
`.gitea/workflows/coder-templates.yml` keeps Coder in sync with this repo on
|
||||
every push to `main` that touches `templates/**`, `scripts/**`, or
|
||||
`profile-templates/**`:
|
||||
every push to `main` that touches `templates/**`:
|
||||
|
||||
- **Add** a new `templates/<env>/` directory -> next push creates a new
|
||||
Coder template `profiles-<env>` automatically. No workflow edits needed.
|
||||
- **Edit** an existing `templates/<env>/main.tf` (or a shared script/profile
|
||||
it references) -> next push updates that template with a new version.
|
||||
- **Edit** an existing `templates/<env>/main.tf` (or its `profile.code-profile`,
|
||||
`cli-setup-wizard.sh`, or `install-skills.sh`) -> next push updates that
|
||||
template with a new version.
|
||||
- **Remove** a `templates/<env>/` directory -> next push deletes
|
||||
`profiles-<env>` from Coder. `coder templates delete` refuses if the
|
||||
template still has active workspaces, so this fails loudly instead of
|
||||
@@ -201,12 +239,14 @@ workflow write to its own repo's secrets):
|
||||
1. Create a Gitea personal access token with **write:repository** scope and
|
||||
**no expiration** (Settings -> Applications -> Generate New Token). This
|
||||
one doesn't rotate itself, so give it a long life up front.
|
||||
2. Add it as a repo/org Actions secret named `GITEA_ROTATION_TOKEN` (same
|
||||
2. Add it as a repo/org Actions secret named `ROTATION_PAT` (same
|
||||
Settings -> Actions -> Secrets page as above - if that tab is missing,
|
||||
see the nav-link workaround / API fallback in the "Auto-provisioning"
|
||||
section above, same `curl -X PUT .../actions/secrets/<name>` pattern,
|
||||
just with `GITEA_ROTATION_TOKEN` as the secret name and the PAT itself as
|
||||
the value).
|
||||
just with `ROTATION_PAT` as the secret name and the PAT itself as the
|
||||
value). Not named `GITEA_ROTATION_TOKEN` - Gitea reserves the `GITEA_`
|
||||
prefix for its own automatic tokens/variables and rejects secrets with
|
||||
that prefix (`Error: invalid variable or secret name`).
|
||||
|
||||
After that, `CODER_SESSION_TOKEN` never needs manual attention again - you
|
||||
can also trigger a rotation on demand from Gitea's Actions tab
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -96,6 +96,47 @@ 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
|
||||
|
||||
# --- 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
|
||||
|
||||
echo ""
|
||||
if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then
|
||||
touch "$WIZARD_DONE_FILE"
|
||||
@@ -6,12 +6,22 @@ terraform {
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
# Not used directly in this config. Existing workspace state from before
|
||||
# the jetbrains module was removed still has resources tagged under this
|
||||
# provider (the module used it internally to fetch IDE metadata) -
|
||||
# terraform init only installs providers the current config declares, so
|
||||
# without this, plan/apply fails with "Missing required provider" while
|
||||
# trying to reconcile/destroy those leftover state entries. Safe to drop
|
||||
# once every workspace has updated past the jetbrains-module version.
|
||||
http = {
|
||||
source = "hashicorp/http"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
env_name = "3D Printing & Engineering"
|
||||
profile = jsondecode(file("${path.module}/../../profile-templates/3D.code-profile"))
|
||||
profile = jsondecode(file("${path.module}/profile.code-profile"))
|
||||
settings_raw = jsondecode(local.profile.settings).settings
|
||||
extensions = [for e in jsondecode(local.profile.extensions) : e.identifier.id]
|
||||
}
|
||||
@@ -141,17 +151,6 @@ module "code-server" {
|
||||
order = 1
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/jetbrains
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "~> 1.1"
|
||||
agent_id = coder_agent.main.id
|
||||
agent_name = "main"
|
||||
folder = "/home/coder"
|
||||
tooltip = "You need to [install JetBrains Toolbox](https://coder.com/docs/user-guides/workspace-access/jetbrains/toolbox) to use this app."
|
||||
}
|
||||
|
||||
resource "docker_volume" "home_volume" {
|
||||
name = "coder-${data.coder_workspace.me.id}-home"
|
||||
# Protect the volume from being deleted due to changes in attributes.
|
||||
@@ -243,7 +242,7 @@ resource "coder_script" "apply_settings" {
|
||||
|
||||
# Drops the shared CLI setup wizard onto the workspace and hooks it into
|
||||
# every new interactive shell (via .bashrc) until the user completes it.
|
||||
# See ../../scripts/cli-setup-wizard.sh for what it actually asks.
|
||||
# See ./cli-setup-wizard.sh for what it actually asks.
|
||||
resource "coder_script" "cli_setup_wizard" {
|
||||
agent_id = coder_agent.main.id
|
||||
display_name = "Install CLI Setup Wizard"
|
||||
@@ -251,8 +250,9 @@ resource "coder_script" "cli_setup_wizard" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
chmod +x /opt/coder/cli-setup-wizard.sh
|
||||
|
||||
MARKER="# >>> coder cli setup wizard >>>"
|
||||
@@ -298,7 +298,7 @@ resource "coder_script" "install_bun" {
|
||||
|
||||
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
|
||||
# Antigravity CLI's skills directories, plus the openscad-parametric skill
|
||||
# from extensions/custom-specialty-plugin. See ../../scripts/install-skills.sh.
|
||||
# from extensions/custom-specialty-plugin. See ./install-skills.sh.
|
||||
resource "coder_script" "install_skills" {
|
||||
agent_id = coder_agent.main.id
|
||||
display_name = "Install Agent Skills"
|
||||
@@ -306,8 +306,9 @@ resource "coder_script" "install_skills" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
chmod +x /opt/coder/install-skills.sh
|
||||
SPECIALTY_SKILLS="openscad-parametric" /opt/coder/install-skills.sh
|
||||
EOT
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,148 @@
|
||||
#!/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
|
||||
|
||||
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
|
||||
|
||||
# --- 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
|
||||
|
||||
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
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Installs this repo's Agent Skills (extensions/{awesome-skills-plugin,
|
||||
# custom-specialty-plugin}/skills/*, each a SKILL.md-based skill directory)
|
||||
# into every AI CLI's personal skills directory:
|
||||
#
|
||||
# Claude Code CLI -> ~/.claude/skills/<name>/
|
||||
# GitHub Copilot CLI -> ~/.copilot/skills/<name>/
|
||||
# Antigravity CLI -> ~/.gemini/config/skills/<name>/ (per antigravity.google/docs/skills;
|
||||
# worth a spot-check if agy doesn't pick these up, some third-party
|
||||
# docs disagree on the exact path)
|
||||
#
|
||||
# Run once at workspace startup via coder_script. Pulls this repo fresh from
|
||||
# Gitea rather than embedding ~2.5MB of skill files into Terraform state.
|
||||
#
|
||||
# Env vars:
|
||||
# SPECIALTY_SKILLS - optional space-separated skill names from
|
||||
# extensions/custom-specialty-plugin/skills/ to install in addition to
|
||||
# the common awesome-skills-plugin bundle (every env gets that one).
|
||||
|
||||
set -e
|
||||
|
||||
REPO_ZIP_URL="https://git.octoturge.com/octoturge/Profiles-for-Coder/archive/main.zip"
|
||||
ZIP_PATH="/tmp/coder-skills-src.zip"
|
||||
WORK_DIR="/tmp/coder-skills-src"
|
||||
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
curl -fsSL "$REPO_ZIP_URL" -o "$ZIP_PATH"
|
||||
mkdir -p "$WORK_DIR"
|
||||
unzip -q -o "$ZIP_PATH" -d "$WORK_DIR"
|
||||
|
||||
INNER_DIR=$(find "$WORK_DIR" -mindepth 1 -maxdepth 1 -type d | head -n1)
|
||||
if [ -z "$INNER_DIR" ]; then
|
||||
echo "install-skills: couldn't find extracted repo contents, skipping." >&2
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TARGET_DIRS=("$HOME/.claude/skills" "$HOME/.copilot/skills" "$HOME/.gemini/config/skills")
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
mkdir -p "$dir"
|
||||
done
|
||||
|
||||
# Common skill bundle, installed for every environment.
|
||||
COMMON_SKILLS_SRC="$INNER_DIR/extensions/awesome-skills-plugin/skills"
|
||||
if [ -d "$COMMON_SKILLS_SRC" ]; then
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
cp -r "$COMMON_SKILLS_SRC/." "$dir/"
|
||||
done
|
||||
echo "install-skills: installed common skill bundle into ${TARGET_DIRS[*]}"
|
||||
else
|
||||
echo "install-skills: common skill bundle not found at $COMMON_SKILLS_SRC, skipping." >&2
|
||||
fi
|
||||
|
||||
# Environment-specific specialty skills, if any were requested.
|
||||
for skill in ${SPECIALTY_SKILLS:-}; do
|
||||
SRC="$INNER_DIR/extensions/custom-specialty-plugin/skills/$skill"
|
||||
if [ -d "$SRC" ]; then
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
cp -r "$SRC" "$dir/$skill"
|
||||
done
|
||||
echo "install-skills: installed specialty skill '$skill'"
|
||||
else
|
||||
echo "install-skills: specialty skill '$skill' not found at $SRC, skipping." >&2
|
||||
fi
|
||||
done
|
||||
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
echo "install-skills: done."
|
||||
+19
-18
@@ -6,12 +6,22 @@ terraform {
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
# Not used directly in this config. Existing workspace state from before
|
||||
# the jetbrains module was removed still has resources tagged under this
|
||||
# provider (the module used it internally to fetch IDE metadata) -
|
||||
# terraform init only installs providers the current config declares, so
|
||||
# without this, plan/apply fails with "Missing required provider" while
|
||||
# trying to reconcile/destroy those leftover state entries. Safe to drop
|
||||
# once every workspace has updated past the jetbrains-module version.
|
||||
http = {
|
||||
source = "hashicorp/http"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
env_name = "COBOL Modern Mainframe"
|
||||
profile = jsondecode(file("${path.module}/../../profile-templates/COBOL.code-profile"))
|
||||
profile = jsondecode(file("${path.module}/profile.code-profile"))
|
||||
settings_raw = jsondecode(local.profile.settings).settings
|
||||
extensions = [for e in jsondecode(local.profile.extensions) : e.identifier.id]
|
||||
}
|
||||
@@ -141,17 +151,6 @@ module "code-server" {
|
||||
order = 1
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/jetbrains
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "~> 1.1"
|
||||
agent_id = coder_agent.main.id
|
||||
agent_name = "main"
|
||||
folder = "/home/coder"
|
||||
tooltip = "You need to [install JetBrains Toolbox](https://coder.com/docs/user-guides/workspace-access/jetbrains/toolbox) to use this app."
|
||||
}
|
||||
|
||||
resource "docker_volume" "home_volume" {
|
||||
name = "coder-${data.coder_workspace.me.id}-home"
|
||||
# Protect the volume from being deleted due to changes in attributes.
|
||||
@@ -243,7 +242,7 @@ resource "coder_script" "apply_settings" {
|
||||
|
||||
# Drops the shared CLI setup wizard onto the workspace and hooks it into
|
||||
# every new interactive shell (via .bashrc) until the user completes it.
|
||||
# See ../../scripts/cli-setup-wizard.sh for what it actually asks.
|
||||
# See ./cli-setup-wizard.sh for what it actually asks.
|
||||
resource "coder_script" "cli_setup_wizard" {
|
||||
agent_id = coder_agent.main.id
|
||||
display_name = "Install CLI Setup Wizard"
|
||||
@@ -251,8 +250,9 @@ resource "coder_script" "cli_setup_wizard" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
chmod +x /opt/coder/cli-setup-wizard.sh
|
||||
|
||||
MARKER="# >>> coder cli setup wizard >>>"
|
||||
@@ -298,7 +298,7 @@ resource "coder_script" "install_bun" {
|
||||
|
||||
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
|
||||
# Antigravity CLI's skills directories, plus the cobol-teacher skill from
|
||||
# extensions/custom-specialty-plugin. See ../../scripts/install-skills.sh.
|
||||
# extensions/custom-specialty-plugin. See ./install-skills.sh.
|
||||
resource "coder_script" "install_skills" {
|
||||
agent_id = coder_agent.main.id
|
||||
display_name = "Install Agent Skills"
|
||||
@@ -306,8 +306,9 @@ resource "coder_script" "install_skills" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
chmod +x /opt/coder/install-skills.sh
|
||||
SPECIALTY_SKILLS="cobol-teacher" /opt/coder/install-skills.sh
|
||||
EOT
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,148 @@
|
||||
#!/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
|
||||
|
||||
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
|
||||
|
||||
# --- 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
|
||||
|
||||
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
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Installs this repo's Agent Skills (extensions/{awesome-skills-plugin,
|
||||
# custom-specialty-plugin}/skills/*, each a SKILL.md-based skill directory)
|
||||
# into every AI CLI's personal skills directory:
|
||||
#
|
||||
# Claude Code CLI -> ~/.claude/skills/<name>/
|
||||
# GitHub Copilot CLI -> ~/.copilot/skills/<name>/
|
||||
# Antigravity CLI -> ~/.gemini/config/skills/<name>/ (per antigravity.google/docs/skills;
|
||||
# worth a spot-check if agy doesn't pick these up, some third-party
|
||||
# docs disagree on the exact path)
|
||||
#
|
||||
# Run once at workspace startup via coder_script. Pulls this repo fresh from
|
||||
# Gitea rather than embedding ~2.5MB of skill files into Terraform state.
|
||||
#
|
||||
# Env vars:
|
||||
# SPECIALTY_SKILLS - optional space-separated skill names from
|
||||
# extensions/custom-specialty-plugin/skills/ to install in addition to
|
||||
# the common awesome-skills-plugin bundle (every env gets that one).
|
||||
|
||||
set -e
|
||||
|
||||
REPO_ZIP_URL="https://git.octoturge.com/octoturge/Profiles-for-Coder/archive/main.zip"
|
||||
ZIP_PATH="/tmp/coder-skills-src.zip"
|
||||
WORK_DIR="/tmp/coder-skills-src"
|
||||
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
curl -fsSL "$REPO_ZIP_URL" -o "$ZIP_PATH"
|
||||
mkdir -p "$WORK_DIR"
|
||||
unzip -q -o "$ZIP_PATH" -d "$WORK_DIR"
|
||||
|
||||
INNER_DIR=$(find "$WORK_DIR" -mindepth 1 -maxdepth 1 -type d | head -n1)
|
||||
if [ -z "$INNER_DIR" ]; then
|
||||
echo "install-skills: couldn't find extracted repo contents, skipping." >&2
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TARGET_DIRS=("$HOME/.claude/skills" "$HOME/.copilot/skills" "$HOME/.gemini/config/skills")
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
mkdir -p "$dir"
|
||||
done
|
||||
|
||||
# Common skill bundle, installed for every environment.
|
||||
COMMON_SKILLS_SRC="$INNER_DIR/extensions/awesome-skills-plugin/skills"
|
||||
if [ -d "$COMMON_SKILLS_SRC" ]; then
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
cp -r "$COMMON_SKILLS_SRC/." "$dir/"
|
||||
done
|
||||
echo "install-skills: installed common skill bundle into ${TARGET_DIRS[*]}"
|
||||
else
|
||||
echo "install-skills: common skill bundle not found at $COMMON_SKILLS_SRC, skipping." >&2
|
||||
fi
|
||||
|
||||
# Environment-specific specialty skills, if any were requested.
|
||||
for skill in ${SPECIALTY_SKILLS:-}; do
|
||||
SRC="$INNER_DIR/extensions/custom-specialty-plugin/skills/$skill"
|
||||
if [ -d "$SRC" ]; then
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
cp -r "$SRC" "$dir/$skill"
|
||||
done
|
||||
echo "install-skills: installed specialty skill '$skill'"
|
||||
else
|
||||
echo "install-skills: specialty skill '$skill' not found at $SRC, skipping." >&2
|
||||
fi
|
||||
done
|
||||
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
echo "install-skills: done."
|
||||
+19
-18
@@ -6,12 +6,22 @@ terraform {
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
# Not used directly in this config. Existing workspace state from before
|
||||
# the jetbrains module was removed still has resources tagged under this
|
||||
# provider (the module used it internally to fetch IDE metadata) -
|
||||
# terraform init only installs providers the current config declares, so
|
||||
# without this, plan/apply fails with "Missing required provider" while
|
||||
# trying to reconcile/destroy those leftover state entries. Safe to drop
|
||||
# once every workspace has updated past the jetbrains-module version.
|
||||
http = {
|
||||
source = "hashicorp/http"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
env_name = "Default"
|
||||
profile = jsondecode(file("${path.module}/../../profile-templates/Default.code-profile"))
|
||||
profile = jsondecode(file("${path.module}/profile.code-profile"))
|
||||
settings_raw = jsondecode(local.profile.settings).settings
|
||||
extensions = [for e in jsondecode(local.profile.extensions) : e.identifier.id]
|
||||
}
|
||||
@@ -141,17 +151,6 @@ module "code-server" {
|
||||
order = 1
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/jetbrains
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "~> 1.1"
|
||||
agent_id = coder_agent.main.id
|
||||
agent_name = "main"
|
||||
folder = "/home/coder"
|
||||
tooltip = "You need to [install JetBrains Toolbox](https://coder.com/docs/user-guides/workspace-access/jetbrains/toolbox) to use this app."
|
||||
}
|
||||
|
||||
resource "docker_volume" "home_volume" {
|
||||
name = "coder-${data.coder_workspace.me.id}-home"
|
||||
# Protect the volume from being deleted due to changes in attributes.
|
||||
@@ -243,7 +242,7 @@ resource "coder_script" "apply_settings" {
|
||||
|
||||
# Drops the shared CLI setup wizard onto the workspace and hooks it into
|
||||
# every new interactive shell (via .bashrc) until the user completes it.
|
||||
# See ../../scripts/cli-setup-wizard.sh for what it actually asks.
|
||||
# See ./cli-setup-wizard.sh for what it actually asks.
|
||||
resource "coder_script" "cli_setup_wizard" {
|
||||
agent_id = coder_agent.main.id
|
||||
display_name = "Install CLI Setup Wizard"
|
||||
@@ -251,8 +250,9 @@ resource "coder_script" "cli_setup_wizard" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
chmod +x /opt/coder/cli-setup-wizard.sh
|
||||
|
||||
MARKER="# >>> coder cli setup wizard >>>"
|
||||
@@ -297,7 +297,7 @@ resource "coder_script" "install_bun" {
|
||||
}
|
||||
|
||||
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
|
||||
# Antigravity CLI's skills directories. See ../../scripts/install-skills.sh.
|
||||
# Antigravity CLI's skills directories. See ./install-skills.sh.
|
||||
# Default has no matching entry in extensions/custom-specialty-plugin/skills,
|
||||
# so it only gets the common awesome-skills-plugin bundle.
|
||||
resource "coder_script" "install_skills" {
|
||||
@@ -307,8 +307,9 @@ resource "coder_script" "install_skills" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
chmod +x /opt/coder/install-skills.sh
|
||||
SPECIALTY_SKILLS="" /opt/coder/install-skills.sh
|
||||
EOT
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/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
|
||||
|
||||
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
|
||||
|
||||
# --- 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
|
||||
|
||||
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
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Installs this repo's Agent Skills (extensions/{awesome-skills-plugin,
|
||||
# custom-specialty-plugin}/skills/*, each a SKILL.md-based skill directory)
|
||||
# into every AI CLI's personal skills directory:
|
||||
#
|
||||
# Claude Code CLI -> ~/.claude/skills/<name>/
|
||||
# GitHub Copilot CLI -> ~/.copilot/skills/<name>/
|
||||
# Antigravity CLI -> ~/.gemini/config/skills/<name>/ (per antigravity.google/docs/skills;
|
||||
# worth a spot-check if agy doesn't pick these up, some third-party
|
||||
# docs disagree on the exact path)
|
||||
#
|
||||
# Run once at workspace startup via coder_script. Pulls this repo fresh from
|
||||
# Gitea rather than embedding ~2.5MB of skill files into Terraform state.
|
||||
#
|
||||
# Env vars:
|
||||
# SPECIALTY_SKILLS - optional space-separated skill names from
|
||||
# extensions/custom-specialty-plugin/skills/ to install in addition to
|
||||
# the common awesome-skills-plugin bundle (every env gets that one).
|
||||
|
||||
set -e
|
||||
|
||||
REPO_ZIP_URL="https://git.octoturge.com/octoturge/Profiles-for-Coder/archive/main.zip"
|
||||
ZIP_PATH="/tmp/coder-skills-src.zip"
|
||||
WORK_DIR="/tmp/coder-skills-src"
|
||||
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
curl -fsSL "$REPO_ZIP_URL" -o "$ZIP_PATH"
|
||||
mkdir -p "$WORK_DIR"
|
||||
unzip -q -o "$ZIP_PATH" -d "$WORK_DIR"
|
||||
|
||||
INNER_DIR=$(find "$WORK_DIR" -mindepth 1 -maxdepth 1 -type d | head -n1)
|
||||
if [ -z "$INNER_DIR" ]; then
|
||||
echo "install-skills: couldn't find extracted repo contents, skipping." >&2
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TARGET_DIRS=("$HOME/.claude/skills" "$HOME/.copilot/skills" "$HOME/.gemini/config/skills")
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
mkdir -p "$dir"
|
||||
done
|
||||
|
||||
# Common skill bundle, installed for every environment.
|
||||
COMMON_SKILLS_SRC="$INNER_DIR/extensions/awesome-skills-plugin/skills"
|
||||
if [ -d "$COMMON_SKILLS_SRC" ]; then
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
cp -r "$COMMON_SKILLS_SRC/." "$dir/"
|
||||
done
|
||||
echo "install-skills: installed common skill bundle into ${TARGET_DIRS[*]}"
|
||||
else
|
||||
echo "install-skills: common skill bundle not found at $COMMON_SKILLS_SRC, skipping." >&2
|
||||
fi
|
||||
|
||||
# Environment-specific specialty skills, if any were requested.
|
||||
for skill in ${SPECIALTY_SKILLS:-}; do
|
||||
SRC="$INNER_DIR/extensions/custom-specialty-plugin/skills/$skill"
|
||||
if [ -d "$SRC" ]; then
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
cp -r "$SRC" "$dir/$skill"
|
||||
done
|
||||
echo "install-skills: installed specialty skill '$skill'"
|
||||
else
|
||||
echo "install-skills: specialty skill '$skill' not found at $SRC, skipping." >&2
|
||||
fi
|
||||
done
|
||||
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
echo "install-skills: done."
|
||||
+19
-18
@@ -6,12 +6,22 @@ terraform {
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
# Not used directly in this config. Existing workspace state from before
|
||||
# the jetbrains module was removed still has resources tagged under this
|
||||
# provider (the module used it internally to fetch IDE metadata) -
|
||||
# terraform init only installs providers the current config declares, so
|
||||
# without this, plan/apply fails with "Missing required provider" while
|
||||
# trying to reconcile/destroy those leftover state entries. Safe to drop
|
||||
# once every workspace has updated past the jetbrains-module version.
|
||||
http = {
|
||||
source = "hashicorp/http"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
env_name = "Python Engineering"
|
||||
profile = jsondecode(file("${path.module}/../../profile-templates/Python.code-profile"))
|
||||
profile = jsondecode(file("${path.module}/profile.code-profile"))
|
||||
settings_raw = jsondecode(local.profile.settings).settings
|
||||
extensions = [for e in jsondecode(local.profile.extensions) : e.identifier.id]
|
||||
}
|
||||
@@ -141,17 +151,6 @@ module "code-server" {
|
||||
order = 1
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/jetbrains
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "~> 1.1"
|
||||
agent_id = coder_agent.main.id
|
||||
agent_name = "main"
|
||||
folder = "/home/coder"
|
||||
tooltip = "You need to [install JetBrains Toolbox](https://coder.com/docs/user-guides/workspace-access/jetbrains/toolbox) to use this app."
|
||||
}
|
||||
|
||||
resource "docker_volume" "home_volume" {
|
||||
name = "coder-${data.coder_workspace.me.id}-home"
|
||||
# Protect the volume from being deleted due to changes in attributes.
|
||||
@@ -243,7 +242,7 @@ resource "coder_script" "apply_settings" {
|
||||
|
||||
# Drops the shared CLI setup wizard onto the workspace and hooks it into
|
||||
# every new interactive shell (via .bashrc) until the user completes it.
|
||||
# See ../../scripts/cli-setup-wizard.sh for what it actually asks.
|
||||
# See ./cli-setup-wizard.sh for what it actually asks.
|
||||
resource "coder_script" "cli_setup_wizard" {
|
||||
agent_id = coder_agent.main.id
|
||||
display_name = "Install CLI Setup Wizard"
|
||||
@@ -251,8 +250,9 @@ resource "coder_script" "cli_setup_wizard" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
chmod +x /opt/coder/cli-setup-wizard.sh
|
||||
|
||||
MARKER="# >>> coder cli setup wizard >>>"
|
||||
@@ -297,7 +297,7 @@ resource "coder_script" "install_bun" {
|
||||
}
|
||||
|
||||
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
|
||||
# Antigravity CLI's skills directories. See ../../scripts/install-skills.sh.
|
||||
# Antigravity CLI's skills directories. See ./install-skills.sh.
|
||||
# Python has no matching entry in extensions/custom-specialty-plugin/skills,
|
||||
# so it only gets the common awesome-skills-plugin bundle.
|
||||
resource "coder_script" "install_skills" {
|
||||
@@ -307,8 +307,9 @@ resource "coder_script" "install_skills" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
chmod +x /opt/coder/install-skills.sh
|
||||
SPECIALTY_SKILLS="" /opt/coder/install-skills.sh
|
||||
EOT
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,148 @@
|
||||
#!/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
|
||||
|
||||
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
|
||||
|
||||
# --- 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
|
||||
|
||||
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
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Installs this repo's Agent Skills (extensions/{awesome-skills-plugin,
|
||||
# custom-specialty-plugin}/skills/*, each a SKILL.md-based skill directory)
|
||||
# into every AI CLI's personal skills directory:
|
||||
#
|
||||
# Claude Code CLI -> ~/.claude/skills/<name>/
|
||||
# GitHub Copilot CLI -> ~/.copilot/skills/<name>/
|
||||
# Antigravity CLI -> ~/.gemini/config/skills/<name>/ (per antigravity.google/docs/skills;
|
||||
# worth a spot-check if agy doesn't pick these up, some third-party
|
||||
# docs disagree on the exact path)
|
||||
#
|
||||
# Run once at workspace startup via coder_script. Pulls this repo fresh from
|
||||
# Gitea rather than embedding ~2.5MB of skill files into Terraform state.
|
||||
#
|
||||
# Env vars:
|
||||
# SPECIALTY_SKILLS - optional space-separated skill names from
|
||||
# extensions/custom-specialty-plugin/skills/ to install in addition to
|
||||
# the common awesome-skills-plugin bundle (every env gets that one).
|
||||
|
||||
set -e
|
||||
|
||||
REPO_ZIP_URL="https://git.octoturge.com/octoturge/Profiles-for-Coder/archive/main.zip"
|
||||
ZIP_PATH="/tmp/coder-skills-src.zip"
|
||||
WORK_DIR="/tmp/coder-skills-src"
|
||||
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
curl -fsSL "$REPO_ZIP_URL" -o "$ZIP_PATH"
|
||||
mkdir -p "$WORK_DIR"
|
||||
unzip -q -o "$ZIP_PATH" -d "$WORK_DIR"
|
||||
|
||||
INNER_DIR=$(find "$WORK_DIR" -mindepth 1 -maxdepth 1 -type d | head -n1)
|
||||
if [ -z "$INNER_DIR" ]; then
|
||||
echo "install-skills: couldn't find extracted repo contents, skipping." >&2
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TARGET_DIRS=("$HOME/.claude/skills" "$HOME/.copilot/skills" "$HOME/.gemini/config/skills")
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
mkdir -p "$dir"
|
||||
done
|
||||
|
||||
# Common skill bundle, installed for every environment.
|
||||
COMMON_SKILLS_SRC="$INNER_DIR/extensions/awesome-skills-plugin/skills"
|
||||
if [ -d "$COMMON_SKILLS_SRC" ]; then
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
cp -r "$COMMON_SKILLS_SRC/." "$dir/"
|
||||
done
|
||||
echo "install-skills: installed common skill bundle into ${TARGET_DIRS[*]}"
|
||||
else
|
||||
echo "install-skills: common skill bundle not found at $COMMON_SKILLS_SRC, skipping." >&2
|
||||
fi
|
||||
|
||||
# Environment-specific specialty skills, if any were requested.
|
||||
for skill in ${SPECIALTY_SKILLS:-}; do
|
||||
SRC="$INNER_DIR/extensions/custom-specialty-plugin/skills/$skill"
|
||||
if [ -d "$SRC" ]; then
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
cp -r "$SRC" "$dir/$skill"
|
||||
done
|
||||
echo "install-skills: installed specialty skill '$skill'"
|
||||
else
|
||||
echo "install-skills: specialty skill '$skill' not found at $SRC, skipping." >&2
|
||||
fi
|
||||
done
|
||||
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
echo "install-skills: done."
|
||||
+19
-18
@@ -6,12 +6,22 @@ terraform {
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
# Not used directly in this config. Existing workspace state from before
|
||||
# the jetbrains module was removed still has resources tagged under this
|
||||
# provider (the module used it internally to fetch IDE metadata) -
|
||||
# terraform init only installs providers the current config declares, so
|
||||
# without this, plan/apply fails with "Missing required provider" while
|
||||
# trying to reconcile/destroy those leftover state entries. Safe to drop
|
||||
# once every workspace has updated past the jetbrains-module version.
|
||||
http = {
|
||||
source = "hashicorp/http"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
env_name = "TTRPG & Lore Building"
|
||||
profile = jsondecode(file("${path.module}/../../profile-templates/TTRPG.code-profile"))
|
||||
profile = jsondecode(file("${path.module}/profile.code-profile"))
|
||||
settings_raw = jsondecode(local.profile.settings).settings
|
||||
extensions = [for e in jsondecode(local.profile.extensions) : e.identifier.id]
|
||||
}
|
||||
@@ -141,17 +151,6 @@ module "code-server" {
|
||||
order = 1
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/jetbrains
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "~> 1.1"
|
||||
agent_id = coder_agent.main.id
|
||||
agent_name = "main"
|
||||
folder = "/home/coder"
|
||||
tooltip = "You need to [install JetBrains Toolbox](https://coder.com/docs/user-guides/workspace-access/jetbrains/toolbox) to use this app."
|
||||
}
|
||||
|
||||
resource "docker_volume" "home_volume" {
|
||||
name = "coder-${data.coder_workspace.me.id}-home"
|
||||
# Protect the volume from being deleted due to changes in attributes.
|
||||
@@ -243,7 +242,7 @@ resource "coder_script" "apply_settings" {
|
||||
|
||||
# Drops the shared CLI setup wizard onto the workspace and hooks it into
|
||||
# every new interactive shell (via .bashrc) until the user completes it.
|
||||
# See ../../scripts/cli-setup-wizard.sh for what it actually asks.
|
||||
# See ./cli-setup-wizard.sh for what it actually asks.
|
||||
resource "coder_script" "cli_setup_wizard" {
|
||||
agent_id = coder_agent.main.id
|
||||
display_name = "Install CLI Setup Wizard"
|
||||
@@ -251,8 +250,9 @@ resource "coder_script" "cli_setup_wizard" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
chmod +x /opt/coder/cli-setup-wizard.sh
|
||||
|
||||
MARKER="# >>> coder cli setup wizard >>>"
|
||||
@@ -299,7 +299,7 @@ resource "coder_script" "install_bun" {
|
||||
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
|
||||
# Antigravity CLI's skills directories, plus the foundryvtt-modding and
|
||||
# ttrpg-lore-weaver skills from extensions/custom-specialty-plugin.
|
||||
# See ../../scripts/install-skills.sh.
|
||||
# See ./install-skills.sh.
|
||||
resource "coder_script" "install_skills" {
|
||||
agent_id = coder_agent.main.id
|
||||
display_name = "Install Agent Skills"
|
||||
@@ -307,8 +307,9 @@ resource "coder_script" "install_skills" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
chmod +x /opt/coder/install-skills.sh
|
||||
SPECIALTY_SKILLS="foundryvtt-modding ttrpg-lore-weaver" /opt/coder/install-skills.sh
|
||||
EOT
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# Web Applications workspace image: Rust (Tauri 2 / gRPC), Bun + Node/pnpm,
|
||||
# Python + CV/ONNX prototyping, and DB CLI clients baked in at build time so
|
||||
# workspace start doesn't pay for a from-scratch toolchain install.
|
||||
#
|
||||
# Built by templates/web/main.tf via the docker provider's `build` block
|
||||
# (context = this directory), not pulled from a registry.
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
locales sudo ca-certificates gnupg curl wget \
|
||||
&& locale-gen en_US.UTF-8 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
LANGUAGE=en_US:en \
|
||||
LC_ALL=en_US.UTF-8
|
||||
|
||||
# Core build toolchain, crypto/DB headers, Tauri 2 / WebKit GUI prerequisites,
|
||||
# X11 dev libs, DB CLI clients, Python + OpenCV, protobuf compiler.
|
||||
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 \
|
||||
libssl-dev libpq-dev libsqlite3-dev \
|
||||
libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev libxdo-dev \
|
||||
libgtk-3-dev libsoup-3.0-dev \
|
||||
libx11-dev libxext-dev libxrender-dev libxtst-dev libxi-dev \
|
||||
postgresql-client redis-tools sqlite3 \
|
||||
python3 python3-pip python3-venv python3-dev \
|
||||
libopencv-dev \
|
||||
protobuf-compiler \
|
||||
&& 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
|
||||
# system PATH for every user.
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& npm install -g pnpm yarn \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 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
|
||||
# throwaway container image, so --break-system-packages is the right call
|
||||
# instead of forcing every user into a venv for basic prototyping. Not
|
||||
# upgrading pip itself first: the Debian-packaged pip 24.0 has no RECORD
|
||||
# file (dpkg-installed, not pip-installed), so `pip install --upgrade pip`
|
||||
# fails trying to uninstall it in place - and it's unneeded anyway, the
|
||||
# packages below install fine under the stock version.
|
||||
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
|
||||
|
||||
# Standard non-root dev user with passwordless sudo. Ubuntu 24.04's base
|
||||
# image already ships a default `ubuntu` user/group at uid/gid 1000, which
|
||||
# collides with the explicit --uid 1000 below - drop it first so `coder`
|
||||
# can take that uid.
|
||||
RUN userdel -r ubuntu 2>/dev/null; \
|
||||
groupdel ubuntu 2>/dev/null; \
|
||||
useradd --uid 1000 --create-home --shell /bin/bash coder \
|
||||
&& echo "coder ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/coder \
|
||||
&& chmod 0440 /etc/sudoers.d/coder
|
||||
|
||||
ENV RUST_BACKTRACE=1 \
|
||||
RUSTUP_HOME=/home/coder/.rustup \
|
||||
CARGO_HOME=/home/coder/.cargo \
|
||||
BUN_INSTALL=/home/coder/.bun \
|
||||
PNPM_HOME=/home/coder/.local/share/pnpm \
|
||||
PATH=/home/coder/.cargo/bin:/home/coder/.bun/bin:/home/coder/.local/share/pnpm:/home/coder/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
USER coder
|
||||
WORKDIR /home/coder
|
||||
|
||||
# Rust via rustup: stable toolchain, rust-analyzer/clippy/rustfmt/rust-src,
|
||||
# native + musl targets for x86_64/aarch64, and cargo helper utilities.
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
|
||||
--default-toolchain stable --profile default \
|
||||
&& rustup component add rustfmt clippy rust-analyzer rust-src \
|
||||
&& rustup target add \
|
||||
x86_64-unknown-linux-gnu \
|
||||
x86_64-unknown-linux-musl \
|
||||
aarch64-unknown-linux-gnu \
|
||||
aarch64-unknown-linux-musl \
|
||||
&& cargo install --locked cargo-watch cargo-edit cross bacon
|
||||
|
||||
# Bun: global runtime for the ElysiaJS backend and fast scripting.
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
|
||||
RUN mkdir -p /home/coder/workspace
|
||||
WORKDIR /home/coder/workspace
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/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
|
||||
|
||||
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
|
||||
|
||||
# --- 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
|
||||
|
||||
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
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Installs this repo's Agent Skills (extensions/{awesome-skills-plugin,
|
||||
# custom-specialty-plugin}/skills/*, each a SKILL.md-based skill directory)
|
||||
# into every AI CLI's personal skills directory:
|
||||
#
|
||||
# Claude Code CLI -> ~/.claude/skills/<name>/
|
||||
# GitHub Copilot CLI -> ~/.copilot/skills/<name>/
|
||||
# Antigravity CLI -> ~/.gemini/config/skills/<name>/ (per antigravity.google/docs/skills;
|
||||
# worth a spot-check if agy doesn't pick these up, some third-party
|
||||
# docs disagree on the exact path)
|
||||
#
|
||||
# Run once at workspace startup via coder_script. Pulls this repo fresh from
|
||||
# Gitea rather than embedding ~2.5MB of skill files into Terraform state.
|
||||
#
|
||||
# Env vars:
|
||||
# SPECIALTY_SKILLS - optional space-separated skill names from
|
||||
# extensions/custom-specialty-plugin/skills/ to install in addition to
|
||||
# the common awesome-skills-plugin bundle (every env gets that one).
|
||||
|
||||
set -e
|
||||
|
||||
REPO_ZIP_URL="https://git.octoturge.com/octoturge/Profiles-for-Coder/archive/main.zip"
|
||||
ZIP_PATH="/tmp/coder-skills-src.zip"
|
||||
WORK_DIR="/tmp/coder-skills-src"
|
||||
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
curl -fsSL "$REPO_ZIP_URL" -o "$ZIP_PATH"
|
||||
mkdir -p "$WORK_DIR"
|
||||
unzip -q -o "$ZIP_PATH" -d "$WORK_DIR"
|
||||
|
||||
INNER_DIR=$(find "$WORK_DIR" -mindepth 1 -maxdepth 1 -type d | head -n1)
|
||||
if [ -z "$INNER_DIR" ]; then
|
||||
echo "install-skills: couldn't find extracted repo contents, skipping." >&2
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TARGET_DIRS=("$HOME/.claude/skills" "$HOME/.copilot/skills" "$HOME/.gemini/config/skills")
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
mkdir -p "$dir"
|
||||
done
|
||||
|
||||
# Common skill bundle, installed for every environment.
|
||||
COMMON_SKILLS_SRC="$INNER_DIR/extensions/awesome-skills-plugin/skills"
|
||||
if [ -d "$COMMON_SKILLS_SRC" ]; then
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
cp -r "$COMMON_SKILLS_SRC/." "$dir/"
|
||||
done
|
||||
echo "install-skills: installed common skill bundle into ${TARGET_DIRS[*]}"
|
||||
else
|
||||
echo "install-skills: common skill bundle not found at $COMMON_SKILLS_SRC, skipping." >&2
|
||||
fi
|
||||
|
||||
# Environment-specific specialty skills, if any were requested.
|
||||
for skill in ${SPECIALTY_SKILLS:-}; do
|
||||
SRC="$INNER_DIR/extensions/custom-specialty-plugin/skills/$skill"
|
||||
if [ -d "$SRC" ]; then
|
||||
for dir in "${TARGET_DIRS[@]}"; do
|
||||
cp -r "$SRC" "$dir/$skill"
|
||||
done
|
||||
echo "install-skills: installed specialty skill '$skill'"
|
||||
else
|
||||
echo "install-skills: specialty skill '$skill' not found at $SRC, skipping." >&2
|
||||
fi
|
||||
done
|
||||
|
||||
rm -rf "$WORK_DIR" "$ZIP_PATH"
|
||||
echo "install-skills: done."
|
||||
+48
-46
@@ -6,12 +6,22 @@ terraform {
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
# Not used directly in this config. Existing workspace state from before
|
||||
# the jetbrains module was removed still has resources tagged under this
|
||||
# provider (the module used it internally to fetch IDE metadata) -
|
||||
# terraform init only installs providers the current config declares, so
|
||||
# without this, plan/apply fails with "Missing required provider" while
|
||||
# trying to reconcile/destroy those leftover state entries. Safe to drop
|
||||
# once every workspace has updated past the jetbrains-module version.
|
||||
http = {
|
||||
source = "hashicorp/http"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
env_name = "Web Applications"
|
||||
profile = jsondecode(file("${path.module}/../../profile-templates/Web.code-profile"))
|
||||
profile = jsondecode(file("${path.module}/profile.code-profile"))
|
||||
settings_raw = jsondecode(local.profile.settings).settings
|
||||
extensions = [for e in jsondecode(local.profile.extensions) : e.identifier.id]
|
||||
}
|
||||
@@ -50,11 +60,18 @@ resource "coder_agent" "main" {
|
||||
# workspace. Note that they take precedence over configuration defined in ~/.gitconfig!
|
||||
# You can remove this block if you'd prefer to configure Git manually or using
|
||||
# dotfiles. (see docs/dotfiles.md)
|
||||
#
|
||||
# RUST_BACKTRACE/PNPM_HOME/BUN_INSTALL are also baked in as image ENV (see
|
||||
# Dockerfile) so every process picks them up; restated here so they surface
|
||||
# on the workspace dashboard too.
|
||||
env = {
|
||||
GIT_AUTHOR_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
|
||||
GIT_AUTHOR_EMAIL = "${data.coder_workspace_owner.me.email}"
|
||||
GIT_COMMITTER_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
|
||||
GIT_COMMITTER_EMAIL = "${data.coder_workspace_owner.me.email}"
|
||||
RUST_BACKTRACE = "1"
|
||||
PNPM_HOME = "/home/coder/.local/share/pnpm"
|
||||
BUN_INSTALL = "/home/coder/.bun"
|
||||
}
|
||||
|
||||
# The following metadata blocks are optional. They are used to display
|
||||
@@ -141,17 +158,6 @@ module "code-server" {
|
||||
order = 1
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/jetbrains
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "~> 1.1"
|
||||
agent_id = coder_agent.main.id
|
||||
agent_name = "main"
|
||||
folder = "/home/coder"
|
||||
tooltip = "You need to [install JetBrains Toolbox](https://coder.com/docs/user-guides/workspace-access/jetbrains/toolbox) to use this app."
|
||||
}
|
||||
|
||||
resource "docker_volume" "home_volume" {
|
||||
name = "coder-${data.coder_workspace.me.id}-home"
|
||||
# Protect the volume from being deleted due to changes in attributes.
|
||||
@@ -179,9 +185,22 @@ resource "docker_volume" "home_volume" {
|
||||
}
|
||||
}
|
||||
|
||||
# Builds the full Web Applications toolchain (Rust/Tauri 2, Bun/Node/pnpm,
|
||||
# Python CV/ONNX, DB clients - see ./Dockerfile) from this template's own
|
||||
# directory, so no external registry push is required. The tag embeds the
|
||||
# Dockerfile's hash so a Dockerfile edit forces a rebuild on next apply/push,
|
||||
# while an unchanged Dockerfile reuses the cached image.
|
||||
resource "docker_image" "web" {
|
||||
name = "coder-profiles-web:${filesha1("${path.module}/Dockerfile")}"
|
||||
build {
|
||||
context = path.module
|
||||
}
|
||||
keep_locally = true
|
||||
}
|
||||
|
||||
resource "docker_container" "workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
image = "codercom/enterprise-base:ubuntu"
|
||||
image = docker_image.web.image_id
|
||||
# Uses lower() to avoid Docker restriction on container names.
|
||||
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
|
||||
# Hostname makes the shell more user friendly: coder@my-workspace:~$
|
||||
@@ -243,7 +262,7 @@ resource "coder_script" "apply_settings" {
|
||||
|
||||
# Drops the shared CLI setup wizard onto the workspace and hooks it into
|
||||
# every new interactive shell (via .bashrc) until the user completes it.
|
||||
# See ../../scripts/cli-setup-wizard.sh for what it actually asks.
|
||||
# See ./cli-setup-wizard.sh for what it actually asks.
|
||||
resource "coder_script" "cli_setup_wizard" {
|
||||
agent_id = coder_agent.main.id
|
||||
display_name = "Install CLI Setup Wizard"
|
||||
@@ -251,8 +270,9 @@ resource "coder_script" "cli_setup_wizard" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/cli-setup-wizard.sh"))}' | base64 -d > /opt/coder/cli-setup-wizard.sh
|
||||
chmod +x /opt/coder/cli-setup-wizard.sh
|
||||
|
||||
MARKER="# >>> coder cli setup wizard >>>"
|
||||
@@ -268,36 +288,17 @@ resource "coder_script" "cli_setup_wizard" {
|
||||
EOT
|
||||
}
|
||||
|
||||
# Installs Bun and uses it (instead of npm) for the CLI installs the wizard
|
||||
# script runs. The installer doesn't reliably add ~/.bun/bin to PATH in
|
||||
# non-interactive shells, so that's hooked into .bashrc explicitly here.
|
||||
resource "coder_script" "install_bun" {
|
||||
agent_id = coder_agent.main.id
|
||||
display_name = "Install Bun"
|
||||
run_on_start = true
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
export BUN_INSTALL="$HOME/.bun"
|
||||
if [ ! -x "$BUN_INSTALL/bin/bun" ]; then
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
fi
|
||||
|
||||
MARKER="# >>> coder bun path >>>"
|
||||
if ! grep -qF "$MARKER" "$HOME/.bashrc" 2>/dev/null; then
|
||||
{
|
||||
echo ""
|
||||
echo "$MARKER"
|
||||
echo 'export BUN_INSTALL="$HOME/.bun"'
|
||||
echo 'export PATH="$BUN_INSTALL/bin:$PATH"'
|
||||
echo "# <<< coder bun path <<<"
|
||||
} >> "$HOME/.bashrc"
|
||||
fi
|
||||
EOT
|
||||
}
|
||||
# Bun (and the rest of the toolchain - Rust, Node/pnpm/yarn, Python CV/ONNX
|
||||
# packages, DB clients) is baked into the workspace image at build time (see
|
||||
# Dockerfile) rather than installed here on every start. Docker populates a
|
||||
# fresh, empty named volume from the image's directory contents on first
|
||||
# mount, so $HOME/.bun, $HOME/.cargo, $HOME/.rustup etc. land in the
|
||||
# persistent home_volume automatically the first time a workspace boots -
|
||||
# same mechanism the /etc/skel copy above relies on. BUN_INSTALL and PATH
|
||||
# are set as image ENV plus restated on coder_agent.env above.
|
||||
|
||||
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
|
||||
# Antigravity CLI's skills directories. See ../../scripts/install-skills.sh.
|
||||
# Antigravity CLI's skills directories. See ./install-skills.sh.
|
||||
# Web has no matching entry in extensions/custom-specialty-plugin/skills,
|
||||
# so it only gets the common awesome-skills-plugin bundle.
|
||||
resource "coder_script" "install_skills" {
|
||||
@@ -307,8 +308,9 @@ resource "coder_script" "install_skills" {
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /opt/coder
|
||||
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
sudo mkdir -p /opt/coder
|
||||
sudo chown "$(id -u):$(id -g)" /opt/coder
|
||||
echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
|
||||
chmod +x /opt/coder/install-skills.sh
|
||||
SPECIALTY_SKILLS="" /opt/coder/install-skills.sh
|
||||
EOT
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user