From b248c78b3e151de333d5286b80ded69222fcbbe8 Mon Sep 17 00:00:00 2001 From: Ross Miles Date: Thu, 23 Oct 2025 00:58:22 +0000 Subject: [PATCH 1/4] add Oh My Posh devcontainer feature: manifest, installer, docs, and tests --- docs/ohmyposh-plan.md | 168 +++++++++++++++++++++++ src/ohmyposh/devcontainer-feature.json | 32 +++++ src/ohmyposh/install.sh | 178 +++++++++++++++++++++++++ test/ohmyposh/custom-install-path.sh | 13 ++ test/ohmyposh/scenarios.json | 34 +++++ test/ohmyposh/shells-bash-only.sh | 17 +++ test/ohmyposh/test.sh | 48 +++++++ test/ohmyposh/theme.sh | 14 ++ test/ohmyposh/version.sh | 18 +++ 9 files changed, 522 insertions(+) create mode 100644 docs/ohmyposh-plan.md create mode 100644 src/ohmyposh/devcontainer-feature.json create mode 100644 src/ohmyposh/install.sh create mode 100644 test/ohmyposh/custom-install-path.sh create mode 100644 test/ohmyposh/scenarios.json create mode 100644 test/ohmyposh/shells-bash-only.sh create mode 100644 test/ohmyposh/test.sh create mode 100644 test/ohmyposh/theme.sh create mode 100644 test/ohmyposh/version.sh diff --git a/docs/ohmyposh-plan.md b/docs/ohmyposh-plan.md new file mode 100644 index 0000000..2de8dcb --- /dev/null +++ b/docs/ohmyposh-plan.md @@ -0,0 +1,168 @@ +# Oh My Posh devcontainer feature - Implementation Plan + +## Overview + +Create a devcontainer feature to install Oh My Posh with comprehensive configuration options. The installation will use GitHub releases API to download pre-compiled binaries directly for reliability and version control. + +## Feature Structure + +``` +src/ohmyposh/ +├── devcontainer-feature.json +└── install.sh +``` + +## Configuration Options + +Define in `devcontainer-feature.json`: + +- **`version`** (string, default: "latest") + - Oh My Posh version to install + - Supports: "latest" or specific version like "v19.0.0" + +- **`theme`** (string, default: "jandedobbeleer") + - Built-in theme name to use as fallback + - Examples: "powerlevel10k_rainbow", "dracula", "agnoster", "pure" + +- **`installPath`** (string, default: "/usr/local/bin") + - Binary installation location + +- **`shells`** (string, default: "bash,zsh") + - Comma-separated list of shells to configure + - Options: "bash", "zsh", "fish" + +## Installation Process (`install.sh`) + +1. **Fetch binary from GitHub releases** + - Use GitHub API to get latest/specific release + - Detect architecture: amd64, arm64, arm + - Download appropriate binary for Linux + +2. **Install binary** + - Move to specified `installPath` + - Set execute permissions + - Verify installation + +3. **Create theme file placeholder** + ```bash + touch /home/vscode/.ohmyposh.json + chown vscode:vscode /home/vscode/.ohmyposh.json + ``` + +4. **Configure shells** + - Parse `shells` option + - Add init commands to respective rc files based on selected shells + +## Shell Integration Logic + +Add to shell rc files (`.bashrc`, `.zshrc`, `.config/fish/config.fish`): + +```bash +# Check if user mounted a custom theme +if [ -s ~/.ohmyposh.json ]; then + # File has content, use custom theme + eval "$(oh-my-posh init --config ~/.ohmyposh.json)" +else + # Empty or invalid, use built-in theme from feature option + eval "$(oh-my-posh init --config )" +fi +``` + +Where: +- `-s` checks if file exists and has content +- `` is replaced with actual shell (bash/zsh/fish) +- `` is replaced with the theme option value + +## Custom Theme Usage + +### For Users + +To use a custom Oh My Posh theme, add a mount to your `devcontainer.json`: + +```jsonc +{ + "features": { + "ghcr.io/rosstaco/devcontainer-features/ohmyposh:1": { + "theme": "jandedobbeleer", + "shells": "bash,zsh" + } + }, + "mounts": [ + "source=${localEnv:HOME}/path/to/your/theme.json,target=/home/vscode/.ohmyposh.json,type=bind" + ] +} +``` + +### How It Works + +1. Feature creates `~/.ohmyposh.json` during installation (empty file) +2. Mount target always exists, so mount operation succeeds +3. If user doesn't mount: file remains empty → feature uses built-in theme +4. If user mounts their theme: file has content → feature uses custom theme +5. User has full control over source path (can be anywhere on host) + +## Key Design Decisions + +### 1. Single Theme File Location +- **Path**: `~/.ohmyposh.json` +- **Rationale**: Simple, predictable, always exists +- **Benefit**: No mount failures, clear documentation + +### 2. Graceful Fallback +- Empty file = use built-in theme from feature option +- Invalid JSON = use built-in theme +- Missing file = impossible (feature creates it) + +### 3. User Flexibility +- Mount source path is user's choice +- Can point to anywhere on host system +- Not restricted to `.config` or specific locations + +### 4. No `initializeCommand` Required +- Feature creates target file during build +- Mount succeeds even if source doesn't exist on host +- Simpler user setup + +## Implementation Steps + +1. Create `src/ohmyposh/devcontainer-feature.json` with options schema +2. Create `src/ohmyposh/install.sh` with: + - GitHub releases API integration + - Architecture detection + - Binary download and installation + - Theme file creation + - Shell configuration +3. Add error handling for: + - Network failures + - Architecture not supported + - Invalid version specified +4. Test on multiple base images (debian, ubuntu, alpine if supported) +5. Document in auto-generated README + +## Testing Scenarios + +1. **Default installation** - no options, uses latest + jandedobbeleer theme +2. **Specific version** - `"version": "v19.0.0"` +3. **Different theme** - `"theme": "dracula"` +4. **Custom shells** - `"shells": "zsh"` or `"shells": "bash,fish"` +5. **With custom theme mount** - user provides own theme file +6. **Without custom theme mount** - falls back to built-in + +## Documentation Notes + +Include in generated README: + +- List of popular built-in themes +- Link to Oh My Posh themes gallery +- Example of mounting custom theme +- Note about Nerd Fonts requirement for most themes +- Shell configuration details +- Troubleshooting common issues + +## Future Enhancements (Optional) + +- Add `autoUpgrade` option for automatic updates +- Support for theme URLs (remote themes) +- Cache directory configuration +- Multiple theme files support +- Theme validation during build diff --git a/src/ohmyposh/devcontainer-feature.json b/src/ohmyposh/devcontainer-feature.json new file mode 100644 index 0000000..0628af0 --- /dev/null +++ b/src/ohmyposh/devcontainer-feature.json @@ -0,0 +1,32 @@ +{ + "id": "ohmyposh", + "version": "1.0.0", + "name": "Oh My Posh", + "description": "A prompt theme engine for any shell with customizable themes and segments", + "documentationURL": "https://github.com/rosstaco/devcontainer-features/tree/main/src/ohmyposh", + "options": { + "version": { + "type": "string", + "default": "latest", + "description": "Version of Oh My Posh to install. Use 'latest' or a specific version like 'v19.0.0'" + }, + "theme": { + "type": "string", + "default": "jandedobbeleer", + "description": "Built-in theme name to use as fallback (e.g., 'jandedobbeleer', 'powerlevel10k_rainbow', 'dracula', 'agnoster')" + }, + "installPath": { + "type": "string", + "default": "/usr/local/bin", + "description": "Directory where the oh-my-posh binary will be installed" + }, + "shells": { + "type": "string", + "default": "bash,zsh", + "description": "Comma-separated list of shells to configure (bash, zsh, fish)" + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] +} diff --git a/src/ohmyposh/install.sh b/src/ohmyposh/install.sh new file mode 100644 index 0000000..42bf42e --- /dev/null +++ b/src/ohmyposh/install.sh @@ -0,0 +1,178 @@ +#!/bin/bash +set -e + +# Oh My Posh installation script for devcontainer features +# https://ohmyposh.dev + +VERSION="${VERSION:-latest}" +THEME="${THEME:-jandedobbeleer}" +INSTALL_PATH="${INSTALLPATH:-/usr/local/bin}" +SHELLS="${SHELLS:-bash,zsh}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}Installing Oh My Posh...${NC}" + +# Ensure curl is available +if ! command -v curl &> /dev/null; then + echo "Installing curl..." + export DEBIAN_FRONTEND=noninteractive + if command -v apt-get &> /dev/null; then + apt-get update && apt-get install -y curl + elif command -v apk &> /dev/null; then + apk add --no-cache curl + elif command -v yum &> /dev/null; then + yum install -y curl + else + echo -e "${RED}Could not install curl. Please install it manually.${NC}" + exit 1 + fi +fi + +# Detect architecture +ARCH=$(uname -m) +case $ARCH in + x86_64) + ARCH="amd64" + ;; + aarch64|arm64) + ARCH="arm64" + ;; + armv7l|armv6l) + ARCH="arm" + ;; + *) + echo -e "${RED}Unsupported architecture: $ARCH${NC}" + exit 1 + ;; +esac + +echo "Detected architecture: $ARCH" + +# Get the download URL +if [ "$VERSION" = "latest" ]; then + echo "Fetching latest version..." + DOWNLOAD_URL="https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/posh-linux-${ARCH}" +else + echo "Using version: $VERSION" + DOWNLOAD_URL="https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/${VERSION}/posh-linux-${ARCH}" +fi + +echo "Downloading Oh My Posh from: $DOWNLOAD_URL" + +# Download the binary +TEMP_FILE="/tmp/oh-my-posh-install" +if ! curl -fsSL "$DOWNLOAD_URL" -o "$TEMP_FILE"; then + echo -e "${RED}Failed to download Oh My Posh${NC}" + exit 1 +fi + +# Create install directory if it doesn't exist +mkdir -p "$INSTALL_PATH" + +# Install the binary +mv "$TEMP_FILE" "$INSTALL_PATH/oh-my-posh" +chmod +x "$INSTALL_PATH/oh-my-posh" + +echo -e "${GREEN}Oh My Posh binary installed to $INSTALL_PATH/oh-my-posh${NC}" + +# Verify installation +if ! "$INSTALL_PATH/oh-my-posh" version; then + echo -e "${RED}Oh My Posh installation verification failed${NC}" + exit 1 +fi + +# Determine the user to configure +if [ -n "$_REMOTE_USER" ]; then + USER_NAME="$_REMOTE_USER" +elif [ -n "$REMOTE_USER" ]; then + USER_NAME="$REMOTE_USER" +else + USER_NAME="${USERNAME:-vscode}" +fi + +USER_HOME=$(eval echo "~$USER_NAME") + +echo "Configuring for user: $USER_NAME (home: $USER_HOME)" + +# Create the theme file placeholder +echo "Creating theme file at $USER_HOME/.ohmyposh.json" +touch "$USER_HOME/.ohmyposh.json" +chown "$USER_NAME:$USER_NAME" "$USER_HOME/.ohmyposh.json" 2>/dev/null || true + +# Parse shells and configure each one +IFS=',' read -ra SHELL_ARRAY <<< "$SHELLS" + +for SHELL_NAME in "${SHELL_ARRAY[@]}"; do + # Trim whitespace + SHELL_NAME=$(echo "$SHELL_NAME" | xargs) + + case "$SHELL_NAME" in + bash) + echo "Configuring bash..." + RC_FILE="$USER_HOME/.bashrc" + SHELL_CMD="bash" + ;; + zsh) + echo "Configuring zsh..." + RC_FILE="$USER_HOME/.zshrc" + SHELL_CMD="zsh" + ;; + fish) + echo "Configuring fish..." + RC_FILE="$USER_HOME/.config/fish/config.fish" + SHELL_CMD="fish" + mkdir -p "$USER_HOME/.config/fish" + ;; + *) + echo -e "${YELLOW}Unknown shell: $SHELL_NAME, skipping...${NC}" + continue + ;; + esac + + # Create RC file if it doesn't exist + touch "$RC_FILE" + + # Check if already configured + if grep -q "oh-my-posh init" "$RC_FILE" 2>/dev/null; then + echo "Oh My Posh already configured in $RC_FILE" + continue + fi + + # Add Oh My Posh initialization + cat >> "$RC_FILE" << EOF + +# Oh My Posh configuration +if [ -s ~/.ohmyposh.json ]; then + # Use custom theme if mounted + eval "\$(oh-my-posh init $SHELL_CMD --config ~/.ohmyposh.json)" +else + # Use built-in theme + eval "\$(oh-my-posh init $SHELL_CMD --config $THEME)" +fi +EOF + + chown "$USER_NAME:$USER_NAME" "$RC_FILE" 2>/dev/null || true + + echo -e "${GREEN}Configured $SHELL_NAME${NC}" +done + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Oh My Posh installation complete!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo "Theme: $THEME (built-in fallback)" +echo "Configured shells: $SHELLS" +echo "" +echo "To use a custom theme, add this to your devcontainer.json:" +echo "" +echo ' "mounts": [' +echo ' "source=${localEnv:HOME}/path/to/your/theme.json,target='$USER_HOME'/.ohmyposh.json,type=bind"' +echo ' ]' +echo "" +echo "For more information: https://ohmyposh.dev" diff --git a/test/ohmyposh/custom-install-path.sh b/test/ohmyposh/custom-install-path.sh new file mode 100644 index 0000000..ba0adda --- /dev/null +++ b/test/ohmyposh/custom-install-path.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# This test verifies custom install path + +set -e + +source dev-container-features-test-lib + +check "oh-my-posh installed in custom path" test -x /usr/bin/oh-my-posh + +check "oh-my-posh is accessible" /usr/bin/oh-my-posh version + +reportResults diff --git a/test/ohmyposh/scenarios.json b/test/ohmyposh/scenarios.json new file mode 100644 index 0000000..f5a3dd8 --- /dev/null +++ b/test/ohmyposh/scenarios.json @@ -0,0 +1,34 @@ +{ + "shells-bash-only": { + "image": "ubuntu:latest", + "features": { + "ohmyposh": { + "shells": "bash" + } + } + }, + "theme": { + "image": "ubuntu:latest", + "features": { + "ohmyposh": { + "theme": "dracula" + } + } + }, + "version": { + "image": "ubuntu:latest", + "features": { + "ohmyposh": { + "version": "v23.0.0" + } + } + }, + "custom-install-path": { + "image": "ubuntu:latest", + "features": { + "ohmyposh": { + "installPath": "/usr/bin" + } + } + } +} diff --git a/test/ohmyposh/shells-bash-only.sh b/test/ohmyposh/shells-bash-only.sh new file mode 100644 index 0000000..5239ff6 --- /dev/null +++ b/test/ohmyposh/shells-bash-only.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# This test verifies shell configuration options + +set -e + +source dev-container-features-test-lib + +check "oh-my-posh is installed" oh-my-posh version + +# Only bash should be configured when shells="bash" +check "bash is configured" grep -q "oh-my-posh init bash" ~/.bashrc || echo "Warning: bash not configured" + +# zsh should NOT be configured +check "zsh is not configured" bash -c "! grep -q 'oh-my-posh init zsh' ~/.zshrc 2>/dev/null || ! test -f ~/.zshrc" + +reportResults diff --git a/test/ohmyposh/test.sh b/test/ohmyposh/test.sh new file mode 100644 index 0000000..c192236 --- /dev/null +++ b/test/ohmyposh/test.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# This test file will be executed against an auto-generated devcontainer.json that +# includes the 'ohmyposh' Feature with no options. +# +# Eg: +# { +# "image": "<..some-base-image...>", +# "features": { +# "ohmyposh": {} +# } +# } +# +# Thus, the value of all options will fall back to the default value in the +# Feature's 'devcontainer-feature.json'. +# +# These scripts are run as 'root' by default. Although that can be changed +# with the '--remote-user' flag. +# +# For more information, see: https://github.com/devcontainers/cli/blob/main/docs/features/test.md + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +source dev-container-features-test-lib + +# Feature-specific tests +# The 'check' command comes from the dev-container-features-test-lib. + +check "oh-my-posh is installed" oh-my-posh version + +check "oh-my-posh is executable" which oh-my-posh + +check "oh-my-posh binary in correct location" test -x /usr/local/bin/oh-my-posh + +check "theme placeholder file exists" test -f ~/.ohmyposh.json + +check "bash is configured" grep -q "oh-my-posh init bash" ~/.bashrc + +check "zsh is configured" grep -q "oh-my-posh init zsh" ~/.zshrc + +check "oh-my-posh can init bash" oh-my-posh init bash --config jandedobbeleer + +check "oh-my-posh can init zsh" oh-my-posh init zsh --config jandedobbeleer + +# Report results +# If any of the checks above exited with a non-zero exit code, the test will fail. +reportResults diff --git a/test/ohmyposh/theme.sh b/test/ohmyposh/theme.sh new file mode 100644 index 0000000..df3d904 --- /dev/null +++ b/test/ohmyposh/theme.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# This test verifies custom theme configuration + +set -e + +source dev-container-features-test-lib + +# Check that dracula theme is configured in shell rc files +check "bash configured with dracula theme" grep -q "jandedobbeleer\|dracula" ~/.bashrc + +check "theme can be used" oh-my-posh init bash --config dracula + +reportResults diff --git a/test/ohmyposh/version.sh b/test/ohmyposh/version.sh new file mode 100644 index 0000000..e0c33c9 --- /dev/null +++ b/test/ohmyposh/version.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# This test verifies that a specific version can be installed + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +source dev-container-features-test-lib + +# Feature-specific tests +check "oh-my-posh is installed" oh-my-posh version + +check "oh-my-posh version output" oh-my-posh version + +# The version should contain a version number (format may vary) +check "version contains number" bash -c "oh-my-posh version | grep -E '[0-9]+\.[0-9]+'" + +reportResults From 5cca238bf030cd94342b5eb4b071f2d7c5126452 Mon Sep 17 00:00:00 2001 From: Ross Miles Date: Wed, 5 Nov 2025 22:11:45 +0000 Subject: [PATCH 2/4] add Oh My Posh feature with installation script, configuration, and documentation --- .devcontainer/devcontainer.json | 7 +- .../ohmyposh/devcontainer-feature.json | 40 ++++ .devcontainer/ohmyposh/install.sh | 178 ++++++++++++++++++ .github/workflows/test.yaml | 6 +- .gitignore | 2 + README.md | 125 +++++------- justfile | 24 +++ src/ohmyposh/devcontainer-feature.json | 9 +- 8 files changed, 304 insertions(+), 87 deletions(-) create mode 100644 .devcontainer/ohmyposh/devcontainer-feature.json create mode 100644 .devcontainer/ohmyposh/install.sh create mode 100644 .gitignore create mode 100644 justfile diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ad4c73b..8b131dd 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -18,7 +18,12 @@ } }, "features": { - "ghcr.io/devcontainers/features/docker-in-docker:2": {} + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/jsburckhardt/devcontainer-features/just:1": {}, + "./ohmyposh": { + "version": "latest", + "theme": "jandedobbeleer" + } }, "remoteUser": "node", "updateContentCommand": "npm install -g @devcontainers/cli" diff --git a/.devcontainer/ohmyposh/devcontainer-feature.json b/.devcontainer/ohmyposh/devcontainer-feature.json new file mode 100644 index 0000000..21326c8 --- /dev/null +++ b/.devcontainer/ohmyposh/devcontainer-feature.json @@ -0,0 +1,40 @@ +{ + "id": "ohmyposh", + "version": "1.0.0", + "name": "Oh My Posh", + "description": "A prompt theme engine for any shell with customizable themes and segments", + "documentationURL": "https://github.com/rosstaco/devcontainer-features/tree/main/src/ohmyposh", + "options": { + "version": { + "type": "string", + "default": "latest", + "description": "Version of Oh My Posh to install. Use 'latest' or a specific version like 'v19.0.0'" + }, + "theme": { + "type": "string", + "default": "jandedobbeleer", + "description": "Built-in theme name to use as fallback (e.g., 'jandedobbeleer', 'powerlevel10k_rainbow', 'dracula', 'agnoster')" + }, + "installPath": { + "type": "string", + "default": "/usr/local/bin", + "description": "Directory where the oh-my-posh binary will be installed" + }, + "shells": { + "type": "string", + "default": "bash,zsh", + "description": "Comma-separated list of shells to configure (bash, zsh, fish)" + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ], + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.fontFamily": "Hack Nerd Font, MesloLGS NF, FiraCode Nerd Font, JetBrainsMono Nerd Font, monospace", + "terminal.integrated.fontLigatures.enabled": true + } + } + } +} diff --git a/.devcontainer/ohmyposh/install.sh b/.devcontainer/ohmyposh/install.sh new file mode 100644 index 0000000..42bf42e --- /dev/null +++ b/.devcontainer/ohmyposh/install.sh @@ -0,0 +1,178 @@ +#!/bin/bash +set -e + +# Oh My Posh installation script for devcontainer features +# https://ohmyposh.dev + +VERSION="${VERSION:-latest}" +THEME="${THEME:-jandedobbeleer}" +INSTALL_PATH="${INSTALLPATH:-/usr/local/bin}" +SHELLS="${SHELLS:-bash,zsh}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}Installing Oh My Posh...${NC}" + +# Ensure curl is available +if ! command -v curl &> /dev/null; then + echo "Installing curl..." + export DEBIAN_FRONTEND=noninteractive + if command -v apt-get &> /dev/null; then + apt-get update && apt-get install -y curl + elif command -v apk &> /dev/null; then + apk add --no-cache curl + elif command -v yum &> /dev/null; then + yum install -y curl + else + echo -e "${RED}Could not install curl. Please install it manually.${NC}" + exit 1 + fi +fi + +# Detect architecture +ARCH=$(uname -m) +case $ARCH in + x86_64) + ARCH="amd64" + ;; + aarch64|arm64) + ARCH="arm64" + ;; + armv7l|armv6l) + ARCH="arm" + ;; + *) + echo -e "${RED}Unsupported architecture: $ARCH${NC}" + exit 1 + ;; +esac + +echo "Detected architecture: $ARCH" + +# Get the download URL +if [ "$VERSION" = "latest" ]; then + echo "Fetching latest version..." + DOWNLOAD_URL="https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/posh-linux-${ARCH}" +else + echo "Using version: $VERSION" + DOWNLOAD_URL="https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/${VERSION}/posh-linux-${ARCH}" +fi + +echo "Downloading Oh My Posh from: $DOWNLOAD_URL" + +# Download the binary +TEMP_FILE="/tmp/oh-my-posh-install" +if ! curl -fsSL "$DOWNLOAD_URL" -o "$TEMP_FILE"; then + echo -e "${RED}Failed to download Oh My Posh${NC}" + exit 1 +fi + +# Create install directory if it doesn't exist +mkdir -p "$INSTALL_PATH" + +# Install the binary +mv "$TEMP_FILE" "$INSTALL_PATH/oh-my-posh" +chmod +x "$INSTALL_PATH/oh-my-posh" + +echo -e "${GREEN}Oh My Posh binary installed to $INSTALL_PATH/oh-my-posh${NC}" + +# Verify installation +if ! "$INSTALL_PATH/oh-my-posh" version; then + echo -e "${RED}Oh My Posh installation verification failed${NC}" + exit 1 +fi + +# Determine the user to configure +if [ -n "$_REMOTE_USER" ]; then + USER_NAME="$_REMOTE_USER" +elif [ -n "$REMOTE_USER" ]; then + USER_NAME="$REMOTE_USER" +else + USER_NAME="${USERNAME:-vscode}" +fi + +USER_HOME=$(eval echo "~$USER_NAME") + +echo "Configuring for user: $USER_NAME (home: $USER_HOME)" + +# Create the theme file placeholder +echo "Creating theme file at $USER_HOME/.ohmyposh.json" +touch "$USER_HOME/.ohmyposh.json" +chown "$USER_NAME:$USER_NAME" "$USER_HOME/.ohmyposh.json" 2>/dev/null || true + +# Parse shells and configure each one +IFS=',' read -ra SHELL_ARRAY <<< "$SHELLS" + +for SHELL_NAME in "${SHELL_ARRAY[@]}"; do + # Trim whitespace + SHELL_NAME=$(echo "$SHELL_NAME" | xargs) + + case "$SHELL_NAME" in + bash) + echo "Configuring bash..." + RC_FILE="$USER_HOME/.bashrc" + SHELL_CMD="bash" + ;; + zsh) + echo "Configuring zsh..." + RC_FILE="$USER_HOME/.zshrc" + SHELL_CMD="zsh" + ;; + fish) + echo "Configuring fish..." + RC_FILE="$USER_HOME/.config/fish/config.fish" + SHELL_CMD="fish" + mkdir -p "$USER_HOME/.config/fish" + ;; + *) + echo -e "${YELLOW}Unknown shell: $SHELL_NAME, skipping...${NC}" + continue + ;; + esac + + # Create RC file if it doesn't exist + touch "$RC_FILE" + + # Check if already configured + if grep -q "oh-my-posh init" "$RC_FILE" 2>/dev/null; then + echo "Oh My Posh already configured in $RC_FILE" + continue + fi + + # Add Oh My Posh initialization + cat >> "$RC_FILE" << EOF + +# Oh My Posh configuration +if [ -s ~/.ohmyposh.json ]; then + # Use custom theme if mounted + eval "\$(oh-my-posh init $SHELL_CMD --config ~/.ohmyposh.json)" +else + # Use built-in theme + eval "\$(oh-my-posh init $SHELL_CMD --config $THEME)" +fi +EOF + + chown "$USER_NAME:$USER_NAME" "$RC_FILE" 2>/dev/null || true + + echo -e "${GREEN}Configured $SHELL_NAME${NC}" +done + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Oh My Posh installation complete!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo "Theme: $THEME (built-in fallback)" +echo "Configured shells: $SHELLS" +echo "" +echo "To use a custom theme, add this to your devcontainer.json:" +echo "" +echo ' "mounts": [' +echo ' "source=${localEnv:HOME}/path/to/your/theme.json,target='$USER_HOME'/.ohmyposh.json,type=bind"' +echo ' ]' +echo "" +echo "For more information: https://ohmyposh.dev" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 4033ca9..b5aa7f6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,8 +13,7 @@ jobs: strategy: matrix: features: - - color - - hello + - ohmyposh baseImage: - debian:latest - ubuntu:latest @@ -34,8 +33,7 @@ jobs: strategy: matrix: features: - - color - - hello + - ohmyposh steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56aa77b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Feature tarballs for local testing +.devcontainer/*.tgz diff --git a/README.md b/README.md index 07706d2..753a14f 100644 --- a/README.md +++ b/README.md @@ -1,111 +1,74 @@ -# Dev Container Features: Self Authoring Template +# Dev Container Features -> This repo provides a starting point and example for creating your own custom [dev container Features](https://containers.dev/implementors/features/), hosted for free on GitHub Container Registry. The example in this repository follows the [dev container Feature distribution specification](https://containers.dev/implementors/features-distribution/). -> -> To provide feedback to the specification, please leave a comment [on spec issue #70](https://github.com/devcontainers/spec/issues/70). For more broad feedback regarding dev container Features, please see [spec issue #61](https://github.com/devcontainers/spec/issues/61). +Custom [dev container Features](https://containers.dev/implementors/features/) for enhancing development containers, hosted on GitHub Container Registry. This repository follows the [dev container Feature distribution specification](https://containers.dev/implementors/features-distribution/). -## Repo and Feature Structure +## Available Features -Similar to the [`devcontainers/features`](https://github.com/devcontainers/features) repo, this repository has a `src` folder. Each Feature has its own sub-folder, containing at least a `devcontainer-feature.json` and an entrypoint script `install.sh`. +### Oh My Posh -``` -├── src -│ ├── my-feature -│ │ ├── devcontainer-feature.json -│ │ └── install.sh -│ ├── another-feature -│ │ ├── devcontainer-feature.json -│ │ └── install.sh -│ ├── ... -│ │ ├── devcontainer-feature.json -│ │ └── install.sh -... -``` - -An [implementing tool](https://containers.dev/supporting#tools) will composite [the documented dev container properties](https://containers.dev/implementors/features/#devcontainer-feature-json-properties) from the feature's `devcontainer-feature.json` file, and execute in the `install.sh` entrypoint script in the container during build time. Implementing tools are also free to process attributes under the `customizations` property as desired. +Installs [Oh My Posh](https://ohmyposh.dev/), a prompt theme engine for customizing your shell prompt across bash, zsh, and fish. -### Options +**Usage:** -All available options for a Feature should be declared in the `devcontainer-feature.json`. The syntax for the `options` property can be found in the [devcontainer Feature json properties reference](https://containers.dev/implementors/features/#devcontainer-feature-json-properties). - -Options are exported as Feature-scoped environment variables. The option name is captialized and sanitized according to [option resolution](https://containers.dev/implementors/features/#option-resolution). -## Distributing Features +```json +{ + "features": { + "ghcr.io/rosstaco/devcontainer-features/ohmyposh:1": { + "version": "latest", + "theme": "jandedobbeleer", + "installPath": "/usr/local/bin", + "shells": "bash,zsh" + } + } +} +``` -### Versioning +**Options:** +- `version` - Oh My Posh version to install (default: "latest") +- `theme` - Built-in theme name to use (default: "jandedobbeleer") +- `installPath` - Installation directory (default: "/usr/local/bin") +- `shells` - Comma-separated list of shells to configure: bash, zsh, fish (default: "bash,zsh") -Features are individually versioned by the `version` attribute in a Feature's `devcontainer-feature.json`. Features are versioned according to the semver specification. More details can be found in [the dev container Feature specification](https://containers.dev/implementors/features/#versioning). +**Custom Themes:** -### Publishing +To use a custom theme file from your host machine, add a mount in your `devcontainer.json`: -> NOTE: The Distribution spec can be [found here](https://containers.dev/implementors/features-distribution/). -> -> While any registry [implementing the OCI Distribution spec](https://github.com/opencontainers/distribution-spec) can be used, this template will leverage GHCR (GitHub Container Registry) as the backing registry. +```json +{ + "features": { + "ghcr.io/rosstaco/devcontainer-features/ohmyposh:1": {} + }, + "mounts": [ + "source=${localEnv:HOME}/path/to/theme.json,target=/home/vscode/.ohmyposh.json,type=bind" + ] +} +``` -Features are meant to be easily sharable units of dev container configuration and installation code. +The feature creates a placeholder file at `~/.ohmyposh.json` during installation. If you mount a custom theme to this location, it will be used automatically. Otherwise, the built-in theme specified in the options will be used. -This repo contains a **GitHub Action** [workflow](.github/workflows/release.yaml) that will publish each Feature to GHCR. +## Publishing -*Allow GitHub Actions to create and approve pull requests* should be enabled in the repository's `Settings > Actions > General > Workflow permissions` for auto generation of `src//README.md` per Feature (which merges any existing `src//NOTES.md`). +This repository uses a **GitHub Action** [workflow](.github/workflows/release.yaml) that publishes each Feature to GHCR (GitHub Container Registry). -By default, each Feature will be prefixed with the `` namespace. For example, Features in this repository can be referenced in a `devcontainer.json` with: +Features in this repository are referenced with: ``` -ghcr.io///:1 +ghcr.io/rosstaco/devcontainer-features/:1 ``` -The provided GitHub Action will also publish a third "metadata" package with just the namespace, eg: `ghcr.io//`. This contains information useful for tools aiding in Feature discovery. - -'`/`' is known as the feature collection namespace. - ### Marking Feature Public -Note that by default, GHCR packages are marked as `private`. To stay within the free tier, Features need to be marked as `public`. - -This can be done by navigating to the Feature's "package settings" page in GHCR, and setting the visibility to 'public`. The URL may look something like: +By default, GHCR packages are marked as `private`. To make them publicly accessible, navigate to the Feature's package settings page in GHCR and set the visibility to `public`: ``` -https://github.com/users//packages/container/%2F/settings +https://github.com/users/rosstaco/packages/container/devcontainer-features%2F/settings ``` -image - ### Adding Features to the Index -If you'd like your Features to appear in our [public index](https://containers.dev/features) so that other community members can find them, you can do the following: +To add Features to the [public index](https://containers.dev/features): * Go to [github.com/devcontainers/devcontainers.github.io](https://github.com/devcontainers/devcontainers.github.io) - * This is the GitHub repo backing the [containers.dev](https://containers.dev/) spec site * Open a PR to modify the [collection-index.yml](https://github.com/devcontainers/devcontainers.github.io/blob/gh-pages/_data/collection-index.yml) file -This index is from where [supporting tools](https://containers.dev/supporting) like [VS Code Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) and [GitHub Codespaces](https://github.com/features/codespaces) surface Features for their dev container creation UI. - -#### Using private Features in Codespaces - -For any Features hosted in GHCR that are kept private, the `GITHUB_TOKEN` access token in your environment will need to have `package:read` and `contents:read` for the associated repository. - -Many implementing tools use a broadly scoped access token and will work automatically. GitHub Codespaces uses repo-scoped tokens, and therefore you'll need to add the permissions in `devcontainer.json` - -An example `devcontainer.json` can be found below. - -```jsonc -{ - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "features": { - "ghcr.io///:1": { - "option": "value" - } - }, - "customizations": { - "codespaces": { - "repositories": { - "/": { - "permissions": { - "packages": "read", - "contents": "read" - } - } - } - } - } -} -``` +This allows tools like VS Code Dev Containers and GitHub Codespaces to surface your Features in their creation UI. diff --git a/justfile b/justfile new file mode 100644 index 0000000..2d58032 --- /dev/null +++ b/justfile @@ -0,0 +1,24 @@ +# Copy feature to .devcontainer for local testing +build-feature feature: + #!/usr/bin/env bash + set -e + echo "Copying {{feature}} feature to .devcontainer..." + rm -rf .devcontainer/{{feature}} + mkdir -p .devcontainer/{{feature}} + cp src/{{feature}}/devcontainer-feature.json .devcontainer/{{feature}}/ + cp src/{{feature}}/install.sh .devcontainer/{{feature}}/ + echo "✓ Feature copied to .devcontainer/{{feature}}" + +# Build Oh My Posh feature +build-ohmyposh: + just build-feature ohmyposh + +# Build all features +build-all: + just build-ohmyposh + +# Clean copied features +clean: + rm -rf .devcontainer/ohmyposh + rm -f .devcontainer/*.tgz + echo "✓ Cleaned local features" diff --git a/src/ohmyposh/devcontainer-feature.json b/src/ohmyposh/devcontainer-feature.json index 0628af0..e0209af 100644 --- a/src/ohmyposh/devcontainer-feature.json +++ b/src/ohmyposh/devcontainer-feature.json @@ -28,5 +28,12 @@ }, "installsAfter": [ "ghcr.io/devcontainers/features/common-utils" - ] + ], + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.fontFamily": "Hack Nerd Font, MesloLGS NF, FiraCode Nerd Font, JetBrainsMono Nerd Font, monospace" + } + } + } } From 0aad8788696c2c62e35227b4187595b95ae888cd Mon Sep 17 00:00:00 2001 From: Ross Miles Date: Wed, 5 Nov 2025 22:35:40 +0000 Subject: [PATCH 3/4] remove Oh My Posh feature and related installation scripts from testing --- .devcontainer/devcontainer.json | 6 +- .../ohmyposh/devcontainer-feature.json | 40 ---- .devcontainer/ohmyposh/install.sh | 178 ------------------ 3 files changed, 1 insertion(+), 223 deletions(-) delete mode 100644 .devcontainer/ohmyposh/devcontainer-feature.json delete mode 100644 .devcontainer/ohmyposh/install.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8b131dd..7201fd5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,11 +19,7 @@ }, "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, - "ghcr.io/jsburckhardt/devcontainer-features/just:1": {}, - "./ohmyposh": { - "version": "latest", - "theme": "jandedobbeleer" - } + "ghcr.io/jsburckhardt/devcontainer-features/just:1": {} }, "remoteUser": "node", "updateContentCommand": "npm install -g @devcontainers/cli" diff --git a/.devcontainer/ohmyposh/devcontainer-feature.json b/.devcontainer/ohmyposh/devcontainer-feature.json deleted file mode 100644 index 21326c8..0000000 --- a/.devcontainer/ohmyposh/devcontainer-feature.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "ohmyposh", - "version": "1.0.0", - "name": "Oh My Posh", - "description": "A prompt theme engine for any shell with customizable themes and segments", - "documentationURL": "https://github.com/rosstaco/devcontainer-features/tree/main/src/ohmyposh", - "options": { - "version": { - "type": "string", - "default": "latest", - "description": "Version of Oh My Posh to install. Use 'latest' or a specific version like 'v19.0.0'" - }, - "theme": { - "type": "string", - "default": "jandedobbeleer", - "description": "Built-in theme name to use as fallback (e.g., 'jandedobbeleer', 'powerlevel10k_rainbow', 'dracula', 'agnoster')" - }, - "installPath": { - "type": "string", - "default": "/usr/local/bin", - "description": "Directory where the oh-my-posh binary will be installed" - }, - "shells": { - "type": "string", - "default": "bash,zsh", - "description": "Comma-separated list of shells to configure (bash, zsh, fish)" - } - }, - "installsAfter": [ - "ghcr.io/devcontainers/features/common-utils" - ], - "customizations": { - "vscode": { - "settings": { - "terminal.integrated.fontFamily": "Hack Nerd Font, MesloLGS NF, FiraCode Nerd Font, JetBrainsMono Nerd Font, monospace", - "terminal.integrated.fontLigatures.enabled": true - } - } - } -} diff --git a/.devcontainer/ohmyposh/install.sh b/.devcontainer/ohmyposh/install.sh deleted file mode 100644 index 42bf42e..0000000 --- a/.devcontainer/ohmyposh/install.sh +++ /dev/null @@ -1,178 +0,0 @@ -#!/bin/bash -set -e - -# Oh My Posh installation script for devcontainer features -# https://ohmyposh.dev - -VERSION="${VERSION:-latest}" -THEME="${THEME:-jandedobbeleer}" -INSTALL_PATH="${INSTALLPATH:-/usr/local/bin}" -SHELLS="${SHELLS:-bash,zsh}" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -echo -e "${GREEN}Installing Oh My Posh...${NC}" - -# Ensure curl is available -if ! command -v curl &> /dev/null; then - echo "Installing curl..." - export DEBIAN_FRONTEND=noninteractive - if command -v apt-get &> /dev/null; then - apt-get update && apt-get install -y curl - elif command -v apk &> /dev/null; then - apk add --no-cache curl - elif command -v yum &> /dev/null; then - yum install -y curl - else - echo -e "${RED}Could not install curl. Please install it manually.${NC}" - exit 1 - fi -fi - -# Detect architecture -ARCH=$(uname -m) -case $ARCH in - x86_64) - ARCH="amd64" - ;; - aarch64|arm64) - ARCH="arm64" - ;; - armv7l|armv6l) - ARCH="arm" - ;; - *) - echo -e "${RED}Unsupported architecture: $ARCH${NC}" - exit 1 - ;; -esac - -echo "Detected architecture: $ARCH" - -# Get the download URL -if [ "$VERSION" = "latest" ]; then - echo "Fetching latest version..." - DOWNLOAD_URL="https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/posh-linux-${ARCH}" -else - echo "Using version: $VERSION" - DOWNLOAD_URL="https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/${VERSION}/posh-linux-${ARCH}" -fi - -echo "Downloading Oh My Posh from: $DOWNLOAD_URL" - -# Download the binary -TEMP_FILE="/tmp/oh-my-posh-install" -if ! curl -fsSL "$DOWNLOAD_URL" -o "$TEMP_FILE"; then - echo -e "${RED}Failed to download Oh My Posh${NC}" - exit 1 -fi - -# Create install directory if it doesn't exist -mkdir -p "$INSTALL_PATH" - -# Install the binary -mv "$TEMP_FILE" "$INSTALL_PATH/oh-my-posh" -chmod +x "$INSTALL_PATH/oh-my-posh" - -echo -e "${GREEN}Oh My Posh binary installed to $INSTALL_PATH/oh-my-posh${NC}" - -# Verify installation -if ! "$INSTALL_PATH/oh-my-posh" version; then - echo -e "${RED}Oh My Posh installation verification failed${NC}" - exit 1 -fi - -# Determine the user to configure -if [ -n "$_REMOTE_USER" ]; then - USER_NAME="$_REMOTE_USER" -elif [ -n "$REMOTE_USER" ]; then - USER_NAME="$REMOTE_USER" -else - USER_NAME="${USERNAME:-vscode}" -fi - -USER_HOME=$(eval echo "~$USER_NAME") - -echo "Configuring for user: $USER_NAME (home: $USER_HOME)" - -# Create the theme file placeholder -echo "Creating theme file at $USER_HOME/.ohmyposh.json" -touch "$USER_HOME/.ohmyposh.json" -chown "$USER_NAME:$USER_NAME" "$USER_HOME/.ohmyposh.json" 2>/dev/null || true - -# Parse shells and configure each one -IFS=',' read -ra SHELL_ARRAY <<< "$SHELLS" - -for SHELL_NAME in "${SHELL_ARRAY[@]}"; do - # Trim whitespace - SHELL_NAME=$(echo "$SHELL_NAME" | xargs) - - case "$SHELL_NAME" in - bash) - echo "Configuring bash..." - RC_FILE="$USER_HOME/.bashrc" - SHELL_CMD="bash" - ;; - zsh) - echo "Configuring zsh..." - RC_FILE="$USER_HOME/.zshrc" - SHELL_CMD="zsh" - ;; - fish) - echo "Configuring fish..." - RC_FILE="$USER_HOME/.config/fish/config.fish" - SHELL_CMD="fish" - mkdir -p "$USER_HOME/.config/fish" - ;; - *) - echo -e "${YELLOW}Unknown shell: $SHELL_NAME, skipping...${NC}" - continue - ;; - esac - - # Create RC file if it doesn't exist - touch "$RC_FILE" - - # Check if already configured - if grep -q "oh-my-posh init" "$RC_FILE" 2>/dev/null; then - echo "Oh My Posh already configured in $RC_FILE" - continue - fi - - # Add Oh My Posh initialization - cat >> "$RC_FILE" << EOF - -# Oh My Posh configuration -if [ -s ~/.ohmyposh.json ]; then - # Use custom theme if mounted - eval "\$(oh-my-posh init $SHELL_CMD --config ~/.ohmyposh.json)" -else - # Use built-in theme - eval "\$(oh-my-posh init $SHELL_CMD --config $THEME)" -fi -EOF - - chown "$USER_NAME:$USER_NAME" "$RC_FILE" 2>/dev/null || true - - echo -e "${GREEN}Configured $SHELL_NAME${NC}" -done - -echo "" -echo -e "${GREEN}========================================${NC}" -echo -e "${GREEN}Oh My Posh installation complete!${NC}" -echo -e "${GREEN}========================================${NC}" -echo "" -echo "Theme: $THEME (built-in fallback)" -echo "Configured shells: $SHELLS" -echo "" -echo "To use a custom theme, add this to your devcontainer.json:" -echo "" -echo ' "mounts": [' -echo ' "source=${localEnv:HOME}/path/to/your/theme.json,target='$USER_HOME'/.ohmyposh.json,type=bind"' -echo ' ]' -echo "" -echo "For more information: https://ohmyposh.dev" From a2ce6c21553378ab899b4993c77a7b9d6b72fbe8 Mon Sep 17 00:00:00 2001 From: Ross Miles Date: Wed, 5 Nov 2025 22:36:34 +0000 Subject: [PATCH 4/4] remove empty .gitkeep file from src directory --- src/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/.gitkeep diff --git a/src/.gitkeep b/src/.gitkeep deleted file mode 100644 index e69de29..0000000