diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d516115 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +# Exclude local env files from Docker build context +.env +.env.local +.env.development +.env.test +.env.docker + +# Rust build artifacts +target + +# Git +.git +.gitignore + +# Node/npm caches if any future UI gets added +node_modules +npm-debug.log +yarn.lock +pnpm-lock.yaml diff --git a/.env.docker.example b/.env.docker.example new file mode 100644 index 0000000..89344d0 --- /dev/null +++ b/.env.docker.example @@ -0,0 +1,30 @@ +# SleepTracker Docker Compose environment file (example) +# Copy this file to ".env.docker" and fill in the values before running: +# docker compose up --build +# +# Notes: +# - COOKIE_SECURE is omitted here; the application defaults to secure cookies (true) when unset. +# You may explicitly set COOKIE_SECURE=1 if you prefer, but do NOT set it to 0 in Docker. +# - Do not commit your real .env.docker with secrets to version control. + +# Use a file-based SQLite database inside the container volume +DATABASE_URL=sqlite:///data/sleep.db + +# Single admin user credentials used by the application +# - Generate an Argon2id password hash with: +# cargo run -p sleep-api --bin pw-hash +# - Paste the full $argon2id$... string below +ADMIN_EMAIL=admin@example.com +ADMIN_PASSWORD_HASH='$argon2id$v=19$REPLACE_WITH_HASH' + +# Session secret (base64-encoded random bytes, 32+ bytes recommended) +# Examples to generate: +# Linux/macOS: head -c 32 /dev/urandom | base64 +# PowerShell: [Convert]::ToBase64String((1..32 | ForEach-Object {[byte](Get-Random -Max 256)})) +SESSION_SECRET=REPLACE_WITH_BASE64_SECRET + +# Optional: enable HSTS when served over HTTPS/behind TLS-terminating proxy +# ENABLE_HSTS=1 + +# Optional: explicitly enforce secure cookies in Docker (default is secure=true when unset) +# COOKIE_SECURE=1 diff --git a/.env.example b/.env.example index bbd67bb..22f8f06 100644 --- a/.env.example +++ b/.env.example @@ -20,5 +20,10 @@ ADMIN_PASSWORD_HASH=$argon2id$v=19$REPLACE_WITH_HASH # Note: CSRF uses a random per-login cookie (double-submit) and does not use a CSRF secret. SESSION_SECRET=REPLACE_WITH_BASE64_SECRET +# Local development over HTTP (do NOT use in production or Docker) +# Set to 0 to allow non-Secure cookies and dev-friendly cookie names ("session"/"csrf"). +# In Docker/production, omit this variable (defaults to secure=true) or set COOKIE_SECURE=1. +COOKIE_SECURE=0 + # Optional: enable HSTS header (only when served over HTTPS/behind TLS) # ENABLE_HSTS=1 diff --git a/.gitignore b/.gitignore index ad67955..1c15468 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ target # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +.env +.env.docker diff --git a/Dockerfile b/Dockerfile index 107ae2d..7616884 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,9 @@ WORKDIR /app ENV DATABASE_URL=sqlite:///data/sleep.db COPY --from=builder /app/target/release/sleep-api /app/sleep-api COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh -RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +# Normalize line endings in case the script was checked out with CRLF on Windows +RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh && chmod +x /usr/local/bin/docker-entrypoint.sh + ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] EXPOSE 8080 diff --git a/README.md b/README.md index b21cf55..1417dc0 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ SleepTracker is a small, single-user web API for tracking sleep sessions, built - Set ADMIN_EMAIL and ADMIN_PASSWORD_HASH - Generate a password hash: cargo run -p sleep-api --bin pw-hash - - Paste the $argon2id$... string into ADMIN_PASSWORD_HASH + - Paste the $argon2id$... string into ADMIN_PASSWORD_HASH (IMPORTANT: use single quotes in .env/.env.docker to prevent $-expansion by dotenv) + Example: + ADMIN_PASSWORD_HASH='$argon2id$v=19$m=19456,t=2,p=1$...$...' - Set SESSION_SECRET to a base64-encoded random value (32+ bytes recommended) - Optional: Set ENABLE_HSTS=1 when serving over HTTPS - Optional: See COOKIE_SECURE below for local HTTP development @@ -21,6 +23,30 @@ SleepTracker is a small, single-user web API for tracking sleep sessions, built Server will listen on 0.0.0.0:8080. +## Run with Docker + +Use Docker Compose to build and run the app. + +- Docker Compose (recommended) + - Copy .env.docker.example to .env.docker and fill values: + - ADMIN_EMAIL, ADMIN_PASSWORD_HASH (quote the $argon2id$... string with single quotes) + - SESSION_SECRET: base64-encoded random value (32+ bytes) + - For local HTTP development, set COOKIE_SECURE=0. For HTTPS/prod, use COOKIE_SECURE=1. + - Build and start: + docker compose up --build + - Add -d to run in the background. + - Access the API at http://localhost:8080 + - Follow logs: + docker compose logs -f api + - Stop: + docker compose down + - Stop and delete the persistent data volume (DESTROYS DB): + docker compose down -v + - Notes: + - Data is stored at /data inside the container and persists in a named volume across restarts. + - Migrations run automatically on startup. + + ## Authentication and sessions - Single-user login based on ADMIN_EMAIL and ADMIN_PASSWORD_HASH. @@ -92,3 +118,17 @@ OpenAPI specification is in openapi.yaml and includes: - The cookie encryption Key is derived from SESSION_SECRET if present; otherwise a random key is generated (sessions will break on restart in that case). - Default database is sqlite::memory: for ephemeral dev/testing. For a persistent DB use DATABASE_URL=sqlite://./data/sleep.db and create the directory. + +## Environments + +- Local (cargo run): + - Use .env for local settings (e.g., COOKIE_SECURE=0 for http://). + - Start: `cargo run -p sleep-api`. + +- Docker Compose: + - Copy `.env.docker.example` to `.env.docker` and fill values (ADMIN_EMAIL, ADMIN_PASSWORD_HASH, SESSION_SECRET; optionally COOKIE_SECURE=1). + - Compose injects only `.env.docker` into the container; your local `.env` is not used inside the container. + - Start: `docker compose up --build`. + +- Paths in Docker: + - DATABASE_URL should point to the named volume path: `sqlite:///data/sleep.db`. diff --git a/compose.yaml b/compose.yaml index fa673e0..74fdc70 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,8 +2,8 @@ services: api: build: . working_dir: /data - environment: - - DATABASE_URL=sqlite://sleep.db + env_file: + - .env.docker ports: - "8080:8080" volumes: