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
28 changes: 22 additions & 6 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,29 @@
"customizations": {
"vscode": {
"extensions": [
"openai.chatgpt",
"redhat.vscode-yaml"
]
"ankurmathur.opencode-provider-bridge",
"redhat.vscode-yaml",
"timonwong.shellcheck"
],
"mcp": {
"servers": {
"context-mode": {
"command": "context-mode"
},
"smart-tree": {
"args": [
"--mcp"
],
"command": "st",
"type": "stdio"
}
}
}
}
},
"mounts": [
"source=codex-config,target=/opt/.codex,type=volume"
"source=claude-code-config,target=/opt/claude-code-config,type=volume",
"source=opencode-config,target=/opt/opencode-config,type=volume"
],
"postCreateCommand": "sudo chown $USER:$USER /opt/.codex && ln -s /opt/.codex $HOME/.codex && bash .devcontainer/install.sh"
}
"postCreateCommand": "sudo chown $USER:$USER /opt/claude-code-config && mkdir -p /opt/claude-code-config/.claude && touch /opt/claude-code-config/.claude.json && ln -sfn /opt/claude-code-config/.claude.json $HOME/.claude.json && ln -sfn /opt/claude-code-config/.claude $HOME/.claude && echo \"export CLAUDE_CODE_DISABLE_CRON=1\" >> ~/.bashrc && sudo chown $USER:$USER /opt/opencode-config && mkdir -p $HOME/.config && ln -sfn /opt/opencode-config $HOME/.config/opencode && bash \"${containerWorkspaceFolder}/.devcontainer/install.sh\""
}
14 changes: 12 additions & 2 deletions .devcontainer/install.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
#!/bin/bash
sudo apt update && sudo apt install ansible -y
curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 sh

set -euo pipefail

DEVCONTAINER_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
containerWorkspaceFolder="$(dirname -- "$DEVCONTAINER_DIR")"
export containerWorkspaceFolder

sudo apt update && sudo apt install ansible shellcheck -y
curl -fsSL https://claude.ai/install.sh | bash
curl -fsSL https://opencode.ai/install | bash
curl -sSL https://raw.githubusercontent.com/8b-is/smart-tree/main/scripts/install.sh | bash
bash -c "source /usr/local/share/nvm/nvm.sh && nvm install 24 --latest-npm --no-progress"
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ roles/devcontainer_sync/defaults/main.yml
vscode_extensions: [...]
vscode_settings: {...}
features: {...}
mcp: {...}
mounts: [...]
post_create_commands: [...]
container_env: {...}
Expand Down Expand Up @@ -60,6 +61,7 @@ roles/devcontainer_sync/defaults/main.yml
vscode_extensions: [...]
vscode_settings: {...}
features: {...}
mcp: {...}
mounts: [...]
post_create_commands: [...]
post_start_commands: [...]
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ endif
find ${WORKSPACE_ROOT} -type d -name ".devcontainer-bak-*" -exec rm -rf {} +

test:
$(ANSIBLE_PLAYBOOK) --diff $(PLAYBOOK) -e @$(EXAMPLE_VARS) -e workspace_root=$(TEST_WORKSPACE_ROOT) -e devcontainer_sync_create_missing=true -e devcontainer_auto_rebuild=false -e devcontainer_sync_backup=false -e devcontainer_sync_backup_existing_dir=false
$(ANSIBLE_PLAYBOOK) --diff $(PLAYBOOK) -e @$(EXAMPLE_VARS) -e workspace_root=$(TEST_WORKSPACE_ROOT) -e devcontainer_sync_create_missing=true -e devcontainer_sync_backup=false -e devcontainer_sync_backup_existing_dir=false
find $(TEST_WORKSPACE_ROOT) -type f ! -path $(TEST_WORKSPACE_ROOT)/golden_output -print0 | sort -z | xargs -0r cat > $(TEST_WORKSPACE_ROOT)/golden_output
$(MAKE) shellcheck
8 changes: 0 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,6 @@ Apply changes:
make apply
```

By default, a changed `.devcontainer` directory is rebuilt after all managed and extra files are written. The role hashes relative file paths and contents before and after synchronization, then runs:

```bash
devcontainer up --workspace-folder /path/to/project --remove-existing-container
```

Set `devcontainer_auto_rebuild: false` to render changes without rebuilding. Check mode never runs the rebuild command.

Missing targets are skipped by default. Create them with:

```bash
Expand Down
5 changes: 4 additions & 1 deletion ansible.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ bin_ansible_callbacks = True
retry_files_enabled = False

