Compare commits

..
6 Commits
Author SHA1 Message Date
finnandClaude Opus 5.5 340006ff37 Disable debug default login
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:39:28 +00:00
finnandClaude Opus 5.5 86fcdfde27 Devcontainer: enable bash-completion and set UTF-8 locale
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:39:28 +00:00
finnandClaude Opus 5.5 e0b5e05cce Use port 8757 for the dev server
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:39:28 +00:00
finnandClaude Opus 5.5 9f25457b24 Remove old docker-compose based dev setup
The devcontainer replaces Dockerfile.dev, dc.dev.yml and the related
start/entrypoint scripts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:39:28 +00:00
finnandClaude Opus 5.5 9895017385 Restructure scripts into dev/ and prod/ for the devcontainer
- scripts/prod: entrypoint, db-setup and schema.sql shipped in the image
- scripts/dev: seed/clear db, psql and cleanup, run natively in the
  devcontainer instead of via docker-compose
- move setup.cfg to the repo root so linters pick it up
- Makefile targets run directly in the devcontainer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:39:28 +00:00
finnandClaude Opus 5.5 7c2b9fb3f4 Refactor logging and drop deprecated FLASK_ENV
Move logging setup into max/log.py. The file handler is only used outside
debug mode and its directory is configurable via LOG_DIR. FLASK_ENV is
deprecated in Flask; debug mode is now controlled via FLASK_DEBUG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:21:07 +00:00
23 changed files with 82 additions and 137 deletions

No files matched your search

