mirror of
https://github.com/token2/TOTPVault.git
synced 2026-06-22 23:42:25 -07:00
Add Docker Compose deployment documentation to README fixing #2
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Docker files (not needed inside the image)
|
||||
docker-compose.yml
|
||||
docker-compose.override.yml
|
||||
Makefile
|
||||
.dockerignore
|
||||
.env
|
||||
.env.example
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
SECURITY.md
|
||||
LICENSE
|
||||
|
||||
# Runtime directories (created at build time or mounted as volumes)
|
||||
sessions/*
|
||||
!sessions/.htaccess
|
||||
@@ -0,0 +1,49 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# TOTPVault — Environment Variables
|
||||
# Copy this file to .env and fill in real values before running docker compose.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# ── Application ──────────────────────────────────────────────────────────────
|
||||
# Base URL of the application (no trailing slash).
|
||||
# For local Docker deployment this is typically http://localhost:8080
|
||||
APP_URL=http://localhost:8080
|
||||
|
||||
# ── Database ─────────────────────────────────────────────────────────────────
|
||||
# DB_HOST must match the Compose service name for the database container.
|
||||
DB_HOST=db
|
||||
DB_PORT=3306
|
||||
DB_NAME=totpvault
|
||||
DB_USER=totpvault
|
||||
DB_PASSWORD=change_me_db_password
|
||||
DB_ROOT_PASSWORD=change_me_root_password
|
||||
|
||||
# ── Encryption ───────────────────────────────────────────────────────────────
|
||||
# Must be exactly 32 bytes, base64-encoded. Generate with:
|
||||
# php -r "echo base64_encode(random_bytes(32)) . PHP_EOL;"
|
||||
# WARNING: Changing this after storing tokens will make them unreadable!
|
||||
ENCRYPTION_KEY=
|
||||
|
||||
# ── Session ──────────────────────────────────────────────────────────────────
|
||||
SESSION_COOKIE_NAME=totpvault_session
|
||||
SESSION_LIFETIME=2592000
|
||||
|
||||
# ── Mail (MailerSend) ────────────────────────────────────────────────────────
|
||||
# Sign up at https://www.mailersend.com and create an API token.
|
||||
MAILERSEND_KEY=
|
||||
MAIL_FROM_EMAIL=noreply@yourdomain.com
|
||||
MAIL_FROM_NAME=TOTPVault
|
||||
|
||||
# ── OAuth: Google ────────────────────────────────────────────────────────────
|
||||
# https://console.cloud.google.com/apis/credentials
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
|
||||
# ── OAuth: Microsoft ─────────────────────────────────────────────────────────
|
||||
# https://portal.azure.com → App registrations
|
||||
MICROSOFT_CLIENT_ID=
|
||||
MICROSOFT_CLIENT_SECRET=
|
||||
|
||||
# ── OAuth: GitHub ────────────────────────────────────────────────────────────
|
||||
# https://github.com/settings/developers
|
||||
GITHUB_CLIENT_ID=
|
||||
GITHUB_CLIENT_SECRET=
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Secrets — never commit real configuration
|
||||
.env
|
||||
|
||||
# PHP sessions
|
||||
sessions/*
|
||||
!sessions/.htaccess
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Docker volumes (if stored locally)
|
||||
docker/data/
|
||||
@@ -0,0 +1,9 @@
|
||||
# Root .htaccess — route all requests to front controller
|
||||
RewriteEngine On
|
||||
|
||||
# If the requested file or directory exists, serve it directly
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
|
||||
# Route everything else through index.php
|
||||
RewriteRule ^(.*)$ index.php [QSA,L]
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# TOTPVault — Production Dockerfile
|
||||
# Base: Official PHP 8.2 with Apache on Debian Bookworm
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
FROM php:8.2-apache-bookworm
|
||||
|
||||
LABEL maintainer="TOTPVault" \
|
||||
description="Self-hosted TOTP manager with AES-256-GCM encryption"
|
||||
|
||||
# ── 1. Enable Apache modules ────────────────────────────────────────────────
|
||||
# mod_rewrite is required for .htaccess URL routing to index.php
|
||||
RUN a2enmod rewrite headers
|
||||
|
||||
# ── 2. Install PHP extensions ────────────────────────────────────────────────
|
||||
# pdo_mysql — database access via PDO
|
||||
# mysqli — fallback MySQL driver (some libraries expect it)
|
||||
# openssl — bundled with php:8.2, but we verify it's present
|
||||
# curl — required by sendMail() for MailerSend API calls
|
||||
RUN docker-php-ext-install pdo pdo_mysql mysqli \
|
||||
&& php -m | grep -qi openssl \
|
||||
&& php -m | grep -qi curl
|
||||
|
||||
# ── 3. Set Apache DocumentRoot to the project directory ─────────────────────
|
||||
# The app expects index.php at the web root, with .htaccess rewriting.
|
||||
ENV APACHE_DOCUMENT_ROOT=/var/www/html
|
||||
RUN sed -ri "s|/var/www/html|${APACHE_DOCUMENT_ROOT}|g" \
|
||||
/etc/apache2/sites-available/*.conf \
|
||||
/etc/apache2/apache2.conf
|
||||
|
||||
# ── 4. Copy custom Apache vhost configuration ───────────────────────────────
|
||||
# Ensures AllowOverride All so .htaccess files work, and denies direct
|
||||
# browsing of protected directories as a defense-in-depth layer.
|
||||
COPY docker/000-default.conf /etc/apache2/sites-available/000-default.conf
|
||||
|
||||
# ── 5. Copy application files ───────────────────────────────────────────────
|
||||
COPY . /var/www/html/
|
||||
|
||||
# ── 6. Create sessions directory with proper permissions ─────────────────────
|
||||
# www-data (Apache user) needs write access for PHP session files.
|
||||
# The .htaccess inside sessions/ blocks direct HTTP access.
|
||||
RUN mkdir -p /var/www/html/sessions \
|
||||
&& chown www-data:www-data /var/www/html/sessions \
|
||||
&& chmod 750 /var/www/html/sessions
|
||||
|
||||
# ── 7. Set secure permissions on config directory ────────────────────────────
|
||||
# config/ contains secrets at runtime; restrict access to www-data only.
|
||||
RUN chown -R www-data:www-data /var/www/html/config \
|
||||
&& chmod 750 /var/www/html/config
|
||||
|
||||
# ── 8. Ensure all .htaccess files are present and readable ──────────────────
|
||||
RUN chmod 644 /var/www/html/.htaccess \
|
||||
/var/www/html/config/.htaccess \
|
||||
/var/www/html/src/.htaccess \
|
||||
/var/www/html/sessions/.htaccess \
|
||||
/var/www/html/templates/.htaccess
|
||||
|
||||
# ── 9. Configure PHP for production ─────────────────────────────────────────
|
||||
# Disable display_errors in the container (log them instead).
|
||||
# Use the sessions/ directory for session files.
|
||||
RUN { \
|
||||
echo 'display_errors = Off'; \
|
||||
echo 'log_errors = On'; \
|
||||
echo 'error_log = /dev/stderr'; \
|
||||
echo 'session.save_path = /var/www/html/sessions'; \
|
||||
echo 'session.cookie_httponly = 1'; \
|
||||
echo 'session.cookie_samesite = Lax'; \
|
||||
echo 'expose_php = Off'; \
|
||||
} > /usr/local/etc/php/conf.d/totpvault.ini
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
# Apache runs in the foreground by default via the base image entrypoint.
|
||||
@@ -0,0 +1,35 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# TOTPVault — Makefile shortcuts for Docker Compose
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
.PHONY: up down logs rebuild shell db-shell status
|
||||
|
||||
## Start all services in the background
|
||||
up:
|
||||
docker compose up -d --build
|
||||
|
||||
## Stop all services
|
||||
down:
|
||||
docker compose down
|
||||
|
||||
## Follow application logs
|
||||
logs:
|
||||
docker compose logs -f app
|
||||
|
||||
## Rebuild images and restart
|
||||
rebuild:
|
||||
docker compose down
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
|
||||
## Open a bash shell inside the app container
|
||||
shell:
|
||||
docker compose exec app bash
|
||||
|
||||
## Open a MySQL shell inside the database container
|
||||
db-shell:
|
||||
docker compose exec db mariadb -u "$${DB_USER:-totpvault}" -p"$${DB_PASSWORD}" "$${DB_NAME:-totpvault}"
|
||||
|
||||
## Show container status
|
||||
status:
|
||||
docker compose ps
|
||||
@@ -411,6 +411,118 @@ CREATE TABLE `users` (
|
||||
└── .htaccess # Rewrites all requests to index.php
|
||||
```
|
||||
|
||||
## Docker Compose Deployment
|
||||
|
||||
A complete Docker setup is included for local or self-hosted deployment. It runs the PHP app under Apache and MariaDB in two containers behind a private bridge network.
|
||||
|
||||
### Quick start
|
||||
|
||||
```bash
|
||||
# 1. Clone and enter the project
|
||||
git clone https://github.com/token2/TOTPvault.git
|
||||
cd TOTPVault
|
||||
|
||||
# 2. Create your environment file
|
||||
cp .env.example .env
|
||||
|
||||
# 3. Generate a secure encryption key (required)
|
||||
php -r "echo base64_encode(random_bytes(32)) . PHP_EOL;"
|
||||
# Paste the output as ENCRYPTION_KEY in .env
|
||||
|
||||
# 4. Edit .env — at minimum fill in:
|
||||
# - ENCRYPTION_KEY (from step 3)
|
||||
# - DB_PASSWORD (choose any strong password)
|
||||
# - DB_ROOT_PASSWORD
|
||||
# - MAILERSEND_KEY (if you want magic-link emails)
|
||||
# - OAuth client IDs/secrets for any providers you use
|
||||
|
||||
# 5. Start everything
|
||||
docker compose up -d --build
|
||||
|
||||
# 6. Open the app
|
||||
open http://localhost:8080
|
||||
```
|
||||
|
||||
### Makefile shortcuts
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `make up` | Build and start all services |
|
||||
| `make down` | Stop all services |
|
||||
| `make logs` | Follow application logs |
|
||||
| `make rebuild` | Full rebuild (no cache) and restart |
|
||||
| `make shell` | Bash shell inside the app container |
|
||||
| `make db-shell` | MariaDB shell inside the database container |
|
||||
|
||||
### Common commands
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
docker compose logs -f app
|
||||
|
||||
# Stop everything
|
||||
docker compose down
|
||||
|
||||
# Reset database (destroys all data!)
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
### Persistent data
|
||||
|
||||
| Volume | Purpose |
|
||||
|---|---|
|
||||
| `totpvault-db-data` | MariaDB database files — survives container rebuilds |
|
||||
| `totpvault-sessions` | PHP session files — keeps users logged in across restarts |
|
||||
|
||||
To list volumes: `docker volume ls | grep totpvault`
|
||||
|
||||
### How configuration works
|
||||
|
||||
- `config/config.docker.php` is mounted as `config/config.php` inside the container (read-only).
|
||||
- All settings are read from environment variables defined in `.env`.
|
||||
- OAuth redirect URIs are derived automatically from `APP_URL`.
|
||||
- The original `config/config.php` on the host is **not modified** and remains usable for non-Docker deployments.
|
||||
- **Never commit `.env` or real secrets to Git.** The `.gitignore` excludes `.env` by default.
|
||||
|
||||
### Database initialisation
|
||||
|
||||
On first startup, MariaDB automatically runs `docker/init-db.sql` to create all tables. This happens only when the data volume is empty. To re-initialise:
|
||||
|
||||
```bash
|
||||
docker compose down -v # removes the data volume
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**App cannot connect to database**
|
||||
- Ensure `DB_HOST=db` in `.env` (must match the Compose service name).
|
||||
- Check that the `db` container is healthy: `docker compose ps`
|
||||
- Verify `DB_USER`, `DB_PASSWORD`, and `DB_NAME` match between the app and db service.
|
||||
- The app waits for the db healthcheck before starting (`depends_on: condition: service_healthy`).
|
||||
|
||||
**Schema not imported**
|
||||
- Init scripts only run on a **fresh** data volume. If you changed the schema, reset with `docker compose down -v` first.
|
||||
- Check MariaDB logs: `docker compose logs db | head -100`
|
||||
|
||||
**.htaccess rewrite not working**
|
||||
- Verify `mod_rewrite` is enabled: `docker compose exec app apache2ctl -M | grep rewrite`
|
||||
- The Dockerfile enables it automatically. If you see 404 errors for routes like `/dashboard`, check that `AllowOverride All` is set in `docker/000-default.conf`.
|
||||
|
||||
**Missing encryption key**
|
||||
- `ENCRYPTION_KEY` must be set in `.env`. Generate it with:
|
||||
```
|
||||
php -r "echo base64_encode(random_bytes(32)) . PHP_EOL;"
|
||||
```
|
||||
- If the key changes after tokens have been stored, existing encrypted secrets become **permanently unreadable**.
|
||||
|
||||
**MailerSend not configured**
|
||||
- Magic-link login requires a valid `MAILERSEND_KEY`. Without it, email sending will silently fail.
|
||||
- Sign up at [mailersend.com](https://www.mailersend.com), verify a domain, and create an API token.
|
||||
- Set `MAIL_FROM_EMAIL` to an address on your verified domain.
|
||||
|
||||
---
|
||||
|
||||
## Demo
|
||||
Check out the live demo here: [Live Demo](https://totp.token2.swiss/)
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Deny all direct HTTP access to config/
|
||||
# Secrets and application configuration must never be served by Apache.
|
||||
Require all denied
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
// config/config.docker.php
|
||||
//
|
||||
// Docker-specific configuration that reads ALL settings from environment
|
||||
// variables. This file is mounted as config/config.php inside the container
|
||||
// by docker-compose.yml. It is never used in a non-Docker deployment.
|
||||
//
|
||||
// Do NOT commit this file with real secrets. All values come from .env.
|
||||
|
||||
$appUrl = rtrim(getenv('APP_URL') ?: 'http://localhost:8080', '/');
|
||||
|
||||
return [
|
||||
|
||||
// ── Application ────────────────────────────────────────────────────────
|
||||
'app_url' => $appUrl,
|
||||
'app_name' => 'TOTPVault',
|
||||
'app_env' => getenv('APP_ENV') ?: 'production',
|
||||
|
||||
// ── Database ───────────────────────────────────────────────────────────
|
||||
// DB_HOST should be the Compose service name (default: "db").
|
||||
'db' => [
|
||||
'host' => getenv('DB_HOST') ?: 'db',
|
||||
'port' => (int)(getenv('DB_PORT') ?: 3306),
|
||||
'dbname' => getenv('DB_NAME') ?: 'totpvault',
|
||||
'charset' => 'utf8mb4',
|
||||
'user' => getenv('DB_USER') ?: 'totpvault',
|
||||
'password' => getenv('DB_PASSWORD') ?: '',
|
||||
],
|
||||
|
||||
// ── Encryption ─────────────────────────────────────────────────────────
|
||||
// Must be exactly 32 bytes, base64-encoded. Generate with:
|
||||
// php -r "echo base64_encode(random_bytes(32)) . PHP_EOL;"
|
||||
'encryption_key' => getenv('ENCRYPTION_KEY') ?: '',
|
||||
|
||||
// ── Session ────────────────────────────────────────────────────────────
|
||||
'session' => [
|
||||
'cookie_name' => getenv('SESSION_COOKIE_NAME') ?: 'totpvault_session',
|
||||
'lifetime' => (int)(getenv('SESSION_LIFETIME') ?: 2592000),
|
||||
],
|
||||
|
||||
// ── Mail (MailerSend) ──────────────────────────────────────────────────
|
||||
'mail' => [
|
||||
'mailersend_key' => getenv('MAILERSEND_KEY') ?: '',
|
||||
'from_email' => getenv('MAIL_FROM_EMAIL') ?: 'noreply@localhost',
|
||||
'from_name' => getenv('MAIL_FROM_NAME') ?: 'TOTPVault',
|
||||
],
|
||||
|
||||
// ── OAuth providers ────────────────────────────────────────────────────
|
||||
// Redirect URIs are derived from APP_URL automatically.
|
||||
// Leave client_id empty to effectively disable a provider.
|
||||
'oauth' => [
|
||||
|
||||
'google' => [
|
||||
'client_id' => getenv('GOOGLE_CLIENT_ID') ?: '',
|
||||
'client_secret' => getenv('GOOGLE_CLIENT_SECRET') ?: '',
|
||||
'redirect_uri' => $appUrl . '/auth/callback/google',
|
||||
'auth_url' => 'https://accounts.google.com/o/oauth2/v2/auth',
|
||||
'token_url' => 'https://oauth2.googleapis.com/token',
|
||||
'userinfo_url' => 'https://www.googleapis.com/oauth2/v3/userinfo',
|
||||
'scope' => 'openid email profile',
|
||||
],
|
||||
|
||||
'microsoft' => [
|
||||
'client_id' => getenv('MICROSOFT_CLIENT_ID') ?: '',
|
||||
'client_secret' => getenv('MICROSOFT_CLIENT_SECRET') ?: '',
|
||||
'redirect_uri' => $appUrl . '/auth/callback/microsoft',
|
||||
'auth_url' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
|
||||
'token_url' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
|
||||
'userinfo_url' => 'https://graph.microsoft.com/v1.0/me',
|
||||
'scope' => 'openid email profile User.Read',
|
||||
],
|
||||
|
||||
'github' => [
|
||||
'client_id' => getenv('GITHUB_CLIENT_ID') ?: '',
|
||||
'client_secret' => getenv('GITHUB_CLIENT_SECRET') ?: '',
|
||||
'redirect_uri' => $appUrl . '/auth/callback/github',
|
||||
'auth_url' => 'https://github.com/login/oauth/authorize',
|
||||
'token_url' => 'https://github.com/login/oauth/access_token',
|
||||
'userinfo_url' => 'https://api.github.com/user',
|
||||
'scope' => 'read:user user:email',
|
||||
],
|
||||
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,95 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# TOTPVault — Docker Compose
|
||||
# Usage:
|
||||
# cp .env.example .env # then fill in real values
|
||||
# docker compose up -d --build
|
||||
# open http://localhost:8080
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
services:
|
||||
|
||||
# ── PHP + Apache application ───────────────────────────────────────────────
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: totpvault-app
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:80"
|
||||
environment:
|
||||
# All env vars are read by config/config.docker.php inside the container.
|
||||
- APP_URL=${APP_URL:-http://localhost:8080}
|
||||
- DB_HOST=${DB_HOST:-db}
|
||||
- DB_PORT=${DB_PORT:-3306}
|
||||
- DB_NAME=${DB_NAME:-totpvault}
|
||||
- DB_USER=${DB_USER:-totpvault}
|
||||
- DB_PASSWORD=${DB_PASSWORD:?DB_PASSWORD is required}
|
||||
- ENCRYPTION_KEY=${ENCRYPTION_KEY:?ENCRYPTION_KEY is required}
|
||||
- SESSION_COOKIE_NAME=${SESSION_COOKIE_NAME:-totpvault_session}
|
||||
- SESSION_LIFETIME=${SESSION_LIFETIME:-2592000}
|
||||
- MAILERSEND_KEY=${MAILERSEND_KEY:-}
|
||||
- MAIL_FROM_EMAIL=${MAIL_FROM_EMAIL:-noreply@localhost}
|
||||
- MAIL_FROM_NAME=${MAIL_FROM_NAME:-TOTPVault}
|
||||
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
|
||||
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-}
|
||||
- MICROSOFT_CLIENT_ID=${MICROSOFT_CLIENT_ID:-}
|
||||
- MICROSOFT_CLIENT_SECRET=${MICROSOFT_CLIENT_SECRET:-}
|
||||
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:-}
|
||||
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-}
|
||||
volumes:
|
||||
# Mount the Docker-specific config as config/config.php inside the container.
|
||||
# This keeps the original config/config.php on the host untouched.
|
||||
- ./config/config.docker.php:/var/www/html/config/config.php:ro
|
||||
# Persist PHP sessions across container restarts.
|
||||
- totpvault-sessions:/var/www/html/sessions
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- totpvault-net
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "-s", "-o", "/dev/null", "http://localhost:80/"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
# ── MariaDB database ──────────────────────────────────────────────────────
|
||||
db:
|
||||
image: mariadb:10.11
|
||||
container_name: totpvault-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?DB_ROOT_PASSWORD is required}
|
||||
MYSQL_DATABASE: ${DB_NAME:-totpvault}
|
||||
MYSQL_USER: ${DB_USER:-totpvault}
|
||||
MYSQL_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
|
||||
volumes:
|
||||
# Persist database data across container restarts and rebuilds.
|
||||
- totpvault-db-data:/var/lib/mysql
|
||||
# Initialise the schema on first startup only.
|
||||
# Files in /docker-entrypoint-initdb.d/ run once when the data volume is empty.
|
||||
- ./docker/init-db.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro
|
||||
# The database port is NOT exposed to the host by default for security.
|
||||
# Uncomment the following lines if you need direct access for debugging:
|
||||
# ports:
|
||||
# - "3306:3306"
|
||||
networks:
|
||||
- totpvault-net
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
volumes:
|
||||
totpvault-db-data:
|
||||
driver: local
|
||||
totpvault-sessions:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
totpvault-net:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,35 @@
|
||||
<VirtualHost *:80>
|
||||
ServerAdmin webmaster@localhost
|
||||
DocumentRoot /var/www/html
|
||||
|
||||
# ── AllowOverride All — required for .htaccess rewrite rules ────────────
|
||||
<Directory /var/www/html>
|
||||
Options -Indexes +FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
# ── Defense-in-depth: deny direct HTTP access to protected directories ──
|
||||
# These directories also have their own .htaccess deny rules, but we
|
||||
# enforce it at the vhost level as well in case AllowOverride is changed.
|
||||
|
||||
<Directory /var/www/html/config>
|
||||
Require all denied
|
||||
</Directory>
|
||||
|
||||
<Directory /var/www/html/src>
|
||||
Require all denied
|
||||
</Directory>
|
||||
|
||||
<Directory /var/www/html/sessions>
|
||||
Require all denied
|
||||
</Directory>
|
||||
|
||||
<Directory /var/www/html/templates>
|
||||
Require all denied
|
||||
</Directory>
|
||||
|
||||
# ── Logging ─────────────────────────────────────────────────────────────
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
||||
@@ -0,0 +1,76 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- TOTPVault — Database initialisation for Docker
|
||||
--
|
||||
-- This script runs automatically on first MariaDB startup via
|
||||
-- /docker-entrypoint-initdb.d/. It creates all tables with proper
|
||||
-- PRIMARY KEYs and AUTO_INCREMENT that the application expects.
|
||||
--
|
||||
-- The MYSQL_DATABASE environment variable (set in docker-compose.yml)
|
||||
-- tells the MariaDB image to CREATE the database and USE it before
|
||||
-- running this script, so we do not need a CREATE DATABASE statement.
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
-- ── magic_links ─────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `magic_links` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`email` varchar(255) NOT NULL,
|
||||
`token_hash` varchar(64) NOT NULL,
|
||||
`used` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`expires_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_magic_links_email` (`email`),
|
||||
INDEX `idx_magic_links_token_hash` (`token_hash`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ── otp_profiles ────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `otp_profiles` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`issuer` varchar(255) DEFAULT '',
|
||||
`secret_encrypted` text NOT NULL COMMENT 'AES-256-GCM encrypted base32 secret',
|
||||
`algorithm` enum('SHA1','SHA256','SHA512') DEFAULT 'SHA1',
|
||||
`digits` tinyint(4) DEFAULT 6 COMMENT '6, 8 or 10',
|
||||
`period` smallint(6) DEFAULT 30 COMMENT 'seconds',
|
||||
`color` varchar(7) DEFAULT '#6366f1',
|
||||
`icon` varchar(50) DEFAULT 'shield',
|
||||
`hide_code` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_otp_profiles_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ── profile_shares ──────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `profile_shares` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`profile_id` int(11) NOT NULL,
|
||||
`shared_by_user_id` int(11) NOT NULL,
|
||||
`shared_with_email` varchar(255) NOT NULL,
|
||||
`shared_with_user_id` int(11) DEFAULT NULL,
|
||||
`can_edit` tinyint(1) DEFAULT 0,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_profile_shares_profile_id` (`profile_id`),
|
||||
INDEX `idx_profile_shares_shared_with_email` (`shared_with_email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ── users ───────────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`email` varchar(255) NOT NULL,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`avatar_url` varchar(500) DEFAULT NULL,
|
||||
`google_id` varchar(255) DEFAULT NULL,
|
||||
`microsoft_id` varchar(255) DEFAULT NULL,
|
||||
`github_id` varchar(255) DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `idx_users_email` (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@@ -0,0 +1,3 @@
|
||||
# Deny all direct HTTP access to sessions/
|
||||
# PHP session files contain sensitive authentication data.
|
||||
Require all denied
|
||||
@@ -0,0 +1,3 @@
|
||||
# Deny all direct HTTP access to src/
|
||||
# PHP source files are included by the front controller, not served directly.
|
||||
Require all denied
|
||||
@@ -0,0 +1,3 @@
|
||||
# Deny all direct HTTP access to templates/
|
||||
# Templates are rendered by PHP, not served directly.
|
||||
Require all denied
|
||||
Reference in New Issue
Block a user