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
2 changes: 2 additions & 0 deletions .devcontainer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.zsh_history
.generated/
50 changes: 9 additions & 41 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,60 +1,28 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
// For format details, see https://containers.dev/implementors/json_reference/
{
"name": "librmcs-develop",
"image": "qzhhhi/librmcs-develop:latest",
"privileged": true,
"mounts": [
{
"source": "/dev",
"target": "/dev",
"type": "bind"
},
{
"source": "/tmp/.X11-unix",
"target": "/tmp/.X11-unix",
"type": "bind"
},
{
"source": "${localEnv:HOME}",
"target": "/mnt/home",
"type": "bind"
}
],
"onCreateCommand": "bash -c '[ -d /mnt/home/.codex ] && ln -sfn /mnt/home/.codex ~/.codex; [ -d /mnt/home/.claude ] && ln -sfn /mnt/home/.claude ~/.claude; true'",
"postCreateCommand": "bash -c '[ -d ~/.codex ] && sudo npm i -g @openai/codex; [ -d ~/.claude ] && curl -fsSL https://claude.ai/install.sh | bash && sudo ln -sf ~/.local/bin/claude /usr/local/bin/claude; true'",
"containerEnv": {
"DISPLAY": "${localEnv:DISPLAY}",
"HTTP_PROXY": "${localEnv:HTTP_PROXY}",
"HTTPS_PROXY": "${localEnv:HTTPS_PROXY}",
"NO_PROXY": "${localEnv:NO_PROXY}",
"http_proxy": "${localEnv:http_proxy}",
"https_proxy": "${localEnv:https_proxy}",
"no_proxy": "${localEnv:no_proxy}",
"HOST_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
},
"runArgs": [
"--network=host"
"dockerComposeFile": [
"docker-compose.yml",
".generated/docker-compose.local.override.yml"
],
"service": "librmcs-develop",
"workspaceFolder": "/workspaces/librmcs",
"initializeCommand": "bash .devcontainer/scripts/generate-local-override.sh && bash .devcontainer/scripts/probe-host-tools.sh",
"postCreateCommand": "bash .devcontainer/scripts/setup-shell-history.sh && bash .devcontainer/scripts/bootstrap-tools.sh",
"customizations": {
"vscode": {
"settings": {
"remote.autoForwardPorts": false
},
"extensions": [
// C++ language support
"llvm-vs-code-extensions.vscode-clangd",
// Python language support
"ms-python.vscode-pylance",
"ms-python.python",
"ms-python.debugpy",
// CMake language support
"KylinIdeTeam.cmake-intellisence",
// Code spell checking
"streetsidesoftware.code-spell-checker",
// Git enhancements
"mhutchie.git-graph"
]
}
}
}
}
16 changes: 16 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
librmcs-develop:
image: qzhhhi/librmcs-develop:latest
network_mode: host
device_cgroup_rules:
- "c 189:* rw"
init: true
command: /bin/sh -c "while sleep 1000; do :; done"
working_dir: /home/ubuntu
volumes:
- type: bind
source: /dev
target: /dev
- type: bind
source: ..
target: /workspaces/librmcs
78 changes: 78 additions & 0 deletions .devcontainer/scripts/bootstrap-tools.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env bash

set -eu

installed_tools=""
skipped_tools=""
manifest_path="/workspaces/librmcs/.devcontainer/.generated/host-tools.manifest"

if [ ! -f "$manifest_path" ]; then
printf 'Warning: host tools manifest not found at %s\n' "$manifest_path" >&2
fi

if ! command -v npm >/dev/null 2>&1; then
printf 'Warning: npm is not available in the container, skipping host tool bootstrap.\n' >&2
exit 0
fi

add_item() {
local current_list="$1"
local item="$2"

if [ -z "$current_list" ]; then
printf '%s' "$item"
else
printf '%s\n%s' "$current_list" "$item"
fi
}

