Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions containers/caddy/Caddyfile.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Auto-generated Caddyfile for Umbrel
# Do not edit manually - changes will be overwritten

{
http_port 80
https_port 443
auto_https off
admin 0.0.0.0:2019
}

:443 {
tls /certs/umbrel.crt /certs/umbrel.key

# Security headers
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
}

# Default route - show Umbrel welcome
handle / {
respond "Welcome to Umbrel! Access apps at /{app-id}/*" 200
}

# App routes will be dynamically added here
# Example:
# handle /mempool* {
# reverse_proxy mempool_app_proxy:4000
# }
}

:80 {
redir https://{host}{uri} permanent
}
17 changes: 17 additions & 0 deletions containers/caddy/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Build Stage
FROM caddy:2.7.6-alpine AS umbrel-caddy

# Final image
FROM caddy:2.7.6-alpine AS umbrel-caddy-final

# Create caddy directory
WORKDIR /config

# Copy Caddyfile from build stage
COPY --from=umbrel-caddy /usr/bin/caddy /usr/bin/caddy

# Expose HTTP and HTTPS ports
EXPOSE 80 443 2019

# Run Caddy
CMD ["caddy", "run", "--config", "/config/Caddyfile", "--adapter", "caddyfile"]
35 changes: 35 additions & 0 deletions containers/caddy/test/Caddyfile.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Test Caddyfile for Caddy integration tests
# This file is used to test Caddy configuration without umbreld

{
http_port 8080
https_port 8443
auto_https off
admin 0.0.0.0:2019
}

:8443 {
tls /certs/test.crt /certs/test.key

header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
}

handle /app1* {
reverse_proxy test_app1:8888
}

handle /app2* {
reverse_proxy test_app2:8889
}

handle / {
respond "Umbrel Caddy Test Server" 200
}
}

:8080 {
redir https://{host}{uri} permanent
}
34 changes: 34 additions & 0 deletions containers/caddy/test/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
version: '3.7'

services:
caddy:
container_name: test_caddy
image: caddy:2.7.6-alpine
ports:
- "8080:80"
- "8443:443"
volumes:
- ./test/Caddyfile.test:/etc/caddy/Caddyfile:ro
- ./test/certs:/certs:ro
networks:
- test_network

test_app1:
container_name: test_app1
image: mendhak/http-https-echo
environment:
HTTP_PORT: 8888
networks:
- test_network

test_app2:
container_name: test_app2
image: mendhak/http-https-echo
environment:
HTTP_PORT: 8889
networks:
- test_network

networks:
test_network:
driver: bridge
116 changes: 116 additions & 0 deletions containers/caddy/test/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/bin/bash

# Test script for Caddy HTTPS proxy integration
# This script tests the basic functionality of the Caddy reverse proxy

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"

echo "🔧 Setting up Caddy test environment..."

# Create test certificates directory
mkdir -p certs

# Generate test certificates if they don't exist
if [ ! -f certs/test.crt ] || [ ! -f certs/test.key ]; then
echo "📜 Generating test certificates..."
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout certs/test.key \
-out certs/test.crt \
-subj "/CN=umbrel.local/O=Umbrel Test/C=US" \
-addext "subjectAltName=DNS:umbrel.local,DNS:localhost,IP:127.0.0.1" \
2>/dev/null
echo "✓ Certificates generated"
else
echo "✓ Certificates already exist"
fi

# Clean up any existing containers
echo "🧹 Cleaning up existing containers..."
docker compose down --remove-orphans 2>/dev/null || true

# Start test environment
echo "🚀 Starting test environment..."
docker compose up -d

# Wait for containers to be ready
echo "⏳ Waiting for containers to start..."
sleep 5

# Check container health
echo "🏥 Checking container health..."
docker compose ps

