🎉(all) bootstrap the Calendars project
This repository was forked from Drive in late December 2025 and boostraped as a minimal demo of backend+caldav server+frontend integration. There is much left to do and to fix!
This commit is contained in:
2358
docker/auth/realm.json
Normal file
2358
docker/auth/realm.json
Normal file
File diff suppressed because it is too large
Load Diff
39
docker/davical/Dockerfile
Normal file
39
docker/davical/Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
||||
# DAViCal CalDAV Server
|
||||
# Based on Debian with Apache and PHP
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
apache2 \
|
||||
libapache2-mod-php \
|
||||
php-pgsql \
|
||||
php-xml \
|
||||
php-curl \
|
||||
php-imap \
|
||||
php-ldap \
|
||||
davical \
|
||||
postgresql-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Enable required Apache modules
|
||||
RUN a2enmod rewrite
|
||||
|
||||
# Copy Apache configuration
|
||||
COPY davical.conf /etc/apache2/sites-available/davical.conf
|
||||
RUN a2dissite 000-default && a2ensite davical
|
||||
|
||||
# Copy DAViCal configuration
|
||||
COPY config.php /etc/davical/config.php
|
||||
|
||||
# Copy and setup entrypoint
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# Set permissions
|
||||
RUN chown -R www-data:www-data /var/log/apache2
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
64
docker/davical/config.php
Normal file
64
docker/davical/config.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* DAViCal Configuration
|
||||
* This file is mounted as /etc/davical/config.php
|
||||
* Overrides the default config generated by the fintechstudios/davical image
|
||||
*/
|
||||
|
||||
// Database connection - uses shared calendars database in public schema
|
||||
// The image will set these from PGHOST, PGDATABASE, PGUSER, PGPASSWORD
|
||||
$c->pg_connect[] = 'host=' . getenv('PGHOST') . ' port=' . (getenv('PGPORT') ?: '5432') . ' dbname=' . getenv('PGDATABASE') . ' user=' . getenv('PGUSER') . ' password=' . getenv('PGPASSWORD');
|
||||
|
||||
// System name
|
||||
$c->system_name = 'Calendars DAViCal Server';
|
||||
|
||||
// Admin email
|
||||
$c->admin_email = 'admin@example.com';
|
||||
|
||||
// Allow public access for CalDAV discovery
|
||||
$c->public_freebusy_url = true;
|
||||
|
||||
// Default locale
|
||||
$c->default_locale = 'en_US.UTF-8';
|
||||
|
||||
// Logging - enable for debugging authentication issues
|
||||
$c->log_caldav_queries = true;
|
||||
|
||||
// Trust proxy headers for auth
|
||||
$c->trust_x_forwarded = true;
|
||||
|
||||
// Configure base path when behind reverse proxy
|
||||
// Override SCRIPT_NAME so DAViCal generates correct URLs
|
||||
// DAViCal uses $_SERVER['SCRIPT_NAME'] to determine the base path for URLs
|
||||
// We set it to the proxy path WITHOUT /caldav.php since DAViCal will add that itself
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_PREFIX'])) {
|
||||
$_SERVER['SCRIPT_NAME'] = rtrim($_SERVER['HTTP_X_FORWARDED_PREFIX'], '/');
|
||||
} elseif (isset($_SERVER['HTTP_X_SCRIPT_NAME'])) {
|
||||
$_SERVER['SCRIPT_NAME'] = rtrim($_SERVER['HTTP_X_SCRIPT_NAME'], '/');
|
||||
}
|
||||
|
||||
// Custom authentication function to use X-Forwarded-User header
|
||||
// This function is called by DAViCal's authentication system
|
||||
function authenticate_via_forwarded_user( $username, $password ) {
|
||||
// Check if X-Forwarded-User header is present
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_USER'])) {
|
||||
$forwarded_user = trim($_SERVER['HTTP_X_FORWARDED_USER']);
|
||||
|
||||
// If the username from Basic Auth matches X-Forwarded-User, authenticate
|
||||
// Users with password '*' are externally authenticated
|
||||
if (strtolower($username) === strtolower($forwarded_user)) {
|
||||
// Return the username to authenticate as this user
|
||||
// DAViCal will check if user exists and has password '*'
|
||||
return $forwarded_user;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to standard authentication
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use custom authentication hook
|
||||
$c->authenticate_hook = array(
|
||||
'call' => 'authenticate_via_forwarded_user',
|
||||
'config' => array()
|
||||
);
|
||||
22
docker/davical/davical.conf
Normal file
22
docker/davical/davical.conf
Normal file
@@ -0,0 +1,22 @@
|
||||
<VirtualHost *:80>
|
||||
ServerName localhost
|
||||
DocumentRoot /usr/share/davical/htdocs
|
||||
DirectoryIndex index.php
|
||||
|
||||
Alias /images/ /usr/share/davical/htdocs/images/
|
||||
|
||||
<Directory /usr/share/davical/htdocs>
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
AcceptPathInfo On
|
||||
|
||||
# CalDAV principal URL
|
||||
RewriteEngine On
|
||||
RewriteRule ^/caldav/(.*)$ /caldav.php/$1 [L]
|
||||
RewriteRule ^/\.well-known/caldav /caldav.php [R=301,L]
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/davical_error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/davical_access.log combined
|
||||
</VirtualHost>
|
||||
93
docker/davical/run-migrations
Executable file
93
docker/davical/run-migrations
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
###
|
||||
# Run DB migrations necessary to use Davical.
|
||||
# Will create the database on first-run, and only run necessary migrations on subsequent runs.
|
||||
#
|
||||
# Requires the following environment variables in addition to the container variables.
|
||||
# - ROOT_PGUSER
|
||||
# - ROOT_PGPASSWORD
|
||||
# - DAVICAL_ADMIN_PASS
|
||||
###
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z ${ROOT_PGUSER+x} ]; then
|
||||
echo "ROOT_PGUSER must be set"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z ${ROOT_PGPASSWORD+x} ]; then
|
||||
echo "ROOT_PGPASSWORD must be set"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z ${DAVICAL_ADMIN_PASS+x} ]; then
|
||||
echo "DAVICAL_ADMIN_PASS must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z ${DBA_PGPASSWORD+x} ]; then
|
||||
DBA_PGPASSWORD=$PGPASSWORD
|
||||
fi
|
||||
|
||||
if [ -z ${DAVICAL_SCHEMA+x} ]; then
|
||||
DAVICAL_SCHEMA=$DBA_PGUSER
|
||||
fi
|
||||
|
||||
# store PG environment so it can be overridden as-needed
|
||||
DAVICAL_PGUSER=$PGUSER
|
||||
DAVICAL_PGPASSWORD=$PGPASSWORD
|
||||
DAVICAL_PGDATABASE=$PGDATABASE
|
||||
|
||||
run_migrations() {
|
||||
echo "Running dba/update-davical-database, which should automatically apply any necessary DB migrations."
|
||||
/usr/share/davical/dba/update-davical-database \
|
||||
--dbname $DAVICAL_PGDATABASE \
|
||||
--dbuser $DBA_PGUSER \
|
||||
--dbhost $PGHOST \
|
||||
--dbpass $DBA_PGPASSWORD \
|
||||
--appuser $DAVICAL_PGUSER \
|
||||
--owner $DBA_PGUSER
|
||||
}
|
||||
|
||||
export PGUSER=$ROOT_PGUSER
|
||||
export PGPASSWORD=$ROOT_PGPASSWORD
|
||||
export PGDATABASE=
|
||||
|
||||
# Wait for PG connection
|
||||
retries=10
|
||||
until pg_isready -q -t 3; do
|
||||
[[ retries -eq 0 ]] && echo "Could not connect to Postgres" && exit 1
|
||||
echo "Waiting for Postgres to be available"
|
||||
retries=$((retries-1))
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Check whether the database has already been setup, with awl tables.
|
||||
tables=$(psql -d $DAVICAL_PGDATABASE -c "\\dt")
|
||||
if echo "$tables" | grep -q "awl_db_revision"; then
|
||||
# The database already exists - just run any outstanding migrations
|
||||
run_migrations
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Database has not been created - running first-time database setup"
|
||||
|
||||
# the rest of the commands are run as the dba superuser
|
||||
export PGUSER=$DBA_PGUSER
|
||||
export PGPASSWORD=$DBA_PGPASSWORD
|
||||
export PGDATABASE=$DAVICAL_PGDATABASE
|
||||
|
||||
psql -qXAt -f /usr/share/awl/dba/awl-tables.sql
|
||||
psql -qXAt -f /usr/share/awl/dba/schema-management.sql
|
||||
psql -qXAt -f /usr/share/davical/dba/davical.sql
|
||||
run_migrations
|
||||
psql -qXAt -f /usr/share/davical/dba/base-data.sql
|
||||
|
||||
# DAViCal only uses salted SHA1 at-best, but it's better than storing the password in plaintext!
|
||||
# see https://wiki.davical.org/index.php?title=Force_Admin_Password
|
||||
# from https://gitlab.com/davical-project/awl/-/blob/3f044e2dc8435c2eeba61a3c41ec11c820711ab3/inc/DataUpdate.php#L48-58
|
||||
salted_password=$(php -r 'require "/usr/share/awl/inc/AWLUtilities.php"; echo session_salted_sha1($argv[1]);' "$DAVICAL_ADMIN_PASS")
|
||||
psql -qX \
|
||||
-v pw="'$salted_password'" \
|
||||
<<EOF
|
||||
UPDATE usr SET password = :pw WHERE user_no = 1;
|
||||
EOF
|
||||
28
docker/files/development/etc/nginx/conf.d/default.conf
Normal file
28
docker/files/development/etc/nginx/conf.d/default.conf
Normal file
@@ -0,0 +1,28 @@
|
||||
server {
|
||||
listen 8083;
|
||||
server_name localhost;
|
||||
charset utf-8;
|
||||
|
||||
# API routes - proxy to Django backend
|
||||
location /api/ {
|
||||
proxy_pass http://backend-dev:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Frontend - proxy to Next.js dev server
|
||||
location / {
|
||||
proxy_pass http://frontend-dev:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# WebSocket support for HMR
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
46
docker/files/docker-entrypoint-initdb.d/init-databases.sh
Normal file
46
docker/files/docker-entrypoint-initdb.d/init-databases.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# Initialize multiple databases on the same PostgreSQL server
|
||||
# This script runs automatically when the PostgreSQL container starts for the first time
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
# Function to create a database and user if they don't exist
|
||||
create_database() {
|
||||
local database=$1
|
||||
local user=$2
|
||||
local password=$3
|
||||
|
||||
echo "Creating database '$database' with user '$user'..."
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
-- Create user if not exists
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '$user') THEN
|
||||
CREATE USER $user WITH PASSWORD '$password';
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
|
||||
-- Create database if not exists
|
||||
SELECT 'CREATE DATABASE $database OWNER $user'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '$database')\gexec
|
||||
|
||||
-- Grant privileges
|
||||
GRANT ALL PRIVILEGES ON DATABASE $database TO $user;
|
||||
EOSQL
|
||||
|
||||
echo "Database '$database' created successfully."
|
||||
}
|
||||
|
||||
# Create databases for all services
|
||||
# The main 'calendar' database is created by default via POSTGRES_DB
|
||||
|
||||
# DAViCal database
|
||||
create_database "davical" "davical" "davical_pass"
|
||||
|
||||
# Keycloak database
|
||||
create_database "keycloak" "keycloak" "keycloak_pass"
|
||||
|
||||
echo "All databases initialized successfully!"
|
||||
15
docker/files/etc/nginx/conf.d/default.conf
Normal file
15
docker/files/etc/nginx/conf.d/default.conf
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
server {
|
||||
listen 8923;
|
||||
server_name localhost;
|
||||
charset utf-8;
|
||||
|
||||
# Keycloak - all auth-related paths
|
||||
location / {
|
||||
proxy_pass http://keycloak:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
36
docker/postgresql/init-databases.sh
Executable file
36
docker/postgresql/init-databases.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
# Initialize shared calendars database for local development
|
||||
# All services (Django, DAViCal, Keycloak) use the same database in public schema
|
||||
# This script runs as POSTGRES_USER on first database initialization
|
||||
|
||||
set -e
|
||||
|
||||
echo "Initializing calendars database..."
|
||||
|
||||
# Ensure pgroot user exists with correct password and permissions
|
||||
# This runs as POSTGRES_USER (which may be different from pgroot on existing databases)
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
-- Ensure pgroot user exists with correct password
|
||||
-- POSTGRES_USER has superuser privileges, so it can create users
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'pgroot') THEN
|
||||
CREATE USER pgroot WITH PASSWORD 'pass' SUPERUSER CREATEDB CREATEROLE;
|
||||
ELSE
|
||||
ALTER USER pgroot WITH PASSWORD 'pass';
|
||||
-- Ensure superuser privileges
|
||||
ALTER USER pgroot WITH SUPERUSER CREATEDB CREATEROLE;
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
|
||||
-- Grant all privileges on calendars database
|
||||
GRANT ALL PRIVILEGES ON DATABASE "$POSTGRES_DB" TO pgroot;
|
||||
|
||||
-- Grant all on public schema
|
||||
GRANT ALL ON SCHEMA public TO pgroot;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO pgroot;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO pgroot;
|
||||
EOSQL
|
||||
|
||||
echo "Calendars database ready!"
|
||||
Reference in New Issue
Block a user