No description
  • Python 66.6%
  • HTML 22.7%
  • Go Template 6.8%
  • JavaScript 2%
  • Dockerfile 1.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-22 14:19:38 +02:00
static Implement account management features: allow users to change username and password, and add admin functionality to delete accounts while preserving ticket assignments. 2026-09-22 12:15:27 +02:00
tech-kanban Add API token management and authentication for ticket creation 2026-09-22 14:05:03 +02:00
templates Implement account management features: allow users to change username and password, and add admin functionality to delete accounts while preserving ticket assignments. 2026-09-22 12:15:27 +02:00
tests Fix ticket status update message to handle zero moved tickets and add assertion for ticket existence in tests 2026-09-22 14:19:38 +02:00
.dockerignore First working app 2026-09-22 10:52:35 +02:00
.env.example First working app 2026-09-22 10:52:35 +02:00
.gitignore First working app 2026-09-22 10:52:35 +02:00
.python-version First working app 2026-09-22 10:52:35 +02:00
.woodpecker.yml Normalize file permissions in Dockerfile and CI configuration 2026-09-22 12:01:46 +02:00
compose.yaml First working app 2026-09-22 10:52:35 +02:00
database.py First working app 2026-09-22 10:52:35 +02:00
Dockerfile Normalize file permissions in Dockerfile and CI configuration 2026-09-22 12:01:46 +02:00
main.py Fix ticket status update message to handle zero moved tickets and add assertion for ticket existence in tests 2026-09-22 14:19:38 +02:00
pyproject.toml First working app 2026-09-22 10:52:35 +02:00
README.md Add command to repair legacy ticket statuses and update ticket creation status 2026-09-22 14:16:48 +02:00
uv.lock First working app 2026-09-22 10:52:35 +02:00

Tech Kanban

A simple shared Flask Kanban board backed by MariaDB. Users can create, edit, assign, move, and delete tickets across To do, In progress, and Done. Administrators create accounts, change roles, disable/enable access, delete accounts, and reset passwords. Everyone can change their own username and password through Account by confirming their current password. There is no public registration. Passwords are hashed, forms use CSRF protection, and disabling an account or resetting its password revokes sessions.

Run locally

Requires Python 3.12+, uv, and Docker Compose (or an existing MariaDB server).

  1. Copy .env.example to .env. Replace the passwords and secret key with random values; keep the password in DATABASE_URL consistent with DB_PASSWORD. Generate a secret with python3 -c 'import secrets; print(secrets.token_hex(32))'.
  2. Start MariaDB and install dependencies:
    docker compose up -d --wait
    uv sync --locked
    
  3. Export your environment, create the tables, and bootstrap the first admin:
    set -a
    source .env
    set +a
    uv run flask --app main:create_app init-db
    uv run flask --app main:create_app create-admin
    
    The command prompts for a username and password (at least 12 characters). It only works when there are no accounts. All subsequent accounts must be created by an administrator through Users.
  4. Start the app:
    uv run flask --app main:create_app run
    
    Visit http://127.0.0.1:5000 and sign in.

All authenticated users share one board and can manage all tickets. Disable users instead of deleting them to preserve ticket assignments. Deleting an account requires confirmation, revokes access, and leaves its tickets unassigned. Admins cannot disable, demote, or delete themselves. Password changes through Account keep the current session and sign out other sessions. Admin password resets sign out all sessions, including your own if you reset your own password.

MariaDB data persists in the mariadb_data Docker volume. init-db creates missing tables; it does not migrate existing schemas. Back up the database before future schema changes. For an existing server, supply its connection in DATABASE_URL; URL-encode any special characters in credentials.

Checks

If tickets created by the older API count toward the total but do not appear in any column, repair their legacy open status to todo after deploying the fix:

kubectl -n tech-kanban exec deployment/tech-kanban -- \
  /app/.venv/bin/python -m flask --app main:create_app repair-ticket-statuses

This command only changes open tickets and is safe to run again.

uv run pytest
uvx ruff check .
uvx ruff format --check .
uvx ty check

Tests use isolated SQLite databases to exercise authentication, authorization, ticket persistence, session revocation, and CSRF without needing a server. Set TEST_DATABASE_URL to an empty, disposable MariaDB database to run the same suite against MariaDB. Tests create and drop tables in that database.

For deployment, run behind HTTPS using a production WSGI server, set COOKIE_SECURE=true, retain a stable secret, and apply login rate limiting at the reverse proxy. The Flask development server is for local use.

