proper src structure, dockerfile, entrypoint
This commit is contained in:
1 parent
e5828c7d31
commit
73c9f580a1
556 files changed
+1390
No files matched your search
+86
@@ -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"]
|
||||||
@@ -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"
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
File renamed without changes.
Vendored
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.
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.
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
Reference in new issue
Block a user