Skip to main content

Creating Scripts - AI Developer Guide

Quick reference for AI assistants creating new tools and services in devcontainer-toolbox.


Script Types​

TypePatternPurpose
Installinstall-*.shInstall tools, runtimes, CLIs
Configconfig-*.shConfigure settings, credentials
Serviceservice-*.shManage background services
Commandcmd-*.shMulti-command utilities

Essential Documentation​

Read these before creating scripts:

DocumentContent
Creating Install ScriptsComplete guide for install/config/cmd scripts
Services OverviewService scripts documentation
Libraries ReferenceLibrary functions reference
Categories ReferenceValid category values

Required Metadata (Core)​

Every script MUST have these metadata fields at the top (used by dev-setup.sh):

SCRIPT_ID="unique-identifier"
SCRIPT_VER="1.0.0"
SCRIPT_NAME="Human Readable Name"
SCRIPT_DESCRIPTION="What this script does"
SCRIPT_CATEGORY="LANGUAGE_DEV"
SCRIPT_CHECK_COMMAND="command --version"

Extended Metadata (Website)​

These fields are for the website only and enable richer documentation. They are NOT used by the terminal-based installer.

# --- Extended metadata (for website) ---
# Required:
SCRIPT_TAGS="keyword1 keyword2 keyword3"
SCRIPT_ABSTRACT="Brief 1-2 sentence description (50-150 chars)"

# Optional:
SCRIPT_LOGO="tool-name-logo.webp"
SCRIPT_WEBSITE="https://official-website.com"
SCRIPT_SUMMARY="Detailed description covering features, use cases, and benefits (150-500 chars)"
SCRIPT_RELATED="related-tool-id-1 related-tool-id-2"
FieldRequiredPurposeGuidelines
SCRIPT_TAGSYesSearch keywordsSpace-separated, lowercase
SCRIPT_ABSTRACTYesBrief description50-150 characters, 1-2 sentences
SCRIPT_LOGONoLogo filenamePlace source in website/static/img/tools/src/
SCRIPT_WEBSITENoOfficial URLMust start with https://
SCRIPT_SUMMARYNoDetailed description150-500 characters, 3-5 sentences
SCRIPT_RELATEDNoRelated tool IDsSpace-separated script IDs

Quick Start: Install Script​

#!/bin/bash

# --- Core metadata (required) ---
SCRIPT_ID="dev-example"
SCRIPT_VER="1.0.0"
SCRIPT_NAME="Example Tool"
SCRIPT_DESCRIPTION="Installs the example development tool"
SCRIPT_CATEGORY="LANGUAGE_DEV"
SCRIPT_CHECK_COMMAND="example --version"

# --- Extended metadata (for website) ---
SCRIPT_TAGS="example demo development"
SCRIPT_ABSTRACT="Example development tool with CLI and VS Code integration."
SCRIPT_LOGO="dev-example-logo.webp"
SCRIPT_WEBSITE="https://example.com"
SCRIPT_SUMMARY="Complete example development environment including CLI tools, VS Code extensions, and common utilities. Ideal for learning and testing."
SCRIPT_RELATED="dev-python dev-typescript"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/logging.sh"
source "$SCRIPT_DIR/lib/tool-auto-enable.sh"

# Handle flags
case "${1:-}" in
--help) echo "Usage: $0 [--help|--uninstall]"; exit 0 ;;
--uninstall)
log_info "Uninstalling example..."
# uninstall commands here
auto_disable_tool "install-dev-example.sh"
exit 0 ;;
esac

# Check if already installed
if command -v example &>/dev/null; then
log_info "Example already installed"
exit 0
fi

# Install
log_info "Installing example..."
# installation commands here

# Auto-enable for future rebuilds
auto_enable_tool "install-dev-example.sh"
log_info "Example installed successfully"

Quick Start: Service Script​

#!/bin/bash

# --- Core metadata (required) ---
SCRIPT_ID="service-example"
SCRIPT_VER="1.0.0"
SCRIPT_NAME="Example Service"
SCRIPT_DESCRIPTION="Manages the example background service"
SCRIPT_CATEGORY="BACKGROUND_SERVICES"
SCRIPT_CHECK_COMMAND="pgrep -f example-daemon"

# --- Extended metadata (for website) ---
SCRIPT_TAGS="service daemon background example"
SCRIPT_ABSTRACT="Background service for example functionality with auto-restart."
SCRIPT_LOGO="service-example-logo.webp"
SCRIPT_WEBSITE="https://example.com/service"
SCRIPT_SUMMARY="Manages the example background service including start, stop, restart, and status commands. Supports auto-restart on failure and logging."
SCRIPT_RELATED="service-litellm service-openwebui"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/logging.sh"
source "$SCRIPT_DIR/lib/cmd-framework.sh"
source "$SCRIPT_DIR/lib/service-auto-enable.sh"

SCRIPT_COMMANDS=(
"start:Start example service:do_start"
"stop:Stop example service:do_stop"
"restart:Restart example service:do_restart"
"status:Show service status:do_status"
)

do_start() { log_info "Starting..."; }
do_stop() { log_info "Stopping..."; }
do_restart() { do_stop; do_start; }
do_status() { log_info "Status check..."; }

parse_and_run "$@"

Key Libraries​

LibraryKey Functions
logging.shlog_info, log_warn, log_error
tool-auto-enable.shauto_enable_tool, auto_disable_tool
service-auto-enable.shauto_enable_service, auto_disable_service
prerequisite-check.shcheck_prerequisite_configs
cmd-framework.shparse_and_run (for service/cmd scripts)

