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
6 changes: 4 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ jobs:
run: |
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"

for TAG in ${{ steps.meta.outputs.tags }}; do
echo '${{ steps.meta.outputs.tags }}' | while read -r TAG; do
[ -z "$TAG" ] && continue
TAG_NAME="${TAG##*:}"

echo "Creating manifest for ${TAG}"
Expand All @@ -118,7 +119,8 @@ jobs:

- name: Inspect manifests
run: |
for TAG in ${{ steps.meta.outputs.tags }}; do
echo '${{ steps.meta.outputs.tags }}' | while read -r TAG; do
[ -z "$TAG" ] && continue
echo "=== Inspecting ${TAG} ==="
docker buildx imagetools inspect "${TAG}"
done
1 change: 0 additions & 1 deletion .tool-versions-example
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
nodejs 24.12.0
ruby 3.4.7
python 3.14.2
opentofu 1.11.0
golang 1.25.5
Expand Down
31 changes: 17 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,19 @@ RUN \
echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# Create config and data directories with proper ownership
RUN mkdir -p /config /data && \
chown -R ${USER_UID}:${USER_GID} /config /data
RUN mkdir -p /config /data /opt/asdf /opt/home/.config/nix /opt/home/.local/bin && \
chown -R ${USER_UID}:${USER_GID} /config /data /opt/asdf /opt/home

# Switch to non-root user
USER ${USER_UID}:${USER_GID}

# Configure shell for user
RUN echo '. ${ASDF_DIR}/asdf.sh' >> /home/${USER_NAME}/.bashrc && \
echo '. ${ASDF_DIR}/asdf.sh' >> /home/${USER_NAME}/.profile && \
echo '. ${ASDF_DIR}/asdf.sh' >> /home/${USER_NAME}/.zshrc
# Copy .tool-versions to /opt/home (copied to ~ at runtime by entrypoint)
COPY --chown=${USER_UID}:${USER_GID} .tool-versions /opt/home/.tool-versions

# Create template shell RC files in /opt/home (copied to ~ at runtime by entrypoint)
RUN echo '. ${ASDF_DIR}/asdf.sh' >> /opt/home/.bashrc && \
echo '. ${ASDF_DIR}/asdf.sh' >> /opt/home/.profile && \
echo '. ${ASDF_DIR}/asdf.sh' >> /opt/home/.zshrc

WORKDIR /home/${USER_NAME}

Expand All @@ -138,24 +141,24 @@ ENV CELL_HOME=/home/${USER_NAME}
ENV HOME=/home/${USER_NAME}

# Install Nix package manager with flakes support (conditional)
# Note: Nix installs to /home/${USER_NAME}/.nix-profile, templates go to /opt/home
RUN if [ "$DEVCELL_NIX_ENABLED" = "true" ]; then \
curl -L https://nixos.org/nix/install | sh -s -- --no-daemon && \
mkdir -p /home/${USER_NAME}/.config/nix && \
echo "experimental-features = nix-command flakes" >> /home/${USER_NAME}/.config/nix/nix.conf && \
echo '. ${HOME}/.nix-profile/etc/profile.d/nix.sh' >> /home/${USER_NAME}/.bashrc && \
echo '. ${HOME}/.nix-profile/etc/profile.d/nix.sh' >> /home/${USER_NAME}/.profile && \
echo '. ${HOME}/.nix-profile/etc/profile.d/nix.sh' >> /home/${USER_NAME}/.zshrc; \
echo "experimental-features = nix-command flakes" >> /opt/home/.config/nix/nix.conf && \
echo '. ${HOME}/.nix-profile/etc/profile.d/nix.sh' >> /opt/home/.bashrc && \
echo '. ${HOME}/.nix-profile/etc/profile.d/nix.sh' >> /opt/home/.profile && \
echo '. ${HOME}/.nix-profile/etc/profile.d/nix.sh' >> /opt/home/.zshrc; \
fi

