4 Commits

Author SHA1 Message Date
octoturge 354536dad5 fix: use sudo for /opt/coder setup in cli_setup_wizard and install_skills
Provision Coder Templates / provision (push) Successful in 2m5s
The coder_agent's coder_script resources run as the unprivileged coder
user, which can't mkdir under root-owned /opt. Every template hit this
identically (mkdir: cannot create directory '/opt/coder': Permission
denied) since coder_script content is shared verbatim across all 6.
coder has passwordless sudo everywhere, so sudo mkdir + chown to the
running user unblocks the rest of each script's non-sudo writes.
2026-08-26 18:38:46 +02:00
octoturge 1be7c00b84 fix(web): resolve Docker build failures in templates/web
Provision Coder Templates / provision (push) Successful in 2m5s
- Drop `pip install --upgrade pip`: Debian-packaged pip 24.0 has no RECORD
  file (dpkg-installed, not pip-installed), so self-upgrade fails trying
  to uninstall in place. Unneeded - packages install fine under stock pip.
- Remove the pre-existing `ubuntu` user/group before `useradd --uid 1000
  coder`: Ubuntu 24.04's base image already provisions a uid/gid 1000
  `ubuntu` user, colliding with the explicit uid.
- Drop the now-pointless `pnpm setup || true` step: it was already
  silently no-oping (EACCES trying to self-manage into PNPM_HOME as a
  non-root user against a root-owned npm-global install) and standard
  pnpm usage (install/run) works fine without it.

Verified via repeated rebuilds directly on the Coder host.
2026-08-26 16:22:39 +02:00
octoturge 52cc84650a Re-declare hashicorp/http provider to unblock existing workspace state
Provision Coder Templates / provision (push) Successful in 2m3s
Removing the jetbrains module (previous commit) dropped the only thing in
these templates that used the hashicorp/http provider (the module fetched
IDE metadata via it internally). terraform init only installs providers the
current config declares, but any workspace whose state still has resources
from a template version that included the jetbrains module needs that
provider available to reconcile/destroy those entries - without it,
plan/apply fails with "Missing required provider ... hashicorp/http".

Re-declaring it in required_providers (all 6 templates) unblocks those
existing workspaces' next apply. Safe to remove again once every workspace
has updated past the jetbrains-module version.
2026-08-26 15:47:25 +02:00
octoturge 0c53bb6554 Add gh/tea to the CLI wizard, drop the JetBrains module from all templates
Provision Coder Templates / provision (push) Successful in 2m7s
- cli-setup-wizard.sh (identical across all 6 templates): add GitHub CLI
  (gh, installed via the official apt repo) and Gitea CLI (tea, official
  binary release to ~/.local/bin) as two more opt-in install-then-login
  prompts, alongside the existing Copilot/Antigravity/Claude Code CLI
  entries. Same pattern: skip if already installed, ask before installing,
  attempt login, never hard-fail the wizard.
- main.tf (all 6 templates): remove the `module "jetbrains"` block - not
  used, dropped per request.
- README.md: update the "Layout" blurb and CLI setup wizard tool list to
  match.
