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
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"name": "TSYSDevStack toolbox-DocStack",
|
||||
"dockerComposeFile": [
|
||||
"../docker-compose.yml"
|
||||
],
|
||||
"service": "toolbox-DocStack",
|
||||
"workspaceFolder": "/workspace",
|
||||
"remoteUser": "toolbox",
|
||||
"runServices": [
|
||||
"toolbox-DocStack"
|
||||
],
|
||||
"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,120 +0,0 @@
|
||||
# 🧰 Toolbox Template Audit Checklist
|
||||
|
||||
This checklist ensures the toolbox-template provides a solid foundation for creating new toolboxes that extend from toolbox-base.
|
||||
|
||||
## 🏗️ Structure Audit
|
||||
|
||||
- [ ] Template Dockerfile properly extends from toolbox-base:release-current
|
||||
- [ ] Template Dockerfile follows best practices for extension
|
||||
- [ ] Template docker-compose.yml properly inherits from base configuration
|
||||
- [ ] Template build.sh script properly wraps docker build with UID/GID mapping
|
||||
- [ ] Template run.sh script properly manages container lifecycle
|
||||
- [ ] Template devcontainer.json properly references base configuration
|
||||
- [ ] Template SEED file properly defines extension objectives
|
||||
- [ ] Template PROMPT file properly guides contributors
|
||||
- [ ] Template README.md properly documents usage and customization
|
||||
- [ ] Template aqua.yaml properly extends from base tooling
|
||||
|
||||
## 🔧 Consistency Audit
|
||||
|
||||
- [ ] Template inherits all base security practices
|
||||
- [ ] Template follows same build process patterns as base
|
||||
- [ ] Template uses same user model as base (non-root with UID/GID mapping)
|
||||
- [ ] Template workspace mounting consistent with base
|
||||
- [ ] Template runtime behavior consistent with base
|
||||
- [ ] Template error handling consistent with base
|
||||
- [ ] Template documentation style consistent with base
|
||||
- [ ] Template testing approach consistent with base
|
||||
- [ ] Template customization points clearly defined
|
||||
- [ ] Template extension patterns well-documented
|
||||
|
||||
## 🛡️ Security Audit
|
||||
|
||||
- [ ] Template maintains all base security guarantees
|
||||
- [ ] Template doesn't introduce security vulnerabilities
|
||||
- [ ] Template doesn't weaken base security model
|
||||
- [ ] Template properly validates user inputs
|
||||
- [ ] Template properly handles file permissions
|
||||
- [ ] Template doesn't expose additional attack surfaces
|
||||
- [ ] Template properly manages secrets/configuration
|
||||
- [ ] Template follows principle of least privilege
|
||||
- [ ] Template properly isolates user processes
|
||||
- [ ] Template maintains non-root execution model
|
||||
|
||||
## 🧪 Testing Audit
|
||||
|
||||
- [ ] Template includes testing framework
|
||||
- [ ] Template tests verify proper extension from base
|
||||
- [ ] Template tests validate added functionality
|
||||
- [ ] Template tests check for regression issues
|
||||
- [ ] Template tests cover error conditions
|
||||
- [ ] Template tests verify security properties
|
||||
- [ ] Template tests run automatically during build
|
||||
- [ ] Template tests provide clear failure diagnostics
|
||||
- [ ] Template tests cover all customization points
|
||||
- [ ] Template tests align with base testing philosophy
|
||||
|
||||
## 📚 Documentation Audit
|
||||
|
||||
- [ ] Template README.md clearly explains purpose and usage
|
||||
- [ ] Template README.md properly documents customization options
|
||||
- [ ] Template README.md links to base documentation
|
||||
- [ ] Template README.md includes quick start guide
|
||||
- [ ] Template README.md covers troubleshooting
|
||||
- [ ] Template README.md explains extension patterns
|
||||
- [ ] Template README.md documents versioning strategy
|
||||
- [ ] Template README.md covers maintenance procedures
|
||||
- [ ] Template README.md explains collaboration guidelines
|
||||
- [ ] Template README.md maintains consistent style with base
|
||||
|
||||
## 🔄 Maintenance Audit
|
||||
|
||||
- [ ] Template properly tracks base image updates
|
||||
- [ ] Template provides clear upgrade paths
|
||||
- [ ] Template maintains backward compatibility
|
||||
- [ ] Template follows same release cadence as base
|
||||
- [ ] Template properly handles dependency updates
|
||||
- [ ] Template includes update automation where appropriate
|
||||
- [ ] Template documents breaking changes
|
||||
- [ ] Template provides migration guides when needed
|
||||
- [ ] Template follows same versioning scheme as base
|
||||
- [ ] Template maintains consistent issue tracking
|
||||
|
||||
## 🎯 Usability Audit
|
||||
|
||||
- [ ] Template is easy to copy and customize
|
||||
- [ ] Template provides clear extension points
|
||||
- [ ] Template includes helpful examples
|
||||
- [ ] Template reduces boilerplate code
|
||||
- [ ] Template provides sensible defaults
|
||||
- [ ] Template includes proper error messages
|
||||
- [ ] Template supports common customization patterns
|
||||
- [ ] Template includes helpful documentation
|
||||
- [ ] Template follows intuitive naming conventions
|
||||
- [ ] Template minimizes configuration complexity
|
||||
|
||||
## 🌐 Compatibility Audit
|
||||
|
||||
- [ ] Template works with all supported platforms
|
||||
- [ ] Template maintains cross-platform consistency
|
||||
- [ ] Template integrates well with base tooling
|
||||
- [ ] Template supports common development workflows
|
||||
- [ ] Template handles various project structures
|
||||
- [ ] Template works with popular IDEs/editors
|
||||
- [ ] Template supports CI/CD integration
|
||||
- [ ] Template compatible with common deployment methods
|
||||
- [ ] Template supports popular version control systems
|
||||
- [ ] Template integrates with common development tools
|
||||
|
||||
## 🧹 Cleanliness Audit
|
||||
|
||||
- [ ] Template includes no unnecessary files
|
||||
- [ ] Template follows consistent file organization
|
||||
- [ ] Template includes proper .gitignore
|
||||
- [ ] Template avoids duplicating base functionality
|
||||
- [ ] Template includes proper licensing information
|
||||
- [ ] Template maintains clean directory structure
|
||||
- [ ] Template includes appropriate comments/documentation
|
||||
- [ ] Template avoids hardcoded values where possible
|
||||
- [ ] Template follows consistent naming conventions
|
||||
- [ ] Template includes proper attribution where needed
|
||||
@@ -1,76 +0,0 @@
|
||||
# Extend from the toolbox-base image
|
||||
# NOTE: Always use the full image name to ensure compatibility in standalone builds
|
||||
FROM tsysdevstack-toolboxstack-toolbox-base:dev
|
||||
|
||||
# Set build arguments (these can be overridden at build time)
|
||||
ARG USER_ID=1000
|
||||
ARG GROUP_ID=1000
|
||||
ARG USERNAME=toolbox
|
||||
|
||||
# Ensure the non-root user exists with the correct UID/GID
|
||||
RUN if getent passwd "${USER_ID}" >/dev/null; then \
|
||||
existing_user="$(getent passwd "${USER_ID}" | cut -d: -f1)"; \
|
||||
userdel --remove "${existing_user}" 2>/dev/null || true; \
|
||||
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}"
|
||||
|
||||
# Switch to root user to install packages
|
||||
USER root
|
||||
|
||||
# Install documentation-specific packages here
|
||||
# Adding pandoc, plantuml, graphviz, and other documentation tools
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
pandoc \
|
||||
plantuml \
|
||||
default-jre \
|
||||
graphviz \
|
||||
texlive-xetex \
|
||||
texlive-fonts-recommended \
|
||||
texlive-latex-extra \
|
||||
librsvg2-bin \
|
||||
npm \
|
||||
nodejs \
|
||||
python3 \
|
||||
python3-pip \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install additional documentation tools
|
||||
# Adding Quarto, mdBook, Marp, Typst, and Markwhen
|
||||
RUN npm install -g @quarto/quarto@1.4.549 \
|
||||
&& npm install -g @marp-team/marp-cli@3.4.0 \
|
||||
&& npm install -g @markwhen/mw@0.4.0 \
|
||||
&& npm install -g joplin-cli@latest
|
||||
|
||||
# Install mdBook
|
||||
RUN curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.41/mdbook-v0.4.41-x86_64-unknown-linux-gnu.tar.gz | tar -xz -C /usr/local/bin
|
||||
|
||||
# Install Typst
|
||||
RUN curl -sSL https://github.com/typst/typst/releases/download/v0.12.0/typst-x86_64-unknown-linux-musl.tar.gz | tar -xz -C /usr/local/bin
|
||||
|
||||
# Add toolbox-specific aqua packages to the existing configuration
|
||||
COPY aqua.yaml /tmp/aqua.additions
|
||||
RUN su - "${USERNAME}" -c ' \
|
||||
cat /tmp/aqua.additions | grep -v "^version\|^registries" >> ~/.config/aquaproj-aqua/aqua.yaml && \
|
||||
AQUA_GLOBAL_CONFIG=/home/${USERNAME}/.config/aquaproj-aqua/aqua.yaml aqua install \
|
||||
'
|
||||
|
||||
# Install toolbox-specific npm packages here
|
||||
# Example:
|
||||
# RUN mise exec -- npm install -g @scope/package@version
|
||||
|
||||
# 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
|
||||
|
||||
# Switch back to the non-root user
|
||||
USER ${USERNAME}
|
||||
WORKDIR /workspace
|
||||
|
||||
# Default command
|
||||
CMD ["/usr/bin/zsh"]
|
||||
|
||||
# Ensure container runs as the toolbox user
|
||||
USER toolbox
|
||||
@@ -1,27 +0,0 @@
|
||||
You are Codex, collaborating with a human on the TSYSDevStack ToolboxStack project.
|
||||
|
||||
- Seed context:
|
||||
- `SEED` captures the initial scope. Edit it once to define goals, then treat it as read-only unless the high-level objectives change.
|
||||
- Start each session by reading it (`cat SEED`) and summarize progress or adjustments here in PROMPT.
|
||||
|
||||
Context snapshot (toolbox-DocStack):
|
||||
- Working directory: artifacts/ToolboxStack/toolbox-DocStack
|
||||
- Image: tsysdevstack-toolboxstack-toolbox-DocStack (extends from tsysdevstack-toolboxstack-toolbox-base:release-current)
|
||||
- Container user: toolbox (non-root, UID/GID mapped to host)
|
||||
- Mounted workspace: current repo at /workspace (rw)
|
||||
|
||||
Current state:
|
||||
- Extends from the standard toolbox-base image, inheriting all base tooling (shells, CLIs, package managers).
|
||||
- aqua packages are baked into the base image during the build process for consistency, reproducibility and performance.
|
||||
- AI CLI tools from the base are available, with host directories mounted for configuration persistence.
|
||||
- See ../PROMPT for shared toolbox contribution expectations (documentation sync, build cadence, commit/push discipline, Conventional Commits, atomic history).
|
||||
|
||||
Collaboration checklist:
|
||||
1. Translate SEED goals into concrete tooling decisions; mirror outcomes in README.md and this PROMPT (do not rewrite SEED unless the scope resets).
|
||||
2. Prefer aqua-managed CLIs and mise-managed runtimes for reproducibility.
|
||||
3. After each tooling change, update README/PROMPT, run ./build.sh, commit (Conventional Commit message, focused diff), and push only once the build succeeds per ../PROMPT.
|
||||
4. Record verification steps (build/test commands) as they are performed.
|
||||
5. Maintain UID/GID mapping and non-root execution.
|
||||
|
||||
Active focus:
|
||||
- Initialize toolbox-DocStack using the toolbox-template scaffolding; evolve the Dockerfile/tooling inventory to satisfy the SEED goals.
|
||||
@@ -1,107 +0,0 @@
|
||||
# 🧰 TSYSDevStack Toolbox Template
|
||||
|
||||
Template for creating new toolboxes that extend from the `toolbox-base` image.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
1. **Create a new toolbox**
|
||||
```bash
|
||||
cp -r /path/to/toolbox-template /path/to/new-toolbox
|
||||
cd /path/to/new-toolbox
|
||||
```
|
||||
|
||||
2. **Customize the toolbox**
|
||||
- Edit `Dockerfile` to add toolbox-specific tooling
|
||||
- Modify `docker-compose.yml` to adjust service configuration
|
||||
- Update `SEED` to define the toolbox's purpose and goals
|
||||
|
||||
3. **Build the toolbox**
|
||||
```bash
|
||||
./build.sh
|
||||
```
|
||||
|
||||
4. **Start the toolbox**
|
||||
```bash
|
||||
./run.sh up
|
||||
```
|
||||
|
||||
5. **Access the toolbox**
|
||||
```bash
|
||||
docker exec -it tsysdevstack-toolboxstack-toolbox-DocStack zsh
|
||||
```
|
||||
|
||||
6. **Stop the toolbox**
|
||||
```bash
|
||||
./run.sh down
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧱 Architecture
|
||||
|
||||
- **Base Image**: Extends from `tsysdevstack-toolboxstack-toolbox-base:release-current`
|
||||
- **User**: Runs as non-root `toolbox` user (UID/GID mapped to host)
|
||||
- **Workspace**: Mounts current directory to `/workspace` (read/write)
|
||||
- **Runtime**: Inherits all tooling from base plus toolbox-specific additions
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Customization
|
||||
|
||||
### Dockerfile
|
||||
Extend the base image with toolbox-specific tooling:
|
||||
```dockerfile
|
||||
# Extend from the toolbox-base image
|
||||
FROM tsysdevstack-toolboxstack-toolbox-base:release-current
|
||||
|
||||
# Add toolbox-specific packages or configurations
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
specific-package \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
### docker-compose.yml
|
||||
Adjust service configuration for toolbox-specific needs:
|
||||
```yaml
|
||||
services:
|
||||
my-toolbox:
|
||||
# Inherits all base configuration
|
||||
# Add toolbox-specific volumes, ports, etc.
|
||||
volumes:
|
||||
- ./custom-config:/home/toolbox/.config/custom-tool
|
||||
```
|
||||
|
||||
### SEED
|
||||
Define the toolbox's purpose and goals:
|
||||
```markdown
|
||||
- Describe what this toolbox should provide (languages, CLIs, workflows)
|
||||
- List required base image modifications or additional mounts
|
||||
- Note verification or testing expectations specific to this toolbox
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📂 Project Layout
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `Dockerfile` | Extends base image with toolbox-specific tooling |
|
||||
| `docker-compose.yml` | Service configuration for the toolbox |
|
||||
| `build.sh` | Wrapper around `docker build` with host UID/GID mapping |
|
||||
| `run.sh` | Helper to bring the service up/down |
|
||||
| `.devcontainer/devcontainer.json` | VS Code remote container definition |
|
||||
| `SEED` | Defines the toolbox's purpose and goals |
|
||||
| `PROMPT` | LLM onboarding prompt for future contributors |
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Collaboration Notes
|
||||
|
||||
- Inherits all collaboration policies from `toolbox-base`
|
||||
- Document toolbox-specific additions in `README.md` and `PROMPT`
|
||||
- Update `SEED` only when the high-level objectives change
|
||||
- Prefer aqua/mise for new tooling to keep installations reproducible
|
||||
- Keep documentation synchronized for future contributors
|
||||
@@ -1,46 +0,0 @@
|
||||
# 📚 DocStack SEED
|
||||
|
||||
## 🎯 Purpose
|
||||
Specialized documentation generation toolbox for creating beautiful PDFs including resumes, proposals, Joplin note exports, and timelines.
|
||||
|
||||
## 🧰 Tooling Goals
|
||||
1. **Core Documentation Tools**
|
||||
- Pandoc for document conversion
|
||||
- PlantUML for UML diagrams
|
||||
- Graphviz for diagram rendering
|
||||
- Markwhen for timeline creation
|
||||
|
||||
2. **PDF Generation Frameworks**
|
||||
- Quarto for scientific and technical documents
|
||||
- mdBook for book-style documentation
|
||||
- Marp for presentation slides
|
||||
- Typst for modern typesetting
|
||||
|
||||
3. **AI-Assisted Documentation**
|
||||
- Joplin CLI for note management and export
|
||||
- Code generation tools for AI-assisted writing
|
||||
|
||||
4. **Verification & Quality**
|
||||
- Document validation and linting
|
||||
- Automated testing of document generation workflows
|
||||
|
||||
## 🏗️ Implementation Plan
|
||||
1. Extend from the toolbox-base image
|
||||
2. Install documentation-specific packages via apt
|
||||
3. Add documentation tools via aqua where available
|
||||
4. Install Node.js-based tools via npm/mise
|
||||
5. Ensure all tools work together in a cohesive workflow
|
||||
6. Provide comprehensive testing and verification
|
||||
|
||||
## 🧪 Testing Expectations
|
||||
- Verify all documentation tools can generate output
|
||||
- Test document conversion workflows (Markdown → PDF, etc.)
|
||||
- Validate diagram generation (PlantUML, Graphviz)
|
||||
- Confirm timeline creation with Markwhen
|
||||
- Ensure PDF generation with all frameworks (Quarto, mdBook, Marp, Typst)
|
||||
|
||||
## 📚 Documentation Requirements
|
||||
- Update README with all installed tools and usage examples
|
||||
- Document common workflows for each tool category
|
||||
- Provide troubleshooting guidance for PDF generation issues
|
||||
- Include examples for resumes, proposals, and note exports
|
||||
@@ -1,9 +0,0 @@
|
||||
version: 1.0.0
|
||||
registries:
|
||||
- type: standard
|
||||
ref: v4.431.0
|
||||
packages:
|
||||
# Documentation tools
|
||||
- name: charmbracelet/glow@v1.5.1 # Terminal markdown reader
|
||||
- name: charmbracelet/gum@v0.14.0 # Stylish shell dialogs
|
||||
- name: charmbracelet/mods@v1.2.1 # AI model interaction
|
||||
@@ -1,195 +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
|
||||
|
||||
# Get the toolbox name from the directory name (or you can pass it as an argument)
|
||||
TOOLBOX_NAME="${TOOLBOX_NAME_OVERRIDE:-$(basename "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")}"
|
||||
sanitized_input "$TOOLBOX_NAME"
|
||||
IMAGE_NAME="tsysdevstack-toolboxstack-$(echo "${TOOLBOX_NAME#toolbox-}" | tr '[:upper:]' '[:lower:]')"
|
||||
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-toolboxstack-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}"
|
||||
# Use the default docker driver instead of docker-container to access local images
|
||||
if ! docker buildx create --driver docker --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}" \
|
||||
--allow network.host \
|
||||
"${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 post-build verification
|
||||
echo "Running post-build verification..."
|
||||
if ! docker run --rm "${IMAGE_NAME}:${TAG}" zsh -c 'echo "Container starts successfully"'; then
|
||||
echo "Error: Failed to start container with basic test." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify critical tools are available
|
||||
echo "Verifying critical tools..."
|
||||
CRITICAL_TOOLS=("zsh" "git" "curl" "jq" "fish" "fzf" "bat" "fd" "rg" "htop" "btop")
|
||||
for tool in "${CRITICAL_TOOLS[@]}"; do
|
||||
if ! docker run --rm "${IMAGE_NAME}:${TAG}" which "$tool" >/dev/null 2>&1; then
|
||||
echo "Error: Critical tool '$tool' not found in PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Verify aqua tools are available
|
||||
echo "Verifying aqua tools..."
|
||||
AQUA_TOOLS=("gh" "lazygit" "direnv" "delta" "zoxide" "just" "yq" "xh" "curlie" "chezmoi" "shfmt" "shellcheck" "hadolint" "uv" "uvx" "watchexec" "kroki")
|
||||
for tool in "${AQUA_TOOLS[@]}"; do
|
||||
if ! docker run --rm "${IMAGE_NAME}:${TAG}" which "$tool" >/dev/null 2>&1; then
|
||||
echo "Error: Aqua tool '$tool' not found in PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Verify AI CLI tools are available
|
||||
echo "Verifying AI CLI tools..."
|
||||
AI_TOOLS=("code" "qwen" "gemini" "codex" "opencode")
|
||||
for tool in "${AI_TOOLS[@]}"; do
|
||||
if ! docker run --rm "${IMAGE_NAME}:${TAG}" which "$tool" >/dev/null 2>&1; then
|
||||
echo "Error: AI CLI tool '$tool' not found in PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Verify testing tools are available
|
||||
echo "Verifying testing tools..."
|
||||
TESTING_TOOLS=("bats" "shellcheck" "shfmt" "hadolint")
|
||||
for tool in "${TESTING_TOOLS[@]}"; do
|
||||
if ! docker run --rm "${IMAGE_NAME}:${TAG}" which "$tool" >/dev/null 2>&1; then
|
||||
echo "Error: Testing tool '$tool' not found in PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All verifications passed."
|
||||
|
||||
# Push if requested
|
||||
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."
|
||||
@@ -1,35 +0,0 @@
|
||||
services:
|
||||
toolbox-DocStack:
|
||||
container_name: tsysdevstack-toolboxstack-toolbox-DocStack
|
||||
image: tsysdevstack-toolboxstack-toolbox-DocStack
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
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}/.config/gemini:/home/toolbox/.config/gemini:rw
|
||||
- ${HOME}/.config/qwen:/home/toolbox/.config/qwen:rw
|
||||
- ${HOME}/.config/code:/home/toolbox/.config/code:rw
|
||||
- ${HOME}/.config/opencode:/home/toolbox/.config/opencode:rw
|
||||
- ${HOME}/.cache/openai:/home/toolbox/.cache/openai:rw
|
||||
- ${HOME}/.cache/gemini:/home/toolbox/.cache/gemini:rw
|
||||
- ${HOME}/.cache/qwen:/home/toolbox/.cache/qwen:rw
|
||||
- ${HOME}/.cache/code:/home/toolbox/.cache/code:rw
|
||||
- ${HOME}/.cache/opencode:/home/toolbox/.cache/opencode:rw
|
||||
# Additional AI tool directories
|
||||
- ${HOME}/.config/codex:/home/toolbox/.config/codex:rw
|
||||
- ${HOME}/.cache/codex:/home/toolbox/.cache/codex:rw
|
||||
@@ -1,111 +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 rebuilds the toolbox image, tags it as:
|
||||
- tsysdevstack-toolboxstack-<name>:dev
|
||||
- tsysdevstack-toolboxstack-<name>:release-current
|
||||
- tsysdevstack-toolboxstack-<name>:v<semver>
|
||||
|
||||
When run without --dry-run it pushes all three tags.
|
||||
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
|
||||
|
||||
# Get the toolbox name from the directory name (or you can pass it as an argument)
|
||||
TOOLBOX_NAME="${TOOLBOX_NAME_OVERRIDE:-$(basename "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")}"
|
||||
IMAGE_NAME="tsysdevstack-toolboxstack-${TOOLBOX_NAME#toolbox-}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
USER_ID="${USER_ID_OVERRIDE:-$(id -u)}"
|
||||
GROUP_ID="${GROUP_ID_OVERRIDE:-$(id -g)}"
|
||||
USERNAME="${USERNAME_OVERRIDE:-toolbox}"
|
||||
TEA_VERSION="${TEA_VERSION_OVERRIDE:-0.11.1}"
|
||||
BUILDER_NAME="${BUILDER_NAME:-tsysdevstack-toolboxstack-builder}"
|
||||
CACHE_DIR="${SCRIPT_DIR}/.build-cache"
|
||||
TAG="${TAG_OVERRIDE:-dev}"
|
||||
RELEASE_TAG="${RELEASE_TAG_OVERRIDE:-release-current}"
|
||||
VERSION_TAG="${VERSION_TAG_OVERRIDE:-}"
|
||||
if [[ -n "$VERSION_TAG" ]]; then
|
||||
VERSION_TAG="$SEMVER"
|
||||
fi
|
||||
PUSH="${PUSH_OVERRIDE:-false}"
|
||||
|
||||
echo "Preparing release for ${SEMVER}"
|
||||
echo " dry-run: ${DRY_RUN}"
|
||||
echo " allow-dirty: ${ALLOW_DIRTY}"
|
||||
|
||||
if [[ "${DRY_RUN}" == "true" ]]; then
|
||||
echo "[dry-run] Would build ${IMAGE_NAME}:${TAG}"
|
||||
TAG_OVERRIDE="${TAG}" PUSH_OVERRIDE=false "${SCRIPT_DIR}/build.sh"
|
||||
echo "[dry-run] Skipped pushing tags."
|
||||
else
|
||||
echo "Building ${IMAGE_NAME}:${TAG}"
|
||||
TAG_OVERRIDE="${TAG}" PUSH_OVERRIDE=true RELEASE_TAG_OVERRIDE="${RELEASE_TAG}" VERSION_TAG_OVERRIDE="${SEMVER}" "${SCRIPT_DIR}/build.sh"
|
||||
echo "Release ${SEMVER} pushed as:"
|
||||
echo " - ${IMAGE_NAME}:dev"
|
||||
echo " - ${IMAGE_NAME}:release-current"
|
||||
echo " - ${IMAGE_NAME}:${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"
|
||||
|
||||
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-DocStack}"
|
||||
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"
|
||||
mkdir -p "${HOME}/.config/codex" "${HOME}/.cache/codex"
|
||||
|
||||
# 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-DocStack 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,155 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Security audit script for the toolbox-template
|
||||
|
||||
IMAGE_NAME="${IMAGE_NAME_OVERRIDE:-tsysdevstack-toolboxstack-toolbox-DocStack}"
|
||||
|
||||
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
|
||||
|
||||
# Check that this template properly extends from the base image
|
||||
echo "🔗 Checking inheritance from base image..."
|
||||
BASE_INHERITANCE=$(docker history "${IMAGE_NAME}" 2>/dev/null | grep "FROM tsysdevstack-toolboxstack-toolbox-base:release-current" | wc -l)
|
||||
if [[ "${BASE_INHERITANCE}" -gt 0 ]]; then
|
||||
echo "✅ Template properly extends from toolbox-base:release-current"
|
||||
else
|
||||
echo "⚠️ Template might not properly extend from toolbox-base:release-current"
|
||||
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"
|
||||
echo " 11. Ensure proper inheritance from base image"
|
||||
@@ -1,112 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Test script to verify all tools are working properly in the toolbox-template
|
||||
|
||||
IMAGE_NAME="${IMAGE_NAME_OVERRIDE:-tsysdevstack-toolboxstack-toolbox-DocStack}"
|
||||
|
||||
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 inherited from base
|
||||
echo "🔍 Testing core tools inherited from base..."
|
||||
|
||||
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 inherited from base
|
||||
echo "🔧 Testing aqua installed tools inherited from base..."
|
||||
|
||||
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 inherited from base
|
||||
echo "🤖 Testing AI CLI tools inherited from base..."
|
||||
|
||||
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 inherited from base
|
||||
echo "🧰 Testing additional tools inherited from base..."
|
||||
|
||||
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
|
||||
@@ -1,2 +0,0 @@
|
||||
# This file keeps the directory in git even when it's empty.
|
||||
# Actual implementation files will be added soon.
|
||||
@@ -1,2 +0,0 @@
|
||||
# This file keeps the directory in git even when it's empty.
|
||||
# Actual implementation files will be added soon.
|
||||
@@ -1,2 +0,0 @@
|
||||
# This file keeps the directory in git even when it's empty.
|
||||
# Actual implementation files will be added soon.
|
||||
@@ -1,2 +0,0 @@
|
||||
# This file keeps the directory in git even when it's empty.
|
||||
# Actual implementation files will be added soon.
|
||||
@@ -1,68 +0,0 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
# Prevent interactive prompts during package installation
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install dependencies needed for tools
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
unzip \
|
||||
gnupg \
|
||||
lsb-release \
|
||||
software-properties-common \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create a non-root user for running tools
|
||||
RUN groupadd -r qadocker && useradd -r -g qadocker -m -s /bin/bash qadocker
|
||||
|
||||
# Install Hadolint
|
||||
RUN wget -q -O /usr/local/bin/hadolint \
|
||||
https://github.com/hadolint/hadolint/releases/latest/download/hadolint-$(uname -s)-$(uname -m) && \
|
||||
chmod +x /usr/local/bin/hadolint
|
||||
|
||||
# Install Dive
|
||||
RUN wget -q -O /tmp/dive_0.10.0_linux_amd64.deb \
|
||||
https://github.com/wagoodman/dive/releases/download/v0.10.0/dive_0.10.0_linux_amd64.deb && \
|
||||
apt-get update && apt-get install -y --no-install-recommends /tmp/dive_0.10.0_linux_amd64.deb && \
|
||||
rm /tmp/dive_0.10.0_linux_amd64.deb && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install ShellCheck
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends shellcheck && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Trivy (vulnerability scanner)
|
||||
RUN wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor -o /usr/share/keyrings/trivy.gpg && \
|
||||
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | tee -a /etc/apt/sources.list.d/trivy.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y trivy && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Dockle (container linter)
|
||||
RUN wget -q -O dockle_0.4.5_linux_amd64.deb \
|
||||
https://github.com/goodwithtech/dockle/releases/download/v0.4.5/dockle_0.4.5_Linux-64bit.deb && \
|
||||
apt-get update && apt-get install -y --no-install-recommends ./dockle_0.4.5_linux_amd64.deb && \
|
||||
rm dockle_0.4.5_linux_amd64.deb && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Docker client
|
||||
RUN curl -fsSL https://get.docker.com -o get-docker.sh && \
|
||||
sh get-docker.sh && \
|
||||
rm get-docker.sh
|
||||
|
||||
# Install Node.js (may be needed for some tools)
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts | bash - && \
|
||||
apt-get install -y --no-install-recommends nodejs && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Ensure non-root user has proper permissions for Docker socket if needed
|
||||
# This should be handled at runtime via volume mounting
|
||||
|
||||
# Switch to non-root user
|
||||
USER qadocker
|
||||
WORKDIR /home/qadocker
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
@@ -1,172 +0,0 @@
|
||||
# toolbox-qadocker
|
||||
|
||||
This is a specialized toolbox container for performing audit and quality assurance work on Docker images and related files in the TSYSDevStack project. It includes essential tools for Dockerfile linting, shell script validation, container auditing, and security scanning.
|
||||
|
||||
## 🛠️ Included Tools
|
||||
|
||||
- **[Hadolint](https://github.com/hadolint/hadolint)**: A Dockerfile linter that checks for best practices
|
||||
- **[Dive](https://github.com/wagoodman/dive)**: Tool to explore layers in Docker images
|
||||
- **[ShellCheck](https://www.shellcheck.net/)**: Static analysis tool for shell scripts
|
||||
- **[Trivy](https://github.com/aquasecurity/trivy)**: Comprehensive vulnerability scanner for containers
|
||||
- **[Dockle](https://github.com/goodwithtech/dockle)**: Container image linter for security best practices
|
||||
- **Docker Client**: Command-line interface for Docker
|
||||
- **Node.js**: JavaScript runtime for additional tooling
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Build the Image
|
||||
|
||||
```bash
|
||||
# Build with default 'dev' tag
|
||||
./build.sh
|
||||
|
||||
# Build with a specific tag
|
||||
./build.sh --tag mytag
|
||||
```
|
||||
|
||||
### Run the Container
|
||||
|
||||
```bash
|
||||
# Run interactively with current directory mounted
|
||||
./run.sh
|
||||
|
||||
# Run with Docker socket access (to use Docker from inside container)
|
||||
./run.sh --with-docker
|
||||
|
||||
# Run with a specific tag
|
||||
./run.sh --tag mytag
|
||||
```
|
||||
|
||||
### Release (Push to Registry)
|
||||
|
||||
```bash
|
||||
# Build and push with version tag (requires clean git tree)
|
||||
./release.sh --version v0.1.0
|
||||
|
||||
# Build and push with version tag (allowing dirty git tree)
|
||||
./release.sh --version v0.1.0 --allow-dirty
|
||||
|
||||
# Dry run to test the process without actually pushing
|
||||
./release.sh --version v0.1.0 --dry-run
|
||||
```
|
||||
|
||||
## 🔍 Using QA Tools
|
||||
|
||||
### Hadolint - Dockerfile Linting
|
||||
|
||||
```bash
|
||||
# Lint a Dockerfile
|
||||
docker run --rm -i hadolint/hadolint < Dockerfile
|
||||
|
||||
# Or when using the toolbox container with current directory mounted:
|
||||
hadolint Dockerfile
|
||||
```
|
||||
|
||||
### ShellCheck - Shell Script Analysis
|
||||
|
||||
```bash
|
||||
# Analyze a shell script
|
||||
shellcheck myscript.sh
|
||||
|
||||
# Or when using the toolbox container:
|
||||
shellcheck /workspace/myscript.sh
|
||||
```
|
||||
|
||||
### Dive - Analyze Docker Image Layers
|
||||
|
||||
```bash
|
||||
# Analyze an image
|
||||
dive myimage:tag
|
||||
```
|
||||
|
||||
### Trivy - Vulnerability Scanning
|
||||
|
||||
```bash
|
||||
# Scan a container image for vulnerabilities
|
||||
trivy image myimage:tag
|
||||
|
||||
# Scan the current directory for vulnerabilities
|
||||
trivy fs .
|
||||
```
|
||||
|
||||
### Dockle - Container Image Linting
|
||||
|
||||
```bash
|
||||
# Lint a container image
|
||||
dockle myimage:tag
|
||||
|
||||
# Or run on current directory
|
||||
dockle .
|
||||
```
|
||||
|
||||
## 🏗️ Project Context
|
||||
|
||||
The toolbox-qadocker is designed to be a minimal, fast-to-rebuild image specifically for auditing and validating Docker images and related files in the TSYSDevStack ecosystem. Unlike other toolboxes, it does not inherit from `toolbox-base` and instead uses a clean Ubuntu base with only the essential QA tools installed.
|
||||
|
||||
## 🎯 Purpose
|
||||
|
||||
- Audit Dockerfiles for best practices and security issues
|
||||
- Validate shell scripts with ShellCheck
|
||||
- Analyze Docker image layers with Dive
|
||||
- Scan for vulnerabilities with Trivy
|
||||
- Check image security with Dockle
|
||||
- Support AI CLI agents in container image creation
|
||||
|
||||
## 📁 Directory Structure
|
||||
|
||||
- `Dockerfile`: Defines the container image
|
||||
- `build.sh`: Builds the container image
|
||||
- `run.sh`: Runs the container with appropriate settings
|
||||
- `release.sh`: Builds and pushes container images with multiple tags
|
||||
- `test.sh`: Test script to verify all tools are working
|
||||
|
||||
## 🛡️ Security
|
||||
|
||||
- Runs as a non-root user (`qadocker`) by default
|
||||
- Contains only essential tools needed for QA work
|
||||
- Designed to be minimal and fast to rebuild if security issues are discovered
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
To verify that all tools are working correctly in the container:
|
||||
|
||||
```bash
|
||||
# Run the test script to verify all tools
|
||||
./test.sh
|
||||
|
||||
# Or run the test script inside the container
|
||||
docker run --rm -v $(pwd)/test.sh:/test.sh tsysdevstack-toolboxstack-toolbox-qadocker:dev bash /test.sh
|
||||
```
|
||||
|
||||
## 🔍 Dockerfile QA
|
||||
|
||||
You can use this toolbox to check Dockerfiles for best practices using Hadolint:
|
||||
|
||||
```bash
|
||||
# Run Hadolint on a Dockerfile
|
||||
docker run --rm -v $(pwd):/workspace -w /workspace tsysdevstack-toolboxstack-toolbox-qadocker:dev hadolint Dockerfile
|
||||
|
||||
# Run ShellCheck on shell scripts
|
||||
docker run --rm -v $(pwd):/workspace -w /workspace tsysdevstack-toolboxstack-toolbox-qadocker:dev shellcheck your_script.sh
|
||||
|
||||
# Run Trivy for vulnerability scanning
|
||||
docker run --rm -v $(pwd):/workspace -w /workspace tsysdevstack-toolboxstack-toolbox-qadocker:dev trivy fs --offline-scan .
|
||||
|
||||
# Run Dockle for container image linter
|
||||
docker run --rm -v $(pwd):/workspace -w /workspace tsysdevstack-toolboxstack-toolbox-qadocker:dev dockle .
|
||||
```
|
||||
|
||||
## 📝 Dockerfile Compliance
|
||||
|
||||
The Dockerfile is designed to meet Docker best practices and security standards. It has been optimized to:
|
||||
|
||||
- Use minimal base image (Ubuntu 24.04)
|
||||
- Install packages with `--no-install-recommends` to reduce bloat
|
||||
- Download packages with quiet mode to reduce output
|
||||
- Create a non-root user for running tools
|
||||
- Follow security best practices for container images
|
||||
- Comply with Hadolint and Dockle recommendations where possible
|
||||
|
||||
## 📝 License
|
||||
|
||||
This project is part of the TSYSDevStack project. See the main LICENSE file in the repository root for details.
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Build script for toolbox-qadocker
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Print colored output
|
||||
print_status() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Default values
|
||||
IMAGE_NAME="tsysdevstack-toolboxstack-toolbox-qadocker"
|
||||
TAG="dev"
|
||||
DOCKERFILE_PATH="Dockerfile"
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--tag)
|
||||
TAG="$2"
|
||||
shift 2
|
||||
;;
|
||||
--file)
|
||||
DOCKERFILE_PATH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help)
|
||||
echo "Usage: $0 [--tag TAG] [--file DOCKERFILE_PATH]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --tag TAG Specify the tag for the image (default: dev)"
|
||||
echo " --file DOCKERFILE_PATH Specify the path to the Dockerfile (default: Dockerfile)"
|
||||
echo " --help Show this help message"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
IMAGE_TAGGED_NAME="${IMAGE_NAME}:${TAG}"
|
||||
|
||||
print_status "Building ${IMAGE_TAGGED_NAME}"
|
||||
|
||||
# Build the Docker image
|
||||
docker build -t "${IMAGE_TAGGED_NAME}" -f "${DOCKERFILE_PATH}" .
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
print_status "Successfully built ${IMAGE_TAGGED_NAME}"
|
||||
else
|
||||
print_error "Failed to build ${IMAGE_TAGGED_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,124 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Release script for toolbox-qadocker
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Print colored output
|
||||
print_status() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Default values
|
||||
IMAGE_NAME="tsysdevstack-toolboxstack-toolbox-qadocker"
|
||||
DEV_TAG="dev"
|
||||
RELEASE_CURRENT_TAG="release-current"
|
||||
DOCKERFILE_PATH="Dockerfile"
|
||||
ALLOW_DIRTY=false
|
||||
DRY_RUN=false
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--version)
|
||||
VERSION_TAG="$2"
|
||||
shift 2
|
||||
;;
|
||||
--file)
|
||||
DOCKERFILE_PATH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--allow-dirty)
|
||||
ALLOW_DIRTY=true
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
echo "Usage: $0 --version VERSION [--file DOCKERFILE_PATH] [--allow-dirty] [--dry-run]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --version VERSION Specify the version tag for the release (required)"
|
||||
echo " --file DOCKERFILE_PATH Specify the path to the Dockerfile (default: Dockerfile)"
|
||||
echo " --allow-dirty Allow release from a dirty git tree"
|
||||
echo " --dry-run Perform a dry run without actually building or pushing"
|
||||
echo " --help Show this help message"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$VERSION_TAG" ]]; then
|
||||
print_error "Version tag is required. Use --version to specify it."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if git tree is clean (unless --allow-dirty is specified)
|
||||
if [[ "$ALLOW_DIRTY" == false ]]; then
|
||||
if [[ -z $(git status --porcelain) ]]; then
|
||||
print_status "Git tree is clean"
|
||||
else
|
||||
print_error "Git tree is not clean. Commit your changes or use --allow-dirty to override."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Determine the build command based on DRY_RUN flag
|
||||
BUILD_CMD="docker build"
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
BUILD_CMD="echo [DRY RUN] Would run: docker build"
|
||||
fi
|
||||
|
||||
# Build the Docker image with all tags
|
||||
print_status "Building ${IMAGE_NAME} with tags: ${DEV_TAG}, ${RELEASE_CURRENT_TAG}, ${VERSION_TAG}"
|
||||
|
||||
$BUILD_CMD -t "${IMAGE_NAME}:${DEV_TAG}" -t "${IMAGE_NAME}:${RELEASE_CURRENT_TAG}" -t "${IMAGE_NAME}:${VERSION_TAG}" -f "${DOCKERFILE_PATH}" .
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
print_error "Failed to build the image(s)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
print_status "Successfully built images with tags: ${DEV_TAG}, ${RELEASE_CURRENT_TAG}, ${VERSION_TAG}"
|
||||
else
|
||||
print_status "Dry run completed - would have built images with tags: ${DEV_TAG}, ${RELEASE_CURRENT_TAG}, ${VERSION_TAG}"
|
||||
fi
|
||||
|
||||
# Push the images unless in dry run mode
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
print_status "Pushing ${IMAGE_NAME}:${DEV_TAG}"
|
||||
docker push "${IMAGE_NAME}:${DEV_TAG}"
|
||||
|
||||
print_status "Pushing ${IMAGE_NAME}:${RELEASE_CURRENT_TAG}"
|
||||
docker push "${IMAGE_NAME}:${RELEASE_CURRENT_TAG}"
|
||||
|
||||
print_status "Pushing ${IMAGE_NAME}:${VERSION_TAG}"
|
||||
docker push "${IMAGE_NAME}:${VERSION_TAG}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
print_status "Successfully pushed all images"
|
||||
else
|
||||
print_error "Failed to push images"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Run script for toolbox-qadocker
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Print colored output
|
||||
print_status() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Default values
|
||||
IMAGE_NAME="tsysdevstack-toolboxstack-toolbox-qadocker"
|
||||
TAG="dev"
|
||||
CONTAINER_NAME="tsysdevstack-toolboxstack-toolbox-qadocker-run"
|
||||
INTERACTIVE=true
|
||||
TTY=true
|
||||
MOUNT_CURRENT_DIR=true
|
||||
DOCKER_SOCKET=false
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--tag)
|
||||
TAG="$2"
|
||||
shift 2
|
||||
;;
|
||||
--name)
|
||||
CONTAINER_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-tty)
|
||||
TTY=false
|
||||
shift
|
||||
;;
|
||||
--no-interactive)
|
||||
INTERACTIVE=false
|
||||
shift
|
||||
;;
|
||||
--no-mount)
|
||||
MOUNT_CURRENT_DIR=false
|
||||
shift
|
||||
;;
|
||||
--with-docker)
|
||||
DOCKER_SOCKET=true
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
echo "Usage: $0 [--tag TAG] [--name NAME] [--no-tty] [--no-interactive] [--no-mount] [--with-docker]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --tag TAG Specify the tag for the image to run (default: dev)"
|
||||
echo " --name NAME Specify the container name (default: tsysdevstack-toolboxstack-toolbox-qadocker-run)"
|
||||
echo " --no-tty Disable TTY allocation"
|
||||
echo " --no-interactive Disable interactive mode"
|
||||
echo " --no-mount Don't mount current directory to /workspace"
|
||||
echo " --with-docker Mount Docker socket to use Docker from inside container"
|
||||
echo " --help Show this help message"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
IMAGE_TAGGED_NAME="${IMAGE_NAME}:${TAG}"
|
||||
|
||||
# Check if the image exists
|
||||
if ! docker images --format "{{.Repository}}:{{.Tag}}" | grep -q "^${IMAGE_NAME}:${TAG}$"; then
|
||||
print_error "Image ${IMAGE_TAGGED_NAME} does not exist. Please build it first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build docker run command
|
||||
RUN_CMD="docker run"
|
||||
|
||||
if [[ "$INTERACTIVE" == true ]]; then
|
||||
RUN_CMD="${RUN_CMD} -i"
|
||||
fi
|
||||
|
||||
if [[ "$TTY" == true ]]; then
|
||||
RUN_CMD="${RUN_CMD} -t"
|
||||
fi
|
||||
|
||||
# Mount current directory to /workspace
|
||||
if [[ "$MOUNT_CURRENT_DIR" == true ]]; then
|
||||
RUN_CMD="${RUN_CMD} -v $(pwd):/workspace -w /workspace"
|
||||
fi
|
||||
|
||||
# Mount Docker socket if requested
|
||||
if [[ "$DOCKER_SOCKET" == true ]]; then
|
||||
RUN_CMD="${RUN_CMD} -v /var/run/docker.sock:/var/run/docker.sock"
|
||||
fi
|
||||
|
||||
RUN_CMD="${RUN_CMD} --name ${CONTAINER_NAME}"
|
||||
|
||||
# Add the image name
|
||||
RUN_CMD="${RUN_CMD} ${IMAGE_TAGGED_NAME}"
|
||||
|
||||
print_status "Running: ${RUN_CMD}"
|
||||
|
||||
# Execute the command
|
||||
eval $RUN_CMD
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Test script for toolbox-qadocker
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Print colored output
|
||||
print_status() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
print_status "Testing all installed QA tools in toolbox-QADocker:"
|
||||
|
||||
print_status "1. Testing Hadolint (Dockerfile linter)..."
|
||||
echo "FROM ubuntu:24.04
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y curl" > /tmp/test.Dockerfile
|
||||
hadolint /tmp/test.Dockerfile || echo "Hadolint found issues (expected in test file)"
|
||||
|
||||
print_status "2. Testing ShellCheck (shell script linter)..."
|
||||
echo '#!/bin/bash
|
||||
var=hello
|
||||
echo $var' > /tmp/test.sh
|
||||
chmod +x /tmp/test.sh
|
||||
shellcheck /tmp/test.sh || echo "ShellCheck found issues (expected in test file)"
|
||||
|
||||
print_status "3. Testing Trivy (vulnerability scanner)..."
|
||||
trivy --version
|
||||
|
||||
print_status "4. Testing Dockle (container linter)..."
|
||||
dockle --version
|
||||
|
||||
print_status "5. Testing Docker client..."
|
||||
docker --version
|
||||
|
||||
print_status "6. Testing Node.js..."
|
||||
node --version
|
||||
|
||||
print_status "All tools are properly installed and functional!"
|
||||
@@ -1,2 +0,0 @@
|
||||
# This file keeps the directory in git even when it's empty.
|
||||
# Actual implementation files will be added soon.
|
||||
Reference in New Issue
Block a user