# Install asdf version manager
ENV ASDF_VERSION=v0.14.1
ENV ASDF_DIR=/home/${USER_NAME}/.asdf
ENV ASDF_DATA_DIR=/home/${USER_NAME}/.asdf
ENV ASDF_DIR=/opt/asdf
ENV ASDF_DATA_DIR=/opt/asdf
ENV PATH="${ASDF_DIR}/bin:${ASDF_DIR}/shims:${PATH}"

# Install and configure asdf to read legacy version files (.python-version, .ruby-version, etc.)
RUN git clone https://github.com/asdf-vm/asdf.git ${ASDF_DIR} --branch ${ASDF_VERSION} && \
echo "legacy_version_file = yes" > /home/${USER_NAME}/.asdfrc
echo "legacy_version_file = yes" > /opt/home/.asdfrc


# Install asdf plugins
Expand Down
37 changes: 32 additions & 5 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ vars:
CODEX: "{{.RUN}} codex --dangerously-bypass-approvals-and-sandbox"
SHELL: "{{.RUN}} bash"

CELL_HOME: '{{.HOME}}/.claude-{{ env "TMUX_SESSION_NAME" | default "sandbox"}}'
CELL_HOME: '{{.HOME}}/.devcell/{{ env "TMUX_SESSION_NAME" | default "sandbox"}}'
PORT_PREFIX: '{{env "TMUX_PANE" | replace "%" "" | default ""}}'
BASE_DIR: "{{ default .USER_WORKING_DIR }}"

# Guest VM paths (convert /Users/<host-user>/... to /Users/vagrant/...)
GUEST_DIR: '{{regexReplaceAll "^/Users/[^/]+" .USER_WORKING_DIR "/Users/vagrant"}}'
VAGRANT_CLAUDE: 'vagrant ssh -c "cd {{.GUEST_DIR}} && claude --dangerously-skip-permissions {{.CLI_ARGS}}"'
VAGRANT_SHELL: 'vagrant ssh -- -t "cd {{.GUEST_DIR}} && exec zsh -l"'
# MACOS_SSH_HOST: "{{.APP_NAME}}.local" # Use Vagrantfile default

env:
APP_NAME: "{{.APP_NAME}}"
Expand All @@ -44,6 +45,7 @@ env:
PORT: "{{.PORT}}"
BUILDKIT_PROGRESS: plain
CELL_HOME: "{{.CELL_HOME}}"
# MACOS_SSH_HOST: "{{.MACOS_SSH_HOST}}" # Use Vagrantfile default

###
# Include this via:
Expand All @@ -53,17 +55,41 @@ env:
# flatten: true

tasks:
devcell:claude-json:backup:
desc: Backup .claude.json with timestamp, keep last 5
internal: true
cmds:
- mkdir -p "{{.CELL_HOME}}"
- touch "{{.CELL_HOME}}/.claude.json"
- |
BACKUP_FILE="{{.CELL_HOME}}/.claude.json-devcell-$(date +%Y-%m-%dT%H-%M-%S)-backup"
cp "{{.CELL_HOME}}/.claude.json" "$BACKUP_FILE" 2>/dev/null || true
ls -t "{{.CELL_HOME}}"/.claude.json-devcell-*-backup 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null || true

devcell:claude-json:restore:
desc: Restore .claude.json from latest backup
cmds:
- |
LATEST=$(ls -t "{{.CELL_HOME}}"/.claude.json-devcell-*-backup 2>/dev/null | head -1)
if [ -n "$LATEST" ]; then
cp "$LATEST" "{{.CELL_HOME}}/.claude.json"
echo "Restored from: $LATEST"
else
echo "No backup found in {{.CELL_HOME}}"
exit 1
fi

claude:
desc: Run Claude CLI in the dev container
silent: true
deps: [devcell:claude-json:backup]
cmds:
- |
echo "APP_NAME: {{.APP_NAME}}"
echo "PORT_PREFIX: {{.PORT_PREFIX}}"

echo "CELL_HOME: {{.CELL_HOME}} / $CELL_HOME"
echo "CELL_HOME: {{.CELL_HOME}}"
echo "DEVCELL_DIR: {{.DEVCELL_DIR}}"
mkdir -p "{{.CELL_HOME}}"