2026-08-26 14:58:51 +02:00
14 changed files with 347 additions and 88 deletions
+4 -2
View File
@@ -47,8 +47,8 @@ extra `coder_script` needed, matching how the `/etc/skel` copy in every
template's `startup_script` already relies on that same mechanism. template's `startup_script` already relies on that same mechanism.
Each `templates/<env>/` is a complete, independent Coder template (agent, Each `templates/<env>/` is a complete, independent Coder template (agent,
docker container, code-server, JetBrains) with its own copy of everything docker container, code-server) with its own copy of everything `main.tf`
`main.tf` needs. They're deliberately not built from a shared Terraform needs. They're deliberately not built from a shared Terraform
module or shared files elsewhere in the repo: `coder templates push -d 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 templates/<env>` only uploads that one directory to the Coder server, so any
`file()` reference reaching outside it (e.g. the old shared `file()` reference reaching outside it (e.g. the old shared
@@ -114,6 +114,8 @@ finishes it - and offers to install + log into:
- **GitHub Copilot CLI** (`bun install -g @github/copilot`, then `copilot login`) - **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`) - **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`) - **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 It does **not** ask about VS Code extensions, since those are handled by
Terraform (see above). Once the user confirms completion it writes a Terraform (see above). Once the user confirms completion it writes a
+41
View File
@@ -96,6 +96,47 @@ else
echo "Skipping Claude Code CLI: bun not found on this workspace image." echo "Skipping Claude Code CLI: bun not found on this workspace image."
fi 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 "" echo ""
if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then
touch "$WIZARD_DONE_FILE" touch "$WIZARD_DONE_FILE"
+14 -13
View File
@@ -6,6 +6,16 @@ terraform {
docker = { docker = {
source = "kreuzwerker/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"
}
} }
} }
@@ -141,17 +151,6 @@ module "code-server" {
order = 1 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" { resource "docker_volume" "home_volume" {
name = "coder-${data.coder_workspace.me.id}-home" name = "coder-${data.coder_workspace.me.id}-home"
# Protect the volume from being deleted due to changes in attributes. # Protect the volume from being deleted due to changes in attributes.
@@ -251,7 +250,8 @@ resource "coder_script" "cli_setup_wizard" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 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 chmod +x /opt/coder/cli-setup-wizard.sh
@@ -306,7 +306,8 @@ resource "coder_script" "install_skills" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="openscad-parametric" /opt/coder/install-skills.sh SPECIALTY_SKILLS="openscad-parametric" /opt/coder/install-skills.sh
+41
View File
@@ -96,6 +96,47 @@ else
echo "Skipping Claude Code CLI: bun not found on this workspace image." echo "Skipping Claude Code CLI: bun not found on this workspace image."
fi 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 "" echo ""
if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then
touch "$WIZARD_DONE_FILE" touch "$WIZARD_DONE_FILE"
+14 -13
View File
@@ -6,6 +6,16 @@ terraform {
docker = { docker = {
source = "kreuzwerker/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"
}
} }
} }
@@ -141,17 +151,6 @@ module "code-server" {
order = 1 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" { resource "docker_volume" "home_volume" {
name = "coder-${data.coder_workspace.me.id}-home" name = "coder-${data.coder_workspace.me.id}-home"
# Protect the volume from being deleted due to changes in attributes. # Protect the volume from being deleted due to changes in attributes.
@@ -251,7 +250,8 @@ resource "coder_script" "cli_setup_wizard" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 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 chmod +x /opt/coder/cli-setup-wizard.sh
@@ -306,7 +306,8 @@ resource "coder_script" "install_skills" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="cobol-teacher" /opt/coder/install-skills.sh SPECIALTY_SKILLS="cobol-teacher" /opt/coder/install-skills.sh
+41
View File
@@ -96,6 +96,47 @@ else
echo "Skipping Claude Code CLI: bun not found on this workspace image." echo "Skipping Claude Code CLI: bun not found on this workspace image."
fi 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 "" echo ""
if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then
touch "$WIZARD_DONE_FILE" touch "$WIZARD_DONE_FILE"
+14 -13
View File
@@ -6,6 +6,16 @@ terraform {
docker = { docker = {
source = "kreuzwerker/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"
}
} }
} }
@@ -141,17 +151,6 @@ module "code-server" {
order = 1 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" { resource "docker_volume" "home_volume" {
name = "coder-${data.coder_workspace.me.id}-home" name = "coder-${data.coder_workspace.me.id}-home"
# Protect the volume from being deleted due to changes in attributes. # Protect the volume from being deleted due to changes in attributes.
@@ -251,7 +250,8 @@ resource "coder_script" "cli_setup_wizard" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 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 chmod +x /opt/coder/cli-setup-wizard.sh
@@ -307,7 +307,8 @@ resource "coder_script" "install_skills" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="" /opt/coder/install-skills.sh SPECIALTY_SKILLS="" /opt/coder/install-skills.sh
+41
View File
@@ -96,6 +96,47 @@ else
echo "Skipping Claude Code CLI: bun not found on this workspace image." echo "Skipping Claude Code CLI: bun not found on this workspace image."
fi 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 "" echo ""
if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then
touch "$WIZARD_DONE_FILE" touch "$WIZARD_DONE_FILE"
+14 -13
View File
@@ -6,6 +6,16 @@ terraform {
docker = { docker = {
source = "kreuzwerker/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"
}
} }
} }
@@ -141,17 +151,6 @@ module "code-server" {
order = 1 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" { resource "docker_volume" "home_volume" {
name = "coder-${data.coder_workspace.me.id}-home" name = "coder-${data.coder_workspace.me.id}-home"
# Protect the volume from being deleted due to changes in attributes. # Protect the volume from being deleted due to changes in attributes.
@@ -251,7 +250,8 @@ resource "coder_script" "cli_setup_wizard" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 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 chmod +x /opt/coder/cli-setup-wizard.sh
@@ -307,7 +307,8 @@ resource "coder_script" "install_skills" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="" /opt/coder/install-skills.sh SPECIALTY_SKILLS="" /opt/coder/install-skills.sh
+41
View File
@@ -96,6 +96,47 @@ else
echo "Skipping Claude Code CLI: bun not found on this workspace image." echo "Skipping Claude Code CLI: bun not found on this workspace image."
fi 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 "" echo ""
if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then
touch "$WIZARD_DONE_FILE" touch "$WIZARD_DONE_FILE"
+14 -13
View File
@@ -6,6 +6,16 @@ terraform {
docker = { docker = {
source = "kreuzwerker/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"
}
} }
} }
@@ -141,17 +151,6 @@ module "code-server" {
order = 1 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" { resource "docker_volume" "home_volume" {
name = "coder-${data.coder_workspace.me.id}-home" name = "coder-${data.coder_workspace.me.id}-home"
# Protect the volume from being deleted due to changes in attributes. # Protect the volume from being deleted due to changes in attributes.
@@ -251,7 +250,8 @@ resource "coder_script" "cli_setup_wizard" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 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 chmod +x /opt/coder/cli-setup-wizard.sh
@@ -307,7 +307,8 @@ resource "coder_script" "install_skills" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="foundryvtt-modding ttrpg-lore-weaver" /opt/coder/install-skills.sh SPECIALTY_SKILLS="foundryvtt-modding ttrpg-lore-weaver" /opt/coder/install-skills.sh
+13 -8
View File
@@ -43,15 +43,23 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
# Global Python prototyping packages: CV, ONNX runtime, CPU-only torch wheel. # Global Python prototyping packages: CV, ONNX runtime, CPU-only torch wheel.
# Ubuntu 24.04's system Python is PEP 668 externally-managed; this is a # Ubuntu 24.04's system Python is PEP 668 externally-managed; this is a
# throwaway container image, so --break-system-packages is the right call # throwaway container image, so --break-system-packages is the right call
# instead of forcing every user into a venv for basic prototyping. # instead of forcing every user into a venv for basic prototyping. Not
RUN python3 -m pip install --break-system-packages --no-cache-dir --upgrade pip \ # upgrading pip itself first: the Debian-packaged pip 24.0 has no RECORD
&& python3 -m pip install --break-system-packages --no-cache-dir \ # 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 \ numpy opencv-python-headless onnxruntime \
&& python3 -m pip install --break-system-packages --no-cache-dir \ && python3 -m pip install --break-system-packages --no-cache-dir \
torch --index-url https://download.pytorch.org/whl/cpu torch --index-url https://download.pytorch.org/whl/cpu
# Standard non-root dev user with passwordless sudo. # Standard non-root dev user with passwordless sudo. Ubuntu 24.04's base
RUN useradd --uid 1000 --create-home --shell /bin/bash coder \ # 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 \ && echo "coder ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/coder \
&& chmod 0440 /etc/sudoers.d/coder && chmod 0440 /etc/sudoers.d/coder
@@ -80,8 +88,5 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
# Bun: global runtime for the ElysiaJS backend and fast scripting. # Bun: global runtime for the ElysiaJS backend and fast scripting.
RUN curl -fsSL https://bun.sh/install | bash RUN curl -fsSL https://bun.sh/install | bash
# Prime the pnpm content-addressable store dir referenced by PNPM_HOME.
RUN pnpm setup || true
RUN mkdir -p /home/coder/workspace RUN mkdir -p /home/coder/workspace
WORKDIR /home/coder/workspace WORKDIR /home/coder/workspace
+41
View File
@@ -96,6 +96,47 @@ else
echo "Skipping Claude Code CLI: bun not found on this workspace image." echo "Skipping Claude Code CLI: bun not found on this workspace image."
fi 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 "" echo ""
if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then if ask_yes_no "Mark setup wizard as complete so it stops asking on new terminals?"; then
touch "$WIZARD_DONE_FILE" touch "$WIZARD_DONE_FILE"
+14 -13
View File
@@ -6,6 +6,16 @@ terraform {
docker = { docker = {
source = "kreuzwerker/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"
}
} }
} }
@@ -148,17 +158,6 @@ module "code-server" {
order = 1 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" { resource "docker_volume" "home_volume" {
name = "coder-${data.coder_workspace.me.id}-home" name = "coder-${data.coder_workspace.me.id}-home"
# Protect the volume from being deleted due to changes in attributes. # Protect the volume from being deleted due to changes in attributes.
@@ -271,7 +270,8 @@ resource "coder_script" "cli_setup_wizard" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 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 chmod +x /opt/coder/cli-setup-wizard.sh
@@ -308,7 +308,8 @@ resource "coder_script" "install_skills" {
script = <<-EOT script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -e
mkdir -p /opt/coder 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 echo '${base64encode(file("${path.module}/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="" /opt/coder/install-skills.sh SPECIALTY_SKILLS="" /opt/coder/install-skills.sh