proper src structure, dockerfile, entrypoint

This commit is contained in:
jarek committed 2025-12-29 08:40:11 +01:00
1 parent e5828c7d31
commit 73c9f580a1
556 files changed
+1390

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
# Build stage - using Debian to avoid Alpine musl thread creation issues
# Alpine's musl libc causes rayon/tokio thread pool panics during svelte-adapter-bun build
FROM oven/bun:1.3.5-debian AS builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends jq git && rm -rf /var/lib/apt/lists/*
# Copy package files and install ALL dependencies (needed for build)
COPY package.json bun.lock* bunfig.toml ./
RUN bun install --frozen-lockfile
# Copy source code and build
COPY . .
# Build with parallelism - dedicated build VM has 16 CPUs and 32GB RAM
# Increased memory limits for parallel compilation with larger semi-space for GC
RUN NODE_OPTIONS="--max-old-space-size=8192 --max-semi-space-size=128" bun run build
# Production stage - minimal Alpine with Bun runtime
FROM oven/bun:1.3.5-alpine
WORKDIR /app
# Install runtime dependencies, create user
# Add sqlite for emergency scripts, git for stack git operations, curl for healthchecks
# Add docker-cli and docker-cli-compose for stack management (uses host's docker socket)
# Add openssh-client for SSH key authentication with git repositories
# Upgrade all packages to latest versions for security patches
RUN apk upgrade --no-cache \
&& apk add --no-cache curl git tini su-exec sqlite docker-cli docker-cli-compose openssh-client iproute2 \
&& addgroup -g 1001 dockhand \
&& adduser -u 1001 -G dockhand -h /home/dockhand -D dockhand
# Copy package files and install production dependencies
# This is needed because svelte-adapter-bun externalizes some packages (croner, etc.)
# that need to be available at runtime. Installing at build time is more reliable
# than Bun's auto-install which requires network access and writable cache.
COPY package.json bun.lock* ./
RUN bun install --production --frozen-lockfile
# Copy built application (Bun adapter output)
COPY --from=builder /app/build ./build
# Copy bundled subprocess scripts (built by scripts/build-subprocesses.ts)
COPY --from=builder /app/build/subprocesses/ ./subprocesses/
# Copy database migrations
COPY drizzle/ ./drizzle/
COPY drizzle-pg/ ./drizzle-pg/
# Copy legal documents
COPY LICENSE.txt PRIVACY.txt ./
# Copy entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Copy emergency scripts (only the emergency subfolder, not license generation scripts)
COPY scripts/emergency/ ./scripts/
RUN chmod +x ./scripts/*.sh 2>/dev/null || true
# Create directories with proper ownership
RUN mkdir -p /home/dockhand/.dockhand/stacks /app/data \
&& chown -R dockhand:dockhand /app /home/dockhand
EXPOSE 3000
# Runtime configuration
ENV NODE_ENV=production
ENV PORT=3000
ENV HOST=0.0.0.0
ENV DATA_DIR=/app/data
ENV HOME=/home/dockhand
# User/group IDs - customize with -e PUID=1000 -e PGID=1000
# The entrypoint will recreate the dockhand user with these IDs
ENV PUID=1001
ENV PGID=1001
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/ || exit 1
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
CMD ["bun", "run", "./build/index.js"]
+9
View File
@@ -0,0 +1,9 @@
# Bun configuration for Dockhand
[install]
# Use exact versions for reproducible builds
exact = true
[run]
# Enable source maps for better error messages
sourcemap = "external"
+16
View File
@@ -0,0 +1,16 @@
{
"$schema": "https://shadcn-svelte.com/schema.json",
"tailwind": {
"css": "src/app.css",
"baseColor": "slate"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks",
"lib": "$lib"
},
"typescript": true,
"registry": "https://shadcn-svelte.com/registry"
}
+122
View File
@@ -0,0 +1,122 @@
#!/bin/sh
set -e
# Dockhand Docker Entrypoint
# === Configuration ===
PUID=${PUID:-1001}
PGID=${PGID:-1001}
# === Detect if running as root ===
RUNNING_AS_ROOT=false
if [ "$(id -u)" = "0" ]; then
RUNNING_AS_ROOT=true
fi
# === User Setup ===
# Root mode: PUID=0 requested OR already running as root with default PUID/PGID
if [ "$PUID" = "0" ]; then
echo "Running as root user (PUID=0)"
RUN_USER="root"
elif [ "$RUNNING_AS_ROOT" = "true" ] && [ "$PUID" = "1001" ] && [ "$PGID" = "1001" ]; then
echo "Running as root user"
RUN_USER="root"
else
RUN_USER="dockhand"
# Only modify if PUID/PGID differ from image defaults (1001:1001)
if [ "$PUID" != "1001" ] || [ "$PGID" != "1001" ]; then
echo "Configuring user with PUID=$PUID PGID=$PGID"
# Remove existing dockhand user/group (only dockhand, not others)
deluser dockhand 2>/dev/null || true
delgroup dockhand 2>/dev/null || true
# Check for UID conflicts - warn but don't delete other users
if getent passwd "$PUID" >/dev/null 2>&1; then
EXISTING=$(getent passwd "$PUID" | cut -d: -f1)
echo "WARNING: UID $PUID already in use by '$EXISTING'. Using default UID 1001."
PUID=1001
fi
# Handle GID - reuse existing group or create new
if getent group "$PGID" >/dev/null 2>&1; then
TARGET_GROUP=$(getent group "$PGID" | cut -d: -f1)
else
addgroup -g "$PGID" dockhand
TARGET_GROUP="dockhand"
fi
adduser -u "$PUID" -G "$TARGET_GROUP" -h /home/dockhand -D dockhand
fi
# === Directory Ownership ===
chown -R dockhand:dockhand /app/data /home/dockhand 2>/dev/null || true
if [ -n "$DATA_DIR" ] && [ "$DATA_DIR" != "/app/data" ] && [ "$DATA_DIR" != "./data" ]; then
mkdir -p "$DATA_DIR"
chown -R dockhand:dockhand "$DATA_DIR" 2>/dev/null || true
fi
fi
# === Docker Socket Access (Optional) ===
# Check if Docker socket is mounted and accessible
# Socket path can be configured via environment-specific settings in the app
SOCKET_PATH="/var/run/docker.sock"
if [ -S "$SOCKET_PATH" ]; then
# Socket exists - check if readable
if [ "$RUN_USER" != "root" ]; then
if ! su-exec "$RUN_USER" test -r "$SOCKET_PATH" 2>/dev/null; then
SOCKET_GID=$(stat -c '%g' "$SOCKET_PATH" 2>/dev/null || echo "unknown")
echo "WARNING: Docker socket at $SOCKET_PATH is not readable by dockhand user"
echo ""
echo "To use local Docker, fix with one of these options:"
echo ""
echo " 1. Add container to docker group (GID: $SOCKET_GID):"
echo " docker run --group-add $SOCKET_GID ..."
echo ""
echo " 2. Use a socket proxy:"
echo " Configure a 'direct' environment pointing to tcp://socket-proxy:2375"
echo ""
echo " 3. Make socket world-readable (less secure):"
echo " chmod 666 /var/run/docker.sock"
echo ""
echo "Continuing startup - configure environments via the web UI..."
else
echo "Docker socket accessible at $SOCKET_PATH"
fi
else
echo "Docker socket accessible at $SOCKET_PATH"
fi
# === Detect Docker Host Hostname (for license validation) ===
# Query Docker API to get the real host hostname (not container ID)
if [ -z "$DOCKHAND_HOSTNAME" ]; then
DETECTED_HOSTNAME=$(curl -s --unix-socket "$SOCKET_PATH" http://localhost/info 2>/dev/null | sed -n 's/.*"Name":"\([^"]*\)".*/\1/p')
if [ -n "$DETECTED_HOSTNAME" ]; then
export DOCKHAND_HOSTNAME="$DETECTED_HOSTNAME"
echo "Detected Docker host hostname: $DOCKHAND_HOSTNAME"
fi
else
echo "Using configured hostname: $DOCKHAND_HOSTNAME"
fi
else
echo "No Docker socket found at $SOCKET_PATH"
echo "Configure Docker environments via the web UI (Settings > Environments)"
fi
# === Run Application ===
if [ "$RUN_USER" = "root" ]; then
# Running as root - execute directly
if [ "$1" = "" ]; then
exec bun run ./build/index.js
else
exec "$@"
fi
else
# Running as dockhand user
if [ "$1" = "" ]; then
exec su-exec dockhand bun run ./build/index.js
else
exec su-exec dockhand "$@"
fi
fi
+16
View File
@@ -0,0 +1,16 @@
import { defineConfig } from 'drizzle-kit';
const databaseUrl = process.env.DATABASE_URL;
const isPostgres = databaseUrl && (databaseUrl.startsWith('postgres://') || databaseUrl.startsWith('postgresql://'));
export default defineConfig({
// Use different schema files for SQLite vs PostgreSQL
schema: isPostgres
? './src/lib/server/db/schema/pg-schema.ts'
: './src/lib/server/db/schema/index.ts',
out: isPostgres ? './drizzle-pg' : './drizzle',
dialect: isPostgres ? 'postgresql' : 'sqlite',
dbCredentials: isPostgres
? { url: databaseUrl! }
: { url: `file:${process.env.DATA_DIR || './data'}/dockhand.db` }
});
+110
View File
@@ -0,0 +1,110 @@
{
"name": "dockhand",
"private": true,
"version": "1.0.4",
"type": "module",
"scripts": {
"dev": "bunx --bun vite dev",
"prebuild": "bunx license-checker --json --production | jq 'to_entries | map({name: (.key | split(\"@\")[0:-1] | join(\"@\")), version: (.key | split(\"@\")[-1]), license: .value.licenses, repository: .value.repository}) | sort_by(.name)' > src/lib/data/dependencies.json.tmp && mv src/lib/data/dependencies.json.tmp src/lib/data/dependencies.json || true",
"build": "bunx --bun vite build && bun scripts/patch-build.ts && bun scripts/build-subprocesses.ts",
"start": "bun ./build/index.js",
"preview": "bun ./build/index.js",
"prepare": "bunx --bun svelte-kit sync || echo ''",
"check": "bunx --bun svelte-kit sync && bunx --bun svelte-check --tsconfig ./tsconfig.json",
"check:watch": "bunx --bun svelte-kit sync && bunx --bun svelte-check --tsconfig ./tsconfig.json --watch",
"test": "bun test",
"test:smoke": "bun test tests/api-smoke.test.ts",
"test:containers": "bun test tests/container-lifecycle.test.ts",
"test:notifications": "bun test tests/notifications.test.ts",
"test:hawser": "bun test tests/hawser-connection.test.ts",
"test:build": "SKIP_BUILD_TEST=1 bun test tests/build.test.ts",
"test:postgres": "bun test tests/database-postgres.test.ts",
"test:crud": "bun test tests/crud-operations.test.ts",
"test:scheduling": "bun test tests/scheduling.test.ts",
"test:images": "bun test tests/images.test.ts",
"test:volumes": "bun test tests/volumes-networks.test.ts",
"test:stacks": "bun test tests/stacks.test.ts",
"test:stacks:matrix": "bun test tests/stack-matrix.test.ts",
"test:stacks:git": "bun test tests/stack-git-flow.test.ts",
"test:stacks:env": "bun test tests/stack-env-vars.test.ts",
"test:stacks:all": "bun test tests/stack-*.test.ts tests/stacks.test.ts",
"test:files": "bun test tests/container-files.test.ts",
"test:license": "bun test tests/license.test.ts",
"test:activity": "bun test tests/activity-dashboard.test.ts",
"test:all": "bun test tests/",
"test:quick": "bun test tests/api-smoke.test.ts tests/notifications.test.ts",
"test:integration": "bun test tests/api-smoke.test.ts tests/crud-operations.test.ts tests/scheduling.test.ts tests/hawser-connection.test.ts",
"test:e2e": "bunx playwright test tests/e2e/",
"generate:legal": "bun scripts/generate-legal-pages.ts"
},
"dependencies": {
"@codemirror/autocomplete": "6.20.0",
"@codemirror/commands": "6.10.0",
"@codemirror/lang-css": "6.3.1",
"@codemirror/lang-html": "6.4.11",
"@codemirror/lang-javascript": "6.2.4",
"@codemirror/lang-json": "6.0.2",
"@codemirror/lang-markdown": "6.5.0",
"@codemirror/lang-python": "6.2.1",
"@codemirror/lang-sql": "6.10.0",
"@codemirror/lang-xml": "6.1.0",
"@codemirror/language": "6.11.3",
"@codemirror/search": "6.5.11",
"@lezer/highlight": "1.2.3",
"@lucide/lab": "^0.1.2",
"croner": "9.1.0",
"cronstrue": "3.9.0",
"drizzle-orm": "0.45.0",
"js-yaml": "^4.1.1",
"ldapts": "^8.0.9",
"nodemailer": "^7.0.11",
"otpauth": "^9.4.1",
"postgres": "3.4.7",
"qrcode": "^1.5.4",
"svelte-dnd-action": "0.9.68",
"svelte-sonner": "1.0.7"
},
"devDependencies": {
"@codemirror/lang-yaml": "^6.1.2",
"@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.8",
"@internationalized/date": "^3.10.0",
"@layerstack/tailwind": "^1.0.1",
"@lucide/svelte": "^0.544.0",
"@playwright/test": "1.57.0",
"@sveltejs/kit": "^2.48.5",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@tailwindcss/vite": "^4.1.17",
"@types/bun": "^1.2.5",
"@types/js-yaml": "^4.0.9",
"@types/nodemailer": "^7.0.4",
"@types/qrcode": "^1.5.6",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0",
"autoprefixer": "^10.4.22",
"bits-ui": "^2.14.4",
"clsx": "^2.1.1",
"codemirror": "^6.0.2",
"cytoscape": "^3.33.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.2.0",
"drizzle-kit": "0.31.8",
"layerchart": "^1.0.12",
"lucide-svelte": "^0.555.0",
"mode-watcher": "^1.1.0",
"postcss": "^8.5.6",
"svelte": "^5.43.8",
"svelte-adapter-bun": "1.0.1",
"svelte-check": "^4.3.4",
"svelte-easy-crop": "^5.0.0",
"svelte-virtual-scroll-list": "^1.3.0",
"tailwind-merge": "^3.4.0",
"tailwind-variants": "^3.2.2",
"tailwindcss": "^4.1.17",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"vite": "^7.2.2"
}
}
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 556 files, more files were not shown because too many files have changed in this diff. Show more