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,26 +5,33 @@ import os
|
||||
|
||||
|
||||
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:
|
||||
import sqlite3
|
||||
db_path = os.getenv("DATABASE_URL", "sqlite:////var/lib/depl0y/db/depl0y.db")
|
||||
# Extract path from sqlite URL
|
||||
if db_path.startswith("sqlite:///"):
|
||||
db_path = db_path.replace("sqlite:///", "")
|
||||
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT value FROM system_settings WHERE key = 'app_version'")
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
|
||||
if result:
|
||||
return result[0]
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
# Fallback to hardcoded version if database query fails
|
||||
pass
|
||||
|
||||
|
||||
# 3) Final fallback to baked-in default
|
||||
return "1.1.9"
|
||||
|
||||
|
||||
@@ -93,6 +100,9 @@ class Settings(BaseSettings):
|
||||
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
|
||||
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:
|
||||
case_sensitive = True
|
||||
env_file = ".env"
|
||||
|
||||
+32
-3
@@ -2,7 +2,8 @@
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
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.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
|
||||
@@ -39,6 +40,27 @@ app.add_middleware(
|
||||
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
|
||||
@app.exception_handler(RequestValidationError)
|
||||
@@ -65,13 +87,20 @@ async def startup_event():
|
||||
logger.info("Database initialized")
|
||||
|
||||
|
||||
@app.get("/")
|
||||
@app.get("/", include_in_schema=False, response_class=FileResponse)
|
||||
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 {
|
||||
"name": settings.APP_NAME,
|
||||
"version": settings.APP_VERSION,
|
||||
"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