install_tool() {
local tool_name="$1"
local package_name="$2"

if [ ! -f "$manifest_path" ]; then
skipped_tools=$(add_item "$skipped_tools" "$tool_name (host manifest missing)")
return 0
fi

local manifest_line=""
local version=""

manifest_line=$(grep -m 1 -E "^${tool_name}=" "$manifest_path" 2>/dev/null || true)
if [ -z "$manifest_line" ]; then
skipped_tools=$(add_item "$skipped_tools" "$tool_name (not installed on host)")
else
version=${manifest_line#*=}
if printf '%s' "$version" | grep -Eq '^(v)?[0-9]+([.][0-9]+)*([-.][0-9A-Za-z]+)*$'; then
version=${version#v}
if sudo npm install -g "$package_name@$version"; then
installed_tools=$(add_item "$installed_tools" "$tool_name@$version")
else
skipped_tools=$(add_item "$skipped_tools" "$tool_name (install failed for $version)")
fi
else
printf 'Warning: skipping %s because version could not be parsed on host\n' "$tool_name" >&2
skipped_tools=$(add_item "$skipped_tools" "$tool_name (version could not be parsed on host)")
fi
fi
}

install_tool "codex" "@openai/codex"
install_tool "claude" "@anthropic-ai/claude-code"
install_tool "opencode" "opencode-ai"
install_tool "lark-cli" "@larksuite/cli"

printf 'Bootstrap summary:\n'
if [ -n "$installed_tools" ]; then
printf 'Installed:\n%s\n' "$installed_tools"
else
printf 'Installed: none\n'
fi

if [ -n "$skipped_tools" ]; then
printf 'Skipped:\n%s\n' "$skipped_tools"
else
printf 'Skipped: none\n'
fi

exit 0
122 changes: 122 additions & 0 deletions .devcontainer/scripts/generate-local-override.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
set -eu

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
generated_dir="$SCRIPT_DIR/../.generated"
output_file="$generated_dir/docker-compose.local.override.yml"
repo_root="$(cd "$SCRIPT_DIR/../.." && pwd)"

environment_entries=""
mounts=""

escape_yaml_double_quoted() {
value=$1
value=${value//\\/\\\\}
value=${value//\"/\\\"}
value=${value//$'\n'/\\n}
printf '%s' "$value"
}

add_env() {
env_name=$1
env_value=$2
escaped_env_value=$(escape_yaml_double_quoted "$env_value")

if [ -n "$environment_entries" ]; then
environment_entries="${environment_entries} ${env_name}: \"${escaped_env_value}\"\n"
else
environment_entries=" ${env_name}: \"${escaped_env_value}\"\n"
fi
}

add_mount() {
host_path=$1
target_path=$2

if [ -e "$host_path" ]; then
escaped_host_path=$(escape_yaml_double_quoted "$host_path")
escaped_target_path=$(escape_yaml_double_quoted "$target_path")
mounts="${mounts} - type: bind\n source: \"${escaped_host_path}\"\n target: \"${escaped_target_path}\"\n"
fi
}

add_ro_mount() {
host_path=$1
target_path=$2

if [ -e "$host_path" ]; then
escaped_host_path=$(escape_yaml_double_quoted "$host_path")
escaped_target_path=$(escape_yaml_double_quoted "$target_path")
mounts="${mounts} - type: bind\n source: \"${escaped_host_path}\"\n target: \"${escaped_target_path}\"\n read_only: true\n"
fi
}

add_env "HOST_WORKSPACE_FOLDER" "$repo_root"

if [ -n "${DISPLAY:-}" ]; then
add_env "DISPLAY" "$DISPLAY"
add_mount "/tmp/.X11-unix" "/tmp/.X11-unix"
fi
if [ -n "${WAYLAND_DISPLAY:-}" ]; then
wayland_display_name=${WAYLAND_DISPLAY##*/}
if [[ "$WAYLAND_DISPLAY" = /* ]]; then
wayland_socket=$WAYLAND_DISPLAY
elif [ -n "${XDG_RUNTIME_DIR:-}" ]; then
wayland_socket="${XDG_RUNTIME_DIR}/${WAYLAND_DISPLAY}"
else
wayland_socket=""
fi

if [ -n "$wayland_socket" ] && [ -S "$wayland_socket" ]; then
add_env "WAYLAND_DISPLAY" "$wayland_display_name"
add_env "XDG_RUNTIME_DIR" "/tmp"
add_mount "$wayland_socket" "/tmp/${wayland_display_name}"
fi
fi
if [ -n "${HTTP_PROXY:-}" ]; then
add_env "HTTP_PROXY" "$HTTP_PROXY"
fi
if [ -n "${HTTPS_PROXY:-}" ]; then
add_env "HTTPS_PROXY" "$HTTPS_PROXY"
fi
if [ -n "${NO_PROXY:-}" ]; then
add_env "NO_PROXY" "$NO_PROXY"
fi
if [ -n "${http_proxy:-}" ]; then
add_env "http_proxy" "$http_proxy"
fi
if [ -n "${https_proxy:-}" ]; then
add_env "https_proxy" "$https_proxy"
fi
if [ -n "${no_proxy:-}" ]; then
add_env "no_proxy" "$no_proxy"
fi

add_mount "$HOME/.codex" "/home/ubuntu/.codex"
add_mount "$HOME/.claude" "/home/ubuntu/.claude"
add_mount "$HOME/.claude.json" "/home/ubuntu/.claude.json"
add_mount "$HOME/.lark-cli" "/home/ubuntu/.lark-cli"
add_mount "$HOME/.config/opencode" "/home/ubuntu/.config/opencode"
add_mount "$HOME/.local/share/lark-cli" "/home/ubuntu/.local/share/lark-cli"
add_mount "$HOME/.local/share/opencode" "/home/ubuntu/.local/share/opencode"
add_ro_mount "$HOME/.agents/skills" "/home/ubuntu/.agents/skills"

mkdir -p "$generated_dir"

{
printf '%s\n' '# Generated by generate-local-override.sh - do not commit'
printf '%s\n' 'services:'
printf '%s\n' ' librmcs-develop:'
printf '%s\n' ' environment:'
if [ -n "$environment_entries" ]; then
printf '%b' "$environment_entries"
else
printf '%s\n' ' {}'
fi
printf '%s\n' ' volumes:'
if [ -n "$mounts" ]; then
printf '%b' "$mounts"
else
printf '%s\n' ' []'
fi
} > "$output_file"
72 changes: 72 additions & 0 deletions .devcontainer/scripts/probe-host-tools.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash

set -eu

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"

parse_version() {
local version_output="$1"
local parsed_version=""
local version_regex='v?[0-9]+([.][0-9]+)*([-.][0-9A-Za-z]+)*'

if [[ $version_output =~ $version_regex ]]; then
parsed_version=${BASH_REMATCH[0]}
parsed_version=${parsed_version#v}
printf '%s' "$parsed_version"
return 0
fi

return 1
}

probe_tool() {
tool_name="$1"

if ! command -v "$tool_name" >/dev/null 2>&1; then
printf '%s\n' 'absent'
return 0
fi

version_output=""
if version_output=$($tool_name --version 2>/dev/null); then
if version=$(parse_version "$version_output"); then
printf '%s\n' "$version"
else
printf '%s\n' 'unparseable'
fi
else
printf '%s\n' 'unparseable'
fi
}

generated_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
manifest_dir="$REPO_ROOT/.devcontainer/.generated"
manifest_path="$manifest_dir/host-tools.manifest"
tmp_manifest_path="$manifest_path.tmp"

mkdir -p "$manifest_dir"

codex_version="$(probe_tool codex)"
claude_version="$(probe_tool claude)"
opencode_version="$(probe_tool opencode)"
lark_cli_version="$(probe_tool lark-cli)"

{
printf '%s\n' '# Generated by probe-host-tools.sh - do not commit'
printf 'generated_at=%s\n' "$generated_at"
printf 'codex=%s\n' "$codex_version"
printf 'claude=%s\n' "$claude_version"
printf 'opencode=%s\n' "$opencode_version"
printf 'lark-cli=%s\n' "$lark_cli_version"
} > "$tmp_manifest_path"

mv "$tmp_manifest_path" "$manifest_path"

printf 'Host tool probe summary:\n'
printf ' codex=%s\n' "$codex_version"
printf ' claude=%s\n' "$claude_version"
printf ' opencode=%s\n' "$opencode_version"
printf ' lark-cli=%s\n' "$lark_cli_version"
printf ' manifest=%s\n' "$manifest_path"
printf 'Tip: rerun `bash /workspaces/librmcs/.devcontainer/scripts/bootstrap-tools.sh` to resync container tool versions with the host.\n'
16 changes: 16 additions & 0 deletions .devcontainer/scripts/setup-shell-history.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash

set -eu

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"

history_path="$REPO_ROOT/.devcontainer/.zsh_history"
history_link="$HOME/.zsh_history"

mkdir -p "$(dirname "$history_path")"
touch "$history_path"
rm -f "$history_link"
ln -s "$history_path" "$history_link"

printf 'Shell history symlinked: %s -> %s\n' "$history_link" "$history_path"
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,3 @@ __pycache__/

build/
compile_commands.json

.devcontainer/.zsh_history
Loading
Loading