You've already forked hotpocket
BTHLABS-63: Production deployment workflow
This commit is contained in:
@@ -45,11 +45,12 @@ COPY --from=deployment-build /srv/packages /srv/packages
|
||||
COPY --from=deployment-build /srv/venv /srv/venv
|
||||
COPY --chown=$APP_USER_UID:$APP_USER_GID backend/ops/bin/*.sh /srv/bin/
|
||||
COPY --chown=$APP_USER_UID:$APP_USER_GID backend/ops/deployment/gunicorn.conf.py backend/ops/deployment/gunicorn.logging.conf /srv/lib/
|
||||
COPY --chown=root:root backend/ops/etc/sudoers_app /etc/sudoers.d/app
|
||||
|
||||
USER root
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y libpq5 dumb-init && \
|
||||
apt-get install -y libpq5 dumb-init sudo && \
|
||||
apt-get clean autoclean && \
|
||||
apt-get autoremove --yes && \
|
||||
rm -rf /var/lib/apt /var/lib/dpkg && \
|
||||
@@ -67,6 +68,8 @@ ARG APP_USER_GID
|
||||
ARG IMAGE_ID
|
||||
|
||||
ENV DJANGO_SETTINGS_MODULE=hotpocket_backend.settings.deployment.webapp
|
||||
ENV HOTPOCKET_BACKEND_APP_USER_UID=${APP_USER_UID}
|
||||
ENV HOTPOCKET_BACKEND_APP_USER_GID=${APP_USER_GID}
|
||||
ENV HOTPOCKET_BACKEND_ENV=deployment
|
||||
ENV HOTPOCKET_BACKEND_APP=webapp
|
||||
|
||||
@@ -79,6 +82,8 @@ ARG APP_USER_GID
|
||||
ARG IMAGE_ID
|
||||
|
||||
ENV DJANGO_SETTINGS_MODULE=hotpocket_backend.settings.aio
|
||||
ENV HOTPOCKET_BACKEND_APP_USER_UID=${APP_USER_UID}
|
||||
ENV HOTPOCKET_BACKEND_APP_USER_GID=${APP_USER_GID}
|
||||
ENV HOTPOCKET_BACKEND_ENV=aio
|
||||
ENV HOTPOCKET_BACKEND_APP=webapp
|
||||
ENV HOTPOCKET_BACKEND_DEBUG=false
|
||||
@@ -92,6 +97,7 @@ ENV HOTPOCKET_BACKEND_CELERY_IGNORE_RESULT=true
|
||||
ENV HOTPOCKET_BACKEND_CELERY_ALWAYS_EAGER=true
|
||||
ENV HOTPOCKET_BACKEND_GUNICORN_WORKERS=2
|
||||
ENV HOTPOCKET_BACKEND_RUN_MIGRATIONS=true
|
||||
ENV HOTPOCKET_BACKEND_CREATE_INITIAL_ACCOUNT=true
|
||||
ENV HOTPOCKET_BACKEND_UPLOADS_PATH=/srv/run/uploads
|
||||
|
||||
VOLUME ["/srv/run"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
version = '25.10.21'
|
||||
version = '25.11.06.b0'
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.core.management import BaseCommand
|
||||
import django.db
|
||||
|
||||
from hotpocket_backend.apps.accounts.models import Account
|
||||
from hotpocket_backend.apps.core.conf import settings
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -17,12 +18,12 @@ class Command(BaseCommand):
|
||||
|
||||
def add_arguments(self, parser: ArgumentParser):
|
||||
parser.add_argument(
|
||||
'username',
|
||||
help='Username for the Account',
|
||||
'-u', '--username', default=None,
|
||||
help='Override username for the Account',
|
||||
)
|
||||
parser.add_argument(
|
||||
'password',
|
||||
help='Password for the Account',
|
||||
'-p', '--password', default=None,
|
||||
help='Override Password for the Account',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-d', '--dry-run', action='store_true', default=False,
|
||||
@@ -31,10 +32,22 @@ class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
LOGGER.debug('args=`%s` options=`%s`', args, options)
|
||||
username = options.get('username')
|
||||
password = options.get('password')
|
||||
dry_run = options.get('dry_run', False)
|
||||
|
||||
username = options.get('username') or settings.SECRETS.INITIAL_ACCOUNT.username
|
||||
if not username:
|
||||
LOGGER.info('Not creating initial Account: empty `username`')
|
||||
return
|
||||
|
||||
password = options.get('password') or settings.SECRETS.INITIAL_ACCOUNT.password
|
||||
assert password, 'Unable to proceed: empty `password`'
|
||||
|
||||
LOGGER.debug(
|
||||
'Creating initial Account: username=`%s` password=`%s`',
|
||||
username,
|
||||
password,
|
||||
)
|
||||
|
||||
with django.db.transaction.atomic():
|
||||
current_account = Account.objects.filter(username=username).first()
|
||||
if current_account is not None:
|
||||
|
||||
@@ -3,7 +3,13 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from keep_it_secret import AbstractField, LiteralField, Secrets, SecretsField
|
||||
from keep_it_secret import (
|
||||
AbstractField,
|
||||
EnvField,
|
||||
LiteralField,
|
||||
Secrets,
|
||||
SecretsField,
|
||||
)
|
||||
|
||||
|
||||
class DatabaseSecrets(Secrets):
|
||||
@@ -84,6 +90,19 @@ class CelerySecrets(Secrets):
|
||||
result_backend: str = AbstractField.new()
|
||||
|
||||
|
||||
class InitialAccountSecrets(Secrets):
|
||||
username: str = EnvField.new(
|
||||
'HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME',
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
password: str = EnvField.new(
|
||||
'HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD',
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
|
||||
|
||||
class BaseSecrets(Secrets):
|
||||
SECRET_KEY: str = AbstractField.new()
|
||||
|
||||
@@ -91,3 +110,4 @@ class BaseSecrets(Secrets):
|
||||
CELERY: CelerySecrets = SecretsField.new(CelerySecrets)
|
||||
|
||||
OIDC: OIDCSecrets = SecretsField.new(OIDCSecrets)
|
||||
INITIAL_ACCOUNT: InitialAccountSecrets = SecretsField.new(InitialAccountSecrets)
|
||||
|
||||
@@ -17,6 +17,9 @@ MIDDLEWARE = [
|
||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||
]
|
||||
|
||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||
SESSION_COOKIE_SECURE = False
|
||||
|
||||
STORAGES['staticfiles'] = { # noqa: F405
|
||||
'BACKEND': 'whitenoise.storage.CompressedManifestStaticFilesStorage',
|
||||
}
|
||||
|
||||
@@ -115,6 +115,8 @@ STATIC_URL = 'static/'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
LOGGING_LEVEL = os.environ.get('HOTPOCKET_BACKEND_LOGGING_LEVEL', 'INFO')
|
||||
|
||||
LOG_FORMAT = '%(asctime)s %(levelname)s [%(name)s] [%(request_id)s] (%(funcName)s:%(lineno)s) %(message)s'
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
@@ -149,17 +151,17 @@ LOGGING = {
|
||||
'loggers': {
|
||||
'hotpocket_backend': {
|
||||
'handlers': ['hotpocket'],
|
||||
'level': 'INFO',
|
||||
'level': LOGGING_LEVEL,
|
||||
'propagate': False,
|
||||
},
|
||||
'hotpocket_common': {
|
||||
'handlers': ['hotpocket'],
|
||||
'level': 'INFO',
|
||||
'level': LOGGING_LEVEL,
|
||||
'propagate': False,
|
||||
},
|
||||
'hotpocket_soa': {
|
||||
'handlers': ['hotpocket'],
|
||||
'level': 'INFO',
|
||||
'level': LOGGING_LEVEL,
|
||||
'propagate': False,
|
||||
},
|
||||
'django': {
|
||||
|
||||
@@ -13,26 +13,21 @@ cat <<EOF
|
||||
|_|
|
||||
production
|
||||
|
||||
HotPocket v25.10.21 [${HOTPOCKET_BACKEND_IMAGE_ID}] (https://hotpocket.app/)
|
||||
HotPocket v25.11.06.b0 [${HOTPOCKET_BACKEND_IMAGE_ID}] (https://hotpocket.app/)
|
||||
Copyright 2025-present by BTHLabs. All rights reserved. (https://bthlabs.pl/)
|
||||
Licensed under Apache-2.0
|
||||
EOF
|
||||
|
||||
export PYTHONPATH="/srv/app:$PYTHONPATH"
|
||||
|
||||
if [ -n "${HOTPOCKET_BACKEND_RUN_MIGRATIONS}" ];then
|
||||
echo; echo "--- Running migrations..."
|
||||
${VIRTUAL_ENV}/bin/python /srv/app/manage.py migrate
|
||||
echo; echo "--- Preparing the system..."
|
||||
|
||||
UPLOADS_PATH="${HOTPOCKET_BACKEND_UPLOADS_PATH:-/srv/uploads}"
|
||||
if [ ! -d "${UPLOADS_PATH}" ];then
|
||||
mkdir -p "${UPLOADS_PATH}"
|
||||
fi
|
||||
|
||||
if [[ -n "${HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME}" && -n "${HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD}" ]]; then
|
||||
echo; echo "--- Creating initial Account..."
|
||||
${VIRTUAL_ENV}/bin/python /srv/app/manage.py create_initial_account "${HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME}" "${HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD}"
|
||||
fi
|
||||
|
||||
if [ "${HOTPOCKET_BACKEND_ENV}" = "aio" ];then
|
||||
mkdir -p "${HOTPOCKET_BACKEND_UPLOADS_PATH:-/srv/run/uploads}"
|
||||
fi
|
||||
sudo /srv/bin/fix-run-uploads-permissions.sh ${HOTPOCKET_BACKEND_APP_USER_UID} "${UPLOADS_PATH}"
|
||||
|
||||
echo; echo "--- Running entrypoint.d parts..."
|
||||
find "/srv/etc/entrypoint.d/" -follow -type f -print | sort -V | while read -r ENTRYPOINT_PART; do
|
||||
@@ -48,6 +43,16 @@ find "/srv/etc/entrypoint.d/" -follow -type f -print | sort -V | while read -r E
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -n "${HOTPOCKET_BACKEND_RUN_MIGRATIONS}" ];then
|
||||
echo; echo "--- Running migrations..."
|
||||
${VIRTUAL_ENV}/bin/python /srv/app/manage.py migrate
|
||||
fi
|
||||
|
||||
if [ -n "${HOTPOCKET_BACKEND_CREATE_INITIAL_ACCOUNT}" ];then
|
||||
echo; echo "--- Creating initial Account..."
|
||||
${VIRTUAL_ENV}/bin/python /srv/app/manage.py create_initial_account
|
||||
fi
|
||||
|
||||
echo; echo "--- Setup done, booting the app..."; echo
|
||||
|
||||
exec /usr/bin/dumb-init "$@"
|
||||
|
||||
9
services/backend/ops/bin/fix-run-uploads-permissions.sh
Executable file
9
services/backend/ops/bin/fix-run-uploads-permissions.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Fixing runtime directory permissions..."
|
||||
|
||||
chown $1 /srv/run || true
|
||||
chmod 775 /srv/run
|
||||
|
||||
chown $1 $2 || true
|
||||
chmod 775 $2
|
||||
1
services/backend/ops/etc/sudoers_app
Normal file
1
services/backend/ops/etc/sudoers_app
Normal file
@@ -0,0 +1 @@
|
||||
app ALL=(root) NOPASSWD: /srv/bin/fix-run-uploads-permissions.sh
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hotpocket-backend",
|
||||
"version": "25.10.21",
|
||||
"version": "25.11.06.b0",
|
||||
"description": "HotPocket Backend",
|
||||
"main": "hotpocket_backend/apps/frontend/src/index.js",
|
||||
"repository": "https://git.bthlabs.pl/tomekwojcik/hotpocket",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "hotpocket-backend"
|
||||
version = "25.10.21"
|
||||
version = "25.11.06.b0"
|
||||
description = "HotPocket Backend"
|
||||
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
||||
license = "Apache-2.0"
|
||||
|
||||
Reference in New Issue
Block a user