- "{{.CLAUDE}} {{.CLI_ARGS}}"

codex:
Expand Down Expand Up @@ -127,14 +153,15 @@ tasks:
macos:claude:
desc: Run Claude CLI in dev vagrant
silent: true
deps: [devcell:claude-json:backup]
var:
TMUX_PANE: ""
cmds:
- |
echo "APP_NAME: {{.APP_NAME}}"
echo "GUEST_DIR: {{.GUEST_DIR}}"
echo "CELL_HOME: {{.CELL_HOME}}"

# Copy Vagrantfile.local from example if it doesn't exist
if [ ! -f "{{.DEVCELL_DIR}}/Vagrantfile.local" ]; then
cp "{{.DEVCELL_DIR}}/Vagrantfile.local-example" "{{.DEVCELL_DIR}}/Vagrantfile.local"
Expand Down
1 change: 1 addition & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Vagrant.configure("2") do |config|
config.vm.box_url = ENV["MACOS_BOX_URL"] if ENV["MACOS_BOX_URL"]

# Direct SSH to VM (no port forwarding for Apple Virtualization)
# Uses mDNS/Bonjour: base VM hostname is "vagrant-macos", accessible at vagrant-macos.local
config.ssh.host = ENV["MACOS_SSH_HOST"] || "vagrant-macos.local"
config.ssh.port = 22
config.ssh.username = "vagrant"
Expand Down
13 changes: 1 addition & 12 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,10 @@ services:
# - ${HOME:-}/.config/git/:/home/${USER}/.config/git/:ro # XDG config git dir - disabled since host'ss config is managed by nix

# Mount Claude and host agents
- ${CELL_HOME}/.claude.json:/home/${USER}/.claude.json # claude json
- ${CELL_HOME}/.claude/:/home/${USER}/.claude/
- ${CELL_HOME}/:/home/${USER}/
- ${HOME:-}/.claude/commands:/home/${USER}/.claude/commands:ro # Host-based commands
- ${HOME:-}/.claude/agents:/home/${USER}/.claude/agents:ro # Host-based agents


# Mount Codex and host agents
- ${CELL_HOME}/.codex/:/home/${USER}/.codex/
- ${HOME}/.codex/config.toml:/home/${USER}/.codex/config.toml

# TODO: Replace with a dynamic path to a taskfile
- ${HOME:-}/dev/Taskfile.yml:/home/${USER}/dev/Taskfile.yml

# Additional mounts can be added via compose.local.yml

environment:
APP_NAME: ${APP_NAME:-app}
IS_SANDBOX: 1
Expand Down
34 changes: 33 additions & 1 deletion entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
#!/bin/bash
# Entrypoint script - optionally starts VNC server and runs command
# Entrypoint script - initializes home directory and optionally starts VNC server

# ============================================================
# Initialize home directory files (needed when ~ is mounted)
# Copy templates from /opt/home to $HOME if they don't exist
# ============================================================

OPT_HOME="/opt/home"

# Copy individual dotfiles from /opt/home if they don't exist in $HOME
for file in .asdfrc .bashrc .profile .zshrc .tool-versions; do
if [ -f "$OPT_HOME/$file" ] && [ ! -f "$HOME/$file" ]; then
echo "Copying $file to ~/"
cp "$OPT_HOME/$file" "$HOME/$file"
fi
done

# Copy .config directory structure (nix config, etc.)
if [ -d "$OPT_HOME/.config" ]; then
mkdir -p "$HOME/.config"
# Copy nix config if it exists and target doesn't
if [ -d "$OPT_HOME/.config/nix" ] && [ ! -d "$HOME/.config/nix" ]; then
echo "Copying .config/nix/ to ~/"
cp -r "$OPT_HOME/.config/nix" "$HOME/.config/"
fi
fi

# Create .local/bin if it doesn't exist
mkdir -p "$HOME/.local/bin"

# ============================================================
# GUI Setup (optional)
# ============================================================

# Only start GUI if DEVCELL_GUI_ENABLED is set to "true"
if [ "$DEVCELL_GUI_ENABLED" = "true" ]; then
Expand Down