[diff]
always = True
always = False

[connection]
pipelining = True
11 changes: 4 additions & 7 deletions roles/devcontainer_sync/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ workspace_root: "{{ lookup('env', 'WORKSPACE_ROOT') | default('~/dev/', true) }}
local_timezone: America/Chicago

devcontainer_sync_create_missing: false
devcontainer_auto_rebuild: true
devcontainer_sync_backup: true
devcontainer_sync_backup_existing_dir: true
devcontainer_sync_extra_files_root: "{{ playbook_dir }}/files"
Expand Down Expand Up @@ -71,8 +70,7 @@ devcontainer_plugins:
contextmode:
install_steps:
- curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name')/install.sh" | bash
- bash -c "source ~/.nvm/nvm.sh && nvm install 24"
- npm install context-mode
- bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress" && npm install --save-dev context-mode
- mkdir -p "${containerWorkspaceFolder}"/.github/hooks
- wget https://raw.githubusercontent.com/mksglu/context-mode/refs/heads/main/configs/vscode-copilot/hooks.json -O .github/hooks/context-mode.json
mcp:
Expand Down Expand Up @@ -190,10 +188,9 @@ devcontainer_plugins:

typescript:
install_steps:
- sudo apt update && sudo apt install -y npm --no-install-recommends
- curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash
- bash -c "source ~/.nvm/nvm.sh && nvm install 24"
- if [ -f "${containerWorkspaceFolder}/package.json" ]; then npm install; fi
- curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name')/install.sh" | bash
- bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress"
- if [ -f "${containerWorkspaceFolder}/package.json" ]; then bash -c "source ~/.nvm/nvm.sh && npm install"; fi

github:
install_steps:
Expand Down
86 changes: 8 additions & 78 deletions roles/devcontainer_sync/tasks/sync_devcontainer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,41 +199,9 @@
path: "{{ [workspace_root, devcontainer_sync_target_root, '.devcontainer/devcontainer.json'] | path_join }}"
register: devcontainer_sync_target

- name: Find initial .devcontainer files
ansible.builtin.find:
paths: "{{ [workspace_root, devcontainer_sync_target_root, '.devcontainer'] | path_join }}"
file_type: file
recurse: true
hidden: true
get_checksum: true
checksum_algorithm: sha256
register: devcontainer_sync_initial_files
changed_when: false
when: devcontainer_sync_target.stat.exists

- name: Store initial .devcontainer hash
- name: Determine whether this devcontainer should be rendered
ansible.builtin.set_fact:
devcontainer_sync_initial_hash: >-
{%- if not devcontainer_sync_target.stat.exists -%}
missing
{%- else -%}
{%- set entries = [] -%}
{%- set target_dir = [workspace_root, devcontainer_sync_target_root, '.devcontainer'] | path_join -%}
{%- for file in devcontainer_sync_initial_files.files | sort(attribute='path') -%}
{%- set _ = entries.append([file.path | relpath(target_dir), file.checksum]) -%}
{%- endfor -%}
{{ entries | to_json | hash('sha256') }}
{%- endif -%}

- name: Check whether .editorconfig would change
ansible.builtin.copy:
src: editorconfig
dest: "{{ [workspace_root, devcontainer_sync_target_root, '.editorconfig'] | path_join }}"
mode: "0644"
backup: false
check_mode: true
register: devcontainer_sync_editorconfig_render_preview
when: devcontainer_sync_target.stat.exists
devcontainer_sync_should_render: "{{ devcontainer_sync_target.stat.exists or devcontainer_sync_create_missing | bool }}"

- name: Check whether devcontainer.json would change
ansible.builtin.copy:
Expand Down Expand Up @@ -287,31 +255,31 @@
path: "{{ [workspace_root, devcontainer_sync_target_root, '.devcontainer'] | path_join }}"
state: directory
mode: "0755"
when: devcontainer_sync_target.stat.exists or devcontainer_sync_create_missing | bool
when: devcontainer_sync_should_render

