Initial commit
Build and Push Multi-Arch Docker Image / build (push) Has been cancelled

This commit is contained in:
dido committed 2026-09-03 11:19:41 +03:00
commit 8ce01cb00a
34 files changed
+8346

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
{
# Disable automatic HTTPS for local development
auto_https off
}
:8080 {
encode gzip
# WebSocket SSH endpoint (keep full path)
handle /ws* {
reverse_proxy 127.0.0.1:8081
}
# HTTP API endpoints (preserve /api/* path)
handle /api/* {
reverse_proxy 127.0.0.1:8082
}
# Static files and SPA fallback
route {
root * /srv
try_files {path} /index.html
file_server
}
}
+148
View File
@@ -0,0 +1,148 @@
# Multi-stage Dockerfile for WebSSH - Optimized for small image size
# Stage 1: Builder - compiles Python packages with build dependencies
FROM python:3.11-slim AS builder
# Install build dependencies needed for compiling cryptography/pynacl on arm/v7
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
make \
python3-dev \
libffi-dev \
libssl-dev \
libsodium-dev \
curl \
ca-certificates \
binutils \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies into a virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt /tmp/
COPY src/ /tmp/src/
# Prefer prebuilt wheels when available to avoid building larger compiled artifacts
RUN pip install --no-cache-dir --prefer-binary -r /tmp/requirements.txt
# Strip compiled extensions and remove tests/pycache in venv to reduce size
RUN set -eux; \
# Strip native extensions to reduce size when possible
if command -v strip >/dev/null 2>&1; then \
find /opt/venv -type f -name '*.so' -print0 | xargs -0 -r strip --strip-unneeded || true; \
find /opt/venv -type f -executable -print0 | xargs -0 -r file | awk -F: '/ELF/{print $1}' | xargs -0 -r strip --strip-unneeded || true; \
fi; \
# Remove Python caches and compiled bytecode
find /opt/venv -type d -name '__pycache__' -print0 | xargs -0 -r rm -rf || true; \
find /opt/venv -type f -name '*.pyc' -print0 | xargs -0 -r rm -f || true; \
# Remove test folders from packages
find /opt/venv -type d \( -name 'tests' -o -name 'test' \) -print0 | xargs -0 -r rm -rf || true; \
# Remove static docs and unnecessary package metadata that are not needed at runtime
find /opt/venv -type d -name 'doc' -print0 | xargs -0 -r rm -rf || true; \
find /opt/venv -type d -name 'docs' -print0 | xargs -0 -r rm -rf || true; \
# Remove pip/setuptools/wheel from the venv to save space (not needed at runtime)
rm -rf /opt/venv/bin/pip* || true; \
find /opt/venv -path '*/site-packages/pip*' -print0 | xargs -0 -r rm -rf || true; \
find /opt/venv -path '*/site-packages/setuptools*' -print0 | xargs -0 -r rm -rf || true; \
find /opt/venv -path '*/site-packages/wheel*' -print0 | xargs -0 -r rm -rf || true; \
# Remove static metadata not required for runtime (careful: keep runtime metadata intact)
find /opt/venv -type d -name '*.dist-info' -prune -print0 | xargs -0 -r rm -rf || true || true
# Download Caddy binary for the build target (multi-arch aware)
ARG TARGETARCH
ARG TARGETVARIANT
RUN set -eux; \
DARCH="${TARGETARCH:-$(dpkg --print-architecture)}"; \
VARIANT="${TARGETVARIANT:-}"; \
case "$DARCH" in \
amd64) ARCH=amd64 ;; \
arm64) ARCH=arm64 ;; \
arm) if [ "${VARIANT}" = "v7" ]; then ARCH=armv7; else ARCH=arm; fi ;; \
*) ARCH=amd64 ;; \
esac; \
echo "Downloading Caddy for arch=${ARCH}"; \
# Try multiple candidate arch names for robustness (especially for arm variants)
candidates="${ARCH}"; \
if [ "${ARCH}" = "armv7" ]; then candidates="armv7 arm armv6"; fi; \
if [ "${ARCH}" = "arm" ]; then candidates="arm armv7 armv6"; fi; \
downloaded=0; \
for a in $candidates; do \
echo "Trying caddy download arch=$a"; \
if curl -fsSL "https://caddyserver.com/api/download?os=linux&arch=$a" -o /tmp/caddy_dl; then \
echo "Downloaded caddy for arch=$a"; downloaded=1; break; \
else \
echo "No binary for arch=$a"; fi; \
done; \
if [ "$downloaded" -ne 1 ]; then echo "Failed to download Caddy for any candidate: $candidates" >&2; exit 1; fi; \
# If it's a tar.gz archive, extract it; otherwise treat as raw binary
if tar -tzf /tmp/caddy_dl >/dev/null 2>&1; then \
tar -xzf /tmp/caddy_dl -C /tmp; \
else \
mv /tmp/caddy_dl /tmp/caddy_raw || true; \
fi; \
mkdir -p /tmp/caddy_bin_dir; \
# Preferred locations
if [ -f /tmp/caddy ]; then mv /tmp/caddy /tmp/caddy_bin_dir/caddy; fi; \
if [ -f /tmp/caddy_linux_${ARCH} ]; then mv /tmp/caddy_linux_${ARCH} /tmp/caddy_bin_dir/caddy; fi; \
if [ -f /tmp/caddy_raw ]; then mv /tmp/caddy_raw /tmp/caddy_bin_dir/caddy; fi; \
# Fallback: find any executable named 'caddy' under /tmp
if [ ! -f /tmp/caddy_bin_dir/caddy ]; then \
found=$(find /tmp -type f -name caddy -perm /111 -print -quit || true); \
if [ -n "$found" ]; then mv "$found" /tmp/caddy_bin_dir/caddy; fi; \
fi; \
if [ ! -f /tmp/caddy_bin_dir/caddy ]; then echo "Caddy binary not found after download" >&2; exit 1; fi; \
chmod +x /tmp/caddy_bin_dir/caddy; \
# Strip Caddy to reduce size
if command -v strip >/dev/null 2>&1; then strip --strip-unneeded /tmp/caddy_bin_dir/caddy || true; fi
# Stage 2: Final - minimal runtime image
FROM python:3.11-slim
# Install only runtime dependencies (no build tools)
RUN apt-get update && apt-get install -y --no-install-recommends \
gosu \
libsodium23 \
libffi8 \
&& rm -rf /var/lib/apt/lists/*
# Copy Python packages from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Reinstall runtime dependencies explicitly in the final image to avoid missing package metadata
COPY requirements.txt /tmp/requirements.txt
RUN /opt/venv/bin/python -m ensurepip --upgrade && /opt/venv/bin/python -m pip install --no-cache-dir --prefer-binary -r /tmp/requirements.txt && rm -f /tmp/requirements.txt
# Security: Run as non-root user
RUN groupadd -r strixwebssh && useradd -r -g strixwebssh strixwebssh
WORKDIR /app/src
# Copy application and static files
COPY src/ /app/src/
COPY deployment/docker-entrypoint.sh /usr/local/bin/
# Create .ssh directory for host keys
RUN mkdir -p /app/src/.ssh && chown -R strixwebssh:strixwebssh /app/src
# Copy entrypoint
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Copy the Caddy binary prepared in the builder stage
COPY --from=builder /tmp/caddy_bin_dir/caddy /usr/local/bin/caddy
RUN chmod +x /usr/local/bin/caddy || true
# Copy Caddyfile and static assets after package installation to avoid dpkg conffile prompts
COPY deployment/Caddyfile /etc/caddy/Caddyfile
COPY src/index.html /srv/
COPY src/help.html /srv/
COPY src/static/ /srv/static/
# Expose host-facing port for Caddy and internal service ports
EXPOSE 8080 8081 8082 8083
# Health check via Caddy (serving root) using Python (no curl required)
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import sys,urllib.request as u;\ntry:\n r=u.urlopen('http://127.0.0.1:8080/', timeout=5);\n code=getattr(r,'status',None) or r.getcode();\n sys.exit(0 if code==200 else 1)\nexcept Exception:\n sys.exit(1)"
# Use shell form to see entrypoint errors
ENTRYPOINT /usr/local/bin/docker-entrypoint.sh
+49
View File
@@ -0,0 +1,49 @@
services:
strixwebssh:
build:
context: ..
dockerfile: deployment/Dockerfile
container_name: strixwebssh-backend
restart: unless-stopped
# StrixWebSSH runs internal services; integrated Caddy will expose port 8080 to the host
ports:
- "8080:8080"
environment:
- TZ=Europe/Sofia
- PYTHONUNBUFFERED=1
- LOG_LEVEL=INFO
# Configure users: format is username:password,username2:password2
- STRIXWEBSSH_USERS=admin:admin
# Max upload size (supports format like "100M" for 100MB or "50M" for 50MB)
- MAX_UPLOAD_SIZE=30M
# Session Recording Configuration
- RECORDING_ENABLED=true
- RECORDING_RETENTION_DAYS=30
# Security hardening
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp:noexec,nosuid,size=100m
# Resource limits
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
volumes:
- strixwebssh_data:/data
# Optionally expose code to other services (read-only)
# - ./:/srv/strixwebssh:ro
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
volumes:
strixwebssh_data:
+47
View File
@@ -0,0 +1,47 @@
#!/bin/sh
set -x
# Check if running as root
if [ "$(id -u)" != "0" ]; then
echo "Not running as root, starting application directly..."
exec python /app/src/run.py
fi
# Create data directory with correct permissions for the StrixWebSSH user
if [ ! -d "/data" ]; then
echo "Creating /data directory..."
mkdir -p /data
fi
echo "Current /data permissions:"
ls -la /data || true
ls -la / | grep data || true
# Ensure /data is writable and owned by the StrixWebSSH user (UID 999)
echo "Setting permissions on /data..."
chmod 755 /data
chown strixwebssh:strixwebssh /data
# Check if chown worked
if [ "$(stat -c %U /data)" != "strixwebssh" ]; then
echo "WARNING: Could not change ownership of /data to strixwebssh"
echo "Current owner: $(stat -c %U /data)"
echo "This may happen with bind mounts. Ensure host directory is writable."
fi
echo "Final /data permissions:"
ls -la /data || true
# Configure nginx max upload size from environment
MAX_UPLOAD_SIZE="${MAX_UPLOAD_SIZE:-100M}"
# Start Caddy in background (serves static files and reverse-proxies to local services)
if command -v caddy >/dev/null 2>&1; then
echo "Starting Caddy..."
caddy run --config /etc/caddy/Caddyfile &
else
echo "Caddy binary not found; continuing without reverse proxy"
fi
# Start the application (run as the StrixWebSSH user)
exec gosu strixwebssh python /app/src/run.py
+88
View File
@@ -0,0 +1,88 @@
#!/bin/bash
# Build and Push Script for WebSSH
# Builds Docker image and pushes to Docker Hub
set -e
# Configuration
IMAGE_NAME="danaildr/webssh"
VERSION="0.3.1"
PLATFORMS="linux/amd64,linux/arm64,linux/arm/v7"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}🐳 Docker Hub Build & Push Script${NC}"
echo -e "${BLUE}=================================${NC}"
ensure_buildx_builder() {
if ! docker buildx inspect >/dev/null 2>&1; then
docker buildx create --use --name webssh-builder >/dev/null
fi
docker buildx inspect --bootstrap >/dev/null
}
# Function to build and push to Docker Hub
build_and_push() {
# Check if user is logged in to Docker Hub
if ! docker info | grep -q "Username"; then
echo -e "${YELLOW}⚠️ You're not logged in to Docker Hub${NC}"
echo -e "${BLUE}ℹ️ Please run: docker login${NC}"
return 1
fi
ensure_buildx_builder
echo -e "${GREEN}🏗 Building & pushing multi-arch image ${IMAGE_NAME}:${VERSION} (${PLATFORMS})...${NC}"
docker buildx build \
--platform "${PLATFORMS}" \
--tag "${IMAGE_NAME}:${VERSION}" \
--tag "${IMAGE_NAME}:latest" \
--push \
--file deployment/Dockerfile \
.
echo -e "${GREEN}✅ Docker build & push successful${NC}"
echo ""
echo -e "${YELLOW}⚠️ Don't forget to create a GitHub release!${NC}"
}
# Function to show usage
show_usage() {
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " -v, --version Show configured version tag"
echo " -b, --build Build and push to Docker Hub"
echo " -h, --help Show this help message"
echo ""
echo "Examples:"
echo " $0 --version # Show version tag"
echo " $0 --build # Build and push multi-arch image"
}
# Parse command line arguments
case "${1:-}" in
-v|--version)
echo "${VERSION}"
;;
-b|--build)
build_and_push
;;
-h|--help|"")
show_usage
;;
*)
echo -e "${RED}❌ Unknown option: $1${NC}"
show_usage
exit 1
;;
esac
+212
View File
@@ -0,0 +1,212 @@
#!/bin/bash
# Version Management Script for WebSSH
# Automatically updates version numbers in help.html and build scripts
set -e
# Configuration
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
VERSION_FILE="$ROOT_DIR/src/help.html"
BUILD_SCRIPT="$ROOT_DIR/deployment/scripts/docker-build-and-push.sh"
GITHUB_WORKFLOW="$ROOT_DIR/.github/workflows/build-and-publish.yml"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}🔧 WebSSH Version Management${NC}"
echo -e "${BLUE}=================================${NC}"
# Function to get current version
get_current_version() {
if [[ -f "$VERSION_FILE" ]]; then
grep -o 'WebSSH Multi-Connection Terminal v' "$VERSION_FILE" | sed 's/.*v\([0-9.]\+\).*/\1/'
else
echo "0.0.0"
fi
}
# Function to update version in help.html
update_help_version() {
local new_version=$1
echo -e "${GREEN}📝 Updating $VERSION_FILE to v$new_version${NC}"
# Update version in help.html
sed -i "s/WebSSH Multi-Connection Terminal v[0-9.]\+\.[0-9]\+/WebSSH Multi-Connection Terminal v$new_version/" "$VERSION_FILE"
echo -e "${GREEN}✅ $VERSION_FILE updated to v$new_version${NC}"
}
# Function to update version in build script
update_build_version() {
local new_version=$1
echo -e "${GREEN}🔨 Updating $BUILD_SCRIPT to v$new_version${NC}"
# Update version in build script
sed -i "s/VERSION=\"[0-9.]\+\.[0-9]\+\"/VERSION=\"$new_version\"/" "$BUILD_SCRIPT"
echo -e "${GREEN}✅ $BUILD_SCRIPT updated to v$new_version${NC}"
}
# Function to update version in GitHub workflow
update_github_workflow() {
local new_version=$1
echo -e "${GREEN}🐙 Updating $GITHUB_WORKFLOW to v$new_version${NC}"
# Update version in GitHub workflow
sed -i "s/WebSSH Multi-Connection Terminal v[0-9.]\+\.[0-9]\+/WebSSH Multi-Connection Terminal v$new_version/" "$GITHUB_WORKFLOW"
echo -e "${GREEN}✅ $GITHUB_WORKFLOW updated to v$new_version${NC}"
}
# Function to update version in Dockerfile (if needed)
update_dockerfile_version() {
local new_version=$1
echo -e "${GREEN}🐳 Checking if Dockerfile needs version update...${NC}"
# Check if Dockerfile has version comment
if [[ -f "Dockerfile" ]] && grep -q "# Production-ready Dockerfile for WebSSH" "Dockerfile"; then
echo -e "${YELLOW}ℹ️ Dockerfile already has version comment${NC}"
else
echo -e "${YELLOW}⚠️ Dockerfile missing version comment${NC}"
echo -e "${BLUE}ℹ️ Consider adding version comment to Dockerfile${NC}"
fi
}
# Function to validate version format
validate_version() {
local version=$1
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo -e "${RED}❌ Invalid version format: $version. Use format: X.Y.Z${NC}"
return 1
fi
return 0
}
# Main menu
show_menu() {
echo -e "${YELLOW}Current version: $(get_current_version)${NC}"
echo ""
echo -e "${BLUE}Choose an option:${NC}"
echo "1) Set new version"
echo "2) Patch version (increment last digit)"
echo "3) Minor version (increment middle digit)"
echo "4) Major version (increment first digit)"
echo "5) Show current files"
echo "6) Exit"
echo ""
read -p "Enter choice [1-6]: " choice
}
# Function to show current files
show_current_files() {
echo -e "${BLUE}📁 Current version information:${NC}"
echo -e "${GREEN} help.html: v$(get_current_version)${NC}"
echo -e "${GREEN} ${BUILD_SCRIPT}: v$(grep -o 'VERSION="[0-9.]\\+"' "${BUILD_SCRIPT}" | head -1 | sed 's/VERSION="\([0-9.]\+\)"/\1/')${NC}"
echo -e "${GREEN} ${GITHUB_WORKFLOW}: v$(grep -o 'WebSSH Multi-Connection Terminal v[0-9.]\\+' "${GITHUB_WORKFLOW}" | sed 's/.*v\([0-9.]\+\)/\1/')${NC}"
echo ""
}
# Main logic
main() {
while true; do
show_menu
case $choice in
1)
read -p "Enter new version (X.Y.Z): " new_version
if validate_version "$new_version"; then
update_help_version "$new_version"
update_build_version "$new_version"
update_github_workflow "$new_version"
update_dockerfile_version "$new_version"
echo -e "${GREEN}🎉 All files updated to v$new_version${NC}"
fi
;;
2)
current=$(get_current_version)
if [[ "$current" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
major=${BASH_REMATCH[1]}
minor=${BASH_REMATCH[2]}
patch=$((${BASH_REMATCH[3]} + 1))
new_version="${major}.${minor}.${patch}"
update_help_version "$new_version"
update_build_version "$new_version"
update_github_workflow "$new_version"
update_dockerfile_version "$new_version"
echo -e "${GREEN}🎉 Patch version updated to v$new_version${NC}"
else
echo -e "${RED}❌ Could not parse current version${NC}"
fi
;;
3)
current=$(get_current_version)
if [[ "$current" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
major=${BASH_REMATCH[1]}
minor=$((${BASH_REMATCH[2]} + 1))
patch=0
new_version="${major}.${minor}.${patch}"
update_help_version "$new_version"
update_build_version "$new_version"
update_github_workflow "$new_version"
update_dockerfile_version "$new_version"
echo -e "${GREEN}🎉 Minor version updated to v$new_version${NC}"
else
echo -e "${RED}❌ Could not parse current version${NC}"
fi
;;
4)
current=$(get_current_version)
if [[ "$current" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
major=$((${BASH_REMATCH[1]} + 1))
minor=0
patch=0
new_version="${major}.${minor}.${patch}"
update_help_version "$new_version"
update_build_version "$new_version"
update_github_workflow "$new_version"
update_dockerfile_version "$new_version"
echo -e "${GREEN}🎉 Major version updated to v$new_version${NC}"
else
echo -e "${RED}❌ Could not parse current version${NC}"
fi
;;
5)
show_current_files
;;
6)
echo -e "${GREEN}👋 Goodbye!${NC}"
exit 0
;;
*)
echo -e "${RED}❌ Invalid choice. Please try again.${NC}"
;;
esac
echo ""
read -p "Press Enter to continue..." -r
done
}
# Check if files exist
if [[ ! -f "$VERSION_FILE" ]]; then
echo -e "${RED}❌ $VERSION_FILE not found${NC}"
exit 1
fi
if [[ ! -f "$BUILD_SCRIPT" ]]; then
echo -e "${RED}❌ $BUILD_SCRIPT not found${NC}"
exit 1
fi
if [[ ! -f "$GITHUB_WORKFLOW" ]]; then
echo -e "${RED}❌ $GITHUB_WORKFLOW not found${NC}"
exit 1
fi
# Run main function
main