Skip to content
Merged
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
12 changes: 9 additions & 3 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ghcr.io/framework-r-d/phlex-dev:latest
FROM ghcr.io/framework-r-d/phlex-dev:2026-07-23

# Validate Python site-packages symlink.
# Invoke bash explicitly: the script uses bash features (pipefail, arrays,
Expand Down Expand Up @@ -93,9 +93,8 @@ INSTALL_MAN_TOOLS
RUN bash <<'INSTALL_DEV_UTILS'
set -euo pipefail
apt-get update
apt-get install -y --no-install-recommends jq podman ssh socat tree yq
apt purge nodejs npm -y && apt autoremove -y || true
apt-get install -y curl --no-install-recommends --autoremove
apt-get install -y --no-install-recommends chrpath curl jq patchelf nano podman ssh socat tree vim yq
curl -fsSL https://deb.nodesource.com/setup_26.x | bash -
apt-get install -y nodejs
apt-get clean
Expand All @@ -115,3 +114,10 @@ RUN printf '. /entrypoint.sh\n' >> /root/.bashrc
# Ensure /run/user/0 exists and is owned by root with 700 permissions to
# allow GPG agent to function correctly when running as root.
RUN mkdir -p /run/user/0 && chmod 700 /run/user/0 && chown root:root /run/user/0

COPY gitignore-global /root/.gitignore-global
RUN git config --global core.excludesfile '/root/.gitignore-global'

