diff --git a/.env.example b/.env.example index 8a0f4d8..1499327 100644 --- a/.env.example +++ b/.env.example @@ -18,3 +18,6 @@ BIND_ADDR=127.0.0.1 # Behind a reverse proxy, the IPs allowed to set X-Forwarded-For (for login rate limiting). # FORWARDED_ALLOW_IPS=127.0.0.1 + +# Development only: build from source instead of pulling the published image. +# COMPOSE_FILE=docker-compose.build.yaml diff --git a/Dockerfile b/Dockerfile index a01eda0..6b84b6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ COPY requirements.txt . RUN pip install -r requirements.txt COPY app ./app -# `docker compose run --rm test` +# `docker compose -f docker-compose.build.yaml run --rm test` FROM base AS test COPY requirements-dev.txt . RUN pip install -r requirements-dev.txt diff --git a/README.md b/README.md index 0cd7dfa..3554236 100644 --- a/README.md +++ b/README.md @@ -16,28 +16,50 @@ A small, self-hosted to-do app for one person. It works in a desktop browser and Stack: Python (FastAPI) + SQLite with a plain HTML/CSS/JS frontend and no build step. Everything runs in Docker. -## Run locally +## Run -Requirements: Docker with the Compose plugin. Nothing else is installed on the host. +Requirements: Docker with the Compose plugin. You don't need the source code: only `docker-compose.yaml` and a `.env`. ```sh +mkdir whattodo && cd whattodo +# copy docker-compose.yaml and .env.example from this repo into this folder cp .env.example .env # then edit .env: set APP_PASSWORD and SECRET_KEY -docker compose up -d --build +docker compose up -d ``` Open and log in with `APP_PASSWORD`. -The database lives in `./data/whattodo.db`, which is git-ignored. +The database lives in `./data/whattodo.db`. Useful commands: ```sh -docker compose logs -f # follow logs -docker compose down # stop -docker compose up -d --build # rebuild after pulling changes +docker compose logs -f # follow logs +docker compose down # stop +docker compose pull && docker compose up -d # update to the newest published version +``` + +To pin a version, set `WHATTODO_TAG=` in `.env` (default: `latest`). + +## Develop (build from source) + +`docker-compose.build.yaml` builds the image from this checkout. Add this line to your `.env` so plain `docker compose` uses it: + +```sh +COMPOSE_FILE=docker-compose.build.yaml +``` + +```sh +docker compose up -d --build # build and start docker compose run --rm test # run the test suite (in Docker) ``` +### Publishing the image + +`./publish.sh` builds the last commit (uncommitted changes are left out) and pushes it to +`git.goyban.com/goyban/whattodo` as `:latest` and `:`. `./publish.sh ` publishes another version. +Log in once first with `docker login git.goyban.com`, using a Gitea token with package write access as the password. + ### Testing on your phone (same Wi-Fi) Set `BIND_ADDR=0.0.0.0` in `.env`, run `docker compose up -d`, then open `http://:8080` on the phone. @@ -45,8 +67,8 @@ Browsers only allow "Install app" / "Add to Home Screen" with offline support ov ## Deploy to production -1. On the server, clone the repo and create `.env` from `.env.example`. Use a **new strong** `APP_PASSWORD` and `SECRET_KEY`, and set `COOKIE_SECURE=true`. -2. `docker compose up -d --build`. The app listens on `127.0.0.1:${PORT}`. +1. On the server, put `docker-compose.yaml` in a folder and create `.env` from `.env.example`. Use a **new strong** `APP_PASSWORD` and `SECRET_KEY`, and set `COOKIE_SECURE=true`. +2. `docker compose up -d`. The app listens on `127.0.0.1:${PORT}`. 3. Put it behind a reverse proxy with HTTPS. For example, with Caddy: ``` @@ -83,6 +105,6 @@ Export regularly from Settings, or copy `./data/whattodo.db` while the app is st app/main.py API: auth, tasks, categories, export/import app/db.py SQLite schema and setup app/static/ frontend (index.html, app.js, style.css, PWA files, icons) -tests/ API tests (run with: docker compose run --rm test) +tests/ API tests (run with: docker compose -f docker-compose.build.yaml run --rm test) tools/make_icons.py regenerates the PNG icons ``` diff --git a/docker-compose.yml b/docker-compose.build.yaml similarity index 77% rename from docker-compose.yml rename to docker-compose.build.yaml index 5d3bcde..e4b12c0 100644 --- a/docker-compose.yml +++ b/docker-compose.build.yaml @@ -1,3 +1,5 @@ +# Builds the image from source (development). Used instead of docker-compose.yaml when +# COMPOSE_FILE=docker-compose.build.yaml is set in .env, or with -f docker-compose.build.yaml. services: whattodo: build: diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..2fd8e4c --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,20 @@ +# Runs the published image; no source code needed. Put this file and a .env (from .env.example) in a folder, then: +# docker compose up -d +# Update to the newest published version: +# docker compose pull && docker compose up -d +# To build from source instead, use docker-compose.build.yaml. +services: + whattodo: + image: git.goyban.com/goyban/whattodo:${WHATTODO_TAG:-latest} + restart: unless-stopped + env_file: .env + ports: + # BIND_ADDR=127.0.0.1 -> this computer only; 0.0.0.0 -> also reachable from your phone on the LAN. + - "${BIND_ADDR:-127.0.0.1}:${PORT:-8080}:8000" + volumes: + - ./data:/data + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/health')"] + interval: 30s + timeout: 5s + retries: 3 diff --git a/publish.sh b/publish.sh new file mode 100755 index 0000000..0f2d83e --- /dev/null +++ b/publish.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Build the app image from a committed version and push it to the Gitea registry. +# Only committed files are used (via `git archive`), so uncommitted work never ends up in the image. +# +# ./publish.sh # builds HEAD +# ./publish.sh v1.2 # builds a tag/branch/commit +# +# One-time setup: docker login git.goyban.com (password = a Gitea token with package write access) +set -euo pipefail + +REGISTRY=git.goyban.com +IMAGE="$REGISTRY/goyban/whattodo" +REF="${1:-HEAD}" + +cd "$(dirname "$0")" +SHA="$(git rev-parse --short "$REF^{commit}")" + +echo "Building $IMAGE from $REF ($SHA)" +git archive --format=tar "$REF" | docker build --target runtime -t "$IMAGE:$SHA" -t "$IMAGE:latest" - + +docker push "$IMAGE:$SHA" +docker push "$IMAGE:latest" +echo "Pushed $IMAGE:$SHA and $IMAGE:latest"