From d0fdda4b20428e7226c9de5c4a8413b6a8fff3bc Mon Sep 17 00:00:00 2001 From: "Stewart Adam (MSFT)" Date: Tue, 2 Dec 2025 17:01:58 -0800 Subject: [PATCH 1/6] feat(devcontainer): improve performance and reliability of devcontainer launch --- .devcontainer/README.md | 8 +++--- .devcontainer/devcontainer.json | 41 ++++++++++++++++++++++++---- .devcontainer/scripts/post-attach.sh | 29 ++++++++++++++++++++ .devcontainer/scripts/post-create.sh | 41 +++++++++++++++++++++++++++- .dockerignore | 2 ++ .gitattributes | 12 +++++++- .gitignore | 4 +++ 7 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 .devcontainer/scripts/post-attach.sh create mode 100644 .dockerignore diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 5ae0a1c2..0afd2762 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -46,14 +46,12 @@ A pre-configured development environment that includes all tools, extensions, an ### Languages & Runtimes - Node.js (LTS) -- Python 3.11 - PowerShell 7.x ### CLI Tools - Git - GitHub CLI (`gh`) -- Azure CLI (`az`) ### Code Quality @@ -93,9 +91,11 @@ gitleaks detect --source . --verbose ## Troubleshooting -**Container won't build**: Ensure Docker Desktop is running and you have sufficient disk space (5GB+). +1. **Container won't build**: Ensure Docker Desktop is running and you have sufficient disk space (5GB+). -**Extensions not loading**: Reload the window (`F1` → **Developer: Reload Window**). +2. **Extensions not loading**: Reload the window (`F1` → **Developer: Reload Window**). + +3. **HTTP/TLS errors during build**: Machines with corporate firewalls performing TLS inspection will need to drop their corporate root trust certificate (PEM-formatted with `.crt` extension) file into the `.devcontainer` and rebuild the devcontainer. Also run `docker buildx use desktop-linux` to ensure you are using the default builder, which honors OS root certificate trust stores. For more help, see [SUPPORT.md](../SUPPORT.md). diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fb10ed57..2d5f403d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,16 +1,35 @@ { "name": "HVE Core - Markdown Editing", "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + // Rename the mount to /workspace for consistency, otherwise its mounted using + // whatever folder name the user cloned the repo as + "workspaceMount": "\"source=${localWorkspaceFolder}\",target=/workspace,type=bind", + "workspaceFolder": "/workspace", + "mounts": [ + // Put GitHub local user data in a volume + { + "type": "volume", + "source": "${devcontainerId}-userconfig", + "target": "/home/vscode/.config" + }, + // Put node modules into volume for better performance + { + "type": "volume", + "source": "${devcontainerId}-nodemodules", + "target": "/workspace/node_modules" + } + ], + "containerEnv": { + "REQUESTS_CA_BUNDLE": "/etc/ssl/certs/ca-certificates.crt", // for pip + "NODE_EXTRA_CA_CERTS": "/etc/ssl/certs/ca-certificates.crt", // for nodejs + "SSL_CERT_FILE": "/etc/ssl/certs/ca-certificates.crt" // for uv (else use --native-tls flag) + }, "features": { "ghcr.io/devcontainers/features/node:1": { "version": "lts" }, - "ghcr.io/devcontainers/features/python:1": { - "version": "3.11" - }, "ghcr.io/devcontainers/features/git:1": {}, "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers/features/azure-cli:1": {}, "ghcr.io/devcontainers/features/powershell:1": {} }, "customizations": { @@ -23,9 +42,21 @@ "bierner.markdown-mermaid", "bpruitt-goddard.mermaid-markdown-syntax-highlighting", "github.vscode-pull-request-github" - ] + ], + "settings": { + // Prevent extensions from stealing focus, see microsoft/vscode#205225 + "workbench.view.showQuietly": { + "workbench.panel.output": true + }, + } } }, + // This is to ensure support for config includes is properly handled, see microsoft/vscode-remote-release/2084 + "initializeCommand": { + "extractGitGlobals": "(git config -l --global --include || true) > .gitconfig.global", + "extractGitLocals": "(git config -l --local --include || true) > .gitconfig.local" + }, + "postAttachCommand": "/bin/bash .devcontainer/scripts/post-attach.sh", "onCreateCommand": "bash .devcontainer/scripts/on-create.sh", "postCreateCommand": "bash .devcontainer/scripts/post-create.sh", "remoteUser": "vscode" diff --git a/.devcontainer/scripts/post-attach.sh b/.devcontainer/scripts/post-attach.sh new file mode 100644 index 00000000..0a4145a4 --- /dev/null +++ b/.devcontainer/scripts/post-attach.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -euo pipefail + +# devcontainers copy your local gitconfig but do not parse conditional includes. +# This re-configures the devcontainer git identities based on the prior exported +# global and local git configurations *after* parsing host includes. See also: +# https://github.com/microsoft/vscode-remote-release/issues/2084#issuecomment-2289987894 +function copy_user_gitconfig() { + for conf in .gitconfig.global .gitconfig.local; do + if [ -f $conf ]; then + echo "*** Parsing ${conf##.gitconfig.} Git configuration export" + while IFS='=' read -r key value; do + case "$key" in + user.name | user.email | user.signingkey | commit.gpgsign) + echo "Set Git config ${key}=${value}" + git config --global "$key" "$value" + ;; + esac + done < "$conf" + rm -f "$conf" + fi + done +} + +# +# Main execution path +# + +copy_user_gitconfig diff --git a/.devcontainer/scripts/post-create.sh b/.devcontainer/scripts/post-create.sh index 1777d342..debf666c 100644 --- a/.devcontainer/scripts/post-create.sh +++ b/.devcontainer/scripts/post-create.sh @@ -5,10 +5,49 @@ set -euo pipefail -main() { +# Volume ownership is not set automatically due to a bug: +# https://github.com/microsoft/vscode-remote-release/issues/9931 +# +# IMPORTANT: workaround requires Docker base image to have password-less sudo. +function fix_volume_ownership() { + volume_path="$1" + + if [ ! -d "$volume_path" ]; then + echo "ERROR: the volume path provided '$volume_path' does not exist." + exit 1 + fi + + echo "Setting volume ownership for $volume_path" + sudo chown $USER:$USER "$volume_path" +} + +function fix_volume_ownerships() { + echo "Applying volume ownership workaround (see microsoft/vscode-remote-release#9931)..." + fix_volume_ownership "/home/$USER/.config" + fix_volume_ownership "/workspace/node_modules" +} + +function npm_install() { echo "Installing NPM dependencies..." npm install echo "NPM dependencies installed successfully" } +function update_ca_certs() { + # Adds a root CA to the system certificate store. Useful if developer machines + # have MITM TLS inspection happening, e.g. with ZScaler. + echo "Updating container system CA certificates..." + if compgen -G ".devcontainer/*.crt" > /dev/null; then + sudo cp .devcontainer/*.crt /usr/local/share/ca-certificates/ + sudo update-ca-certificates + fi + echo "Container's system CA certificates updated successfully" +} + +main() { + fix_volume_ownerships + npm_install + update_ca_certs +} + main "$@" diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..ffddaad6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +**/.git/ +**/node_modules/ diff --git a/.gitattributes b/.gitattributes index c17faff2..3246faa9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,18 @@ # Set the default behavior, in case core.autocrlf has not been set. * text=auto -# Declare files that will always have LF line endings on checkout. +# Declare files that must have specific line endings on checkout. +## Windows scripts - must be CRLF +*.ps text eol=crlf +*.ps1 text eol=crlf +*.bat text eol=crlf +*.cmd text eol=crlf +*.bat text eol=crlf + +## Linux scripts - must be LF *.sh text eol=lf +*.Dockerfile text eol=lf +Dockerfile text eol=lf # Denote all files that are truly binary and should not be modified. *.docx binary diff --git a/.gitignore b/.gitignore index 0b1a5003..b05d3757 100644 --- a/.gitignore +++ b/.gitignore @@ -442,3 +442,7 @@ pr-reference.xml .mcp/*-local.json .mcp/*.local.json .mcp/.env + +# devcontainer rebuild +/.gitconfig.global +/.gitconfig.local \ No newline at end of file From 73add7db82d357aa20a5dbfe2d729bef40b1b698 Mon Sep 17 00:00:00 2001 From: Stewart Adam Date: Tue, 2 Dec 2025 17:25:08 -0800 Subject: [PATCH 2/6] Quote $USER in shell script Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .devcontainer/scripts/post-create.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/scripts/post-create.sh b/.devcontainer/scripts/post-create.sh index debf666c..a7b7d6ce 100644 --- a/.devcontainer/scripts/post-create.sh +++ b/.devcontainer/scripts/post-create.sh @@ -18,7 +18,7 @@ function fix_volume_ownership() { fi echo "Setting volume ownership for $volume_path" - sudo chown $USER:$USER "$volume_path" + sudo chown "$USER:$USER" "$volume_path" } function fix_volume_ownerships() { From c9992cf8ca319e749727673dfcdc9fe8fb450a63 Mon Sep 17 00:00:00 2001 From: Stewart Adam Date: Tue, 2 Dec 2025 17:25:42 -0800 Subject: [PATCH 3/6] Use braces for string with inline variable expansion Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .devcontainer/scripts/post-attach.sh | 2 +- .devcontainer/scripts/post-create.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/scripts/post-attach.sh b/.devcontainer/scripts/post-attach.sh index 0a4145a4..c88cf23c 100644 --- a/.devcontainer/scripts/post-attach.sh +++ b/.devcontainer/scripts/post-attach.sh @@ -17,7 +17,7 @@ function copy_user_gitconfig() { ;; esac done < "$conf" - rm -f "$conf" + rm -f "${conf}" fi done } diff --git a/.devcontainer/scripts/post-create.sh b/.devcontainer/scripts/post-create.sh index a7b7d6ce..daeb21a3 100644 --- a/.devcontainer/scripts/post-create.sh +++ b/.devcontainer/scripts/post-create.sh @@ -23,7 +23,7 @@ function fix_volume_ownership() { function fix_volume_ownerships() { echo "Applying volume ownership workaround (see microsoft/vscode-remote-release#9931)..." - fix_volume_ownership "/home/$USER/.config" + fix_volume_ownership "/home/${USER}/.config" fix_volume_ownership "/workspace/node_modules" } From d1e7095497302a2673cd374ef2cede068c59dc3c Mon Sep 17 00:00:00 2001 From: "Stewart Adam (MSFT)" Date: Tue, 2 Dec 2025 17:32:26 -0800 Subject: [PATCH 4/6] ignore crt files dropped into .devcontainer folder --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b05d3757..5b6885f0 100644 --- a/.gitignore +++ b/.gitignore @@ -443,6 +443,7 @@ pr-reference.xml .mcp/*.local.json .mcp/.env -# devcontainer rebuild +# devcontainer /.gitconfig.global -/.gitconfig.local \ No newline at end of file +/.gitconfig.local +/.devcontainer/*.crt From 8174a53643065b5e42f57af09b0998f01de52035 Mon Sep 17 00:00:00 2001 From: "Stewart Adam (MSFT)" Date: Tue, 2 Dec 2025 17:32:42 -0800 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .devcontainer/devcontainer.json | 8 ++++---- .devcontainer/scripts/post-attach.sh | 9 ++++----- .devcontainer/scripts/post-create.sh | 12 ++++++------ .gitattributes | 4 +--- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2d5f403d..c3f13a48 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,8 +1,8 @@ { "name": "HVE Core - Markdown Editing", "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - // Rename the mount to /workspace for consistency, otherwise its mounted using - // whatever folder name the user cloned the repo as + // Rename the mount to /workspace for a predictable workspace paths in our scripts. + // The source path might also contain special characters, so it needs escaped double quotes. "workspaceMount": "\"source=${localWorkspaceFolder}\",target=/workspace,type=bind", "workspaceFolder": "/workspace", "mounts": [ @@ -47,11 +47,11 @@ // Prevent extensions from stealing focus, see microsoft/vscode#205225 "workbench.view.showQuietly": { "workbench.panel.output": true - }, + } } } }, - // This is to ensure support for config includes is properly handled, see microsoft/vscode-remote-release/2084 + // This is to ensure support for config includes is properly handled, see microsoft/vscode-remote-release#2084 "initializeCommand": { "extractGitGlobals": "(git config -l --global --include || true) > .gitconfig.global", "extractGitLocals": "(git config -l --local --include || true) > .gitconfig.local" diff --git a/.devcontainer/scripts/post-attach.sh b/.devcontainer/scripts/post-attach.sh index c88cf23c..62e00c52 100644 --- a/.devcontainer/scripts/post-attach.sh +++ b/.devcontainer/scripts/post-attach.sh @@ -1,15 +1,16 @@ -#!/bin/bash +#!/usr/bin/env bash set -euo pipefail # devcontainers copy your local gitconfig but do not parse conditional includes. # This re-configures the devcontainer git identities based on the prior exported # global and local git configurations *after* parsing host includes. See also: # https://github.com/microsoft/vscode-remote-release/issues/2084#issuecomment-2289987894 -function copy_user_gitconfig() { +copy_user_gitconfig() { for conf in .gitconfig.global .gitconfig.local; do - if [ -f $conf ]; then + if [[ -f "$conf" ]]; then echo "*** Parsing ${conf##.gitconfig.} Git configuration export" while IFS='=' read -r key value; do + local key value case "$key" in user.name | user.email | user.signingkey | commit.gpgsign) echo "Set Git config ${key}=${value}" @@ -22,8 +23,6 @@ function copy_user_gitconfig() { done } -# # Main execution path -# copy_user_gitconfig diff --git a/.devcontainer/scripts/post-create.sh b/.devcontainer/scripts/post-create.sh index daeb21a3..eddd2d7b 100644 --- a/.devcontainer/scripts/post-create.sh +++ b/.devcontainer/scripts/post-create.sh @@ -9,10 +9,10 @@ set -euo pipefail # https://github.com/microsoft/vscode-remote-release/issues/9931 # # IMPORTANT: workaround requires Docker base image to have password-less sudo. -function fix_volume_ownership() { - volume_path="$1" +fix_volume_ownership() { + local volume_path="$1" - if [ ! -d "$volume_path" ]; then + if [[ ! -d "$volume_path" ]]; then echo "ERROR: the volume path provided '$volume_path' does not exist." exit 1 fi @@ -21,19 +21,19 @@ function fix_volume_ownership() { sudo chown "$USER:$USER" "$volume_path" } -function fix_volume_ownerships() { +fix_volume_ownerships() { echo "Applying volume ownership workaround (see microsoft/vscode-remote-release#9931)..." fix_volume_ownership "/home/${USER}/.config" fix_volume_ownership "/workspace/node_modules" } -function npm_install() { +npm_install() { echo "Installing NPM dependencies..." npm install echo "NPM dependencies installed successfully" } -function update_ca_certs() { +update_ca_certs() { # Adds a root CA to the system certificate store. Useful if developer machines # have MITM TLS inspection happening, e.g. with ZScaler. echo "Updating container system CA certificates..." diff --git a/.gitattributes b/.gitattributes index 3246faa9..c2db5bca 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,16 +3,14 @@ # Declare files that must have specific line endings on checkout. ## Windows scripts - must be CRLF -*.ps text eol=crlf *.ps1 text eol=crlf *.bat text eol=crlf *.cmd text eol=crlf -*.bat text eol=crlf ## Linux scripts - must be LF *.sh text eol=lf *.Dockerfile text eol=lf -Dockerfile text eol=lf +Dockerfile* text eol=lf # Denote all files that are truly binary and should not be modified. *.docx binary From 8414bbdedce452f9852629173b7b7bd85fc616c4 Mon Sep 17 00:00:00 2001 From: "Stewart Adam (MSFT)" Date: Tue, 27 Jan 2026 10:23:57 -0800 Subject: [PATCH 6/6] refactor(devcontainer): remove custom CA workarounds per PR feedback --- .devcontainer/README.md | 3 ++- .devcontainer/devcontainer.json | 5 ----- .devcontainer/scripts/post-create.sh | 12 ------------ .gitignore | 1 - 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 0afd2762..eb426c5f 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -95,7 +95,8 @@ gitleaks detect --source . --verbose 2. **Extensions not loading**: Reload the window (`F1` → **Developer: Reload Window**). -3. **HTTP/TLS errors during build**: Machines with corporate firewalls performing TLS inspection will need to drop their corporate root trust certificate (PEM-formatted with `.crt` extension) file into the `.devcontainer` and rebuild the devcontainer. Also run `docker buildx use desktop-linux` to ensure you are using the default builder, which honors OS root certificate trust stores. +3. **HTTP/TLS errors during build**: Machines with corporate firewalls performing TLS inspection should ensure they are using the default `desktop-linux` builder, which honors OS root certificate trust stores. + You can change the active builder back to `desktop-linux` by running `docker buildx use desktop-linux`. For more help, see [SUPPORT.md](../SUPPORT.md). diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c3f13a48..156eebb0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,11 +19,6 @@ "target": "/workspace/node_modules" } ], - "containerEnv": { - "REQUESTS_CA_BUNDLE": "/etc/ssl/certs/ca-certificates.crt", // for pip - "NODE_EXTRA_CA_CERTS": "/etc/ssl/certs/ca-certificates.crt", // for nodejs - "SSL_CERT_FILE": "/etc/ssl/certs/ca-certificates.crt" // for uv (else use --native-tls flag) - }, "features": { "ghcr.io/devcontainers/features/node:1": { "version": "lts" diff --git a/.devcontainer/scripts/post-create.sh b/.devcontainer/scripts/post-create.sh index eddd2d7b..124940be 100644 --- a/.devcontainer/scripts/post-create.sh +++ b/.devcontainer/scripts/post-create.sh @@ -33,21 +33,9 @@ npm_install() { echo "NPM dependencies installed successfully" } -update_ca_certs() { - # Adds a root CA to the system certificate store. Useful if developer machines - # have MITM TLS inspection happening, e.g. with ZScaler. - echo "Updating container system CA certificates..." - if compgen -G ".devcontainer/*.crt" > /dev/null; then - sudo cp .devcontainer/*.crt /usr/local/share/ca-certificates/ - sudo update-ca-certificates - fi - echo "Container's system CA certificates updated successfully" -} - main() { fix_volume_ownerships npm_install - update_ca_certs } main "$@" diff --git a/.gitignore b/.gitignore index 5b6885f0..7515b301 100644 --- a/.gitignore +++ b/.gitignore @@ -446,4 +446,3 @@ pr-reference.xml # devcontainer /.gitconfig.global /.gitconfig.local -/.devcontainer/*.crt