# Install Kilo environment script that sets KILO_CONFIG_CONTENT globally
COPY kilo-env.sh /etc/profile.d/kilo-env.sh
RUN chmod +x /etc/profile.d/kilo-env.sh
51 changes: 14 additions & 37 deletions .devcontainer/codespace.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
"folders": [
{
"name": "Phlex",
"path": "/workspaces/phlex"
"path": ".."
},
{
"name": "Phlex Design",
"path": "/workspaces/phlex-design"
"path": "../../phlex-design"
},
{
"name": "Phlex Examples",
"path": "/workspaces/phlex-examples"
"path": "../../phlex-examples"
},
{
"name": "Phlex Coding Guidelines",
"path": "/workspaces/phlex-coding-guidelines"
"path": "../../phlex-coding-guidelines"
},
{
"name": "Phlex Spack Recipes",
"path": "/workspaces/phlex-spack-recipes"
"path": "../../phlex-spack-recipes"
}
],
"settings": {
Expand Down Expand Up @@ -63,39 +63,16 @@
},
"python.languageServer": "Pylance",
"python.terminal.activateEnvironment": false,
"python.defaultInterpreterPath": "/opt/spack-environments/phlex-ci/.spack-env/view/bin/python",
"esbonio.server.pythonPath": "/opt/spack-environments/phlex-ci/.spack-env/view/bin/python",
// Container-only interpreter paths (python.defaultInterpreterPath,
// esbonio.server.pythonPath) are intentionally not set here: this file
// is shared with host (non-devcontainer) checkouts, where
// /opt/spack-environments does not exist. The devcontainer supplies
// them via the Remote scope in devcontainer.json (Remote settings only
// apply when actually connected to that container). Host users should
// set their own interpreter in a local .vscode/settings.json (Folder
// scope overrides this Workspace scope) -- see
// .vscode/settings.json.example.
"esbonio.logging.level": "debug",
"python.analysis.typeCheckingMode": "basic",
"python.analysis.diagnosticMode": "workspace",
"python.analysis.include": [
"/workspaces/phlex"
],
"python.analysis.extraPaths": [
"/workspaces/phlex/plugins/python/python",
"/opt/spack-environments/phlex-ci/.spack-env/view/lib/root",
"/opt/spack-environments/phlex-ci/.spack-env/view/lib/python/site-packages"
],
"python.analysis.exclude": [
"/opt/spack-environments",
"/workspaces/phlex/local",
"/workspaces/phlex/build/_deps",
"/workspaces/phlex/build/CMakeFiles",
"/workspaces/phlex/**/.venv",
"/workspaces/phlex/**/py_virtual_env"
],
"python.analysis.ignore": [
"/opt/spack-environments",
"/workspaces/phlex-design",
"/workspaces/phlex-examples",
"/workspaces/phlex-coding-guidelines",
"/workspaces/phlex-spack-recipes",
"/workspaces/phlex/local",
"/workspaces/phlex/build/_deps",
"/workspaces/phlex/build/CMakeFiles",
"/workspaces/phlex/**/.venv",
"/workspaces/phlex/**/py_virtual_env"
],
"flake8.enabled": false,
"pylint.enabled": false,
"[python]": {
Expand Down
74 changes: 38 additions & 36 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,64 @@
"name": "Phlex CI Dev Container",
"dockerComposeFile": "docker-compose.yml",
"service": "phlex-dev",
"workspaceFolder": "/workspaces/phlex",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"remoteUser": "root",
"remoteEnv": {
"KILO_API_KEY": "${localEnv:HEADROOM_UPSTREAM_KEY}"
},
"initializeCommand": "bash .devcontainer/ensure-repos.sh",
"onCreateCommand": "bash .devcontainer/setup-repos.sh /workspaces",
"postCreateCommand": "bash -lc 'bash .devcontainer/post-create.sh'",
"postCreateCommand": "git config --global --add safe.directory '${containerWorkspaceFolder}' && bash -lc 'bash .devcontainer/post-create.sh'",
"customizations": {

"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"terminal.integrated.profiles.linux": {
"bash": {
"path": "/bin/bash",
"args": [
"-i"
],
"icon": "terminal-bash"
}
"chat.agent.sandbox.enabled": "on",
"chat.agent.sandbox.allowNetwork": true,
"chat.agent.sandbox.advanced.runtime": {
"enableWeakerNestedSandbox": false
},
"terminal.integrated.shellIntegration.suggestEnablement": false,
"python.terminal.activateEnvironment": false,
"python.defaultInterpreterPath": "/opt/spack-environments/phlex-ci/.spack-env/view/bin/python",
"python.analysis.extraPaths": [
"${workspaceFolder}/build",
"/opt/spack-environments/phlex-ci/.spack-env/view/lib/root",
"/opt/spack-environments/phlex-ci/.spack-env/view/lib/python/site-packages"
],
"cmake.defaultConfigurePreset": "default",
"cmake.buildDirectory": "${workspaceFolder}/build",
"cmake.defaultConfigurePreset": "default",
"cmake.generator": "Ninja",
"cmake.useCMakePresets": "always",
"C_Cpp.default.cStandard": "c17",
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
"C_Cpp.default.cppStandard": "c++23",
"C_Cpp.default.intelliSenseMode": "linux-gcc-x64",
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
"python.languageServer": "Pylance",
"python.analysis.typeCheckingMode": "basic",
"python.analysis.diagnosticMode": "workspace",
"python.analysis.exclude": [
"**/.venv/**",
"**/py_virtual_env/**"
],
"python.analysis.ignore": [
"**/.venv/**",
"**/py_virtual_env/**"
],
"search.exclude": {
"files.watcherExclude": {
"**/.venv/**": true,
"**/py_virtual_env/**": true
},
"files.watcherExclude": {
"git.autoRepositoryDetection": "subFolders",
"git.repositoryScanMaxDepth": 1,
"git.scanRepositories": [
"/workspaces/phlex",
"/workspaces/phlex-coding-guidelines",
"/workspaces/phlex-design",
"/workspaces/phlex-examples",
"/workspaces/phlex-spack-recipes"
],
"kilocode.new.extraCaCerts": "",
"python.defaultInterpreterPath": "/opt/spack-environments/phlex-ci/.spack-env/view/bin/python",
"python.languageServer": "Pylance",
"python.terminal.activateEnvironment": false,
"esbonio.server.pythonPath": "/opt/spack-environments/phlex-ci/.spack-env/view/bin/python",
"esbonio.logging.level": "debug",
"search.exclude": {
"**/.venv/**": true,
"**/py_virtual_env/**": true
},
"kilocode.new.extraCaCerts": ""
"terminal.integrated.defaultProfile.linux": "bash",
"terminal.integrated.profiles.linux": {
"bash": {
"path": "/bin/bash",
"args": [
"-i"
],
"icon": "terminal-bash"
}
},
"terminal.integrated.shellIntegration.suggestEnablement": false
},
"extensions": [
"charliermarsh.ruff",
Expand All @@ -82,6 +83,7 @@
"ms-python.vscode-pylance",
"ms-python.vscode-python-envs",
"ms-vscode.cmake-tools",
"ms-vscode.cpptools",
"ms-vscode.cpptools-extension-pack",
"ms-vscode.cpptools-themes",
"ms-vscode.hexeditor",
Expand Down
24 changes: 13 additions & 11 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ services:
context: .
dockerfile: Dockerfile
volumes:
- ..:/workspaces/phlex
- ../../phlex-coding-guidelines:/workspaces/phlex-coding-guidelines
- ../../phlex-design:/workspaces/phlex-design
- ../../phlex-examples:/workspaces/phlex-examples
- ../../phlex-spack-recipes:/workspaces/phlex-spack-recipes
- ${HOME}/.config/gh:/root/.config/gh:ro
- ${HOME}/.config/kilo:/root/.config/kilo
- ${HOME}/.gnupg:/root/.gnupg
- ${HOME}/.podman-proxy/podman.sock:/tmp/podman.sock
- ${HOME}/.local/share/kilo:/root/.local/share/kilo:ro
- phlex-vscode-user-data:/root/.vscode-server-insiders/data/User
- ..:/workspaces/phlex:Z
- ../../phlex-coding-guidelines:/workspaces/phlex-coding-guidelines:Z
- ../../phlex-design:/workspaces/phlex-design:Z
- ../../phlex-examples:/workspaces/phlex-examples:Z
- ../../phlex-spack-recipes:/workspaces/phlex-spack-recipes:Z
- ${HOME}/.config/gh:/root/.config/gh:ro,Z
- ${HOME}/.config/kilo:/root/.config/kilo:Z
- ${HOME}/.gnupg:/root/.gnupg:ro,Z
- ${HOME}/.podman-proxy/podman.sock:/tmp/podman.sock:Z
- phlex-kilo-data:/root/.local/share/kilo:Z
- ${HOME}/.local/share/kilo/auth.json:/root/.local/share/kilo/auth.json:ro,Z
- phlex-vscode-user-data:/root/.vscode-server-insiders/data/User:Z
environment:
- CMAKE_GENERATOR=Ninja
- GH_CONFIG_DIR=/root/.config/gh
Expand All @@ -29,3 +30,4 @@ services:

volumes:
phlex-vscode-user-data:
phlex-kilo-data:
4 changes: 4 additions & 0 deletions .devcontainer/gitignore-global
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build/
out/
_deps/
_build/
22 changes: 22 additions & 0 deletions .devcontainer/kilo-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Prepare KILO_CONFIG_CONTENT with rewritten headroom base URLs for container runtime.
# This script is sourced by /etc/profile.d/ so the variable is available to all processes.

# Unset any previous KILO_CONFIG_CONTENT to ensure a clean slate.
unset KILO_CONFIG_CONTENT

# If the headroom proxy ports (9797, 9798) are active via socat relays,
# Kilo needs a modified config with baseURL pointing to host.docker.internal.
# Parse the host config, rewrite baseURLs, and export as KILO_CONFIG_CONTENT.
if command -v kilo >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
# Only rewrite if we have a working Kilo config to work with.
KILO_CONFIG_CONTENT="$(perl -pe 's{("(?:[^"\\]|\\.)*"|'"'"'(?:[^'"'"'\\]|\\.)*'"'"')|//.*}{$1 // ""}ge' "${HOME}/.config/kilo/kilo.jsonc" | jq -c '.provider |= with_entries(
.value.options.baseURL =
(if .value.options.baseURL == null then null
elif (.value.options.baseURL | test("^https?://127\\.0\\.0\\.1:"))
then (.value.options.baseURL | capture("https?://127\\.0\\.0\\.1:(?<port>[0-9]+)(?<rest>.*)") | "http://host.docker.internal:" + ((.port | tonumber + 10000) | tostring) + .rest)
else .value.options.baseURL
end)
)')"
export KILO_CONFIG_CONTENT
fi
36 changes: 1 addition & 35 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ cat > ~/.actrc <<'EOF'
--container-options --userns=keep-id
EOF

# Clear the VS Code extension-install marker so that VS Code always installs the
# extensions listed in devcontainer.json on a fresh container. Without this, the
# marker persists on the host via the Machine data bind-mount and VS Code skips
# installation on every rebuild.
rm -f /root/.vscode-server-insiders/data/Machine/.installExtensionsMarker

# Seed the Kilo Code auth token into the container-private data volume.
# The volume is not shared with the host to avoid SQLite conflicts between
# the Remote-SSH and devcontainer Kilo Code instances. The API key is
Expand Down Expand Up @@ -98,32 +92,4 @@ elif command -v pre-commit >/dev/null 2>&1; then
pre-commit install || true
fi

# Configure Kilo to use host.docker.internal for headroom proxy ports.
# The headroom SSH tunnels bind to 127.0.0.1 only, but rootless Podman's
# pasta networking only routes host.docker.internal to ports listening on
# 0.0.0.0. We use socat relays (set up in ensure-repos.sh) to forward
# 127.0.0.1:9797 -> 0.0.0.0:19797 and 127.0.0.1:9798 -> 0.0.0.0:19798.
# This code rewrites Kilo's baseURL from 127.0.0.1:<port> to
# host.docker.internal:<port+10000> for the relays to work.
cat >> /root/.bashrc <<'EOF'

# Unset any previous KILO_CONFIG_CONTENT to ensure a clean slate.
unset KILO_CONFIG_CONTENT

# If the headroom proxy ports (9797, 9798) are active via socat relays,
# Kilo needs a modified config with baseURL pointing to host.docker.internal.
# Parse 'kilo debug config', rewrite baseURLs, and export as KILO_CONFIG_CONTENT.
if command -v kilo >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
# Only rewrite if we have a working Kilo config to work with.
if KILO_CONFIG_CONTENT="$(perl -pe 's{("(?:[^"\\]|\\.)*"|'"'"'(?:[^'"'"'\\]|\\.)*'"'"')|//.*}{$1 // ""}ge' "${HOME}/.config/kilo/kilo.jsonc" | jq -c '.provider |= with_entries(
.value.options.baseURL =
(if .value.options.baseURL == null then null
elif (.value.options.baseURL | test("^https?://127\\.0\\.0\\.1:"))
then (.value.options.baseURL | capture("https?://127\\.0\\.0\\.1:(?<port>[0-9]+)(?<rest>.*)") | "http://host.docker.internal:" + ((.port | tonumber + 10000) | tostring) + .rest)
else .value.options.baseURL
end)
)')"; then
export KILO_CONFIG_CONTENT
fi
fi
EOF
# KILO configuration now provided by /etc/profile.d/kilo-env.sh
42 changes: 26 additions & 16 deletions .devcontainer/setup-repos.sh
Original file line number Diff line number Diff line change
@@ -1,44 +1,54 @@
#!/bin/bash

# Clone companion repositories into the codespace so they are available
# for reference by developers and AI agents.
#
# Intended to be called from devcontainer.json onCreateCommand.

set -euo pipefail

WORKSPACE_ROOT="${1:-/workspaces}"

if [ ! -d "$WORKSPACE_ROOT" ]; then
echo "Error: workspace root does not exist: $WORKSPACE_ROOT" >&2
exit 1
fi

FAILED_REPOS=()

clone_if_absent() {
local repo=$1
local dest="${WORKSPACE_ROOT}/${repo}"
local need_clone=1
if [ -e "$dest/.git" ]; then
echo "Repository already present: $dest"
return
need_clone=0
elif [ -e "$dest" ]; then
echo "WARNING: refusing to overwrite non-repository $dest:"
ls -ld "$dest"
return
FAILED_REPOS+=("$repo")
return 1
fi
echo "Cloning Framework-R-D/${repo} into ${dest} ..."
local max_tries=5 current_try=0
while ! git clone --depth 1 "https://github.com/Framework-R-D/${repo}.git" "$dest"; do
(( ++current_try ))
echo "Attempt $current_try/$max_tries to clone $repo from GitHub FAILED"
(( current_try < max_tries )) || break
sleep 5
done
if (( current_try == max_tries )); then
echo "WARNING: unable to check out $repo to $dest from GitHub" 1>&2
if (( need_clone )); then
echo "Cloning Framework-R-D/${repo} into ${dest} ..."
local max_tries=5 current_try=0
while ! git clone --depth 1 "https://github.com/Framework-R-D/${repo}.git" "$dest"; do
(( ++current_try ))
echo "Attempt $current_try/$max_tries to clone $repo from GitHub FAILED"
(( current_try < max_tries )) || break
sleep 5
done
if (( current_try == max_tries )); then
echo "WARNING: unable to check out $repo to $dest from GitHub" 1>&2
FAILED_REPOS+=("$repo")
return 1
fi
fi
git config --global --add safe.directory "$dest"
}

clone_if_absent phlex-design
clone_if_absent phlex-examples
clone_if_absent phlex-coding-guidelines
clone_if_absent phlex-spack-recipes

if (( ${#FAILED_REPOS[@]} > 0 )); then
echo "Error: Failed to clone repositories: ${FAILED_REPOS[*]}" >&2
exit 1
fi
Loading
Loading