SQLite development mode

No MariaDB or environment configuration is needed:

uv sync --locked
uv run main.py dev create-admin
uv run main.py dev

The first argument dev explicitly selects SQLite and enables Flask's local debug server. Both commands create missing tables automatically. Data lives in instance/kanban.db; a persistent development signing key lives in instance/dev-secret. Both are ignored by Git and excluded from container builds. The same models and account permissions apply to both databases; there are no seeded users or default passwords. Development mode ignores MariaDB environment settings. Normal startup still requires the production configuration.

Kubernetes / Helm

The chart in tech-kanban/ follows the registry, Traefik, secret, and persistent MariaDB conventions in the CTF example. It includes a standalone MariaDB StatefulSet (no chart dependencies), a persistent volume, database initialization, health probes, and a Gunicorn application image.

Build and push your application image to your chosen registry:

docker build -t YOUR_REGISTRY/tech-kanban:0.1.0 .
docker push YOUR_REGISTRY/tech-kanban:0.1.0
kubectl create namespace tech-kanban

Create a Secret in that namespace with keys mariadb-password and mariadb-root-password. For example, using temporary files avoids putting passwords into shell history or command arguments:

secret_dir=$(mktemp -d)
python3 -c 'import secrets; print(secrets.token_hex(32), end="")' > "$secret_dir/mariadb-password"
python3 -c 'import secrets; print(secrets.token_hex(32), end="")' > "$secret_dir/mariadb-root-password"
kubectl -n tech-kanban create secret generic mariadb-secret --from-file="$secret_dir"
rm -r "$secret_dir"

Copy tech-kanban/values-platform4.example.yaml to a local values file and adapt its image repository, hostname, TLS secret and storage class. Create the referenced gitlab-registry image-pull secret and TLS secret in the same namespace (or remove the image-pull-secret reference for a public registry). Then install:

helm upgrade --install tech-kanban ./tech-kanban \
  --namespace tech-kanban -f YOUR_VALUES.yaml --wait --timeout 10m
kubectl -n tech-kanban exec -it deployment/tech-kanban -- \
  /app/.venv/bin/python -m flask --app main:create_app create-admin

The init container creates tables before Gunicorn starts. MariaDB advisory locking serializes initialization across replicas; it is not a schema migration system. The first admin is deliberately created interactively, never from default credentials. The chart generates a session signing key and reuses the existing Secret during Helm upgrades. For GitOps rendering, supply a stable externally managed Secret via secretKey.existingSecret and set secretKey.create=false instead.

The chart also generates a separate API token Secret and injects it as API_TOKEN into the application and initialization containers. Helm upgrades reuse the existing token. The ticket API expects the raw token in the Authorization header (without a Bearer prefix). To supply your own token or use GitOps rendering, create a Kubernetes Secret containing an api-token key and configure:

apiToken:
  create: false
  existingSecret: kanban-api-token
  key: api-token

Restart the application pods after changing an externally managed token.

To use an existing MariaDB server, set mariadb.enabled=false and configure database.host, port, name, user, secretName, and passwordKey. The app accepts those as DB_HOST, DB_PORT, DB_NAME, DB_USER, and DB_PASSWORD; DATABASE_URL takes precedence outside the default chart configuration. Separate credentials are escaped by SQLAlchemy, so special characters in passwords work.

HTTPS cookies are enabled by default. For local port-forward testing, override cookieSecure=false. /health/live checks the app, while /health/ready checks database connectivity. Application replicas share MariaDB; SQLite is only for local development. Database PVCs survive uninstall; back them up and manage their cleanup separately. Secret updates require an app restart; database password rotation also requires updating the actual database user.

Validate chart changes with helm lint ./tech-kanban and helm template tech-kanban ./tech-kanban. No deployment is performed by the tests.

Updating the deployed image

CI publishes both latest and sha-<first-seven-commit-characters>. Deploy the unique SHA tag so Kubernetes runs the intended build even with IfNotPresent:

helm upgrade --install tech-kanban ./tech-kanban -n tech-kanban \
  -f YOUR_VALUES.yaml --set-string image.tag=sha-YOUR_SHA \
  --wait --timeout 10m

Wait for the image build to finish before upgrading. The Docker build verifies that Flask and Gunicorn run as the container user. The init container invokes /app/.venv/bin/python -m flask, avoiding dependence on the runtime PATH.