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
29 changes: 16 additions & 13 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
FROM ghcr.io/framework-r-d/phlex-dev:latest

ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=1000
ARG SPACK_GID=2000

# Validate Python site-packages symlink
RUN <<'VALIDATE_PYTHON_SYMLINK'
set -euo pipefail
Expand Down Expand Up @@ -61,12 +56,20 @@ fi
echo "Python symlink validated: $python_link -> $(readlink "$python_link")"
VALIDATE_PYTHON_SYMLINK

# Create the user and add to spack group
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID --create-home $USERNAME \
&& usermod -aG $SPACK_GID $USERNAME \
&& echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME
# Install podman client and socat inside the container to support nested
# container communication and provide a 'docker' command alias.
RUN <<'INSTALL_PODMAN_CLIENT'
set -euo pipefail
apt-get update
apt-get install -y --no-install-recommends podman socat
apt-get clean
rm -rf /var/lib/apt/lists/*
INSTALL_PODMAN_CLIENT

# Wire up the root user's .bashrc to source the Spack environment via
# /entrypoint.sh on every interactive shell.
RUN printf '. /entrypoint.sh\n' >> /root/.bashrc

# Setup entrypoint usage in bashrc
RUN echo '. /entrypoint.sh' >> /home/$USERNAME/.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
7 changes: 5 additions & 2 deletions .devcontainer/codespace.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"**/build/CMakeFiles/**": true
},
"python.languageServer": "Pylance",
"python.terminal.activateEnvironment": false,
"python.defaultInterpreterPath": "/opt/spack-environments/phlex-ci/.spack-env/view/bin/python",
"python.analysis.typeCheckingMode": "basic",
"python.analysis.diagnosticMode": "workspace",
Expand Down Expand Up @@ -97,10 +98,11 @@
"ms-vscode.cpptools-extension-pack",
"ms-vscode.hexeditor",
"ms-vscode.vscode-json",
"redhat.vscode-yaml",
"twxs.cmake"
"redhat.vscode-yaml"
],
"unwantedRecommendations": [
"amazonwebservices.amazon-q-vscode",
"amazonwebservices.aws-toolkit-vscode",
"github.copilot",
"ikuyadeu.r",
"ms-python.flake8",
Expand All @@ -110,6 +112,7 @@
"ms-vscode.r-debugger",
"redhat.java",
"reditorsupport.r",
"twxs.cmake",
"vscjava.vscode-gradle",
"vscjava.vscode-java-debug",
"vscjava.vscode-java-pack",
Expand Down
25 changes: 13 additions & 12 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,27 @@
]
},
"workspaceFolder": "/workspaces/phlex",
"remoteUser": "vscode",
"remoteUser": "root",
"userEnvProbe": "none",
"containerEnv": {
"GH_CONFIG_DIR": "/home/vscode/.config/gh",
"DOCKER_HOST": "unix:///run/podman/podman.sock"
"GH_CONFIG_DIR": "/root/.config/gh",
"DOCKER_HOST": "unix:///tmp/podman.sock",
"CONTAINER_HOST": "unix:///tmp/podman.sock",
"GNUPGHOME": "/root/.gnupg"
},
"mounts": [
"source=${localWorkspaceFolder}/../phlex-design,target=/workspaces/phlex-design,type=bind",
"source=${localWorkspaceFolder}/../phlex-examples,target=/workspaces/phlex-examples,type=bind",
"source=${localWorkspaceFolder}/../phlex-coding-guidelines,target=/workspaces/phlex-coding-guidelines,type=bind",
"source=${localWorkspaceFolder}/../phlex-spack-recipes,target=/workspaces/phlex-spack-recipes,type=bind",
"source=${localEnv:XDG_RUNTIME_DIR}/podman,target=/run/podman,type=bind",
"source=${localEnv:HOME}/.config/gh,target=/home/vscode/.config/gh,type=bind,readonly"
"source=${localEnv:HOME}/.podman-proxy/podman.sock,target=/tmp/podman.sock,type=bind",
"source=${localEnv:HOME}/.aws,target=/root/.aws,type=bind",
"source=${localEnv:HOME}/.config/gh,target=/root/.config/gh,type=bind,readonly",
"source=${localEnv:HOME}/.gnupg,target=/root/.gnupg,type=bind"
],
"initializeCommand": "bash .devcontainer/ensure-repos.sh",
"onCreateCommand": "bash .devcontainer/setup-repos.sh /workspaces",
"postCreateCommand": "bash -lc 'if command -v prek >/dev/null 2>&1; then prek install || true; elif command -v pre-commit >/dev/null 2>&1; then pre-commit install || true; fi'",
"postCreateCommand": "bash -lc 'bash .devcontainer/post-create.sh'",
"customizations": {
"vscode": {
"settings": {
Expand All @@ -37,13 +42,14 @@
}
},
"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.cmakePath": "${workspaceFolder}/.devcontainer/cmake_wrapper.sh",
"cmake.defaultConfigurePreset": "default",
"cmake.ctestPath": "${workspaceFolder}/.devcontainer/ctest_wrapper.sh",
"cmake.generator": "Ninja",
"C_Cpp.default.cStandard": "c17",
Expand All @@ -58,7 +64,6 @@
"charliermarsh.ruff",
"cheshirekow.cmake-format",
"chrisjsewell.myst-tml-syntax",

"davidanson.vscode-markdownlint",
"donjayamanne.githistory",
"dotjoshjohnson.xml",
Expand All @@ -71,11 +76,9 @@
"lextudio.restructuredtext-pack",
"lfs.vscode-emacs-friendly",
"links-req-tracer.links-requirement-tracer",

"ms-python.debugpy",
"ms-python.mypy-type-checker",
"ms-python.pylint",

"ms-python.vscode-pylance",
"ms-python.vscode-python-envs",
"ms-vscode.cmake-tools",
Expand All @@ -87,11 +90,9 @@
"ms-vscode.makefile-tools",
"ms-vscode.vscode-websearchforcopilot",
"redhat.vscode-yaml",

"shd101wyy.markdown-preview-enhanced",
"swyddfa.esbonio",
"trond-snekvik.simple-rst",
"twxs.cmake",
"vadimcn.vscode-lldb",
"wequick.coverage-gutters",
"xaver.clang-format"
Expand Down
69 changes: 58 additions & 11 deletions .devcontainer/ensure-repos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,64 @@ clone_if_absent phlex-examples
clone_if_absent phlex-coding-guidelines
clone_if_absent phlex-spack-recipes

# Ensure the Podman runtime directory exists so the devcontainer bind mount
# has a valid source even when the Podman socket is not yet active. The
# socket itself is created by Podman at runtime; we only need the parent
# directory to exist for the mount to succeed.
PODMAN_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/podman"
if mkdir -p "${PODMAN_RUNTIME_DIR}" 2>/dev/null; then
if [ ! -S "${PODMAN_RUNTIME_DIR}/podman.sock" ]; then
echo "NOTE: Podman socket not found at ${PODMAN_RUNTIME_DIR}/podman.sock" >&2
echo " To use 'act' inside the devcontainer, enable the Podman socket:" >&2
echo " systemctl --user enable --now podman.socket" >&2
# --- Podman Socket Proxy for Nested Containers (act) ---
#
# Rootless Podman nested volume mounts (like act's Docker SDK mounting the
# Podman socket) require that the source and target paths match across the
# host/container boundary. We use 'socat' on the host to provide a proxy socket
# at a stable, matching path in the user's home directory.

USER_ID=$(id -u)
PODMAN_REAL_SOCKET="${XDG_RUNTIME_DIR:-/run/user/${USER_ID}}/podman/podman.sock"
PROXY_DIR="${HOME}/.podman-proxy"
PROXY_SOCKET="${PROXY_DIR}/podman.sock"

# Kill any existing socat proxy for this socket
pkill -f "socat UNIX-LISTEN:${PROXY_SOCKET}" || true
# Wait for old process to die and socket to be removed
sleep 0.2
mkdir -p "${PROXY_DIR}"
chmod 700 "${PROXY_DIR}"

mkdir -p "${PROXY_DIR}"
chmod 700 "${PROXY_DIR}"

if [ -S "${PODMAN_REAL_SOCKET}" ]; then
if command -v socat >/dev/null 2>&1; then
echo "Proxying Podman socket ${PODMAN_REAL_SOCKET} -> ${PROXY_SOCKET} ..."
# Use a background socat to proxy the real socket to the proxy socket.
# This socket will be bind-mounted at the SAME PATH in the container.
nohup setsid socat "UNIX-LISTEN:${PROXY_SOCKET},fork,reuseaddr,unlink-early" "UNIX-CONNECT:${PODMAN_REAL_SOCKET}" > /tmp/socat-podman.log 2>&1 &
# Wait for socket to be created
i=0
while [ $i -lt 20 ] && [ ! -S "${PROXY_SOCKET}" ]; do
sleep 0.1
i=$((i + 1))
done
if [ ! -S "${PROXY_SOCKET}" ]; then
echo "WARNING: Socket creation timed out; creating placeholder" >&2
socat "UNIX-LISTEN:${PROXY_SOCKET},fork,reuseaddr" "UNIX-CONNECT:${PODMAN_REAL_SOCKET}" &
fi
else
echo "WARNING: socat not found on host; act will not work inside the container" >&2
fi
else
echo "WARNING: unable to create ${PODMAN_RUNTIME_DIR}; 'act' will not work inside the container without the Podman socket" >&2
echo "WARNING: Podman socket not found at ${PODMAN_REAL_SOCKET}" >&2
echo " To use 'act' inside the devcontainer, enable the Podman socket:" >&2
echo " systemctl --user enable --now podman.socket" >&2
fi

# Ensure the bind-mount source always exists so 'podman run' never fails with
# "no such file or directory", even when socat is unavailable or the real
# Podman socket is absent.
if [ ! -S "${PROXY_SOCKET}" ]; then
# Create a dummy socket so the mount source is valid. The container will
# start successfully; nested container support (act) simply won't work.
python3 -c "
import socket, os
s = socket.socket(socket.AF_UNIX)
s.bind('${PROXY_SOCKET}')
" 2>/dev/null || touch "${PROXY_SOCKET}"
fi

echo "SUCCESS: .devcontainer/ensure-repos.sh completed successfully"
19 changes: 19 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
# Run inside the dev container after creation.

set -euo pipefail

# Configure act to use the Podman socket and run privileged so that nested
# container operations (e.g. workflow_dispatch testing) work correctly.
cat > ~/.actrc <<'EOF'
--container-daemon-socket unix:///tmp/podman.sock
--container-options --privileged
--container-options --userns=keep-id
EOF

# Install pre-commit hooks if available.
if command -v prek >/dev/null 2>&1; then
prek install || true
elif command -v pre-commit >/dev/null 2>&1; then
pre-commit install || true
fi
32 changes: 14 additions & 18 deletions dev/jules/prepare-environment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,10 @@ git clone https://github.com/spack/spack.git "$SPACK_ROOT"
sudo env INSTALLER_NO_MODIFY_PATH=1 UV_INSTALL_DIR=/usr/local/bin sh "${_uv_installer}"
rm -f "${_uv_installer}"

sudo env UV_TOOL_BIN_DIR=/usr/local/bin UV_TOOL_DIR=/usr/local/share/uv/tools \
/usr/local/bin/uv tool install ruff
sudo env UV_TOOL_BIN_DIR=/usr/local/bin UV_TOOL_DIR=/usr/local/share/uv/tools \
/usr/local/bin/uv tool install gersemi
sudo env UV_TOOL_BIN_DIR=/usr/local/bin UV_TOOL_DIR=/usr/local/share/uv/tools \
/usr/local/bin/uv tool install prek
sudo env UV_TOOL_BIN_DIR=/usr/local/bin UV_TOOL_DIR=/usr/local/share/uv/tools \
/usr/local/bin/uv tool install jsonnet
for tool in ruff gersemi prek; do
sudo env UV_TOOL_BIN_DIR=/usr/local/bin UV_TOOL_DIR=/usr/local/share/uv/tools \
/usr/local/bin/uv tool install "$tool"
done
sudo /usr/local/bin/uv cache clean

# Clean all Spack caches
Expand All @@ -204,13 +200,11 @@ git clone https://github.com/spack/spack.git "$SPACK_ROOT"
{ set +x; } >/dev/null 2>&1
echo "--> Spack and Python tools setup complete."

echo "--> Installing additional developer tools (act, gh)..."
echo "--> Installing act..."
set -x

# Install GitHub's act CLI
download_url=$(curl -s https://api.github.com/repos/nektos/act/releases/latest | \
grep -Ee '"browser_download_url": .*/act_Linux_x86_64\.tar\.gz"' | \
head -n1 | \
grep -Ee '"browser_download_url": .*/act_Linux_x86_64\.' | \
cut -d '"' -f 4)
if [ -z "$download_url" ]; then
echo "Failed to determine act download URL" >&2
Expand All @@ -219,16 +213,18 @@ fi
curl -sfSL "$download_url" | sudo tar -C /usr/local/bin -zx act
sudo chmod +x /usr/local/bin/act

# Install GitHub CLI (gh)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
{ set +x; } >/dev/null 2>&1
echo "--> Installing gh..."
set -x

curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \
sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/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
sudo apt-get install -y --no-install-recommends gh

{ set +x; } >/dev/null 2>&1
echo "--> Additional developer tools installed successfully."

echo "--> Performing final cleanup..."
set -x

Expand Down
Loading