bloody murder.... ship or bust here we go...
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"name": "TSYSDevStack Toolbox Base",
|
||||
"dockerComposeFile": [
|
||||
"../docker-compose.yml"
|
||||
],
|
||||
"service": "toolbox-base",
|
||||
"workspaceFolder": "/workspace",
|
||||
"remoteUser": "toolbox",
|
||||
"runServices": [
|
||||
"toolbox-base"
|
||||
],
|
||||
"overrideCommand": false,
|
||||
"postCreateCommand": "zsh -lc 'starship --version >/dev/null'"
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
# This file keeps the directory in git even when it's empty.
|
||||
# Actual implementation files will be added soon.
|
||||
@@ -1,102 +0,0 @@
|
||||
# 🧰 Toolbox Base Image Audit Checklist
|
||||
|
||||
This checklist ensures the toolbox-base image meets all security, functionality, and maintainability requirements.
|
||||
|
||||
## 🔒 Security Audit
|
||||
|
||||
- [ ] All packages installed with specific versions (no `latest` tags)
|
||||
- [ ] All external downloads verified with checksums/signatures
|
||||
- [ ] No root access possible at runtime (sudo removed)
|
||||
- [ ] Non-root user properly configured with UID/GID mapping
|
||||
- [ ] No hardcoded secrets or credentials in image
|
||||
- [ ] Minimal attack surface (unnecessary packages removed)
|
||||
- [ ] Regular security scanning implemented (Trivy integration)
|
||||
- [ ] Base image (Ubuntu) regularly updated
|
||||
- [ ] All aqua packages verified through registry
|
||||
|
||||
## 🛠️ Functionality Audit
|
||||
|
||||
- [ ] All CLI tools properly installed and accessible
|
||||
- [ ] All tools respond to `--version` flag correctly
|
||||
- [ ] Aqua proxy mechanism properly configured
|
||||
- [ ] Node.js and npm properly installed with correct version
|
||||
- [ ] AI CLI tools properly installed via npm
|
||||
- [ ] Shell configurations properly set up (zsh, bash, fish)
|
||||
- [ ] Environment variables properly configured
|
||||
- [ ] PATH correctly set for all tools
|
||||
- [ ] User home directory properly configured
|
||||
- [ ] Workspace directory properly set up with correct permissions
|
||||
|
||||
## 🏗️ Build Process Audit
|
||||
|
||||
- [ ] Dockerfile follows best practices
|
||||
- [ ] Multi-stage build optimizations implemented
|
||||
- [ ] Build cache properly utilized
|
||||
- [ ] Build arguments properly validated
|
||||
- [ ] Error handling in build scripts comprehensive
|
||||
- [ ] Build verification tests implemented
|
||||
- [ ] Image tagging strategy consistent
|
||||
- [ ] Release process properly documented
|
||||
|
||||
## 🧪 Testing Audit
|
||||
|
||||
- [ ] Automated testing of all installed tools
|
||||
- [ ] Integration tests for critical workflows
|
||||
- [ ] Regression tests for known issues
|
||||
- [ ] Performance benchmarks
|
||||
- [ ] Security scanning during build
|
||||
- [ ] Compatibility tests across platforms
|
||||
|
||||
## 📚 Documentation Audit
|
||||
|
||||
- [ ] README.md accurately reflects current state
|
||||
- [ ] All tools properly documented
|
||||
- [ ] Usage examples provided
|
||||
- [ ] Troubleshooting guide included
|
||||
- [ ] Contribution guidelines clear
|
||||
- [ ] License information up to date
|
||||
|
||||
## 🔄 Maintenance Audit
|
||||
|
||||
- [ ] Dependency update strategy defined
|
||||
- [ ] Version pinning strategy consistent
|
||||
- [ ] Backward compatibility maintained
|
||||
- [ ] Deprecation policy established
|
||||
- [ ] Release notes properly maintained
|
||||
- [ ] Issue tracking process defined
|
||||
|
||||
## 🎯 Template Consistency Audit
|
||||
|
||||
- [ ] Template properly extends from base image
|
||||
- [ ] Template follows same security practices
|
||||
- [ ] Template build process consistent
|
||||
- [ ] Template documentation complete
|
||||
- [ ] Template testing approach aligned
|
||||
- [ ] Template customization points clear
|
||||
|
||||
## 📈 Performance Audit
|
||||
|
||||
- [ ] Image size optimized
|
||||
- [ ] Startup time acceptable
|
||||
- [ ] Memory footprint reasonable
|
||||
- [ ] CPU usage within expected bounds
|
||||
- [ ] Disk I/O efficient
|
||||
- [ ] Network usage minimized
|
||||
|
||||
## 🌐 Compatibility Audit
|
||||
|
||||
- [ ] Works on all supported platforms
|
||||
- [ ] Backward compatibility maintained
|
||||
- [ ] Forward compatibility considered
|
||||
- [ ] Cross-platform consistency ensured
|
||||
- [ ] Integration with common tools verified
|
||||
- [ ] Standards compliance checked
|
||||
|
||||
## 🧹 Cleanup Audit
|
||||
|
||||
- [ ] Temporary files properly removed
|
||||
- [ ] Build artifacts cleaned up
|
||||
- [ ] Cache directories properly managed
|
||||
- [ ] Log files rotated or removed
|
||||
- [ ] Orphaned processes prevented
|
||||
- [ ] Resource leaks eliminated
|
||||
@@ -1,303 +0,0 @@
|
||||
# Multi-stage approach to minimize final image size and attack surface
|
||||
FROM ubuntu:24.04 AS installer
|
||||
|
||||
ARG USER_ID=1000
|
||||
ARG GROUP_ID=1000
|
||||
ARG USERNAME=toolbox
|
||||
ARG TEA_VERSION=0.11.1
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# ROOT STAGE 1: System package installation only
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
fish \
|
||||
fzf \
|
||||
git \
|
||||
jq \
|
||||
bc \
|
||||
htop \
|
||||
btop \
|
||||
locales \
|
||||
openssh-client \
|
||||
ripgrep \
|
||||
tmux \
|
||||
screen \
|
||||
entr \
|
||||
fd-find \
|
||||
bat \
|
||||
httpie \
|
||||
# Build dependencies needed for Node.js native modules \
|
||||
build-essential \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
zlib1g-dev \
|
||||
libffi-dev \
|
||||
libsqlite3-dev \
|
||||
libreadline-dev \
|
||||
wget \
|
||||
zsh \
|
||||
unzip \
|
||||
zip \
|
||||
gnupg \
|
||||
software-properties-common \
|
||||
apt-transport-https \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-dev \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ROOT: System-wide utilities
|
||||
RUN ln -sf /usr/bin/fdfind /usr/local/bin/fd \
|
||||
&& ln -sf /usr/bin/batcat /usr/local/bin/bat
|
||||
|
||||
# ROOT: Install Gitea tea CLI (system-wide)
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN curl -fsSL "https://dl.gitea.io/tea/${TEA_VERSION}/tea-${TEA_VERSION}-linux-amd64" -o /tmp/tea \
|
||||
&& curl -fsSL "https://dl.gitea.io/tea/${TEA_VERSION}/tea-${TEA_VERSION}-linux-amd64.sha256" -o /tmp/tea.sha256 \
|
||||
&& sed -n 's/ .*//p' /tmp/tea.sha256 | awk '{print $1 " /tmp/tea"}' | sha256sum -c - \
|
||||
&& install -m 0755 /tmp/tea /usr/local/bin/tea \
|
||||
&& rm -f /tmp/tea /tmp/tea.sha256
|
||||
|
||||
# ROOT: Configure locale
|
||||
RUN locale-gen en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
LANGUAGE=en_US:en \
|
||||
LC_ALL=en_US.UTF-8
|
||||
|
||||
# ROOT: Install Starship prompt (system-wide)
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b /usr/local/bin
|
||||
|
||||
# Install aqua package manager (manages additional CLI tooling)
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/aquaproj/aqua-installer/v2.3.1/aqua-installer | AQUA_ROOT_DIR=/usr/local/share/aquaproj-aqua bash \
|
||||
&& ln -sf /usr/local/share/aquaproj-aqua/bin/aqua /usr/local/bin/aqua
|
||||
|
||||
# Install mise for runtime management (no global toolchains pre-installed)
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN curl -sSfL https://mise.jdx.dev/install.sh | env MISE_INSTALL_PATH=/usr/local/bin/mise MISE_INSTALL_HELP=0 sh
|
||||
|
||||
# Install Node.js via mise to enable npm package installation
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN mise install node@22.13.0 && mise global node@22.13.0
|
||||
|
||||
# Create non-root user with matching UID/GID for host mapping
|
||||
# Check if user/group already exists and handle appropriately
|
||||
RUN set -eux; \
|
||||
if getent passwd "${USER_ID}" >/dev/null; then \
|
||||
existing_user="$(getent passwd "${USER_ID}" | cut -d: -f1)"; \
|
||||
echo "User with UID ${USER_ID} already exists: ${existing_user}" >&2; \
|
||||
elif ! getent group "${GROUP_ID}" >/dev/null; then \
|
||||
groupadd --gid "${GROUP_ID}" "${USERNAME}"; \
|
||||
useradd --uid "${USER_ID}" --gid "${GROUP_ID}" --shell /usr/bin/zsh --create-home "${USERNAME}"; \
|
||||
else \
|
||||
useradd --uid "${USER_ID}" --gid "${GROUP_ID}" --shell /usr/bin/zsh --create-home "${USERNAME}"; \
|
||||
fi
|
||||
|
||||
# ROOT: Set up toolbox user home directory with proper permissions
|
||||
RUN chown -R "${USER_ID}:${GROUP_ID}" "/home/${USERNAME}"
|
||||
|
||||
# SWITCH TO NON-ROOT USER: All further operations as toolbox user
|
||||
USER ${USERNAME}
|
||||
WORKDIR /home/${USERNAME}
|
||||
|
||||
# Ensure the workspace directory exists with proper permissions
|
||||
RUN mkdir -p /workspace && chmod 755 /workspace
|
||||
|
||||
# NON-ROOT: Install mise runtime manager for toolbox user
|
||||
RUN curl -sSfL https://mise.jdx.dev/install.sh | sh
|
||||
|
||||
# NON-ROOT: Update PATH for mise tools
|
||||
ENV PATH=/home/${USERNAME}/.local/bin:/home/${USERNAME}/.local/share/mise/shims:$PATH
|
||||
|
||||
# NON-ROOT: Install Node.js via mise as toolbox user
|
||||
RUN mise install node@22.13.0 && mise use -g node@22.13.0
|
||||
|
||||
# Install AI CLI tools via npm using mise to ensure Node.js is available
|
||||
RUN mise exec -- npm install -g @just-every/code@0.4.6 @qwen-code/qwen-code@0.1.1 @google/gemini-cli@0.11.0 @openai/codex@0.50.0 opencode-ai@0.15.29
|
||||
|
||||
# NON-ROOT: Install aqua package manager for toolbox user
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/aquaproj/aqua-installer/v2.3.1/aqua-installer > /tmp/aqua-installer.sh && \
|
||||
chmod +x /tmp/aqua-installer.sh && \
|
||||
AQUA_ROOT_DIR=/home/${USERNAME}/.local/share/aquaproj-aqua /tmp/aqua-installer.sh && \
|
||||
rm /tmp/aqua-installer.sh
|
||||
|
||||
# NON-ROOT: Update PATH for aqua tools
|
||||
ENV PATH=/home/${USERNAME}/.local/share/aquaproj-aqua/bin:$PATH
|
||||
|
||||
# NON-ROOT: Install Oh My Zsh
|
||||
RUN git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git ~/.oh-my-zsh
|
||||
|
||||
# NON-ROOT: Configure shells (zsh, bash, fish) with all customizations
|
||||
RUN cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc \
|
||||
&& mkdir -p ~/.config \
|
||||
&& sed -i "s/^plugins=(git)$/plugins=(git fzf)/" ~/.zshrc \
|
||||
&& printf "\nexport PATH=\"\$HOME/.local/share/aquaproj-aqua/bin:\$HOME/.local/share/mise/shims:\$HOME/.local/bin:\$PATH\"\n" >> ~/.zshrc \
|
||||
&& printf "\nexport AQUA_GLOBAL_CONFIG=\"\$HOME/.config/aquaproj-aqua/aqua.yaml\"\n" >> ~/.zshrc \
|
||||
&& printf "\n# Starship prompt\neval \"\$(starship init zsh)\"\n" >> ~/.zshrc \
|
||||
&& printf "\n# mise runtime manager\neval \"\$(mise activate zsh)\"\n" >> ~/.zshrc \
|
||||
&& printf "\n# direnv\nexport DIRENV_LOG_FORMAT=\"\"\neval \"\$(direnv hook zsh)\"\n" >> ~/.zshrc \
|
||||
&& printf "\n# zoxide\neval \"\$(zoxide init zsh)\"\n" >> ~/.zshrc \
|
||||
&& printf "\nexport AQUA_GLOBAL_CONFIG=\"\$HOME/.config/aquaproj-aqua/aqua.yaml\"\n" >> ~/.bashrc \
|
||||
&& printf "\n# mise runtime manager (bash)\neval \"\$(mise activate bash)\"\n" >> ~/.bashrc \
|
||||
&& printf "\n# direnv\nexport DIRENV_LOG_FORMAT=\"\"\neval \"\$(direnv hook bash)\"\n" >> ~/.bashrc \
|
||||
&& printf "\n# zoxide\neval \"\$(zoxide init bash)\"\n" >> ~/.bashrc \
|
||||
&& mkdir -p ~/.config/fish \
|
||||
&& printf "\nset -gx AQUA_GLOBAL_CONFIG \$HOME/.config/aquaproj-aqua/aqua.yaml\n# Shell prompt and runtime manager\nstarship init fish | source\nmise activate fish | source\ndirenv hook fish | source\nzoxide init fish | source\n" >> ~/.config/fish/config.fish
|
||||
|
||||
# NON-ROOT: Install aqua packages from aqua.yaml (all tools baked into image)
|
||||
COPY --chown=${USER_ID}:${GROUP_ID} aqua.yaml /tmp/aqua.yaml
|
||||
RUN mkdir -p ~/.config/aquaproj-aqua \
|
||||
&& cp /tmp/aqua.yaml ~/.config/aquaproj-aqua/aqua.yaml \
|
||||
&& aqua install
|
||||
|
||||
# NON-ROOT: Install all AI CLI tools during build using mise (baked into image)
|
||||
RUN mise exec -- npm install -g \
|
||||
@just-every/code@0.4.6 \
|
||||
@qwen-code/qwen-code@0.1.1 \
|
||||
@google/gemini-cli@0.11.0 \
|
||||
@openai/codex@0.50.0 \
|
||||
opencode-ai@0.15.29 && \
|
||||
mise reshim
|
||||
|
||||
# NON-ROOT: Install Joplin CLI during build using mise (baked into image)
|
||||
# Skipping Joplin due to build issues with sqlite3 dependencies
|
||||
# RUN mise exec -- npm install -g joplin-cli@latest --legacy-peer-deps && mise reshim
|
||||
|
||||
# NON-ROOT: Install additional testing tools during build
|
||||
RUN mise exec -- npm install -g bats@1.11.0 && mise reshim
|
||||
|
||||
# NON-ROOT: Install BATS testing framework from source (baked into image)
|
||||
RUN git clone https://github.com/bats-core/bats-core.git /tmp/bats-core \
|
||||
&& git -C /tmp/bats-core checkout v1.11.0 \
|
||||
&& /tmp/bats-core/install.sh "$HOME/.local" \
|
||||
&& rm -rf /tmp/bats-core
|
||||
|
||||
# Prepare workspace directory with appropriate ownership
|
||||
RUN mkdir -p /workspace \
|
||||
&& chown "${USER_ID}:${GROUP_ID}" /workspace
|
||||
|
||||
# Remove sudo to ensure no root escalation is possible at runtime
|
||||
RUN apt-get remove -y sudo 2>/dev/null || true && apt-get autoremove -y 2>/dev/null || true && rm -rf /var/lib/apt/lists/* 2>/dev/null || true
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
ENV SHELL=/usr/bin/zsh \
|
||||
AQUA_GLOBAL_CONFIG=/home/${USERNAME}/.config/aquaproj-aqua/aqua.yaml \
|
||||
PATH=/home/${USERNAME}/.local/share/aquaproj-aqua/bin:/home/${USERNAME}/.local/share/mise/shims:/home/${USERNAME}/.local/bin:${PATH}
|
||||
|
||||
WORKDIR /workspace
|
||||
USER ${USERNAME}
|
||||
|
||||
# NON-ROOT: Verify all tools are accessible during build
|
||||
RUN bash -c 'command -v node && command -v npm && command -v mise && command -v aqua' \
|
||||
&& bash -c 'node --version && npm --version && mise --version && aqua --version'
|
||||
|
||||
# NON-ROOT: Final mise reshim to ensure all tools are properly linked
|
||||
RUN mise reshim
|
||||
|
||||
# FINAL STAGE: Copy completed setup to minimize image and enhance security
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ARG USER_ID=1000
|
||||
ARG GROUP_ID=1000
|
||||
ARG USERNAME=toolbox
|
||||
ARG TEA_VERSION=0.11.1
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# ROOT: Install minimal runtime dependencies only
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
fish \
|
||||
fzf \
|
||||
git \
|
||||
jq \
|
||||
bc \
|
||||
htop \
|
||||
btop \
|
||||
locales \
|
||||
openssh-client \
|
||||
ripgrep \
|
||||
tmux \
|
||||
screen \
|
||||
entr \
|
||||
fd-find \
|
||||
bat \
|
||||
httpie \
|
||||
zsh \
|
||||
wget \
|
||||
unzip \
|
||||
zip \
|
||||
python3 \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ROOT: Restore system-wide utilities
|
||||
RUN ln -sf /usr/bin/fdfind /usr/local/bin/fd \
|
||||
&& ln -sf /usr/bin/batcat /usr/local/bin/bat
|
||||
|
||||
# ROOT: Restore system-wide configurations
|
||||
RUN locale-gen en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
LANGUAGE=en_US:en \
|
||||
LC_ALL=en_US.UTF-8
|
||||
|
||||
# ROOT: Create user/group structure
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
# First clean up any existing user/group with the same ID
|
||||
RUN set -eux; \
|
||||
if getent passwd "${USER_ID}" >/dev/null; then \
|
||||
existing_user="$(getent passwd "${USER_ID}" | cut -d: -f1)"; \
|
||||
userdel --remove "${existing_user}"; \
|
||||
fi; \
|
||||
if getent group "${GROUP_ID}" >/dev/null; then \
|
||||
groupdel "$(getent group "${GROUP_ID}" | cut -d: -f1)"; \
|
||||
fi; \
|
||||
# Create the group and user
|
||||
groupadd --gid "${GROUP_ID}" "${USERNAME}"; \
|
||||
useradd --uid "${USER_ID}" --gid "${GROUP_ID}" --shell /usr/bin/zsh --create-home "${USERNAME}"; \
|
||||
# Ensure proper ownership of home directory
|
||||
chown -R "${USER_ID}:${GROUP_ID}" "/home/${USERNAME}"
|
||||
|
||||
# ROOT: Copy the complete user environment from the installer stage
|
||||
COPY --from=installer --chown=${USER_ID}:${GROUP_ID} /home/${USERNAME} /home/${USERNAME}
|
||||
|
||||
# ROOT: Create workspace directory
|
||||
RUN mkdir -p /workspace && chown "${USER_ID}:${GROUP_ID}" /workspace
|
||||
|
||||
# ROOT: Install system-wide tools (tea and starship) which were in the source image
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN curl -fsSL "https://dl.gitea.io/tea/${TEA_VERSION}/tea-${TEA_VERSION}-linux-amd64" -o /tmp/tea \
|
||||
&& curl -fsSL "https://dl.gitea.io/tea/${TEA_VERSION}/tea-${TEA_VERSION}-linux-amd64.sha256" -o /tmp/tea.sha256 \
|
||||
&& sed -n 's/ .*//p' /tmp/tea.sha256 | awk '{print $1 " /tmp/tea"}' | sha256sum -c - \
|
||||
&& install -m 0755 /tmp/tea /usr/local/bin/tea \
|
||||
&& rm -f /tmp/tea /tmp/tea.sha256
|
||||
|
||||
RUN curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b /usr/local/bin
|
||||
|
||||
# ROOT: Security hardening - remove sudo if present
|
||||
RUN apt-get remove -y sudo 2>/dev/null || true && apt-get autoremove -y 2>/dev/null || true && rm -rf /var/lib/apt/lists/* 2>/dev/null || true
|
||||
|
||||
# ROOT: Final environment variables
|
||||
ENV PATH=/home/${USERNAME}/.local/share/aquaproj-aqua/bin:/home/${USERNAME}/.local/share/mise/shims:/home/${USERNAME}/.local/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
ENV SHELL=/usr/bin/zsh \
|
||||
AQUA_GLOBAL_CONFIG=/home/${USERNAME}/.config/aquaproj-aqua/aqua.yaml
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# FINAL USER: Switch to toolbox user for runtime
|
||||
USER ${USERNAME}
|
||||
WORKDIR /workspace
|
||||
|
||||
CMD ["/usr/bin/zsh"]
|
||||
@@ -1,182 +0,0 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ARG USER_ID=1000
|
||||
ARG GROUP_ID=1000
|
||||
ARG USERNAME=toolbox
|
||||
ARG TEA_VERSION=0.11.1
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install base packages with proper caching
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
fish \
|
||||
fzf \
|
||||
git \
|
||||
jq \
|
||||
bc \
|
||||
htop \
|
||||
btop \
|
||||
locales \
|
||||
openssh-client \
|
||||
ripgrep \
|
||||
tmux \
|
||||
screen \
|
||||
entr \
|
||||
fd-find \
|
||||
bat \
|
||||
httpie \
|
||||
build-essential \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
zlib1g-dev \
|
||||
libffi-dev \
|
||||
libsqlite3-dev \
|
||||
libreadline-dev \
|
||||
wget \
|
||||
zsh \
|
||||
# Additional packages for better tool support
|
||||
unzip \
|
||||
zip \
|
||||
gnupg \
|
||||
software-properties-common \
|
||||
apt-transport-https \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg-agent \
|
||||
software-properties-common \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Provide common aliases for fd and bat binaries
|
||||
RUN ln -sf /usr/bin/fdfind /usr/local/bin/fd \
|
||||
&& ln -sf /usr/bin/batcat /usr/local/bin/bat
|
||||
|
||||
# Install Gitea tea CLI
|
||||
RUN curl -fsSL "https://dl.gitea.io/tea/${TEA_VERSION}/tea-${TEA_VERSION}-linux-amd64" -o /tmp/tea \
|
||||
&& curl -fsSL "https://dl.gitea.io/tea/${TEA_VERSION}/tea-${TEA_VERSION}-linux-amd64.sha256" -o /tmp/tea.sha256 \
|
||||
&& sed -n 's/ .*//p' /tmp/tea.sha256 | awk '{print $1 " /tmp/tea"}' | sha256sum -c - \
|
||||
&& install -m 0755 /tmp/tea /usr/local/bin/tea \
|
||||
&& rm -f /tmp/tea /tmp/tea.sha256
|
||||
|
||||
# Configure locale to ensure consistent tool behavior
|
||||
RUN locale-gen en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
LANGUAGE=en_US:en \
|
||||
LC_ALL=en_US.UTF-8
|
||||
|
||||
# Install Starship prompt
|
||||
RUN curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b /usr/local/bin
|
||||
|
||||
# Install aqua package manager (manages additional CLI tooling)
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/aquaproj/aqua-installer/v2.3.1/aqua-installer | AQUA_ROOT_DIR=/usr/local/share/aquaproj-aqua bash \
|
||||
&& ln -sf /usr/local/share/aquaproj-aqua/bin/aqua /usr/local/bin/aqua
|
||||
|
||||
# Install system dependencies needed for mise and potential build tools
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
build-essential \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
zlib1g-dev \
|
||||
libffi-dev \
|
||||
libsqlite3-dev \
|
||||
libreadline-dev \
|
||||
# Only install non-Node.js dependencies via apt-get
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install mise for runtime management
|
||||
RUN curl -sSfL https://mise.jdx.dev/install.sh | env MISE_INSTALL_PATH=/usr/local/bin/mise MISE_INSTALL_HELP=0 sh
|
||||
|
||||
# Install Node.js runtime via mise and set globally (only once)
|
||||
RUN mise install node@22.13.0 && mise global node@22.13.0
|
||||
|
||||
# Create non-root user with matching UID/GID for host mapping
|
||||
RUN if getent passwd "${USER_ID}" >/dev/null; then \
|
||||
existing_user="$(getent passwd "${USER_ID}" | cut -d: -f1)"; \
|
||||
userdel --remove "${existing_user}"; \
|
||||
fi \
|
||||
&& if ! getent group "${GROUP_ID}" >/dev/null; then \
|
||||
groupadd --gid "${GROUP_ID}" "${USERNAME}"; \
|
||||
fi \
|
||||
&& useradd --uid "${USER_ID}" --gid "${GROUP_ID}" --shell /usr/bin/zsh --create-home "${USERNAME}"
|
||||
|
||||
# Install Oh My Zsh and configure shells for the unprivileged user
|
||||
RUN su - "${USERNAME}" -c 'git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git ~/.oh-my-zsh' \
|
||||
&& su - "${USERNAME}" -c 'cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc' \
|
||||
&& su - "${USERNAME}" -c 'mkdir -p ~/.config' \
|
||||
&& su - "${USERNAME}" -c 'sed -i "s/^plugins=(git)$/plugins=(git fzf)/" ~/.zshrc' \
|
||||
&& su - "${USERNAME}" -c 'printf "\nexport PATH=\"\$HOME/.local/share/aquaproj-aqua/bin:\$HOME/.local/share/mise/shims:\$HOME/.local/bin:\$PATH\"\n" >> ~/.zshrc' \
|
||||
&& su - "${USERNAME}" -c 'printf "\nexport AQUA_GLOBAL_CONFIG=\"\$HOME/.config/aquaproj-aqua/aqua.yaml\"\n" >> ~/.zshrc' \
|
||||
&& su - "${USERNAME}" -c 'printf "\n# Starship prompt\neval \"\$(starship init zsh)\"\n" >> ~/.zshrc' \
|
||||
&& su - "${USERNAME}" -c 'printf "\n# mise runtime manager\neval \"\$(mise activate zsh)\"\n" >> ~/.zshrc' \
|
||||
&& su - "${USERNAME}" -c 'printf "\n# direnv\nexport DIRENV_LOG_FORMAT=\"\"\neval \"\$(direnv hook zsh)\"\n" >> ~/.zshrc' \
|
||||
&& su - "${USERNAME}" -c 'printf "\n# zoxide\neval \"\$(zoxide init zsh)\"\n" >> ~/.zshrc' \
|
||||
&& su - "${USERNAME}" -c 'printf "\nexport AQUA_GLOBAL_CONFIG=\"\$HOME/.config/aquaproj-aqua/aqua.yaml\"\n" >> ~/.bashrc' \
|
||||
&& su - "${USERNAME}" -c 'printf "\n# mise runtime manager (bash)\neval \"\$(mise activate bash)\"\n" >> ~/.bashrc' \
|
||||
&& su - "${USERNAME}" -c 'printf "\n# direnv\nexport DIRENV_LOG_FORMAT=\"\"\neval \"\$(direnv hook bash)\"\n" >> ~/.bashrc' \
|
||||
&& su - "${USERNAME}" -c 'printf "\n# zoxide\neval \"\$(zoxide init bash)\"\n" >> ~/.bashrc' \
|
||||
&& su - "${USERNAME}" -c 'mkdir -p ~/.config/fish' \
|
||||
&& su - "${USERNAME}" -c 'printf "\nset -gx AQUA_GLOBAL_CONFIG \$HOME/.config/aquaproj-aqua/aqua.yaml\n# Shell prompt and runtime manager\nstarship init fish | source\nmise activate fish | source\ndirenv hook fish | source\nzoxide init fish | source\n" >> ~/.config/fish/config.fish'
|
||||
|
||||
# Ensure toolbox user has access to Node.js runtime from mise
|
||||
RUN su - "${USERNAME}" -c 'mise use -g node@22.13.0'
|
||||
|
||||
COPY aqua.yaml /tmp/aqua.yaml
|
||||
|
||||
# Install aqua packages at both root and user level to ensure they're baked into the image
|
||||
RUN chown "${USER_ID}:${GROUP_ID}" /tmp/aqua.yaml \
|
||||
&& su - "${USERNAME}" -c 'mkdir -p ~/.config/aquaproj-aqua' \
|
||||
&& su - "${USERNAME}" -c 'cp /tmp/aqua.yaml ~/.config/aquaproj-aqua/aqua.yaml' \
|
||||
&& AQUA_GLOBAL_CONFIG=/tmp/aqua.yaml aqua install \
|
||||
&& su - "${USERNAME}" -c 'AQUA_GLOBAL_CONFIG=~/.config/aquaproj-aqua/aqua.yaml aqua install'
|
||||
|
||||
# Install all AI CLI tools in one npm command to optimize layers
|
||||
RUN mise exec -- npm install -g \
|
||||
@just-every/code@0.4.6 \
|
||||
@qwen-code/qwen-code@0.1.1 \
|
||||
@google/gemini-cli@0.11.0 \
|
||||
@openai/codex@0.50.0 \
|
||||
opencode-ai@0.15.29 && \
|
||||
mise reshim
|
||||
|
||||
# Install the same AI CLI tools for the toolbox user so they are available in the container runtime
|
||||
RUN su - "${USERNAME}" -c 'mise exec -- npm install -g @just-every/code@0.4.6 @qwen-code/qwen-code@0.1.1 @google/gemini-cli@0.11.0 @openai/codex@0.50.0 opencode-ai@0.15.29' && \
|
||||
# Ensure mise shims are properly generated for the installed tools
|
||||
su - "${USERNAME}" -c 'mise reshim'
|
||||
|
||||
# Install BATS for testing framework
|
||||
RUN git clone https://github.com/bats-core/bats-core.git /tmp/bats-core \
|
||||
&& cd /tmp/bats-core \
|
||||
&& git checkout v1.11.0 \
|
||||
&& ./install.sh /usr/local \
|
||||
&& rm -rf /tmp/bats-core
|
||||
|
||||
# Install additional testing tools
|
||||
# Using mise exec to ensure npm is available in the PATH
|
||||
RUN mise exec -- npm install -g bats@1.11.0
|
||||
|
||||
# Prepare workspace directory with appropriate ownership
|
||||
RUN mkdir -p /workspace \
|
||||
&& chown "${USER_ID}:${GROUP_ID}" /workspace
|
||||
|
||||
# Remove sudo to ensure no root escalation is possible at runtime (if installed)
|
||||
RUN apt-get remove -y sudo 2>/dev/null || true && apt-get autoremove -y 2>/dev/null || true && rm -rf /var/lib/apt/lists/* 2>/dev/null || true
|
||||
|
||||
ENV SHELL=/usr/bin/zsh \
|
||||
AQUA_GLOBAL_CONFIG=/home/${USERNAME}/.config/aquaproj-aqua/aqua.yaml \
|
||||
PATH=/home/${USERNAME}/.local/share/aquaproj-aqua/bin:/home/${USERNAME}/.local/share/mise/shims:/home/${USERNAME}/.local/bin:${PATH}
|
||||
|
||||
WORKDIR /workspace
|
||||
USER ${USERNAME}
|
||||
|
||||
CMD ["/usr/bin/zsh"]
|
||||
@@ -1,29 +0,0 @@
|
||||
You are Codex, collaborating with a human on the TSYSDevStack ToolboxStack project.
|
||||
|
||||
Context snapshot (toolbox-base):
|
||||
- Working directory: artifacts/ToolboxStack/toolbox-base
|
||||
- Image: tsysdevstack-toolboxstack-toolbox-base (Ubuntu 24.04)
|
||||
- Container user: toolbox (non-root, UID/GID mapped to host)
|
||||
- Mounted workspace: current repo at /workspace (rw)
|
||||
|
||||
Current state:
|
||||
- Dockerfile installs shell tooling (zsh/bash/fish with Starship & oh-my-zsh), core CLI utilities (curl, wget, git, tmux, screen, htop, btop, entr, httpie, tea, bc, etc.), build-essential + headers, aqua, and mise. Aqua is pinned to specific versions for gh, lazygit, direnv, git-delta, zoxide, just, yq, xh, curlie, chezmoi, shfmt, shellcheck, hadolint, uv, watchexec; direnv/zoxide hooks are enabled for all shells (direnv logging muted).
|
||||
- aqua-managed CLI inventory lives in README.md alongside usage notes; tea installs via direct download with checksum verification (TEA_VERSION build arg).
|
||||
- aqua packages are baked into the image during the build process for consistency, reproducibility and performance.
|
||||
- mise handles language/tool runtimes; activation wired into zsh, bash, and fish. Node.js is pinned to version 22.13.0 for build consistency.
|
||||
- AI CLI tools (just-every/code, QwenLM/qwen-code, google-gemini/gemini-cli, openai/codex, sst/opencode) are installed via npm and baked into the image with pinned versions.
|
||||
- Host directories for AI tool configuration and cache are mounted to maintain persistent settings across container runs.
|
||||
- docker-compose.yml runs container with host UID/GID, `sleep infinity`, and docker socket mount; run via run.sh/build.sh. Host directories `~/.local/share/mise` and `~/.cache/mise` are mounted for persistent runtimes.
|
||||
- Devcontainer config ( .devcontainer/devcontainer.json ) references the compose service.
|
||||
- Documentation: README.md (tooling inventory & workflow) and this PROMPT must stay current, and both should stay aligned with the shared guidance in ../PROMPT. README also notes that build.sh now uses docker buildx with a local cache directory and documents the `dev` → `release-current` → semantic tagging workflow.
|
||||
|
||||
Collaboration guidelines:
|
||||
1. Default to non-destructive operations; respect existing scripts run.sh/build.sh.
|
||||
2. Any tooling changes require updating README.md (inventory) and this prompt summary, rebuilding via `./build.sh` (local dev tag), then committing (Conventional Commits, atomic diffs) and pushing after a successful build per ../PROMPT. Use `./release.sh <semver>` (clean git tree required; `--dry-run`/`--allow-dirty` only for rehearsal) to promote to `release-current` + semantic tag.
|
||||
3. Keep configurations reproducible: prefer aqua/mise for new CLI/runtimes over apt unless prerequisites.
|
||||
4. Mention verification steps (build/test) after changes and note which tag was built/pushed.
|
||||
5. Downstream consumers should inherit from `:release-current` (or a pinned semantic tag); maintain UID/GID mapping and non-root execution.
|
||||
|
||||
Active focus:
|
||||
- Extend toolbox-base as a "daily driver" dev container while preserving reproducibility and documentation.
|
||||
- Next contributor should review README.md before modifying tooling and ensure both README and this prompt reflect new state.
|
||||
@@ -1,177 +0,0 @@
|
||||
# 🧰 TSYSDevStack Toolbox Base
|
||||
|
||||
> **Daily-driver development container with curated tooling**
|
||||
|
||||
Daily-driver development container for ToolboxStack work. It provides a reproducible Ubuntu 24.04 environment with curated shell tooling, package managers, and helper scripts.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
| 📋 Step | 🛠️ Command | 📝 Description |
|
||||
|---------|------------|----------------|
|
||||
| 1. 🏗️ **Build the image** | `./build.sh` | Builds and tags the image as `tsysdevstack-toolboxstack-toolbox-base:dev`. Uses `docker buildx` with a local cache at `.build-cache/` for faster rebuilds. |
|
||||
| 2. ▶️ **Start the container** | `./run.sh up` | Defaults to the `release-current` tag; override with `TOOLBOX_IMAGE_OVERRIDE=...` when testing other tags. Mise runtimes persist to your host in `~/.local/share/mise` and `~/.cache/mise` so language/tool downloads are shared across projects. |
|
||||
| 3. 🔗 **Attach to a shell** | `docker exec -it tsysdevstack-toolboxstack-toolbox-base zsh` | or: `bash` / `fish` |
|
||||
| 4. ⏹️ **Stop the container** | `./run.sh down` | Stops the running container |
|
||||
|
||||
> **💡 Note:** The compose service mounts the current repo to `/workspace` (read/write) and runs as the mapped host user (`toolbox`).
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ Image Tagging & Releases
|
||||
|
||||
| 🛠️ Operation | 📋 Command | 📝 Details |
|
||||
|--------------|------------|------------|
|
||||
| 🏗️ Build Development | `./build.sh` | Builds `:dev` for active development |
|
||||
| 🚀 Release | `./release.sh <semver>` | Rebuilds, retags, and pushes `:dev`, `:release-current`, and `v<semver>` (e.g., `./release.sh 0.2.0`). Requires a clean git tree. |
|
||||
| 🧪 Dry Run | `./release.sh --dry-run <semver>` | Rehearse the release without pushing (optionally `--allow-dirty` for experimentation only) |
|
||||
| 📦 Downstream | `FROM tsysdevstack-toolboxstack-toolbox-base:release-current` | Downstream Dockerfiles should inherit from `release-current` (or pin to a semantic tag for reproducibility) |
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Tooling Inventory
|
||||
|
||||
### 🐚 Shells & Prompts
|
||||
| 🛠️ Tool | 📋 Name | 📝 Notes |
|
||||
|---------|---------|---------|
|
||||
| 🐚 | `zsh` | Z shell with oh-my-zsh framework |
|
||||
| 🐟 | `fish` | Friendly interactive shell |
|
||||
| 🧑💻 | `bash` | Bourne again shell |
|
||||
| ⭐ | `starship` | Cross-shell prompt |
|
||||
| 💎 | `oh-my-zsh` | Zsh framework |
|
||||
|
||||
> ⭐ Starship prompt enabled for all shells; oh-my-zsh configured with `git` + `fzf` plugins.
|
||||
|
||||
### 🪄 Runtime & CLI Managers
|
||||
| 🛠️ Tool | 📋 Name | 📝 Notes |
|
||||
|---------|---------|---------|
|
||||
| 🪄 | `mise` | Runtime manager for languages and tools |
|
||||
| 💧 | `aqua` | CLI version manager |
|
||||
|
||||
> `mise` handles language/tool runtimes (activation wired into zsh/bash/fish); `aqua` manages standalone CLIs with config at `~/.config/aquaproj-aqua/aqua.yaml`.
|
||||
|
||||
### 🧰 Core CLI Utilities
|
||||
| 🛠️ Tool | 📋 Name | 📝 Notes |
|
||||
|---------|---------|---------|
|
||||
| 📦 | `curl` | Command-line data transfer |
|
||||
| 📥 | `wget` | Network downloader |
|
||||
| 🔐 | `ca-certificates` | Common CA certificates |
|
||||
| 🧭 | `git` | Distributed version control |
|
||||
| 🔧 | `build-essential` | Essential build tools |
|
||||
| 🔍 | `ripgrep` | Fast search tool |
|
||||
| 🧭 | `fzf` | Fuzzy finder |
|
||||
| 📁 | `fd` | Simple, fast & user-friendly alternative to find |
|
||||
| 📖 | `bat` | Cat clone with syntax highlighting |
|
||||
| 🔗 | `openssh-client` | OpenSSH client applications |
|
||||
| 🧵 | `tmux` | Terminal multiplexer |
|
||||
| 🖥️ | `screen` | Terminal multiplexer |
|
||||
| 📈 | `htop` | Interactive process viewer |
|
||||
| 📉 | `btop` | A monitor of resources |
|
||||
| ♻️ | `entr` | Run arbitrary commands when files change |
|
||||
| 📊 | `jq` | Command-line JSON processor |
|
||||
| 🌐 | `httpie` | User-friendly curl replacement |
|
||||
| ☕ | `tea` | Package manager for dev projects |
|
||||
| 🧮 | `bc` | Arbitrary precision calculator language |
|
||||
|
||||
> Provides ergonomic defaults plus toolchain deps for compiling runtimes (no global language installs).
|
||||
|
||||
### 🌊 Aqua-Managed CLIs
|
||||
| 🛠️ Tool | 📋 Name |
|
||||
|---------|---------|
|
||||
| 🐙 | `gh` (GitHub CLI) |
|
||||
| 🌀 | `lazygit` |
|
||||
| 🪄 | `direnv` |
|
||||
| 🎨 | `git-delta` |
|
||||
| 🧭 | `zoxide` |
|
||||
| 🧰 | `just` |
|
||||
| 🧾 | `yq` |
|
||||
| ⚡ | `xh` |
|
||||
| 🌍 | `curlie` |
|
||||
| 🏠 | `chezmoi` |
|
||||
| 🛠️ | `shfmt` |
|
||||
| ✅ | `shellcheck` |
|
||||
| 🐳 | `hadolint` |
|
||||
| 🐍 | `uv` |
|
||||
| 🔁 | `watchexec` |
|
||||
|
||||
> Extend via `~/.config/aquaproj-aqua/aqua.yaml`. These packages are baked into the image at build time for consistency and reproducibility. Direnv logging is muted and hooks for direnv/zoxide are pre-configured for zsh, bash, and fish.
|
||||
|
||||
### 🤖 AI CLI Tools
|
||||
| 🛠️ Tool | 📋 Name |
|
||||
|---------|---------|
|
||||
| 🧠 | `@just-every/code` |
|
||||
| 🤖 | `@qwen-code/qwen-code` |
|
||||
| 💎 | `@google/gemini-cli` |
|
||||
| 🔮 | `@openai/codex` |
|
||||
| 🌐 | `opencode-ai` |
|
||||
|
||||
> AI-powered command-line tools for enhanced development workflows. Node.js is installed via mise to support npm package installation.
|
||||
|
||||
### 🐳 Container Workflow
|
||||
| 🛠️ Feature | 📋 Description |
|
||||
|------------|----------------|
|
||||
| 🐳 | Docker socket mount (`/var/run/docker.sock`) - Enables Docker CLIs inside the container; host Docker daemon required. |
|
||||
|
||||
### 🧠 AI Tool Configuration
|
||||
| 🛠️ Feature | 📋 Description |
|
||||
|------------|----------------|
|
||||
| 🧠 | Host directories for AI tools - Host directories for AI tool configuration and cache are mounted to maintain persistent settings and data across container runs. |
|
||||
|
||||
### 👤 Runtime Environment
|
||||
| 🛠️ Feature | 📋 Description |
|
||||
|------------|----------------|
|
||||
| 👤 | Non-root user `toolbox` (UID/GID mapped) |
|
||||
| 🗂️ | `/workspace` mount - Maintains host permissions and isolates artifacts under `artifacts/ToolboxStack/toolbox-base` |
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Extending the Sandbox
|
||||
|
||||
| 🧩 Task | 🛠️ Command | 📝 Description |
|
||||
|---------|------------|----------------|
|
||||
| 🧮 **Add a runtime** | `mise use python@3.12` | (per project). Run inside `/workspace` to persist `.mise.toml`. |
|
||||
| 🧰 **Add a CLI tool** | Update `~/.config/aquaproj-aqua/aqua.yaml`, then run `aqua install` | Extend the available tools in the environment |
|
||||
| 🛠️ **Adjust base image** | Modify `Dockerfile`, run `./build.sh`, and keep this README & `PROMPT` in sync | Make changes to the base environment |
|
||||
|
||||
> 🔁 **Documentation policy:** Whenever you add/remove tooling or change the developer experience, update both this README and the `PROMPT` file so the next collaborator has an accurate snapshot.
|
||||
|
||||
---
|
||||
|
||||
## 📂 Project Layout
|
||||
|
||||
| 📁 Path | 📝 Purpose |
|
||||
|---------|------------|
|
||||
| `Dockerfile` | Defines the toolbox-base image. |
|
||||
| `docker-compose.yml` | Compose service providing the container runtime. |
|
||||
| `build.sh` | Wrapper around `docker build` with host UID/GID mapping. |
|
||||
| `run.sh` | Helper to bring the compose service up/down (exports UID/GID env vars). |
|
||||
| `.devcontainer/devcontainer.json` | VS Code remote container definition. |
|
||||
| `aqua.yaml` | Default aqua configuration (gh, tea, lazygit). |
|
||||
| `PROMPT` | LLM onboarding prompt for future contributors (must remain current). |
|
||||
|
||||
---
|
||||
|
||||
## ✅ Verification Checklist
|
||||
|
||||
After any image changes:
|
||||
|
||||
1. 🏗️ **Build Test**: Run `./build.sh` and ensure it succeeds.
|
||||
2. 🧪 **Functionality Test**: Optionally `./run.sh up` and sanity-check key tooling (e.g., `mise --version`, `gh --version`).
|
||||
3. 📝 **Documentation Sync**: Update this README and the `PROMPT` with any new or removed tooling.
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Collaboration Notes
|
||||
|
||||
| 📋 Best Practice | 📝 Description |
|
||||
|------------------|----------------|
|
||||
| 👤 **Non-Root Policy** | Container always runs as the mapped non-root user; avoid adding steps that require root login. |
|
||||
| 🧩 **Tooling Consistency** | Prefer `mise`/`aqua` for new tooling to keep installations reproducible. |
|
||||
| 📚 **Documentation Sync** | Keep documentation synchronized (README + PROMPT) so future contributors can resume quickly. |
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
See [LICENSE](../../LICENSE) for full terms.
|
||||
@@ -1,46 +0,0 @@
|
||||
version: 1.0.0
|
||||
registries:
|
||||
- type: standard
|
||||
ref: v4.431.0
|
||||
packages:
|
||||
# GitHub CLI and related tools
|
||||
- name: cli/cli@v2.82.1
|
||||
- name: jesseduffield/lazygit@v0.55.1
|
||||
|
||||
# Environment and runtime management
|
||||
- name: direnv/direnv@v2.37.1
|
||||
- name: dandavison/delta@0.18.2
|
||||
- name: ajeetdsouza/zoxide@v0.9.8
|
||||
|
||||
# Development and build tools
|
||||
- name: casey/just@1.43.0
|
||||
- name: mikefarah/yq@v4.48.1
|
||||
- name: ducaale/xh@v0.25.0
|
||||
- name: rs/curlie@v1.8.2
|
||||
|
||||
# Configuration management
|
||||
- name: twpayne/chezmoi@v2.66.1
|
||||
|
||||
# Shell scripting tools
|
||||
- name: mvdan/sh@v3.12.0
|
||||
- name: koalaman/shellcheck@v0.11.0
|
||||
- name: mvdan/shfmt@v3.12.0
|
||||
|
||||
# Container and Docker tools
|
||||
- name: hadolint/hadolint@v2.14.0
|
||||
|
||||
# Python package management
|
||||
- name: astral-sh/uv@0.9.6
|
||||
|
||||
# File watching and automation
|
||||
- name: watchexec/watchexec@v2.3.2
|
||||
|
||||
# Diagram generation
|
||||
- name: yuzutech/kroki-cli@0.10.0
|
||||
|
||||
# AI CLI tools (baked into image)
|
||||
- name: just-every/code@0.4.6
|
||||
- name: QwenLM/qwen-code@0.1.1
|
||||
- name: google-gemini/gemini-cli@0.11.0
|
||||
- name: openai/codex@0.50.0
|
||||
- name: sst/opencode@0.15.29
|
||||
@@ -1,213 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Security: Validate input parameters to prevent command injection
|
||||
sanitized_input() {
|
||||
local input="$1"
|
||||
# Check for potentially dangerous characters/commands
|
||||
case "$input" in
|
||||
*[\;\|\&\`\$]*)
|
||||
echo "Error: Invalid input detected: $input" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Validate dependencies
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "Error: docker is required but not installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker buildx version &> /dev/null; then
|
||||
echo "Error: docker buildx is required but not available." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IMAGE_NAME="tsysdevstack-toolboxstack-toolbox-base"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Sanitize user input
|
||||
USER_ID="${USER_ID_OVERRIDE:-$(id -u)}"
|
||||
sanitized_input "$USER_ID"
|
||||
GROUP_ID="${GROUP_ID_OVERRIDE:-$(id -g)}"
|
||||
sanitized_input "$GROUP_ID"
|
||||
USERNAME="${USERNAME_OVERRIDE:-toolbox}"
|
||||
sanitized_input "$USERNAME"
|
||||
TEA_VERSION="${TEA_VERSION_OVERRIDE:-0.11.1}"
|
||||
sanitized_input "$TEA_VERSION"
|
||||
BUILDER_NAME="${BUILDER_NAME:-tsysdevstack-builder}"
|
||||
sanitized_input "$BUILDER_NAME"
|
||||
CACHE_DIR="${SCRIPT_DIR}/.build-cache"
|
||||
TAG="${TAG_OVERRIDE:-dev}"
|
||||
sanitized_input "$TAG"
|
||||
RELEASE_TAG="${RELEASE_TAG_OVERRIDE:-release-current}"
|
||||
sanitized_input "$RELEASE_TAG"
|
||||
VERSION_TAG="${VERSION_TAG_OVERRIDE:-}"
|
||||
if [[ -n "$VERSION_TAG" ]]; then
|
||||
sanitized_input "$VERSION_TAG"
|
||||
fi
|
||||
PUSH="${PUSH_OVERRIDE:-false}"
|
||||
|
||||
echo "Building ${IMAGE_NAME} with UID=${USER_ID} GID=${GROUP_ID} USERNAME=${USERNAME}"
|
||||
echo "Primary tag: ${TAG}"
|
||||
|
||||
# Ensure builder exists
|
||||
if ! docker buildx inspect "${BUILDER_NAME}" >/dev/null 2>&1; then
|
||||
echo "Creating builder: ${BUILDER_NAME}"
|
||||
if ! docker buildx create --driver docker-container --name "${BUILDER_NAME}" --use >/dev/null; then
|
||||
echo "Error: Failed to create Docker buildx builder." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Using existing builder: ${BUILDER_NAME}"
|
||||
if ! docker buildx use "${BUILDER_NAME}" >/dev/null; then
|
||||
echo "Error: Failed to use Docker buildx builder." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ensure cache directory exists
|
||||
if ! mkdir -p "${CACHE_DIR}"; then
|
||||
echo "Error: Failed to create cache directory: ${CACHE_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Starting build..."
|
||||
BUILD_OUTPUT=$(mktemp)
|
||||
trap 'rm -f "$BUILD_OUTPUT"' EXIT
|
||||
|
||||
# Build the image
|
||||
if ! docker buildx build \
|
||||
--builder "${BUILDER_NAME}" \
|
||||
--load \
|
||||
--progress=plain \
|
||||
--build-arg USER_ID="${USER_ID}" \
|
||||
--build-arg GROUP_ID="${GROUP_ID}" \
|
||||
--build-arg USERNAME="${USERNAME}" \
|
||||
--build-arg TEA_VERSION="${TEA_VERSION}" \
|
||||
--cache-from "type=local,src=${CACHE_DIR}" \
|
||||
--cache-to "type=local,dest=${CACHE_DIR},mode=max" \
|
||||
--tag "${IMAGE_NAME}:${TAG}" \
|
||||
"${SCRIPT_DIR}" 2>&1 | tee "${BUILD_OUTPUT}"; then
|
||||
echo "Error: Docker build failed. Check output above for details." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Build completed successfully."
|
||||
|
||||
# Run comprehensive verification tests
|
||||
echo "Running comprehensive verification tests..."
|
||||
if ! docker run --rm "${IMAGE_NAME}:${TAG}" zsh -c 'echo "Container starts successfully as $(whoami) user"'; then
|
||||
echo "Error: Failed to start container with basic test." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify core tools are available to toolbox user
|
||||
echo "Verifying core tools for toolbox user..."
|
||||
CORE_TOOLS=("zsh" "git" "curl" "jq" "fish" "fzf" "bat" "fd" "rg" "htop" "btop")
|
||||
for tool in "${CORE_TOOLS[@]}"; do
|
||||
if ! docker run --rm --user toolbox "${IMAGE_NAME}:${TAG}" which "$tool" >/dev/null 2>&1; then
|
||||
echo "Error: Core tool '$tool' not found in PATH for toolbox user." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Verify aqua tools are available to toolbox user
|
||||
echo "Verifying aqua tools for toolbox user..."
|
||||
AQUA_TOOLS=("gh" "lazygit" "direnv" "delta" "zoxide" "just" "yq" "xh" "curlie" "shfmt" "shellcheck" "hadolint")
|
||||
for tool in "${AQUA_TOOLS[@]}"; do
|
||||
if ! docker run --rm --user toolbox "${IMAGE_NAME}:${TAG}" which "$tool" >/dev/null 2>&1; then
|
||||
echo "Error: Aqua tool '$tool' not found in PATH for toolbox user." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Verify AI CLI tools are available to toolbox user
|
||||
echo "Verifying AI CLI tools for toolbox user..."
|
||||
AI_TOOLS=("code" "qwen" "gemini" "codex" "opencode" "joplin")
|
||||
for tool in "${AI_TOOLS[@]}"; do
|
||||
if ! docker run --rm --user toolbox "${IMAGE_NAME}:${TAG}" which "$tool" >/dev/null 2>&1; then
|
||||
echo "Error: AI CLI tool '$tool' not found in PATH for toolbox user." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Verify Node.js and npm are working properly
|
||||
echo "Verifying Node.js runtime..."
|
||||
if ! docker run --rm --user toolbox "${IMAGE_NAME}:${TAG}" node --version >/dev/null 2>&1; then
|
||||
echo "Error: Node.js not working properly for toolbox user." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker run --rm --user toolbox "${IMAGE_NAME}:${TAG}" npm --version >/dev/null 2>&1; then
|
||||
echo "Error: npm not working properly for toolbox user." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify mise is managing tools properly
|
||||
echo "Verifying mise runtime management..."
|
||||
if ! docker run --rm --user toolbox "${IMAGE_NAME}:${TAG}" mise --version >/dev/null 2>&1; then
|
||||
echo "Error: Mise not available for toolbox user." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify aqua is managing tools properly
|
||||
echo "Verifying aqua package management..."
|
||||
if ! docker run --rm --user toolbox "${IMAGE_NAME}:${TAG}" aqua --version >/dev/null 2>&1; then
|
||||
echo "Error: Aqua not available for toolbox user." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Final security check: verify container runs as toolbox user
|
||||
echo "Verifying runtime security model..."
|
||||
RUNTIME_USER=$(docker run --rm "${IMAGE_NAME}:${TAG}" whoami)
|
||||
if [ "$RUNTIME_USER" != "toolbox" ]; then
|
||||
echo "Error: Container is not running as toolbox user. Current user: $RUNTIME_USER" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All verifications passed. Security model is correct."
|
||||
|
||||
if [[ "${PUSH}" == "true" ]]; then
|
||||
echo "Pushing ${IMAGE_NAME}:${TAG}"
|
||||
if ! docker push "${IMAGE_NAME}:${TAG}"; then
|
||||
echo "Error: Failed to push ${IMAGE_NAME}:${TAG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${TAG}" == "dev" && -n "${VERSION_TAG}" ]; then
|
||||
if ! docker tag "${IMAGE_NAME}:${TAG}" "${IMAGE_NAME}:${VERSION_TAG}"; then
|
||||
echo "Error: Failed to tag ${IMAGE_NAME}:${VERSION_TAG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Pushing ${IMAGE_NAME}:${VERSION_TAG}"
|
||||
if ! docker push "${IMAGE_NAME}:${VERSION_TAG}"; then
|
||||
echo "Error: Failed to push ${IMAGE_NAME}:${VERSION_TAG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${TAG}" == "dev" ]]; then
|
||||
if ! docker tag "${IMAGE_NAME}:${TAG}" "${IMAGE_NAME}:${RELEASE_TAG}"; then
|
||||
echo "Error: Failed to tag ${IMAGE_NAME}:${RELEASE_TAG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Pushing ${IMAGE_NAME}:${RELEASE_TAG}"
|
||||
if ! docker push "${IMAGE_NAME}:${RELEASE_TAG}"; then
|
||||
echo "Error: Failed to push ${IMAGE_NAME}:${RELEASE_TAG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run security scan if TRIVY is available
|
||||
if command -v trivy &> /dev/null; then
|
||||
echo "Running security scan with Trivy..."
|
||||
trivy image --exit-code 0 --severity HIGH,CRITICAL "${IMAGE_NAME}:${TAG}"
|
||||
else
|
||||
echo "Trivy not found. Install Trivy to perform security scanning."
|
||||
fi
|
||||
|
||||
echo "Build process completed successfully with all verifications and security checks."
|
||||
@@ -1,23 +0,0 @@
|
||||
services:
|
||||
toolbox-base:
|
||||
container_name: tsysdevstack-toolboxstack-toolbox-base
|
||||
image: ${TOOLBOX_IMAGE:-tsysdevstack-toolboxstack-toolbox-base:release-current}
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
USER_ID: ${LOCAL_UID:-1000}
|
||||
GROUP_ID: ${LOCAL_GID:-1000}
|
||||
USERNAME: ${LOCAL_USERNAME:-toolbox}
|
||||
user: "${LOCAL_UID:-1000}:${LOCAL_GID:-1000}"
|
||||
working_dir: /workspace
|
||||
command: ["sleep", "infinity"]
|
||||
init: true
|
||||
tty: true
|
||||
stdin_open: true
|
||||
volumes:
|
||||
- .:/workspace:rw
|
||||
- ${HOME}/.local/share/mise:/home/toolbox/.local/share/mise:rw
|
||||
- ${HOME}/.cache/mise:/home/toolbox/.cache/mise:rw
|
||||
# AI CLI tool configuration and cache directories
|
||||
- ${HOME}/.config/openai:/home/toolbox/.config/openai:rw
|
||||
- ${HOME}/.cache/openai:/home/toolbox/.cache/openai:rw
|
||||
@@ -1,100 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOU'
|
||||
Usage: ./release.sh [--dry-run] [--allow-dirty] <semver>
|
||||
|
||||
Examples:
|
||||
./release.sh 0.2.0
|
||||
./release.sh --dry-run 0.2.0
|
||||
|
||||
This script promotes the dev tag to:
|
||||
- tsysdevstack-toolboxstack-toolbox-base:release-current
|
||||
- tsysdevstack-toolboxstack-toolbox-base:v<semver>
|
||||
EOU
|
||||
}
|
||||
|
||||
DRY_RUN=false
|
||||
ALLOW_DIRTY=false
|
||||
VERSION=""
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--allow-dirty)
|
||||
ALLOW_DIRTY=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
VERSION="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${VERSION}" ]]; then
|
||||
echo "Error: semantic version is required." >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${VERSION}" =~ ^v?([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
SEMVER="v${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}"
|
||||
else
|
||||
echo "Error: version must be semantic (e.g., 0.2.0 or v0.2.0)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}" && git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
|
||||
if [[ -n "${REPO_ROOT}" && "${ALLOW_DIRTY}" != "true" ]]; then
|
||||
if ! git -C "${REPO_ROOT}" diff --quiet --ignore-submodules --exit-code; then
|
||||
echo "Error: git working tree has uncommitted changes. Please commit or stash before releasing." >&2
|
||||
exit 1
|
||||
fi
|
||||
elif [[ -z "${REPO_ROOT}" ]]; then
|
||||
echo "Warning: unable to resolve git repository root; skipping clean tree check." >&2
|
||||
fi
|
||||
|
||||
echo "Preparing release for ${SEMVER}"
|
||||
echo " dry-run: ${DRY_RUN}"
|
||||
echo " allow-dirty: ${ALLOW_DIRTY}"
|
||||
|
||||
# First, ensure we have the dev tag built
|
||||
if [[ "${DRY_RUN}" == "true" ]]; then
|
||||
echo "[dry-run] Would build dev tag"
|
||||
else
|
||||
echo "Building dev tag..."
|
||||
"${SCRIPT_DIR}/build.sh"
|
||||
fi
|
||||
|
||||
# Tag the dev image as release-current and with the version
|
||||
IMAGE_NAME="tsysdevstack-toolboxstack-toolbox-base"
|
||||
|
||||
if [[ "${DRY_RUN}" == "true" ]]; then
|
||||
echo "[dry-run] Would tag ${IMAGE_NAME}:dev as:"
|
||||
echo " - ${IMAGE_NAME}:release-current"
|
||||
echo " - ${IMAGE_NAME}:${SEMVER}"
|
||||
else
|
||||
echo "Tagging ${IMAGE_NAME}:dev as release-current and ${SEMVER}..."
|
||||
docker tag "${IMAGE_NAME}:dev" "${IMAGE_NAME}:release-current"
|
||||
docker tag "${IMAGE_NAME}:dev" "${IMAGE_NAME}:${SEMVER}"
|
||||
echo "Release ${SEMVER} tagged as:"
|
||||
echo " - tsysdevstack-toolboxstack-toolbox-base:release-current"
|
||||
echo " - tsysdevstack-toolboxstack-toolbox-base:${SEMVER}"
|
||||
fi
|
||||
@@ -1,74 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Security: Validate input parameters to prevent command injection
|
||||
sanitized_input() {
|
||||
local input="$1"
|
||||
# Check for potentially dangerous characters/commands
|
||||
case "$input" in
|
||||
*[\;\|\&\`\$]*)
|
||||
echo "Error: Invalid input detected: $input" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Validate dependencies
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "Error: docker is required but not installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v docker compose &> /dev/null; then
|
||||
echo "Error: docker compose is required but not installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COMPOSE_FILE="${SCRIPT_DIR}/docker-compose.yml"
|
||||
|
||||
# Sanitize user input
|
||||
export LOCAL_UID="${USER_ID_OVERRIDE:-$(id -u)}"
|
||||
sanitized_input "$LOCAL_UID"
|
||||
export LOCAL_GID="${GROUP_ID_OVERRIDE:-$(id -g)}"
|
||||
sanitized_input "$LOCAL_GID"
|
||||
export LOCAL_USERNAME="${USERNAME_OVERRIDE:-toolbox}"
|
||||
sanitized_input "$LOCAL_USERNAME"
|
||||
export TOOLBOX_IMAGE="${TOOLBOX_IMAGE_OVERRIDE:-tsysdevstack-toolboxstack-toolbox-base:release-current}"
|
||||
sanitized_input "$TOOLBOX_IMAGE"
|
||||
|
||||
if [[ ! -f "${COMPOSE_FILE}" ]]; then
|
||||
echo "Error: docker-compose.yml not found at ${COMPOSE_FILE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ACTION="${1:-up}"
|
||||
sanitized_input "$ACTION"
|
||||
shift || true
|
||||
|
||||
if [[ "${ACTION}" == "up" ]]; then
|
||||
# Create necessary directories for the toolbox tools with proper permissions
|
||||
mkdir -p "${HOME}/.local/share/mise" "${HOME}/.cache/mise"
|
||||
mkdir -p "${HOME}/.config" "${HOME}/.local/share"
|
||||
mkdir -p "${HOME}/.cache/openai" "${HOME}/.cache/gemini" "${HOME}/.cache/qwen" "${HOME}/.cache/code" "${HOME}/.cache/opencode"
|
||||
mkdir -p "${HOME}/.config/openai" "${HOME}/.config/gemini" "${HOME}/.config/qwen" "${HOME}/.config/code" "${HOME}/.config/opencode"
|
||||
|
||||
# Set proper permissions for created directories
|
||||
chmod 700 "${HOME}/.config" "${HOME}/.local/share" "${HOME}/.cache" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
case "${ACTION}" in
|
||||
up)
|
||||
docker compose -f "${COMPOSE_FILE}" up --build --detach "$@"
|
||||
echo "Container started. Use 'docker exec -it tsysdevstack-toolboxstack-toolbox-base zsh' to access the shell."
|
||||
;;
|
||||
down)
|
||||
docker compose -f "${COMPOSE_FILE}" down "$@"
|
||||
echo "Container stopped."
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [up|down] [additional docker compose args]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -1,145 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Security audit script for the toolbox-base image
|
||||
|
||||
IMAGE_NAME="${IMAGE_NAME_OVERRIDE:-tsysdevstack-toolboxstack-toolbox-base:release-current}"
|
||||
|
||||
echo "🔒 Running security audit on ${IMAGE_NAME}"
|
||||
|
||||
# Check if Trivy is available for security scanning
|
||||
if command -v trivy &> /dev/null; then
|
||||
echo "🔍 Running Trivy security scan..."
|
||||
trivy image --exit-code 0 --severity HIGH,CRITICAL "${IMAGE_NAME}"
|
||||
echo "✅ Trivy scan completed"
|
||||
else
|
||||
echo "⚠️ Trivy not found. Install Trivy to perform security scanning."
|
||||
echo " Visit https://aquasecurity.github.io/trivy/ for installation instructions."
|
||||
fi
|
||||
|
||||
# Check for outdated packages
|
||||
echo "📦 Checking for outdated packages..."
|
||||
OUTDATED_PACKAGES=$(docker run --rm "${IMAGE_NAME}" apt list --upgradable 2>/dev/null | grep -v "Listing..." | wc -l)
|
||||
if [[ "${OUTDATED_PACKAGES}" -gt 0 ]]; then
|
||||
echo "⚠️ ${OUTDATED_PACKAGES} packages can be upgraded"
|
||||
echo " Run 'apt update && apt upgrade' to update packages"
|
||||
else
|
||||
echo "✅ All system packages are up to date"
|
||||
fi
|
||||
|
||||
# Check for unnecessary packages that increase attack surface
|
||||
echo "🛡️ Checking for unnecessary packages..."
|
||||
UNNECESSARY_PACKAGES=$(docker run --rm "${IMAGE_NAME}" dpkg -l | grep -E "(telnet|ftp|rsh-client|nfs-common|rpcbind)" | wc -l)
|
||||
if [[ "${UNNECESSARY_PACKAGES}" -gt 0 ]]; then
|
||||
echo "⚠️ Found ${UNNECESSARY_PACKAGES} potentially unnecessary packages that increase attack surface"
|
||||
echo " Consider removing packages like telnet, ftp, rsh-client, nfs-common, rpcbind"
|
||||
else
|
||||
echo "✅ No unnecessary packages found that increase attack surface"
|
||||
fi
|
||||
|
||||
# Check for world-writable files/directories
|
||||
echo "📁 Checking for world-writable files/directories..."
|
||||
WORLD_WRITABLE=$(docker run --rm "${IMAGE_NAME}" find / -xdev -type f -perm -0002 -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null | wc -l)
|
||||
if [[ "${WORLD_WRITABLE}" -gt 0 ]]; then
|
||||
echo "⚠️ Found ${WORLD_WRITABLE} world-writable files/directories"
|
||||
echo " These should be reviewed and permissions adjusted if necessary"
|
||||
else
|
||||
echo "✅ No world-writable files/directories found"
|
||||
fi
|
||||
|
||||
# Check for setuid/setgid binaries
|
||||
echo "🔑 Checking for setuid/setgid binaries..."
|
||||
SETUID_BINARIES=$(docker run --rm "${IMAGE_NAME}" find / -xdev \( -perm -4000 -o -perm -2000 \) -type f -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null | wc -l)
|
||||
if [[ "${SETUID_BINARIES}" -gt 0 ]]; then
|
||||
echo "⚠️ Found ${SETUID_BINARIES} setuid/setgid binaries"
|
||||
echo " These should be reviewed for security implications"
|
||||
else
|
||||
echo "✅ No setuid/setgid binaries found"
|
||||
fi
|
||||
|
||||
# Check for running services
|
||||
echo "サービ Checking for running services..."
|
||||
RUNNING_SERVICES=$(docker run --rm "${IMAGE_NAME}" ps aux 2>/dev/null | grep -v "PID" | wc -l)
|
||||
if [[ "${RUNNING_SERVICES}" -gt 1 ]]; then
|
||||
echo "⚠️ Found ${RUNNING_SERVICES} running processes"
|
||||
echo " These should be reviewed for necessity"
|
||||
else
|
||||
echo "✅ No unnecessary running services found"
|
||||
fi
|
||||
|
||||
# Check for listening ports
|
||||
echo "📡 Checking for listening ports..."
|
||||
LISTENING_PORTS=$(docker run --rm "${IMAGE_NAME}" netstat -tuln 2>/dev/null | grep LISTEN | wc -l)
|
||||
if [[ "${LISTENING_PORTS}" -gt 0 ]]; then
|
||||
echo "⚠️ Found ${LISTENING_PORTS} listening ports"
|
||||
echo " These should be reviewed for security implications"
|
||||
else
|
||||
echo "✅ No unnecessary listening ports found"
|
||||
fi
|
||||
|
||||
# Check for sudo availability
|
||||
echo "🛑 Checking for sudo availability..."
|
||||
if docker run --rm "${IMAGE_NAME}" which sudo >/dev/null 2>&1; then
|
||||
echo "❌ Sudo is available in the image - this is a security risk"
|
||||
echo " Sudo should be removed to prevent privilege escalation"
|
||||
else
|
||||
echo "✅ Sudo is not available in the image"
|
||||
fi
|
||||
|
||||
# Check for root login capability
|
||||
echo "🔐 Checking for root login capability..."
|
||||
ROOT_LOGIN_ENABLED=$(docker run --rm "${IMAGE_NAME}" cat /etc/passwd | grep root | grep -v "nologin" | wc -l)
|
||||
if [[ "${ROOT_LOGIN_ENABLED}" -gt 0 ]]; then
|
||||
echo "⚠️ Root login might be enabled"
|
||||
echo " Ensure root login is disabled for security"
|
||||
else
|
||||
echo "✅ Root login is properly disabled"
|
||||
fi
|
||||
|
||||
# Check user configuration
|
||||
echo "👤 Checking user configuration..."
|
||||
USER_ID=$(docker run --rm "${IMAGE_NAME}" id -u toolbox 2>/dev/null || echo "not_found")
|
||||
if [[ "${USER_ID}" == "1000" ]]; then
|
||||
echo "✅ Non-root user 'toolbox' with UID 1000 is properly configured"
|
||||
else
|
||||
echo "⚠️ Non-root user configuration might be incorrect"
|
||||
fi
|
||||
|
||||
# Check for hardcoded passwords
|
||||
echo "🔑 Checking for hardcoded passwords..."
|
||||
HARDCODED_PASSWORDS=$(docker run --rm "${IMAGE_NAME}" grep -r "password\|passwd" /etc/ 2>/dev/null | grep -v "shadow" | wc -l)
|
||||
if [[ "${HARDCODED_PASSWORDS}" -gt 0 ]]; then
|
||||
echo "⚠️ Found ${HARDCODED_PASSWORDS} potential hardcoded password references"
|
||||
echo " These should be reviewed for security implications"
|
||||
else
|
||||
echo "✅ No hardcoded password references found"
|
||||
fi
|
||||
|
||||
# Check for exposed secrets
|
||||
echo " секр Checking for exposed secrets..."
|
||||
EXPOSED_SECRETS=$(docker run --rm "${IMAGE_NAME}" find / -xdev -type f -name "*.key" -o -name "*.pem" -o -name "*.cert" 2>/dev/null | wc -l)
|
||||
if [[ "${EXPOSED_SECRETS}" -gt 0 ]]; then
|
||||
echo "⚠️ Found ${EXPOSED_SECRETS} potential secret files"
|
||||
echo " These should be reviewed for security implications"
|
||||
else
|
||||
echo "✅ No exposed secret files found"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "🔒 Security Audit Summary:"
|
||||
echo " - Image: ${IMAGE_NAME}"
|
||||
echo " - Scan completed with recommendations above"
|
||||
echo ""
|
||||
echo "💡 Recommendations:"
|
||||
echo " 1. Install Trivy for comprehensive security scanning"
|
||||
echo " 2. Regularly update packages to address vulnerabilities"
|
||||
echo " 3. Remove unnecessary packages to reduce attack surface"
|
||||
echo " 4. Review world-writable files/directories"
|
||||
echo " 5. Review setuid/setgid binaries"
|
||||
echo " 6. Remove sudo to prevent privilege escalation"
|
||||
echo " 7. Ensure root login is disabled"
|
||||
echo " 8. Verify non-root user configuration"
|
||||
echo " 9. Review hardcoded password references"
|
||||
echo " 10. Check for exposed secrets"
|
||||
@@ -1,112 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Test script to verify all tools are working properly in the toolbox-base image
|
||||
|
||||
IMAGE_NAME="${IMAGE_NAME_OVERRIDE:-tsysdevstack-toolboxstack-toolbox-base:release-current}"
|
||||
|
||||
echo "🧪 Testing all tools in ${IMAGE_NAME}"
|
||||
|
||||
# Function to test a command
|
||||
test_cmd() {
|
||||
local cmd="$1"
|
||||
local description="$2"
|
||||
|
||||
echo -n "Testing ${cmd} (${description})... "
|
||||
|
||||
if docker run --rm "${IMAGE_NAME}" "${cmd}" --version >/dev/null 2>&1; then
|
||||
echo "✅ PASS"
|
||||
return 0
|
||||
else
|
||||
echo "❌ FAIL"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to test a command with specific args
|
||||
test_cmd_args() {
|
||||
local cmd="$1"
|
||||
local args="$2"
|
||||
local description="$3"
|
||||
|
||||
echo -n "Testing ${cmd} ${args} (${description})... "
|
||||
|
||||
if docker run --rm "${IMAGE_NAME}" "${cmd}" ${args} >/dev/null 2>&1; then
|
||||
echo "✅ PASS"
|
||||
return 0
|
||||
else
|
||||
echo "❌ FAIL"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Counter for tracking results
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
# Test core tools
|
||||
echo "🔍 Testing core tools..."
|
||||
|
||||
test_cmd "zsh" "Z shell" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "git" "Git version control" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "curl" "cURL utility" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "jq" "JSON processor" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "fish" "Fish shell" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "fzf" "Fuzzy finder" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "bat" "Cat clone with wings" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "fd" "Simple, fast alternative to find" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "rg" "Ripgrep - line-oriented search tool" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "htop" "Interactive process viewer" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "btop" "Modern and colorful terminal monitor" && ((PASSED++)) || ((FAILED++))
|
||||
|
||||
# Test aqua installed tools
|
||||
echo "🔧 Testing aqua installed tools..."
|
||||
|
||||
test_cmd "gh" "GitHub CLI" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "lazygit" "Simple terminal UI for git commands" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "direnv" "Unclutter your .profile" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "delta" "Syntax-highlighting pager for git, diff, and grep output" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "zoxide" "Smarter cd command" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "just" "Just a command runner" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "yq" "Portable command-line YAML processor" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "xh" "Friendly and fast tool for sending HTTP requests" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "curlie" "The power of curl, the ease of use of httpie" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "chezmoi" "Manage your dotfiles across multiple machines" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "shfmt" "Shell formatter" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "shellcheck" "Shell script analysis tool" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "hadolint" "Dockerfile linter" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "uv" "Python package installer and resolver" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "watchexec" "Execute commands in response to file modifications" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "tea" "Gitea CLI" && ((PASSED++)) || ((FAILED++))
|
||||
|
||||
# Test AI CLI tools
|
||||
echo "🤖 Testing AI CLI tools..."
|
||||
|
||||
test_cmd_args "code" "--version" "just-every/code AI CLI" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd_args "qwen" "--version" "QwenLM/qwen-code AI CLI" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd_args "gemini" "--version" "google-gemini/gemini-cli AI CLI" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd_args "codex" "--version" "openai/codex AI CLI" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd_args "opencode" "--version" "sst/opencode AI CLI" && ((PASSED++)) || ((FAILED++))
|
||||
|
||||
# Test additional tools
|
||||
echo "🧰 Testing additional tools..."
|
||||
|
||||
test_cmd "starship" "Cross-shell prompt" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd "mise" "Polyglot runtime manager" && ((PASSED++)) || ((FAILED++))
|
||||
test_cmd_args "aqua" "--version" "Declarative CLI Version Manager" && ((PASSED++)) || ((FAILED++))
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "📊 Test Results:"
|
||||
echo " Passed: ${PASSED}"
|
||||
echo " Failed: ${FAILED}"
|
||||
echo " Total: $((PASSED + FAILED))"
|
||||
|
||||
if [[ "${FAILED}" -eq 0 ]]; then
|
||||
echo "🎉 All tests passed!"
|
||||
exit 0
|
||||
else
|
||||
echo "💥 ${FAILED} tests failed!"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user