File Location​

All scripts go in: .devcontainer/additions/

.devcontainer/additions/
├── install-*.sh # Install scripts
├── config-*.sh # Config scripts
├── service-*.sh # Service scripts
├── cmd-*.sh # Command scripts
└── lib/ # Shared libraries (add new ones for reusable functionality)

Image mode: At build time, the Docker image copies these scripts to /opt/devcontainer-toolbox/additions/ (set as $DCT_HOME/additions/). When developing and testing locally in the repo, you work with the .devcontainer/additions/ path as normal. The image build handles the copy.


When to Create Library Functions​

If you find yourself writing the same code in multiple scripts, extract it to a library:

  1. Create lib/your-library.sh with reusable functions
  2. Document functions with comments
  3. Source it in scripts: source "$SCRIPT_DIR/lib/your-library.sh"
  4. Update libraries.md with documentation

See existing libraries in .devcontainer/additions/lib/ for patterns.


Version Pinning Rules​

When a script installs software, decide whether to pin a DEFAULT_VERSION or install latest. Wrong choice either way causes problems: unpinned runtimes break user code on surprise upgrades, stale pins give users 3-year-old software.

Pin when​

Rule 1: Pin if the version affects user code compatibility. Languages, runtimes, SDKs, and frameworks. A version change can break builds, imports, or APIs. Pin: Go, Java, .NET, PHP, Node.js, Hugo, Python. Don't pin: shellcheck, jq, curl, wget, git.

Rule 2: Pin if config format changes between versions. Tools where the user writes config files tied to a specific schema. Pin: OTel Collector (config YAML schema changes), Kubernetes tools (API versions).

Don't pin when​

Rule 3: Don't pin if installed via a package manager that handles compatibility. apt, npm global, pip, rustup — these resolve dependencies themselves.

Rule 4: Don't pin utilities where "latest" is always safe. Small CLI tools, formatters, dev helpers. Breaking changes are rare and low-impact.

How to pin​

Rule 5: Every pin MUST have a Renovate annotation. No pin without a maintenance path. Format:

# renovate: datasource=github-releases depName=golang/go
DEFAULT_VERSION="1.26.1"

If Renovate is not yet set up, document the pin with a date comment:

DEFAULT_VERSION="1.26.1"  # Pinned 2026-04-06, check https://go.dev/dl/

Rule 6: Pin to the latest stable, not an arbitrary old version. When adding a new pin, always check upstream for the current stable release. Never copy a version from a tutorial or blog post without verifying.

Rule 7: Use LTS/stable tracks, not bleeding edge. For software with LTS releases, pin to the active LTS:

  • Node.js: latest v22.x (Jod LTS), not v24.x until it becomes LTS
  • Java: 17 or 21 (LTS), not 22 (short-term)
  • .NET: 8.0 (LTS), not 10.0 (current but short-term support)

Rule 8: --version flag must always be available. Every script with a DEFAULT_VERSION must accept --version X.Y.Z so users can override. The pin is a sensible default, not a constraint.


Validation Checklist​

Before committing a new script:

Core metadata:

  1. All core metadata fields are complete (ID, VER, NAME, DESCRIPTION, CATEGORY, CHECK_COMMAND)
  2. Category is valid (check categories.md)

Extended metadata: 3. [ ] SCRIPT_TAGS is set (space-separated keywords) 4. [ ] SCRIPT_ABSTRACT is set (50-150 characters) 5. [ ] SCRIPT_LOGO file exists in website/static/img/tools/src/ (if specified) 6. [ ] SCRIPT_RELATED references valid script IDs (if specified)

Functionality: 7. [ ] --help flag works 8. [ ] --uninstall flag works (for install scripts) 9. [ ] Script is idempotent (safe to run twice) 10. [ ] All tests pass (CI will reject PRs with failing tests) 11. [ ] Install cycle test passes if you changed a version pin or install logic (run-all-tests.sh install <script>)


Testing​

Tests must pass before merging. CI runs static and unit tests automatically on every PR.

Run inside the devcontainer:

# Run all tests on your script
.devcontainer/additions/tests/run-all-tests.sh static install-dev-example.sh
.devcontainer/additions/tests/run-all-tests.sh unit install-dev-example.sh
.devcontainer/additions/tests/run-all-tests.sh install install-dev-example.sh

If tests fail:

  • Static test failures → Check metadata, syntax, categories
  • Unit test failures → Check --help and --verify implementations
  • Install test failures → Check install/uninstall logic

When to run install cycle tests (Level 3)​

The install cycle test (run-all-tests.sh install) actually downloads, installs, verifies, uninstalls, and re-verifies. It is not run in CI — it runs locally in the devcontainer only.

You MUST run it when:

  • Bumping a DEFAULT_VERSION pin (e.g., Go 1.21 → 1.26)
  • Changing download URLs or archive handling logic
  • Adding a new install script
  • Modifying install/uninstall logic
# After changing install-dev-golang.sh:
.devcontainer/additions/tests/run-all-tests.sh install install-dev-golang.sh

# Full cycle for ALL scripts (slow — downloads everything, 15-30 min):
.devcontainer/additions/tests/run-all-tests.sh install

The test auto-discovers all install-*.sh scripts — no hardcoded list. New scripts are automatically included.

See testing.md for details on the test framework. See CI/CD Pipeline for what CI checks.


After Adding a Script​

Regenerate documentation (run inside the devcontainer):

dev-docs

This updates website/docs/tools/index.mdx so users can see the new tool. CI will fail if this is not done.