Organization Stack
A self-hosted collaboration and authentication platform featuring Single Sign-On (SSO), Two-Factor Authentication (2FA), centralized user management, Git hosting, and wiki collaboration.
Overview
This project provides a complete, production-ready stack for small to medium organizations seeking self-hosted alternatives to cloud services. All components communicate through modern authentication standards (LDAP, OIDC, Forward-Auth) providing seamless single sign-on across all services.
Components
- lldap - Lightweight LDAP directory for centralized user and group management
- Authelia - SSO authentication server with 2FA/TOTP support, OIDC provider, and forward-auth endpoint
- Gitea - Self-hosted Git service with web UI (authenticated via OIDC)
- JSPWiki - Collaborative wiki platform (authenticated via Forward-Auth)
- Registration Service - User self-provisioning with admin approval (FastAPI + SQLite)
- Caddy - Reverse proxy with automatic HTTPS via Let's Encrypt
Key Features
- Single Sign-On (SSO) - One set of credentials for all services
- Two-Factor Authentication - TOTP (Google Authenticator, Authy, etc.) support
- Automatic HTTPS - Let's Encrypt certificates managed by Caddy
- File-Based Secrets - Secure secret management following Docker best practices
- One-Command Deployment - Automated setup script handles everything
- Zero Manual Configuration - Template-based config generation from
.env
Quick Start
Prerequisites
- Remote Server: Linux server with Docker and Docker Compose v2 installed
- Local Machine: SSH access to remote server, rsync installed
- Domain: Domain name with DNS pointing to your server (or
/etc/hostsfor testing)
Installation
Note
: For production deployments with multiple administrators, see MULTI_ADMIN_SETUP.md for system-wide installation with proper permissions.
-
Clone the repository
git clone https://github.com/yourorg/org-stack.git cd org-stack -
Configure environment
cp .env.example .env nano .env # Edit BASE_DOMAIN, REMOTE_USER, REMOTE_HOSTRequired changes in
.env:BASE_DOMAIN=example.com→ your actual domainREMOTE_USER=user→ your SSH username (ordeployfor multi-admin)REMOTE_HOST=example.com→ your server hostname/IPREMOTE_PATH=/opt/org-stack→ optional, for system-wide installation
-
Deploy
./deploy.shThe deploy script will:
- Generate all required secrets automatically
- Create configuration files from templates
- Sync files to your remote server via rsync
- Start all Docker containers
-
Access services
- lldap: https://ldap.yourdomain.com (create users here first)
- Authelia: https://auth.yourdomain.com
- Gitea: https://git.yourdomain.com
- Wiki: https://wiki.yourdomain.com
- Registration: https://register.yourdomain.com (public user registration)
First-Time Setup
After deployment, complete these steps:
-
Create users in lldap
- Access https://ldap.yourdomain.com
- Login with admin credentials (shown by deploy.sh)
- Create user accounts and assign to
lldap_admingroup for admin privileges
-
Setup Gitea
- Access https://git.yourdomain.com
- Complete the installation wizard (use defaults)
- Go to Site Admin → Authentication Sources → Add Authentication Source
- Type:
OAuth2 - Authentication Name:
authelia⚠️ IMPORTANT: Must be lowercase! - Provider:
OpenID Connect - Client ID:
gitea - Client Secret: (shown by deploy.sh)
- OpenID Connect Auto Discovery URL:
http://authelia:9091/.well-known/openid-configuration
- Type:
- Logout and test "Sign in with OpenID Connect"
-
Test the wiki
- Access https://wiki.yourdomain.com
- You'll be redirected to Authelia for login
- After authentication, you'll have admin access if you're in the
lldap_admingroup
-
Enable user self-registration (optional)
- Users can submit registration requests at https://register.yourdomain.com
- Admins review requests at https://register.yourdomain.com/admin (requires Authelia login)
- Admin dashboard shows:
- Pending requests: Approve or reject with optional reason
- Audit log: Historical record of all approval/rejection decisions
- When approved:
- User is automatically created in lldap with random password
- User receives email with credentials (if SMTP configured in .env)
- Request is moved to audit log
- User management: After approval, manage users directly in lldap (single source of truth)
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Internet / Users │
└──────────────────────────────┬──────────────────────────────────────┘
│
┌─────────▼──────────┐
│ Caddy (HTTPS) │ ← Automatic Let's Encrypt
│ Reverse Proxy │
└──────────┬─────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌───────▼────────┐ ┌──────▼────────┐ ┌──────▼────────┐
│ Gitea (Git) │ │ JSPWiki (Wiki)│ │ lldap (Admin)│
│ via OIDC ────┼──┤ via Fwd-Auth ─┼──┤ via Fwd-Auth ─┼──┐
└────────────────┘ └───────────────┘ └───────────────┘ │
│
┌────────────────────────────────────┘
│
┌────────▼─────────┐
│ Authelia │ ← SSO/2FA/OIDC/Forward-Auth
│ (Auth Server) │
└────────┬─────────┘
│
┌────────▼─────────┐
│ lldap │ ← User Database (LDAP)
└──────────────────┘
Authentication Flow
OIDC Flow (Gitea):
- User accesses Gitea → redirected to Authelia
- Authelia validates credentials against lldap via LDAP
- Authelia enforces 2FA (TOTP)
- Authelia issues OIDC tokens
- User redirected back to Gitea, logged in
Forward-Auth Flow (Wiki, lldap):
- User accesses Wiki → Caddy forwards auth request to Authelia
- Authelia validates session (or prompts login + 2FA)
- Authelia returns
Remote-Userheader - Caddy forwards request to Wiki with authenticated user header
- Wiki trusts the
Remote-Userheader (container authentication)
For detailed architecture documentation, see ARCHITECTURE.md.
Configuration
All configuration is managed through a single .env file. The deployment script generates service-specific configs from templates.
Key Configuration Options
# Domain Configuration
BASE_DOMAIN=example.com # Your domain
GITEA_SUBDOMAIN=git # Creates git.example.com
WIKI_SUBDOMAIN=wiki # Creates wiki.example.com
AUTH_SUBDOMAIN=auth # Creates auth.example.com
LLDAP_SUBDOMAIN=ldap # Creates ldap.example.com
# TLS Mode
USE_SELF_SIGNED_CERTS=false # false=Let's Encrypt, true=self-signed
# Authentication
REQUIRE_2FA=true # Require TOTP for all services
# Advanced
SESSION_EXPIRATION=1h # How long sessions last
MAX_RETRIES=3 # Failed login attempts before ban
See .env.example for complete documentation of all options.
SMTP Email Notifications
To enable email notifications for password resets, 2FA codes, and registration approvals:
-
Edit
.envand configure SMTP settings:SMTP_ENABLED=true SMTP_HOST="smtp.gmail.com" SMTP_PORT=587 SMTP_USER="your-email@gmail.com" SMTP_PASSWORD='your-app-password' # Use quotes for special characters SMTP_FROM="noreply@yourdomain.com" -
Deploy changes:
./deploy.sh
See SMTP_SETUP.md for detailed configuration examples and troubleshooting.
Management
The manage.sh script provides common operations:
# View logs
./manage.sh logs # All services
./manage.sh logs authelia # Specific service
# Restart services
./manage.sh restart
# Update to latest images
./manage.sh update
# Backup data volumes
./manage.sh backup
# Restore from backup
./manage.sh restore backup-YYYYMMDD-HHMMSS.tar.gz
# Complete reset (DESTRUCTIVE - deletes all data)
./manage.sh reset
# Check service status
./manage.sh status
Security Features
Secrets Management
All secrets are stored as individual files (not environment variables) following Docker security best practices:
- Mounted read-only to containers
- Not visible in
docker inspector process listings - Individual file permissions (600)
- Never committed to git (secrets/ in .gitignore)
Authentication Layers
- Centralized User Database: Single source of truth in lldap
- SSO Authentication: Authelia validates all login attempts
- Two-Factor Authentication: TOTP enforcement for all services
- Session Management: Configurable expiration and inactivity timeouts
- Rate Limiting: Brute-force protection with automatic bans
Network Security
Hardened Configuration:
- Zero Direct Access: No service ports exposed except through Caddy
- Exposed Ports: Only 80/443 (HTTP/HTTPS) and 2222 (Git SSH)
- Internal Communication: All services use Docker network hostnames
- Authentication Required: Every service requires Authelia login (except public registration form)
- TLS Termination: Caddy handles all HTTPS with automatic certificate management
- Network Isolation: Services isolated on internal Docker bridge network
Port Summary:
80/443→ Caddy (reverse proxy) → All web services2222→ Gitea SSH (for Git operations only)- All other services accessible ONLY via Caddy with Authelia authentication
Protocols & Technologies
This project demonstrates integration of several authentication protocols:
LDAP (Lightweight Directory Access Protocol)
- Implementation: lldap
- RFC: RFC 4511
- Used for: Centralized user/group storage, credential verification
- Resources:
OIDC (OpenID Connect)
- Implementation: Authelia (provider), Gitea (client)
- Spec: OpenID Connect Core 1.0
- Used for: Gitea SSO authentication
- Resources:
Forward-Auth
- Implementation: Authelia (endpoint), Caddy (proxy)
- Used for: Wiki and lldap authentication via trusted headers
- How it works: Proxy delegates authentication to external service before forwarding requests
- Resources:
TOTP (Time-Based One-Time Password)
- Implementation: Authelia, compatible with Google Authenticator/Authy
- RFC: RFC 6238
- Used for: Two-factor authentication
- Resources:
Troubleshooting
Check notifications (2FA codes, password resets)
ssh user@server 'cd ~/org-stack && docker compose exec authelia cat /data/notification.txt'
Or use the manage script:
./manage.sh logs authelia | grep -A 20 "one-time code"
Gitea OIDC not working
Error: "invalid_request" or "redirect_uri does not match"
- Cause: Authentication source name is case-sensitive
- Solution: Name must be exactly
authelia(lowercase) in Gitea admin panel - Why: Gitea uses the name in redirect URI:
/user/oauth2/{name}/callback
Other common issues:
- Verify OIDC configuration uses internal URL:
- Auto Discovery URL:
http://authelia:9091/.well-known/openid-configuration(NOT https://)
- Auto Discovery URL:
- Check Authelia logs:
./manage.sh logs authelia - Verify client secret matches what deploy.sh showed
Services showing 502 errors
- Check if all containers are running:
./manage.sh status - Check Authelia logs:
./manage.sh logs authelia - Verify secrets are mounted:
ssh user@server 'ls -la ~/org-stack/secrets/authelia'
2FA not working
- Verify
REQUIRE_2FA=truein.env - Redeploy:
./deploy.sh - Check Authelia configuration:
cat authelia/configuration.yml | grep policy
Let's Encrypt certificate failures
- Verify DNS points to your server:
dig yourdomain.com - Verify ports 80 and 443 are accessible externally
- Check Caddy logs:
./manage.sh logs caddy - If testing, use self-signed:
USE_SELF_SIGNED_CERTS=truein.env
Backup & Recovery
Backup
./manage.sh backup
Creates timestamped tarball of all Docker volumes in backups/ directory.
Restore
./manage.sh restore backups/backup-YYYYMMDD-HHMMSS.tar.gz
What's Backed Up
- lldap database (all users and groups)
- Authelia data (sessions, 2FA registrations)
- Gitea data (all repositories)
- JSPWiki data (all wiki pages)
- Caddy data (TLS certificates)
Note: The secrets/ directory is NOT backed up by design. Back it up separately and securely.
Development
Testing Changes Locally
# Use self-signed certs to avoid Let's Encrypt rate limits
echo "USE_SELF_SIGNED_CERTS=true" >> .env
# For local testing without DNS, add to /etc/hosts:
echo "127.0.0.1 git.example.com wiki.example.com auth.example.com ldap.example.com" | sudo tee -a /etc/hosts
# Deploy
./deploy.sh
Project Structure
org-stack/
├── .env.example # Configuration template
├── deploy.sh # Automated deployment script
├── manage.sh # Management utilities
├── compose.yml # Docker Compose service definitions
├── Caddyfile.*.template # Caddy templates (prod/test)
├── authelia/
│ └── configuration.yml.template # Authelia config template
├── jspwiki/
│ ├── Dockerfile # Custom JSPWiki image
│ ├── RemoteUserFilter.java # Servlet filter for SSO
│ ├── ldap-sync.sh # LDAP user synchronization
│ ├── configure-web-xml.sh # web.xml modification for container auth
│ └── entrypoint.sh # Container startup script
├── jspwiki-custom.properties # JSPWiki config (container auth)
├── jspwiki.policy # JSPWiki security policy
└── secrets/ # Auto-generated secrets (gitignored)
├── lldap/
│ ├── JWT_SECRET
│ └── LDAP_USER_PASS
└── authelia/
├── JWT_SECRET
├── SESSION_SECRET
├── STORAGE_ENCRYPTION_KEY
├── OIDC_HMAC_SECRET
└── OIDC_PRIVATE_KEY
Production Recommendations
Network Security
- Firewall Rules:
# Allow only necessary ports ufw allow 80/tcp # HTTP (redirects to HTTPS) ufw allow 443/tcp # HTTPS ufw allow 2222/tcp # Git SSH (optional, only if using Git SSH) ufw allow 22/tcp # SSH for server management ufw enable - Port Verification: Only 80, 443, and 2222 should be accessible externally
- DNS Configuration: Ensure A records point to your server for all subdomains
Operational Security
- Backups: Schedule regular backups with
./manage.sh backup - Monitoring: Set up monitoring for container health and authentication failures
- Updates: Regularly update with
./manage.sh update - Secrets Rotation: Periodically regenerate secrets and redeploy
- SMTP: Configure real email notifier in
.env(replace file-based logging)
Performance
- Database: Consider PostgreSQL instead of SQLite for Gitea in high-traffic scenarios
- Rate Limiting: Configure Caddy rate limiting for public endpoints
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Submit a pull request with clear description
License
MIT License - see LICENSE file for details
Acknowledgments
- lldap: Nitnelave and contributors
- Authelia: Authelia team
- Gitea: Gitea maintainers
- JSPWiki: Apache JSPWiki project
- Caddy: Caddy team
This project demonstrates integration of these excellent open-source tools into a cohesive authentication platform.
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Authelia Support: Authelia Discord
- lldap Support: lldap Discussions