mirror of
https://github.com/Grace-Solutions/Depl0y-Custom.git
synced 2026-07-26 11:28:19 +00:00
feat(docker): add split backend/frontend stacks and dynamic versioning
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
###BEGIN GENERAL###
|
||||||
|
STACK_NAME=stk-depl0y-backend-001
|
||||||
|
STACK_BINDMOUNTROOT=custom/docker/stacks
|
||||||
|
TZ=America/New_York
|
||||||
|
UID=0
|
||||||
|
GID=0
|
||||||
|
SERVICE_BIND_ADDRESS_EXTERNAL=0.0.0.0
|
||||||
|
SERVICE_BIND_ADDRESS_INTERNAL=127.0.0.1
|
||||||
|
DNSSERVER=1.1.1.1
|
||||||
|
###END GENERAL###
|
||||||
|
|
||||||
|
###BEGIN BACKEND###
|
||||||
|
BACKEND_IMAGENAME=Agit8or/Depl0y-backend
|
||||||
|
BACKEND_IMAGEVERSION=latest
|
||||||
|
BACKEND_ENABLEAUTOMATICUPDATES=true
|
||||||
|
BACKEND_PORT_EXTERNAL=8081
|
||||||
|
###END BACKEND###
|
||||||
|
|
||||||
|
###BEGIN APPLICATION###
|
||||||
|
SECRET_KEY=changeme
|
||||||
|
ENCRYPTION_KEY=changeme
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=admin
|
||||||
|
|
||||||
|
# Optional path overrides inside the container
|
||||||
|
DATABASE_URL=sqlite:////var/lib/depl0y/db/depl0y.db
|
||||||
|
ISO_STORAGE_PATH=/var/lib/depl0y/isos
|
||||||
|
UPLOAD_DIR=/var/lib/depl0y
|
||||||
|
CLOUDINIT_TEMPLATE_PATH=/var/lib/depl0y/cloud-init
|
||||||
|
SSH_KEY_PATH=/var/lib/depl0y/ssh_keys
|
||||||
|
LOG_FILE=/var/log/depl0y/app.log
|
||||||
|
###END APPLICATION###
|
||||||
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
ARG APP_VERSION=1.1.9
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
|
# System dependencies required by the backend (SSH, PDF, etc.)
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
sshpass \
|
||||||
|
openssh-client \
|
||||||
|
ca-certificates \
|
||||||
|
libpango-1.0-0 \
|
||||||
|
libpangocairo-1.0-0 \
|
||||||
|
libgdk-pixbuf2.0-0 \
|
||||||
|
libffi-dev \
|
||||||
|
shared-mime-info \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create application directories
|
||||||
|
RUN mkdir -p /opt/depl0y/backend \
|
||||||
|
/var/lib/depl0y/db \
|
||||||
|
/var/lib/depl0y/cloud-images \
|
||||||
|
/var/lib/depl0y/isos \
|
||||||
|
/var/lib/depl0y/ssh_keys \
|
||||||
|
/var/lib/depl0y/cloud-init \
|
||||||
|
/var/log/depl0y
|
||||||
|
|
||||||
|
WORKDIR /opt/depl0y/backend
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
COPY src/backend/requirements.txt /opt/depl0y/backend/requirements.txt
|
||||||
|
RUN pip install --no-cache-dir --upgrade pip \
|
||||||
|
&& pip install --no-cache-dir -r requirements.txt \
|
||||||
|
&& pip install --no-cache-dir weasyprint markdown requests
|
||||||
|
|
||||||
|
# Copy backend code
|
||||||
|
COPY src/backend /opt/depl0y/backend
|
||||||
|
|
||||||
|
# Default environment values (can be overridden at runtime)
|
||||||
|
ENV DATABASE_URL=sqlite:////var/lib/depl0y/db/depl0y.db \
|
||||||
|
ISO_STORAGE_PATH=/var/lib/depl0y/isos \
|
||||||
|
UPLOAD_DIR=/var/lib/depl0y \
|
||||||
|
CLOUDINIT_TEMPLATE_PATH=/var/lib/depl0y/cloud-init \
|
||||||
|
SSH_KEY_PATH=/var/lib/depl0y/ssh_keys \
|
||||||
|
LOG_FILE=/var/log/depl0y/app.log \
|
||||||
|
APPLICATION_PORT_INTERNAL=8080
|
||||||
|
|
||||||
|
# Entrypoint script
|
||||||
|
COPY deployment/docker/backend/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||||
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||||
|
|
||||||
|
IMAGE_NAME="${IMAGE_NAME:-Agit8or/Depl0y-backend}"
|
||||||
|
APP_VERSION="${APP_VERSION:-$(date +%Y.%m.%d.%H%M)}"
|
||||||
|
VERSION_TAG="${IMAGE_TAG:-${APP_VERSION}}"
|
||||||
|
|
||||||
|
echo "Building ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest from ${ROOT_DIR}..."
|
||||||
|
docker build \
|
||||||
|
-f "${ROOT_DIR}/deployment/docker/backend/Dockerfile" \
|
||||||
|
--build-arg APP_VERSION="${VERSION_TAG}" \
|
||||||
|
-t "${IMAGE_NAME}:${VERSION_TAG}" \
|
||||||
|
-t "${IMAGE_NAME}:latest" \
|
||||||
|
"${ROOT_DIR}"
|
||||||
|
|
||||||
|
if [ "${PUSH:-false}" = "true" ]; then
|
||||||
|
echo "Pushing ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest..."
|
||||||
|
docker push "${IMAGE_NAME}:${VERSION_TAG}"
|
||||||
|
docker push "${IMAGE_NAME}:latest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Done."
|
||||||
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
name: '${STACK_NAME:-stk-depl0y-backend-001}'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
EXTERNAL:
|
||||||
|
name: DEPL0Y-BACKEND-EXTERNAL
|
||||||
|
driver: bridge
|
||||||
|
internal: false
|
||||||
|
attachable: true
|
||||||
|
|
||||||
|
INTERNAL:
|
||||||
|
name: DEPL0Y-BACKEND-INTERNAL
|
||||||
|
driver: bridge
|
||||||
|
internal: true
|
||||||
|
attachable: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
Backend:
|
||||||
|
image: '${BACKEND_IMAGENAME:-Agit8or/Depl0y-backend}:${BACKEND_IMAGEVERSION:-latest}'
|
||||||
|
container_name: DEPL0Y-BACKEND-001
|
||||||
|
hostname: DEPL0Y-BACKEND-001
|
||||||
|
restart: unless-stopped
|
||||||
|
stop_signal: SIGTERM
|
||||||
|
stop_grace_period: 90s
|
||||||
|
user: '${UID:-0}:${GID:-0}'
|
||||||
|
logging:
|
||||||
|
driver: 'local'
|
||||||
|
#env_file: '.env'
|
||||||
|
environment:
|
||||||
|
SECRET_KEY: '${SECRET_KEY:-changeme}'
|
||||||
|
ENCRYPTION_KEY: '${ENCRYPTION_KEY:-changeme}'
|
||||||
|
LOG_LEVEL: '${LOG_LEVEL:-INFO}'
|
||||||
|
DATABASE_URL: '${DATABASE_URL:-sqlite:////var/lib/depl0y/db/depl0y.db}'
|
||||||
|
ISO_STORAGE_PATH: '${ISO_STORAGE_PATH:-/var/lib/depl0y/isos}'
|
||||||
|
UPLOAD_DIR: '${UPLOAD_DIR:-/var/lib/depl0y}'
|
||||||
|
CLOUDINIT_TEMPLATE_PATH: '${CLOUDINIT_TEMPLATE_PATH:-/var/lib/depl0y/cloud-init}'
|
||||||
|
SSH_KEY_PATH: '${SSH_KEY_PATH:-/var/lib/depl0y/ssh_keys}'
|
||||||
|
LOG_FILE: '${LOG_FILE:-/var/log/depl0y/app.log}'
|
||||||
|
APPLICATION_PORT_INTERNAL: '${APPLICATION_PORT_INTERNAL:-8080}'
|
||||||
|
ADMIN_USERNAME: '${ADMIN_USERNAME:-admin}'
|
||||||
|
ADMIN_PASSWORD: '${ADMIN_PASSWORD:-admin}'
|
||||||
|
networks:
|
||||||
|
INTERNAL:
|
||||||
|
EXTERNAL:
|
||||||
|
ports:
|
||||||
|
- "${SERVICE_BIND_ADDRESS_EXTERNAL:-0.0.0.0}:${BACKEND_PORT_EXTERNAL:-8081}:${APPLICATION_PORT_INTERNAL:-8080}"
|
||||||
|
dns:
|
||||||
|
- '${DNSSERVER:-127.0.0.53}'
|
||||||
|
volumes:
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/Data:/var/lib/depl0y:rw"
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/Logs:/var/log/depl0y:rw"
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/CloudInit:/var/lib/depl0y/cloud-init:rw"
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/SSHKeys:/var/lib/depl0y/ssh_keys:rw"
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/ISOs:/var/lib/depl0y/isos:rw"
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/CloudImages:/var/lib/depl0y/cloud-images:rw"
|
||||||
|
labels:
|
||||||
|
com.centurylinklabs.watchtower.enable: ${BACKEND_ENABLEAUTOMATICUPDATES:-true}
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
APP_PORT="${APPLICATION_PORT_INTERNAL:-8080}"
|
||||||
|
|
||||||
|
# Ensure core data directories exist (may be bind-mounted)
|
||||||
|
mkdir -p /var/lib/depl0y/db \
|
||||||
|
/var/lib/depl0y/cloud-images \
|
||||||
|
/var/lib/depl0y/isos \
|
||||||
|
/var/lib/depl0y/ssh_keys \
|
||||||
|
/var/lib/depl0y/cloud-init \
|
||||||
|
/var/log/depl0y
|
||||||
|
|
||||||
|
echo "Initializing database schema..."
|
||||||
|
python - <<'PY'
|
||||||
|
from app.core.database import init_db
|
||||||
|
|
||||||
|
init_db()
|
||||||
|
PY
|
||||||
|
echo "Database schema initialized."
|
||||||
|
|
||||||
|
echo "Initializing system settings..."
|
||||||
|
python /opt/depl0y/backend/init_system_settings.py || echo "Warning: system settings initialization failed"
|
||||||
|
echo "System settings initialization step completed."
|
||||||
|
|
||||||
|
echo "Ensuring default admin user exists..."
|
||||||
|
python /opt/depl0y/backend/init_admin_user.py || echo "Warning: admin user initialization failed"
|
||||||
|
echo "Admin user initialization step completed."
|
||||||
|
|
||||||
|
echo "Starting Depl0y backend with uvicorn on port ${APP_PORT}..."
|
||||||
|
exec uvicorn app.main:app --host 0.0.0.0 --port "${APP_PORT}"
|
||||||
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Stack identification
|
||||||
|
STACK_NAME=stk-depl0y-001
|
||||||
|
STACK_BINDMOUNTROOT=custom/docker/stacks
|
||||||
|
|
||||||
|
# Image configuration
|
||||||
|
APPLICATION_IMAGENAME=Agit8or/Depl0y
|
||||||
|
APPLICATION_IMAGEVERSION=latest
|
||||||
|
|
||||||
|
# Networking
|
||||||
|
SERVICE_BIND_ADDRESS_EXTERNAL=0.0.0.0
|
||||||
|
APPLICATION_PORT_EXTERNAL=8080
|
||||||
|
APPLICATION_PORT_INTERNAL=8080
|
||||||
|
DNSSERVER=127.0.0.53
|
||||||
|
|
||||||
|
# Runtime user (mapped to host UID/GID)
|
||||||
|
UID=1000
|
||||||
|
GID=1000
|
||||||
|
|
||||||
|
# Application secrets (change these!)
|
||||||
|
SECRET_KEY=changeme
|
||||||
|
ENCRYPTION_KEY=changeme
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=admin
|
||||||
|
|
||||||
|
# Paths inside the container (normally leave at defaults)
|
||||||
|
DATABASE_URL=sqlite:////var/lib/depl0y/db/depl0y.db
|
||||||
|
ISO_STORAGE_PATH=/var/lib/depl0y/isos
|
||||||
|
UPLOAD_DIR=/var/lib/depl0y
|
||||||
|
CLOUDINIT_TEMPLATE_PATH=/var/lib/depl0y/cloud-init
|
||||||
|
SSH_KEY_PATH=/var/lib/depl0y/ssh_keys
|
||||||
|
LOG_FILE=/var/log/depl0y/app.log
|
||||||
|
FRONTEND_DIST_PATH=/opt/depl0y/frontend/dist
|
||||||
|
|
||||||
|
# Watchtower / auto-update
|
||||||
|
APPLICATION_ENABLEAUTOMATICUPDATES=true
|
||||||
|
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
ARG APP_VERSION=1.1.9
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
|
# System dependencies required by the backend (SSH, PDF, etc.)
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
sshpass \
|
||||||
|
openssh-client \
|
||||||
|
ca-certificates \
|
||||||
|
libpango-1.0-0 \
|
||||||
|
libpangocairo-1.0-0 \
|
||||||
|
libgdk-pixbuf2.0-0 \
|
||||||
|
libffi-dev \
|
||||||
|
shared-mime-info \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create application directories
|
||||||
|
RUN mkdir -p /opt/depl0y/backend \
|
||||||
|
/opt/depl0y/frontend/dist \
|
||||||
|
/var/lib/depl0y/db \
|
||||||
|
/var/lib/depl0y/cloud-images \
|
||||||
|
/var/lib/depl0y/isos \
|
||||||
|
/var/lib/depl0y/ssh_keys \
|
||||||
|
/var/lib/depl0y/cloud-init \
|
||||||
|
/var/log/depl0y
|
||||||
|
|
||||||
|
WORKDIR /opt/depl0y/backend
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
COPY src/backend/requirements.txt /opt/depl0y/backend/requirements.txt
|
||||||
|
RUN pip install --no-cache-dir --upgrade pip \
|
||||||
|
&& pip install --no-cache-dir -r requirements.txt \
|
||||||
|
&& pip install --no-cache-dir weasyprint markdown requests
|
||||||
|
|
||||||
|
# Copy backend code and built frontend assets
|
||||||
|
COPY src/backend /opt/depl0y/backend
|
||||||
|
COPY src/frontend/dist /opt/depl0y/frontend/dist
|
||||||
|
|
||||||
|
# Default environment values (can be overridden at runtime)
|
||||||
|
ENV DATABASE_URL=sqlite:////var/lib/depl0y/db/depl0y.db \
|
||||||
|
ISO_STORAGE_PATH=/var/lib/depl0y/isos \
|
||||||
|
UPLOAD_DIR=/var/lib/depl0y \
|
||||||
|
CLOUDINIT_TEMPLATE_PATH=/var/lib/depl0y/cloud-init \
|
||||||
|
SSH_KEY_PATH=/var/lib/depl0y/ssh_keys \
|
||||||
|
LOG_FILE=/var/log/depl0y/app.log \
|
||||||
|
FRONTEND_DIST_PATH=/opt/depl0y/frontend/dist \
|
||||||
|
APPLICATION_PORT_INTERNAL=8080
|
||||||
|
|
||||||
|
# Entrypoint script
|
||||||
|
COPY deployment/docker/combined/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||||
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||||
|
|
||||||
|
IMAGE_NAME="${IMAGE_NAME:-Agit8or/Depl0y}"
|
||||||
|
APP_VERSION="${APP_VERSION:-$(date +%Y.%m.%d.%H%M)}"
|
||||||
|
VERSION_TAG="${IMAGE_TAG:-${APP_VERSION}}"
|
||||||
|
|
||||||
|
echo "Building ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest from ${ROOT_DIR}..."
|
||||||
|
docker build \
|
||||||
|
-f "${ROOT_DIR}/deployment/docker/combined/Dockerfile" \
|
||||||
|
--build-arg APP_VERSION="${VERSION_TAG}" \
|
||||||
|
-t "${IMAGE_NAME}:${VERSION_TAG}" \
|
||||||
|
-t "${IMAGE_NAME}:latest" \
|
||||||
|
"${ROOT_DIR}"
|
||||||
|
|
||||||
|
if [ "${PUSH:-false}" = "true" ]; then
|
||||||
|
echo "Pushing ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest..."
|
||||||
|
docker push "${IMAGE_NAME}:${VERSION_TAG}"
|
||||||
|
docker push "${IMAGE_NAME}:latest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Done."
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
name: '${STACK_NAME:-stk-depl0y-001}'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
EXTERNAL:
|
||||||
|
name: DEPL0Y-EXTERNAL
|
||||||
|
driver: bridge
|
||||||
|
internal: false
|
||||||
|
attachable: true
|
||||||
|
|
||||||
|
INTERNAL:
|
||||||
|
name: DEPL0Y-INTERNAL
|
||||||
|
driver: bridge
|
||||||
|
internal: true
|
||||||
|
attachable: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
Application:
|
||||||
|
image: '${APPLICATION_IMAGENAME:-Agit8or/Depl0y}:${APPLICATION_IMAGEVERSION:-latest}'
|
||||||
|
container_name: DEPL0Y-APP-001
|
||||||
|
hostname: DEPL0Y-APP-001
|
||||||
|
restart: unless-stopped
|
||||||
|
stop_signal: SIGTERM
|
||||||
|
stop_grace_period: 90s
|
||||||
|
user: '${UID:-0}:${GID:-0}'
|
||||||
|
logging:
|
||||||
|
driver: 'local'
|
||||||
|
#env_file: '.env'
|
||||||
|
environment:
|
||||||
|
SECRET_KEY: '${SECRET_KEY:-changeme}'
|
||||||
|
ENCRYPTION_KEY: '${ENCRYPTION_KEY:-changeme}'
|
||||||
|
LOG_LEVEL: '${LOG_LEVEL:-INFO}'
|
||||||
|
DATABASE_URL: '${DATABASE_URL:-sqlite:////var/lib/depl0y/db/depl0y.db}'
|
||||||
|
ISO_STORAGE_PATH: '${ISO_STORAGE_PATH:-/var/lib/depl0y/isos}'
|
||||||
|
UPLOAD_DIR: '${UPLOAD_DIR:-/var/lib/depl0y}'
|
||||||
|
CLOUDINIT_TEMPLATE_PATH: '${CLOUDINIT_TEMPLATE_PATH:-/var/lib/depl0y/cloud-init}'
|
||||||
|
SSH_KEY_PATH: '${SSH_KEY_PATH:-/var/lib/depl0y/ssh_keys}'
|
||||||
|
LOG_FILE: '${LOG_FILE:-/var/log/depl0y/app.log}'
|
||||||
|
FRONTEND_DIST_PATH: '${FRONTEND_DIST_PATH:-/opt/depl0y/frontend/dist}'
|
||||||
|
APPLICATION_PORT_INTERNAL: '${APPLICATION_PORT_INTERNAL:-8080}'
|
||||||
|
ADMIN_USERNAME: '${ADMIN_USERNAME:-admin}'
|
||||||
|
ADMIN_PASSWORD: '${ADMIN_PASSWORD:-admin}'
|
||||||
|
networks:
|
||||||
|
INTERNAL:
|
||||||
|
EXTERNAL:
|
||||||
|
ports:
|
||||||
|
- "${SERVICE_BIND_ADDRESS_EXTERNAL:-0.0.0.0}:${APPLICATION_PORT_EXTERNAL:-8080}:${APPLICATION_PORT_INTERNAL:-8080}"
|
||||||
|
dns:
|
||||||
|
- '${DNSSERVER:-127.0.0.53}'
|
||||||
|
volumes:
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/Data:/var/lib/depl0y:rw"
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/Logs:/var/log/depl0y:rw"
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/CloudInit:/var/lib/depl0y/cloud-init:rw"
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/SSHKeys:/var/lib/depl0y/ssh_keys:rw"
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/ISOs:/var/lib/depl0y/isos:rw"
|
||||||
|
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/CloudImages:/var/lib/depl0y/cloud-images:rw"
|
||||||
|
labels:
|
||||||
|
com.centurylinklabs.watchtower.enable: ${APPLICATION_ENABLEAUTOMATICUPDATES:-true}
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
APP_PORT="${APPLICATION_PORT_INTERNAL:-8080}"
|
||||||
|
|
||||||
|
# Ensure core data directories exist (may be bind-mounted)
|
||||||
|
mkdir -p /var/lib/depl0y/db \
|
||||||
|
/var/lib/depl0y/cloud-images \
|
||||||
|
/var/lib/depl0y/isos \
|
||||||
|
/var/lib/depl0y/ssh_keys \
|
||||||
|
/var/lib/depl0y/cloud-init \
|
||||||
|
/var/log/depl0y
|
||||||
|
|
||||||
|
echo "Initializing database schema..."
|
||||||
|
python - <<'PY'
|
||||||
|
from app.core.database import init_db
|
||||||
|
|
||||||
|
init_db()
|
||||||
|
PY
|
||||||
|
echo "Database schema initialized."
|
||||||
|
|
||||||
|
echo "Initializing system settings..."
|
||||||
|
python /opt/depl0y/backend/init_system_settings.py || echo "Warning: system settings initialization failed"
|
||||||
|
echo "System settings initialization step completed."
|
||||||
|
|
||||||
|
echo "Ensuring default admin user exists..."
|
||||||
|
python /opt/depl0y/backend/init_admin_user.py || echo "Warning: admin user initialization failed"
|
||||||
|
echo "Admin user initialization step completed."
|
||||||
|
|
||||||
|
echo "Starting Depl0y with uvicorn on port ${APP_PORT}..."
|
||||||
|
exec uvicorn app.main:app --host 0.0.0.0 --port "${APP_PORT}"
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
###BEGIN GENERAL###
|
||||||
|
STACK_NAME=stk-depl0y-frontend-001
|
||||||
|
STACK_BINDMOUNTROOT=custom/docker/stacks
|
||||||
|
TZ=America/New_York
|
||||||
|
UID=0
|
||||||
|
GID=0
|
||||||
|
SERVICE_BIND_ADDRESS_EXTERNAL=0.0.0.0
|
||||||
|
SERVICE_BIND_ADDRESS_INTERNAL=127.0.0.1
|
||||||
|
DNSSERVER=1.1.1.1
|
||||||
|
###END GENERAL###
|
||||||
|
|
||||||
|
###BEGIN FRONTEND###
|
||||||
|
FRONTEND_IMAGENAME=Agit8or/Depl0y-frontend
|
||||||
|
FRONTEND_IMAGEVERSION=latest
|
||||||
|
FRONTEND_ENABLEAUTOMATICUPDATES=true
|
||||||
|
FRONTEND_PORT_EXTERNAL=8080
|
||||||
|
###END FRONTEND###
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
ARG APP_VERSION=1.1.9
|
||||||
|
|
||||||
|
ENV APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
|
# Copy built frontend assets into nginx web root
|
||||||
|
COPY src/frontend/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Custom nginx config for SPA routing (serve index.html for unknown routes)
|
||||||
|
COPY deployment/docker/frontend/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||||
|
|
||||||
|
IMAGE_NAME="${IMAGE_NAME:-Agit8or/Depl0y-frontend}"
|
||||||
|
APP_VERSION="${APP_VERSION:-$(date +%Y.%m.%d.%H%M)}"
|
||||||
|
VERSION_TAG="${IMAGE_TAG:-${APP_VERSION}}"
|
||||||
|
|
||||||
|
echo "Building ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest from ${ROOT_DIR}..."
|
||||||
|
docker build \
|
||||||
|
-f "${ROOT_DIR}/deployment/docker/frontend/Dockerfile" \
|
||||||
|
--build-arg APP_VERSION="${VERSION_TAG}" \
|
||||||
|
-t "${IMAGE_NAME}:${VERSION_TAG}" \
|
||||||
|
-t "${IMAGE_NAME}:latest" \
|
||||||
|
"${ROOT_DIR}"
|
||||||
|
|
||||||
|
if [ "${PUSH:-false}" = "true" ]; then
|
||||||
|
echo "Pushing ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest..."
|
||||||
|
docker push "${IMAGE_NAME}:${VERSION_TAG}"
|
||||||
|
docker.push "${IMAGE_NAME}:latest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Done."
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: '${STACK_NAME:-stk-depl0y-frontend-001}'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
EXTERNAL:
|
||||||
|
name: DEPL0Y-FRONTEND-EXTERNAL
|
||||||
|
driver: bridge
|
||||||
|
internal: false
|
||||||
|
attachable: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
Frontend:
|
||||||
|
image: '${FRONTEND_IMAGENAME:-Agit8or/Depl0y-frontend}:${FRONTEND_IMAGEVERSION:-latest}'
|
||||||
|
container_name: DEPL0Y-FRONTEND-001
|
||||||
|
hostname: DEPL0Y-FRONTEND-001
|
||||||
|
restart: unless-stopped
|
||||||
|
stop_signal: SIGTERM
|
||||||
|
stop_grace_period: 90s
|
||||||
|
user: '${UID:-0}:${GID:-0}'
|
||||||
|
logging:
|
||||||
|
driver: 'local'
|
||||||
|
#env_file: '.env'
|
||||||
|
networks:
|
||||||
|
EXTERNAL:
|
||||||
|
ports:
|
||||||
|
- "${SERVICE_BIND_ADDRESS_EXTERNAL:-0.0.0.0}:${FRONTEND_PORT_EXTERNAL:-8080}:80"
|
||||||
|
dns:
|
||||||
|
- '${DNSSERVER:-127.0.0.53}'
|
||||||
|
volumes:
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
labels:
|
||||||
|
com.centurylinklabs.watchtower.enable: ${FRONTEND_ENABLEAUTOMATICUPDATES:-true}
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -5,7 +5,13 @@ import os
|
|||||||
|
|
||||||
|
|
||||||
def get_app_version():
|
def get_app_version():
|
||||||
"""Get application version from database or fallback to default"""
|
"""Get application version from env, database, or fallback to default."""
|
||||||
|
# 1) Prefer explicit APP_VERSION env var (e.g. from Docker image build)
|
||||||
|
env_version = os.getenv("APP_VERSION")
|
||||||
|
if env_version:
|
||||||
|
return env_version
|
||||||
|
|
||||||
|
# 2) Attempt to read from system_settings table in the database
|
||||||
try:
|
try:
|
||||||
import sqlite3
|
import sqlite3
|
||||||
db_path = os.getenv("DATABASE_URL", "sqlite:////var/lib/depl0y/db/depl0y.db")
|
db_path = os.getenv("DATABASE_URL", "sqlite:////var/lib/depl0y/db/depl0y.db")
|
||||||
@@ -21,10 +27,11 @@ def get_app_version():
|
|||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result[0]
|
return result[0]
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# Fallback to hardcoded version if database query fails
|
# Fallback to hardcoded version if database query fails
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# 3) Final fallback to baked-in default
|
||||||
return "1.1.9"
|
return "1.1.9"
|
||||||
|
|
||||||
|
|
||||||
@@ -93,6 +100,9 @@ class Settings(BaseSettings):
|
|||||||
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
|
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
|
||||||
LOG_FILE: str = os.getenv("LOG_FILE", "/var/log/depl0y/app.log")
|
LOG_FILE: str = os.getenv("LOG_FILE", "/var/log/depl0y/app.log")
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
FRONTEND_DIST_PATH: str = os.getenv("FRONTEND_DIST_PATH", "/opt/depl0y/frontend/dist")
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
case_sensitive = True
|
case_sensitive = True
|
||||||
env_file = ".env"
|
env_file = ".env"
|
||||||
|
|||||||
+32
-3
@@ -2,7 +2,8 @@
|
|||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.exceptions import RequestValidationError
|
from fastapi.exceptions import RequestValidationError
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse, FileResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.database import init_db
|
from app.core.database import init_db
|
||||||
from app.api import auth, users, proxmox, vms, isos, cloud_images, updates, dashboard, bug_report, logs, docs, setup, system_updates, ha, system
|
from app.api import auth, users, proxmox, vms, isos, cloud_images, updates, dashboard, bug_report, logs, docs, setup, system_updates, ha, system
|
||||||
@@ -39,6 +40,27 @@ app.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FRONTEND_INDEX = os.path.join(settings.FRONTEND_DIST_PATH, "index.html")
|
||||||
|
FRONTEND_ASSETS_DIR = os.path.join(settings.FRONTEND_DIST_PATH, "assets")
|
||||||
|
|
||||||
|
if os.path.isdir(settings.FRONTEND_DIST_PATH):
|
||||||
|
if os.path.isdir(FRONTEND_ASSETS_DIR):
|
||||||
|
app.mount(
|
||||||
|
"/assets",
|
||||||
|
StaticFiles(directory=FRONTEND_ASSETS_DIR),
|
||||||
|
name="assets",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
"Frontend assets directory '%s' not found; static assets will not be served.",
|
||||||
|
FRONTEND_ASSETS_DIR,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
"Frontend dist path '%s' does not exist; UI will not be served.",
|
||||||
|
settings.FRONTEND_DIST_PATH,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Add validation error handler for debugging
|
# Add validation error handler for debugging
|
||||||
@app.exception_handler(RequestValidationError)
|
@app.exception_handler(RequestValidationError)
|
||||||
@@ -65,13 +87,20 @@ async def startup_event():
|
|||||||
logger.info("Database initialized")
|
logger.info("Database initialized")
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/", include_in_schema=False, response_class=FileResponse)
|
||||||
async def root():
|
async def root():
|
||||||
"""Root endpoint"""
|
"""Serve frontend SPA index.html or fallback to JSON status."""
|
||||||
|
if os.path.exists(FRONTEND_INDEX):
|
||||||
|
return FileResponse(FRONTEND_INDEX)
|
||||||
|
logger.warning(
|
||||||
|
"Frontend index '%s' not found; returning JSON status instead.",
|
||||||
|
FRONTEND_INDEX,
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
"name": settings.APP_NAME,
|
"name": settings.APP_NAME,
|
||||||
"version": settings.APP_VERSION,
|
"version": settings.APP_VERSION,
|
||||||
"status": "running",
|
"status": "running",
|
||||||
|
"detail": "Frontend assets not found; API is running but UI is unavailable.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Initialize the default admin user in the database.
|
||||||
|
|
||||||
|
This mirrors scripts/create_admin.py but is designed to be safe
|
||||||
|
to run multiple times without resetting existing credentials.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from app.core.database import SessionLocal
|
||||||
|
from app.models import User, UserRole
|
||||||
|
from app.core.security import get_password_hash
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
admin_username = os.getenv("ADMIN_USERNAME", "admin")
|
||||||
|
admin_password = os.getenv("ADMIN_PASSWORD", "admin")
|
||||||
|
admin_email = os.getenv("ADMIN_EMAIL", f"{admin_username}@localhost")
|
||||||
|
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
admin = (
|
||||||
|
db.query(User)
|
||||||
|
.filter(User.username == admin_username)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if admin:
|
||||||
|
print(
|
||||||
|
f"\u2713 Admin user '{admin_username}' already exists; "
|
||||||
|
"leaving credentials unchanged"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
hashed_password = get_password_hash(admin_password)
|
||||||
|
admin = User(
|
||||||
|
username=admin_username,
|
||||||
|
email=admin_email,
|
||||||
|
hashed_password=hashed_password,
|
||||||
|
role=UserRole.ADMIN,
|
||||||
|
is_active=True,
|
||||||
|
totp_enabled=False,
|
||||||
|
totp_secret=None,
|
||||||
|
)
|
||||||
|
db.add(admin)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(admin)
|
||||||
|
print(f"\u2713 Created default admin user '{admin_username}'")
|
||||||
|
except Exception as exc: # pragma: no cover - init helper
|
||||||
|
db.rollback()
|
||||||
|
print(f"\u2717 Failed to initialize admin user: {exc}")
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Initialize core system settings in the database.
|
||||||
|
|
||||||
|
This mirrors the behaviour of the installer, making sure that
|
||||||
|
app_name and app_version keys exist in the system_settings table.
|
||||||
|
|
||||||
|
The script is safe to run multiple times (idempotent).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from app.core.database import SessionLocal
|
||||||
|
from app.models.database import SystemSettings
|
||||||
|
|
||||||
|
|
||||||
|
def upsert_setting(db, key: str, value: str, description: Optional[str] = None) -> None:
|
||||||
|
"""Insert or update a single setting row by key."""
|
||||||
|
setting = db.query(SystemSettings).filter(SystemSettings.key == key).first()
|
||||||
|
if setting:
|
||||||
|
setting.value = value
|
||||||
|
if description is not None:
|
||||||
|
setting.description = description
|
||||||
|
else:
|
||||||
|
setting = SystemSettings(key=key, value=value, description=description)
|
||||||
|
db.add(setting)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
app_version = os.getenv("APP_VERSION", "1.1.9")
|
||||||
|
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
upsert_setting(
|
||||||
|
db,
|
||||||
|
"app_version",
|
||||||
|
app_version,
|
||||||
|
"Current application version",
|
||||||
|
)
|
||||||
|
upsert_setting(
|
||||||
|
db,
|
||||||
|
"app_name",
|
||||||
|
"Depl0y",
|
||||||
|
"Application name",
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
print("\u2713 System settings initialized/updated")
|
||||||
|
except Exception as exc: # pragma: no cover - init helper
|
||||||
|
db.rollback()
|
||||||
|
print(f"\u2717 Failed to initialize system settings: {exc}")
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
Reference in New Issue
Block a user