- name: Render .editorconfig
ansible.builtin.copy:
src: editorconfig
dest: "{{ [workspace_root, devcontainer_sync_target_root, '.editorconfig'] | path_join }}"
mode: "0644"
backup: "{{ devcontainer_sync_backup }}"
when: devcontainer_sync_target.stat.exists or devcontainer_sync_create_missing | bool
when: devcontainer_sync_should_render

- name: Render devcontainer.json
ansible.builtin.copy:
content: "{{ devcontainer_json | to_nice_json(indent=4) }}\n"
dest: "{{ [workspace_root, devcontainer_sync_target_root, '.devcontainer/devcontainer.json'] | path_join }}"
mode: "0644"
backup: "{{ devcontainer_sync_backup }}"
when: devcontainer_sync_target.stat.exists or devcontainer_sync_create_missing | bool
when: devcontainer_sync_should_render

- name: Render install.sh
ansible.builtin.template:
src: install.sh.j2
dest: "{{ [workspace_root, devcontainer_sync_target_root, '.devcontainer/install.sh'] | path_join }}"
mode: "0755"
backup: "{{ devcontainer_sync_backup }}"
when: devcontainer_sync_target.stat.exists or devcontainer_sync_create_missing | bool
when: devcontainer_sync_should_render

- name: Render aws_configure.sh
ansible.builtin.template:
Expand All @@ -320,50 +288,12 @@
mode: "0755"
backup: "{{ devcontainer_sync_backup }}"
when:
- devcontainer_sync_target.stat.exists or devcontainer_sync_create_missing | bool
- devcontainer_sync_should_render
- "'aws' in devcontainer_sync_plugin_names"

- name: Copy project extra .devcontainer files
ansible.builtin.include_tasks: extra_files.yml
when: devcontainer_sync_target.stat.exists or devcontainer_sync_create_missing | bool

- name: Find rendered .devcontainer files
ansible.builtin.find:
paths: "{{ [workspace_root, devcontainer_sync_target_root, '.devcontainer'] | path_join }}"
file_type: file
recurse: true
hidden: true
get_checksum: true
checksum_algorithm: sha256
register: devcontainer_sync_rendered_files
changed_when: false
when: devcontainer_sync_target.stat.exists or devcontainer_sync_create_missing | bool

- name: Store rendered .devcontainer hash
ansible.builtin.set_fact:
devcontainer_sync_rendered_hash: >-
{%- set entries = [] -%}
{%- set target_dir = [workspace_root, devcontainer_sync_target_root, '.devcontainer'] | path_join -%}
{%- for file in devcontainer_sync_rendered_files.files | sort(attribute='path') -%}
{%- set _ = entries.append([file.path | relpath(target_dir), file.checksum]) -%}
{%- endfor -%}
{{ entries | to_json | hash('sha256') }}
when: devcontainer_sync_target.stat.exists or devcontainer_sync_create_missing | bool

# - name: Rebuild changed devcontainer
# ansible.builtin.command:
# argv:
# - devcontainer
# - up
# - --workspace-folder
# - "{{ [workspace_root, devcontainer_sync_target_root] | path_join }}"
# - --remove-existing-container
# when:
# - devcontainer_auto_rebuild | bool
# - not ansible_check_mode
# - devcontainer_sync_target.stat.exists or devcontainer_sync_create_missing | bool
# - devcontainer_sync_initial_hash != devcontainer_sync_rendered_hash
# changed_when: true
when: devcontainer_sync_should_render

- name: Report skipped missing devcontainer file
ansible.builtin.debug:
Expand Down
4 changes: 1 addition & 3 deletions tests/cloud-example/.devcontainer/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ curl -sSL https://raw.githubusercontent.com/8b-is/smart-tree/main/scripts/instal

curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name')/install.sh" | bash

bash -c "source ~/.nvm/nvm.sh && nvm install 24"

npm install context-mode
bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress" && npm install --save-dev context-mode

mkdir -p "${containerWorkspaceFolder}"/.github/hooks

Expand Down
22 changes: 6 additions & 16 deletions tests/golden_output
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,7 @@ curl -sSL https://raw.githubusercontent.com/8b-is/smart-tree/main/scripts/instal

curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name')/install.sh" | bash

bash -c "source ~/.nvm/nvm.sh && nvm install 24"

npm install context-mode
bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress" && npm install --save-dev context-mode

mkdir -p "${containerWorkspaceFolder}"/.github/hooks

Expand Down Expand Up @@ -216,11 +214,9 @@ sudo apt update && sudo apt install -y ripgrep ncat

sudo ln -sf /usr/share/zoneinfo/America/Chicago /etc/localtime && sudo dpkg-reconfigure -f noninteractive tzdata

sudo apt update && sudo apt install -y npm --no-install-recommends

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash
bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress"

if [ -f "${containerWorkspaceFolder}/package.json" ]; then npm install; fi
if [ -f "${containerWorkspaceFolder}/package.json" ]; then bash -c "source ~/.nvm/nvm.sh && npm install"; fi

curl -fsSL https://claude.ai/install.sh | bash

Expand All @@ -230,9 +226,7 @@ curl -sSL https://raw.githubusercontent.com/8b-is/smart-tree/main/scripts/instal

curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name')/install.sh" | bash

bash -c "source ~/.nvm/nvm.sh && nvm install 24"

npm install context-mode
bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress" && npm install --save-dev context-mode

mkdir -p "${containerWorkspaceFolder}"/.github/hooks

Expand Down Expand Up @@ -404,9 +398,7 @@ curl -sSL https://raw.githubusercontent.com/8b-is/smart-tree/main/scripts/instal

curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name')/install.sh" | bash

bash -c "source ~/.nvm/nvm.sh && nvm install 24"

npm install context-mode
bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress" && npm install --save-dev context-mode

mkdir -p "${containerWorkspaceFolder}"/.github/hooks

Expand Down Expand Up @@ -513,9 +505,7 @@ curl -sSL https://raw.githubusercontent.com/8b-is/smart-tree/main/scripts/instal

curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name')/install.sh" | bash

bash -c "source ~/.nvm/nvm.sh && nvm install 24"

npm install context-mode
bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress" && npm install --save-dev context-mode

mkdir -p "${containerWorkspaceFolder}"/.github/hooks

Expand Down
10 changes: 3 additions & 7 deletions tests/node-example/.devcontainer/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ sudo apt update && sudo apt install -y ripgrep ncat

sudo ln -sf /usr/share/zoneinfo/America/Chicago /etc/localtime && sudo dpkg-reconfigure -f noninteractive tzdata

sudo apt update && sudo apt install -y npm --no-install-recommends
bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress"

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash

if [ -f "${containerWorkspaceFolder}/package.json" ]; then npm install; fi
if [ -f "${containerWorkspaceFolder}/package.json" ]; then bash -c "source ~/.nvm/nvm.sh && npm install"; fi

curl -fsSL https://claude.ai/install.sh | bash

Expand All @@ -23,9 +21,7 @@ curl -sSL https://raw.githubusercontent.com/8b-is/smart-tree/main/scripts/instal

curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name')/install.sh" | bash

bash -c "source ~/.nvm/nvm.sh && nvm install 24"

npm install context-mode
bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress" && npm install --save-dev context-mode

mkdir -p "${containerWorkspaceFolder}"/.github/hooks

Expand Down
4 changes: 1 addition & 3 deletions tests/python-example/.devcontainer/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ curl -sSL https://raw.githubusercontent.com/8b-is/smart-tree/main/scripts/instal

curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name')/install.sh" | bash

bash -c "source ~/.nvm/nvm.sh && nvm install 24"

npm install context-mode
bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress" && npm install --save-dev context-mode

mkdir -p "${containerWorkspaceFolder}"/.github/hooks

Expand Down
4 changes: 1 addition & 3 deletions tests/rust-example/.devcontainer/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ curl -sSL https://raw.githubusercontent.com/8b-is/smart-tree/main/scripts/instal

curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name')/install.sh" | bash

bash -c "source ~/.nvm/nvm.sh && nvm install 24"

npm install context-mode
bash -c "source ~/.nvm/nvm.sh && nvm install 24 --latest-npm --no-progress" && npm install --save-dev context-mode

mkdir -p "${containerWorkspaceFolder}"/.github/hooks

Expand Down