Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions features/src/gemini-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

# Gemini CLI (gemini-cli)

Installs the Gemini CLI globally

## Example Usage

```json
"features": {
"./.devcontainer/features/gemini-cli": {}
}
```

## Options

| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| username | Username of the container user | string | root |



---

_Note: This file was auto-generated from the [devcontainer-feature.json](devcontainer-feature.json). Add additional notes to a `NOTES.md`._
17 changes: 17 additions & 0 deletions features/src/gemini-cli/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "Gemini CLI",
"id": "gemini-cli",
"version": "1.0.0",
"description": "Installs the Gemini CLI globally",
"documentationURL": "https://github.com/google-gemini/gemini-cli",
"options": {
"username": {
"type": "string",
"default": "root",
"description": "Username of the container user"
}
},
"installsAfter": [
"ghcr.io/devcontainers/features/node"
]
}
74 changes: 74 additions & 0 deletions features/src/gemini-cli/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -eu
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:
set -o errexit
set -o nounset
set -o pipefail
set -o xtrace


# Function to install Gemini CLI
install_gemini_cli() {
echo "Installing Gemini CLI..."
npm install -g @google/gemini-cli

if command -v gemini >/dev/null; then
echo "Gemini CLI installed successfully!"
return 0
else
echo "ERROR: Gemini CLI installation failed!"
return 1
fi
}

# Function to fix permissions for non-root users
fix_permissions() {
local username="${1:-root}"

if [ "${username}" = "root" ]; then
return 0
fi

# Fix NVM permissions: node feature installs as root, causing "Permission denied" in non-root containers
local nvm_dir="${NVM_DIR:-/usr/local/share/nvm}"
if [ -d "${nvm_dir}" ]; then
echo "Fixing NVM permissions for user ${username}..."
chown -R "${username}:" "${nvm_dir}"
fi

# Fix npm cache: npm install -g as root creates root-owned files in user's ~/.npm
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if you installed it as the user?
sudo -u "${username}" npm ...

local user_home
user_home=$(eval echo "~${username}" 2>/dev/null || echo "/home/${username}")
if [ -d "${user_home}/.npm" ]; then
echo "Fixing npm cache ownership for user ${username}..."
chown -R "${username}:" "${user_home}/.npm"
fi

# Edge case: Disable auto-update to prevent gemini from trying to re-exec
# itself on first run, which fails on freshly provisioned machines.
mkdir -p "${user_home}/.gemini"
printf '{"general.enableAutoUpdate": false}\n' > "${user_home}/.gemini/settings.json"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did this fix that freezing issue you showed me?

chown -R "${username}:" "${user_home}/.gemini"
}

# Print error message about requiring Node.js feature
print_nodejs_requirement() {
cat <<EOF
ERROR: Node.js and npm are required but not found!
Please add the Node.js feature to your devcontainer.json:
"features": {
"ghcr.io/devcontainers/features/node:1": {},
"./.devcontainer/features/gemini-cli": { "username": "your-user" }
}
EOF
exit 1
}

echo "Activating feature 'gemini-cli'"

if ! command -v node >/dev/null || ! command -v npm >/dev/null; then
print_nodejs_requirement
fi

install_gemini_cli || exit 1

fix_permissions "${USERNAME:-root}"

echo "Done!"
12 changes: 12 additions & 0 deletions features/src/workbench-tools/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@ chown -R "${USERNAME}:" "${LIBRARIES_ENV_DIR}"

# Set CROMWELL_JAR environment variable
printf 'export CROMWELL_JAR="%s"\n' "${BINARIES_ENV_DIR}/share/cromwell/cromwell.jar"

# Wrap gcloud to unset DISPLAY per-invocation so auth commands don't try
# to open a browser via X11 in headless devcontainer environments.
printf 'function gcloud() { DISPLAY= command gcloud "$@"; }\n'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could these env variables be set and exported rather than wrapping the commands? I wouldn't think they would cause issues with other commands?


# Wrap claude to clear BROWSER per-invocation so any app-managed browser
# handler does not intercept the auth URL in headless environments.
printf 'function claude() { BROWSER= command claude "$@"; }\n'

# Wrap gemini to set NO_BROWSER=1 per-invocation to force device code flow
# instead of opening a browser window.
printf 'function gemini() { NO_BROWSER=1 NO_COLOR=1 command gemini "$@"; }\n'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why NO_COLOR?

} >> "${USER_HOME_DIR}/.bashrc"