+8 -4
View File
@@ -1,4 +1,4 @@
# Keep the Python version in sync with Dockerfile.dev # Keep the Python version in sync with the production Dockerfile
FROM python:3.15-slim-trixie FROM python:3.15-slim-trixie
ARG USERNAME=dev ARG USERNAME=dev
@@ -7,17 +7,21 @@ ARG USER_GID=$USER_UID
# git + openssh-client: git over the host's forwarded SSH agent # git + openssh-client: git over the host's forwarded SSH agent
# gcc, libc-dev, libpq-dev: build psycopg2 from requirements.txt # gcc, libc-dev, libpq-dev: build psycopg2 from requirements.txt
# postgresql-client, wait-for-it: scripts/db-setup.sh, scripts/psql.sh # postgresql-client, wait-for-it: database scripts
# curl, ca-certificates: Claude Code installer # curl, ca-certificates: Claude Code installer
# vim: personal preferences # vim, bash-completion: personal preferences
RUN apt-get -y update && \ RUN apt-get -y update && \
apt-get install --no-install-recommends -y \ apt-get install --no-install-recommends -y \
git openssh-client make curl ca-certificates sudo \ git openssh-client make curl ca-certificates sudo \
gcc libc-dev libpq-dev \ gcc libc-dev libpq-dev \
postgresql-client wait-for-it \ postgresql-client wait-for-it \
vim && \ vim bash-completion && \
apt-get clean && rm -rf /var/lib/apt/lists/* apt-get clean && rm -rf /var/lib/apt/lists/*
# Load bash-completion in every interactive shell (e.g. `make <TAB>`). Not in
# ~/.bashrc, since the home dir is a volume (see devcontainer.json).
RUN echo '. /usr/share/bash-completion/bash_completion' >> /etc/bash.bashrc
RUN groupadd --gid $USER_GID $USERNAME && \ RUN groupadd --gid $USER_GID $USERNAME && \
useradd --uid $USER_UID --gid $USER_GID -m -s /bin/bash $USERNAME && \ useradd --uid $USER_UID --gid $USER_GID -m -s /bin/bash $USERNAME && \
echo "$USERNAME ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME && \ echo "$USERNAME ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME && \
-2
View File
@@ -9,8 +9,6 @@ services:
- home:/home/dev - home:/home/dev
environment: environment:
- PYTHONPATH=/workspaces/max - PYTHONPATH=/workspaces/max
- FLASK_APP=max.app
- FLASK_ENV=development
- PGSQL_HOST=postgresql - PGSQL_HOST=postgresql
- PGSQL_PORT=5432 - PGSQL_PORT=5432
- PGSQL_USER=max - PGSQL_USER=max
+3 -2
View File
@@ -13,10 +13,11 @@
// Bind mounts are resolved by the host's daemon, so they need host paths: // Bind mounts are resolved by the host's daemon, so they need host paths:
// use ${LOCAL_WORKSPACE_FOLDER} instead of paths inside this container. // use ${LOCAL_WORKSPACE_FOLDER} instead of paths inside this container.
"remoteEnv": { "remoteEnv": {
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}",
"LANG": "C.UTF-8"
}, },
"postCreateCommand": "pip install -U pip && pip install -U -r requirements.txt -r requirements-dev.txt", "postCreateCommand": "pip install -U pip && pip install -U -r requirements.txt -r requirements-dev.txt",
"forwardPorts": [ "forwardPorts": [
8000 8757
] ]
} }
+1
View File
@@ -1 +1,2 @@
*.pyc *.pyc
log/
+1 -3
View File
@@ -12,11 +12,9 @@ RUN mkdir log
ENV PYTHONPATH=/app/ ENV PYTHONPATH=/app/
ENV FLASK_APP=max.app ENV FLASK_APP=max.app
ENV FLASK_ENV=production
COPY max/ ./max COPY max/ ./max
COPY scripts/entrypoint.sh . COPY scripts/prod/ .
COPY scripts/db-setup.sh .
ENTRYPOINT ["./entrypoint.sh"] ENTRYPOINT ["./entrypoint.sh"]
CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:8000", "max.app:app"] CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:8000", "max.app:app"]
-21
View File
@@ -1,21 +0,0 @@
FROM python:3.15-slim-trixie
RUN apt -y update && apt -y upgrade && \
apt install --no-install-recommends -y wait-for-it lsb-release wget gnupg2 gcc libpq-dev libc-dev vim postgresql-client && apt clean all
WORKDIR /app
COPY requirements.txt .
COPY requirements-dev.txt .
RUN pip install -U -r requirements.txt -r requirements-dev.txt
RUN mkdir log
ENV PYTHONPATH=/app/
ENV FLASK_APP=max.app
ENV FLASK_ENV=development
COPY scripts/* ./
ENTRYPOINT ["./entrypoint-dev.sh"]
CMD ["flask", "run", "--host", "0.0.0.0", "--port", "8000"]
+12 -27
View File
@@ -1,36 +1,21 @@
# DEVELOPMENT SERVER # DEV
seed-dev: clear-dev
FLASK_APP=max.app FLASK_DEBUG=1 scripts/dev/seed-db.sh
seed-dev: clear-dev:
scripts/entrypoint-dev.sh scripts/dev/clear-db.sh
build-dev: run-dev:
docker build -t max:dev -f Dockerfile.dev . FLASK_APP=max.app FLASK_DEBUG=1 flask run --host 0.0.0.0 --port 8757
run-dev: | build-dev run-dev-as-prod:
# bash -c "trap 'docker-compose -f dc.dev.yml down' EXIT; MAX_COMMAND=\"flask run --host 0.0.0.0 --port 8000\" docker-compose -f dc.dev.yml up --build" mkdir -p log
MAX_COMMAND="flask run --host 0.0.0.0 --port 8000" docker-compose -f dc.dev.yml up FLASK_APP=max.app LOG_DIR=log gunicorn -w 1 -b 0.0.0.0:8757 max.app:app
run-bash: | build-dev run-cleanup:
MAX_COMMAND="sleep infinity" docker-compose -f dc.dev.yml up -d scripts/dev/cleanup.sh
docker-compose -f dc.dev.yml exec max "./entrypoint-dev.sh"
docker-compose -f dc.dev.yml exec max chown -R $$(id -u $${USER}):$$(id -g $${USER}) /app
docker-compose -f dc.dev.yml exec -u $$(id -u $${USER}):$$(id -g $${USER}) max bash || true
docker-compose -f dc.dev.yml down
run-bash-root: | build-dev
MAX_COMMAND="sleep infinity" docker-compose -f dc.dev.yml up -d
docker-compose -f dc.dev.yml exec max "./entrypoint-dev.sh"
docker-compose -f dc.dev.yml exec max bash || true
docker-compose -f dc.dev.yml down
stop-dev:
docker-compose -f dc.dev.yml down
run-cleanup: | build-dev
docker run -ti -v `pwd`/max:/app/max --entrypoint="" max-dev ./cleanup.sh
# PROD # PROD
build-prod: build-prod:
docker build -t max:$(shell cat VERSION) . docker build -t max:$(shell cat VERSION) .
+2 -2
View File
@@ -10,7 +10,7 @@
<script> <script>
function test() { function test() {
$.post("http://localhost:8000/user/1/toggle-enabled", null, function () { $.post("http://localhost:8757/user/1/toggle-enabled", null, function () {
console.log("success", arguments); console.log("success", arguments);
}); });
} }
@@ -18,7 +18,7 @@
<button onclick="test()">Change</button> <button onclick="test()">Change</button>
<form action="http://localhost:8000/user/1/toggle-enabled" method="post"> <form action="http://localhost:8757/user/1/toggle-enabled" method="post">
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
</body> </body>
-2
View File
@@ -1,2 +0,0 @@
#!/bin/bash
docker-compose -f dc.dev.yml $@
-27
View File
@@ -1,27 +0,0 @@
services:
max:
image: max:dev
volumes:
- ./max:/app/max
depends_on:
- postgresql
command: $MAX_COMMAND
ports:
- 8000:8000/tcp
environment:
- PGSQL_HOST=postgresql
- PGSQL_USER=max
- PGSQL_PASSWORD=max
- PGSQL_NAME=max
networks:
- postgresql
postgresql:
image: postgres:18.6
environment:
- POSTGRES_USER=max
- POSTGRES_PASSWORD=max
- POSTGRES_DB=max
networks:
- postgresql
networks:
postgresql:
+1 -37
View File
@@ -1,8 +1,5 @@
import base64 import base64
import logging
import os import os
import sys
from logging.config import dictConfig
from flask import Flask from flask import Flask
from werkzeug.routing import Rule as WerkzeugRule from werkzeug.routing import Rule as WerkzeugRule
@@ -11,6 +8,7 @@ from .auth import AuthMiddleware, init as init_auth
from .cli import register_cli from .cli import register_cli
from .csrf import CsrfMiddleware from .csrf import CsrfMiddleware
from .db import database from .db import database
from .log import init_logging
from .middlewares import ( from .middlewares import (
ExceptionMiddleware, ExceptionMiddleware,
I18NMiddleware, I18NMiddleware,
@@ -27,40 +25,6 @@ class Rule(WerkzeugRule):
super().__init__(*args, **kwargs) # do not set methods super().__init__(*args, **kwargs) # do not set methods
def init_logging(app):
dictConfig(
{
"version": 1,
"formatters": {
"default": {"format": "[%(asctime)s] [%(levelname)s]: %(message)s"}
},
"handlers": {
"wsgi": {
"class": "logging.StreamHandler",
"stream": "ext://flask.logging.wsgi_errors_stream",
"formatter": "default",
},
"stdout": {
"class": "logging.StreamHandler",
"stream": sys.stdout,
"formatter": "default",
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "/app/log/max.log",
"formatter": "default",
"maxBytes": 1024 * 1024,
"backupCount": 5,
},
},
"root": {"level": "INFO", "handlers": ["stdout", "file"]},
"gunicorn": {"level": "INFO", "handlers": ["stdout", "file"]},
}
)
logger = logging.getLogger(app.name)
logger.setLevel(logging.DEBUG if app.debug else logging.INFO)
def main(): def main():
app = Flask(__name__) app = Flask(__name__)
init_logging(app) init_logging(app)
+1 -1
View File
@@ -10,7 +10,7 @@ from max.views import redirect
from .auth import logout from .auth import logout
DEBUG_DEFAULT_LOGIN = True DEBUG_DEFAULT_LOGIN = False
class AuthMiddleware(BaseMiddleware): class AuthMiddleware(BaseMiddleware):
+38
View File
@@ -0,0 +1,38 @@
import logging
import os
import sys
from logging.config import dictConfig
def init_logging(app):
handlers = {
"stdout": {
"class": "logging.StreamHandler",
"stream": sys.stdout,
"formatter": "default",
},
}
active_handlers = ["stdout"]
if not app.debug:
handlers["file"] = {
"class": "logging.handlers.RotatingFileHandler",
"filename": os.path.join(os.environ.get("LOG_DIR", "/app/log"), "max.log"),
"formatter": "default",
"maxBytes": 1024 * 1024,
"backupCount": 5,
}
active_handlers.append("file")
dictConfig(
{
"version": 1,
"formatters": {
"default": {"format": "[%(asctime)s] [%(levelname)s]: %(message)s"}
},
"handlers": handlers,
"root": {"level": "INFO", "handlers": active_handlers},
"gunicorn": {"level": "INFO", "handlers": active_handlers},
}
)
logger = logging.getLogger(app.name)
logger.setLevel(logging.DEBUG if app.debug else logging.INFO)
@@ -1,4 +1,7 @@
#!/bin/bash #!/bin/bash
set -e
printf "Black:\n" printf "Black:\n"
black max/ black max/
printf "\nIsort:\n" printf "\nIsort:\n"
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
set -e
PGPASSWORD="$PGSQL_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$PGSQL_HOST" -p "$PGSQL_PORT" -U "$PGSQL_USER" -d "$PGSQL_NAME" \
-c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
+3
View File
@@ -1,2 +1,5 @@
#!/bin/bash #!/bin/bash
set -e
PGPASSWORD="$PGSQL_PASSWORD" psql -h "$PGSQL_HOST" -p "$PGSQL_PORT" -U "$PGSQL_USER" -d "$PGSQL_NAME" PGPASSWORD="$PGSQL_PASSWORD" psql -h "$PGSQL_HOST" -p "$PGSQL_PORT" -U "$PGSQL_USER" -d "$PGSQL_NAME"
@@ -1,11 +1,10 @@
#!/bin/bash #!/bin/bash
set -e set -e
cd "$(dirname "$0")"
source "$(dirname "$0")/db-setup.sh" source ../prod/db-setup.sh
flask add-domain finn.st flask add-domain finn.st
flask add-domain stutzenste.in flask add-domain stutzenste.in
flask reset-admin admin@finn.st admin flask reset-admin admin@finn.st admin
exec "$@"
@@ -15,6 +15,6 @@ wait-for-it --timeout=15 "$PGSQL_HOST:$PGSQL_PORT"
# Create schema in postgresql # Create schema in postgresql
export PGPASSWORD="$PGSQL_PASSWORD" export PGPASSWORD="$PGSQL_PASSWORD"
psql -1 -h "$PGSQL_HOST" -p "$PGSQL_PORT" -U "$PGSQL_USER" -d "$PGSQL_NAME" -f max/db/schema.sql psql -1 -v ON_ERROR_STOP=1 -h "$PGSQL_HOST" -p "$PGSQL_PORT" -U "$PGSQL_USER" -d "$PGSQL_NAME" -f "$(dirname "${BASH_SOURCE[0]}")/schema.sql"
echo "end db setup" echo "end db setup"
File renamed without changes.
File renamed without changes.
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
export FLASK_ENV=production
gunicorn -w 1 -b 0.0.0.0:8000 max.app:app
-2
View File
@@ -1,2 +0,0 @@
#!/bin/bash
flask run --host 0.0.0.0 --port 8000
View File
File renamed without changes.