# Test HTTP to HTTPS redirect
echo ""
echo "🌐 Testing HTTP to HTTPS redirect..."
HTTP_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/)
if [ "$HTTP_RESPONSE" = "301" ]; then
echo "✓ HTTP redirect working (status: $HTTP_RESPONSE)"
else
echo "✗ HTTP redirect failed (status: $HTTP_RESPONSE)"
fi

# Test HTTPS access
echo ""
echo "🔒 Testing HTTPS access..."
HTTPS_RESPONSE=$(curl -sk -o /dev/null -w "%{http_code}" https://localhost:8443/)
if [ "$HTTPS_RESPONSE" = "200" ]; then
echo "✓ HTTPS access working (status: $HTTPS_RESPONSE)"
else
echo "✗ HTTPS access failed (status: $HTTPS_RESPONSE)"
fi

# Test app1 routing
echo ""
echo "📱 Testing app1 routing..."
APP1_RESPONSE=$(curl -sk -o /dev/null -w "%{http_code}" https://localhost:8443/app1/)
if [ "$APP1_RESPONSE" = "200" ]; then
echo "✓ App1 routing working (status: $APP1_RESPONSE)"
else
echo "✗ App1 routing failed (status: $APP1_RESPONSE)"
fi

# Test app2 routing
echo ""
echo "📱 Testing app2 routing..."
APP2_RESPONSE=$(curl -sk -o /dev/null -w "%{http_code}" https://localhost:8443/app2/)
if [ "$APP2_RESPONSE" = "200" ]; then
echo "✓ App2 routing working (status: $APP2_RESPONSE)"
else
echo "✗ App2 routing failed (status: $APP2_RESPONSE)"
fi

# Test security headers
echo ""
echo "🔐 Testing security headers..."
HEADERS=$(curl -sk -I https://localhost:8443/)
if echo "$HEADERS" | grep -q "Strict-Transport-Security"; then
echo "✓ HSTS header present"
else
echo "✗ HSTS header missing"
fi

if echo "$HEADERS" | grep -q "X-Frame-Options"; then
echo "✓ X-Frame-Options header present"
else
echo "✗ X-Frame-Options header missing"
fi

# Show certificate info
echo ""
echo "📋 Certificate information:"
echo | openssl s_client -connect localhost:8443 -servername umbrel.local 2>/dev/null | \
openssl x509 -noout -subject -issuer -dates 2>/dev/null || echo "Could not retrieve certificate info"

echo ""
echo "✅ Caddy integration tests completed!"
echo ""
echo "To access the test server:"
echo " - Main: https://localhost:8443/"
echo " - App1: https://localhost:8443/app1/"
echo " - App2: https://localhost:8443/app2/"
echo ""
echo "To clean up, run: docker compose down"
14 changes: 11 additions & 3 deletions packages/umbreld/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions packages/umbreld/source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
reboot,
} from './modules/system/system.js'
import {cleanupFactoryResetBackups} from './modules/system/factory-reset.js'
import Caddy from './modules/caddy/index.js'

type StoreSchema = {
version: string
Expand Down Expand Up @@ -132,6 +133,7 @@ export default class Umbreld {
dbus: Dbus
backups: Backups
systemNg: SystemNg
caddy: Caddy
isBackupRestoreFirstStart = false

constructor({
Expand All @@ -158,6 +160,7 @@ export default class Umbreld {
this.dbus = new Dbus(this)
this.backups = new Backups(this)
this.systemNg = new SystemNg(this)
this.caddy = new Caddy(this)
}

async start() {
Expand Down Expand Up @@ -228,6 +231,7 @@ export default class Umbreld {
this.dbus.start(),
this.server.start(),
this.systemNg.start(),
this.caddy.start(),
])

// Start backups last because it depends on files
Expand Down Expand Up @@ -261,6 +265,7 @@ export default class Umbreld {
this.appStore.stop(),
this.dbus.stop(),
this.systemNg.stop(),
this.caddy.stop(),
])
return true
} catch (error) {
Expand Down
Loading