# Allow .bashrc to be sourced in non-interactive shells
Expand Down
5 changes: 5 additions & 0 deletions src/custom-workbench-jupyter-template/.devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"${templateOption:login}"
],
"features": {
"ghcr.io/devcontainers/features/node": {
"version": "24.11.0"
},
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
Comment on lines +24 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we pin these features to a hash and add them to https://github.com/verily-src/workbench-app-devcontainers/blob/master/feature-versions/state.json so they can be auto-updated?

"./.devcontainer/features/gemini-cli": { "username": "jupyter" },
"./.devcontainer/features/workbench-tools": {
"libEnv": "/opt/conda/envs/jupyter", // Use the jupyter conda environment
"cloud": "${templateOption:cloud}",
Expand Down
2 changes: 1 addition & 1 deletion src/custom-workbench-jupyter-template/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM us-west2-docker.pkg.dev/shared-pub-buckets-94mvrf/workbench-artifacts/app-workbench-jupyter@sha256:229166b2be902aef0a2a621fae38037fe0e069875df1b8bfd5bcff8766d6036c
FROM us-west2-docker.pkg.dev/shared-pub-buckets-94mvrf/workbench-artifacts/app-workbench-jupyter@sha256:229166b2be902aef0a2a621fae38037fe0e069875df1b8bfd5bcff8766d6036c

# Install jupyter extensions
RUN --mount=type=bind,from=jupyter-extension-builder,source=/dist,target=/tmp/extensions \
Expand Down
5 changes: 5 additions & 0 deletions src/nemo_jupyter/.devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
"version": "17"
},
"ghcr.io/dhoeric/features/google-cloud-cli@sha256:fa5d894718825c5ad8009ac8f2c9f0cea3d1661eb108a9d465cba9f3fc48965f": {},
"ghcr.io/devcontainers/features/node": {
"version": "24.11.0"
},
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
"./.devcontainer/features/gemini-cli": { "username": "jupyter" },
"./.devcontainer/features/workbench-tools": {
"libPythonVersion": "3.12", // Must match python version in nemo image
"cloud": "${templateOption:cloud}",
Expand Down
5 changes: 5 additions & 0 deletions src/r-analysis/.devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
},
"ghcr.io/devcontainers/features/aws-cli@sha256:17cb4a40151f59144b46957b9264683663b0214371a041ecd53dccc015a4b923": {},
"ghcr.io/dhoeric/features/google-cloud-cli@sha256:fa5d894718825c5ad8009ac8f2c9f0cea3d1661eb108a9d465cba9f3fc48965f": {},
"ghcr.io/devcontainers/features/node": {
"version": "24.11.0"
},
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
"./.devcontainer/features/gemini-cli": { "username": "rstudio" },
"./.devcontainer/features/workbench-tools": {
"cloud": "${templateOption:cloud}",
"username": "rstudio",
Expand Down
5 changes: 5 additions & 0 deletions src/vscode/.devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@
"ghcr.io/devcontainers/features/java@sha256:e75d274ac969b29a59ba6f34c2d098f6a52144d0ec027ef326b724ea4b8b7b4e": {
"version": "17"
},
"ghcr.io/devcontainers/features/node": {
"version": "24.11.0"
},
"ghcr.io/devcontainers/features/aws-cli@sha256:17cb4a40151f59144b46957b9264683663b0214371a041ecd53dccc015a4b923": {},
"ghcr.io/dhoeric/features/google-cloud-cli@sha256:fa5d894718825c5ad8009ac8f2c9f0cea3d1661eb108a9d465cba9f3fc48965f": {},
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
"./.devcontainer/features/gemini-cli": { "username": "abc" },
"./.devcontainer/features/workbench-tools": {
"cloud": "${templateOption:cloud}",
"username": "abc",
Expand Down
18 changes: 18 additions & 0 deletions src/vscode/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM lscr.io/linuxserver/code-server:4.100.3

# Gemini: https://open-vsx.org/extension/Google/geminicodeassist
# Claude: https://open-vsx.org/extension/Anthropic/claude-code
RUN GEMINI_VERSION=$(curl -fsSL "https://open-vsx.org/api/Google/geminicodeassist/latest" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4) && \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use jq here instead of grep/head/cut?

We can even get the full download url with curl ... | jq -r '.files.download'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I recently learned in Dockerfiles it's preferred to have the && at the beginning of the line rather than the end. Also could you indent continuations of commands to distinguish them a bit more?

    && curl ... \
        ... \
        -o ... \
    && CLAUDE_VERSION=...

curl -fL --compressed \
"https://open-vsx.org/api/Google/geminicodeassist/${GEMINI_VERSION}/file/Google.geminicodeassist-${GEMINI_VERSION}.vsix" \
-o /opt/geminicodeassist.vsix && \
CLAUDE_VERSION=$(curl -fsSL "https://open-vsx.org/api/Anthropic/claude-code/latest" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4) && \
curl -fL --compressed \
"https://open-vsx.org/api/Anthropic/claude-code/${CLAUDE_VERSION}/file/Anthropic.claude-code-${CLAUDE_VERSION}.vsix" \
-o /opt/claudecode.vsix

# Install extensions during container init, before code-server starts
COPY install-extensions.sh /etc/cont-init.d/99-install-extensions
RUN chmod +x /etc/cont-init.d/99-install-extensions

WORKDIR /config
4 changes: 3 additions & 1 deletion src/vscode/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ version: "2.4"
services:
app:
container_name: "application-server"
image: "lscr.io/linuxserver/code-server:4.100.3"
build:
context: .
dockerfile: Dockerfile
restart: always
volumes:
- .:/workspace:cached
Expand Down
10 changes: 10 additions & 0 deletions src/vscode/install-extensions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/with-contenv bash
# shellcheck shell=bash
# Installs VS Code extensions once on first container boot.

[ -f /config/.extensions-installed ] && exit 0

HOME=/config s6-setuidgid abc /app/code-server/bin/code-server --extensions-dir /config/extensions --install-extension /opt/geminicodeassist.vsix
HOME=/config s6-setuidgid abc /app/code-server/bin/code-server --extensions-dir /config/extensions --install-extension /opt/claudecode.vsix

touch /config/.extensions-installed
5 changes: 5 additions & 0 deletions src/workbench-jupyter-parabricks/.devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
},
"ghcr.io/dhoeric/features/google-cloud-cli@sha256:fa5d894718825c5ad8009ac8f2c9f0cea3d1661eb108a9d465cba9f3fc48965f": {},
"ghcr.io/devcontainers/features/aws-cli@sha256:17cb4a40151f59144b46957b9264683663b0214371a041ecd53dccc015a4b923":{},
"ghcr.io/devcontainers/features/node": {
"version": "24.11.0"
},
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
"./.devcontainer/features/gemini-cli": { "username": "jupyter" },
"./.devcontainer/features/workbench-tools": {
"cloud": "${templateOption:cloud}",
"username": "jupyter",
Expand Down
Loading