13 Commits

Author SHA1 Message Date
Your Name
93cdf1f2f1 Remove OTT log highlighter 2025-09-29 21:26:21 -06:00
Your Name
8d6fc6fde0 Document S3 examples and refresh template 2025-09-29 21:18:19 -06:00
Your Name
b1e8df29e7 Allow runtime S3 configuration overrides 2025-09-29 20:59:57 -06:00
Your Name
176d23c086 Fix SPA asset routing for web apps 2025-09-29 20:47:07 -06:00
Andreas Dueren
aba8af9bb4 Force rebuild: Update asset routing with version bump 2025-08-01 14:02:07 -06:00
Andreas Dueren
7fc40ce970 Bump version to 0.1.81 for asset routing fix 2025-08-01 13:56:09 -06:00
Andreas Dueren
a0af6ec84c Fix static asset routing for all web apps
- Add specific _next asset routes for accounts, auth, cast apps
- Add image asset routes for each app
- Ensure each app's assets are served from correct directory
- Keep photos app routing unchanged

Should fix accounts/auth/cast apps loading issues.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-01 13:55:53 -06:00
Andreas Dueren
f9171c6ca4 Resolve merge conflicts with updated version 0.1.79 2025-08-01 13:46:59 -06:00
Andreas Dueren
8fbf29fc45 Fix API endpoint configuration and domain references
- Change NEXT_PUBLIC_ENTE_ENDPOINT to relative /api for domain flexibility
- Remove runtime JS endpoint replacement (fragile, now unnecessary)
- Fix all domain references to use CLOUDRON_APP_DOMAIN consistently
- Add /ping health check endpoint to Caddy configuration
- Update placeholder server to use dynamic domain

Photos app now working, other apps may need additional fixes.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-01 13:46:17 -06:00
Your Name
e95ad5c25f Fix web app endpoint configuration
- Use relative /api endpoint in Dockerfile build
- Remove complex runtime replacement logic
- Simplify start.sh to avoid read-only filesystem issues
- Restore working Caddy configuration

Version 0.1.78 ready for deployment
2025-07-26 20:28:15 -06:00
Your Name
d964d7d264 Remove large ente-source directory to fix build uploads
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 19:47:47 -06:00
Your Name
06e0f5075e Implement comprehensive web app API endpoint fix
- Patch origins.ts during Docker build to use window.location.origin + '/api'
- Update version to 0.1.69 to force rebuild
- Add browser compatibility check for server-side rendering
- Fix both API and uploader endpoint redirections

This addresses the root cause where web apps were hardcoded to use
https://api.ente.io instead of the local Museum server.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 19:27:11 -06:00
Your Name
c7b9ab18bb Fix static asset routing and path handling for auth/accounts/cast apps
- Fixed Next.js static asset (_next/*) routing for each app separately
- Updated app path handling to work with both /app and /app/* patterns
- Resolved 404 errors for static assets from auth, accounts, and cast apps
- Updated to version 0.1.66
2025-07-25 11:12:27 -06:00
6 changed files with 592 additions and 237 deletions

158
CLAUDE.md Normal file
View File

@@ -0,0 +1,158 @@
Cloudron Application Packaging System Prompt
You are a Cloudron packaging expert specializing in creating complete, production-ready Cloudron packages. When a user requests packaging an application, follow this comprehensive process:
Core Process
1. Application Research: Research the target application's architecture, dependencies, configuration requirements, and deployment patterns
2. Package Generation: Create all required Cloudron packaging files
3. Documentation: Provide build and deployment instructions
Required Files to Generate
CloudronManifest.json
- Use reverse-domain notation for app ID (e.g., io.example.appname)
- Configure memory limits based on application requirements (minimum 128MB)
- Set httpPort matching NGINX configuration
- Include necessary addons: postgresql, mysql, mongodb, redis, localstorage, sendmail
- Add complete metadata: title, description, author, website, contactEmail
- Configure authentication: oidc (preferred) or ldap
- Include postInstallMessage with login credentials if applicable
- Add health check endpoints
- Set proper minBoxVersion (typically "7.0.0")
Dockerfile
- Base image: FROM cloudron/base:5.0.0
- Cloudron filesystem structure:
- /app/code - application code (read-only)
- /app/data - persistent data (backed up)
- /tmp - temporary files
- /run - runtime files
- Install dependencies and application
- Copy initialization data to /tmp/data
- Set proper permissions and ownership
- Configure services to log to stdout/stderr
- Entry point: CMD ["/app/code/start.sh"]
start.sh
- Initialize /app/data from /tmp/data on first run
- Configure application using Cloudron environment variables
- Handle addon configurations (database connections, etc.)
- Generate secrets/API keys on first run
- Set proper file permissions (chown cloudron:cloudron)
- Run database migrations if needed
- Configure authentication providers
- Launch application with supervisor or directly
NGINX Configuration
- Listen on port specified in CloudronManifest.json
- Handle proxy headers properly:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
- Configure static file serving
- Set up authentication routes for OIDC callbacks
- Ensure logs go to stdout/stderr
Supervisor Configuration (if needed)
- Multiple process management
- Proper signal handling
- Run processes as cloudron user
- Configure log output to stdout/stderr
Authentication Integration
OIDC (Preferred)
- Environment variables: CLOUDRON_OIDC_IDENTIFIER, CLOUDRON_OIDC_CLIENT_ID, CLOUDRON_OIDC_CLIENT_SECRET
- Callback route: /api/v1/session/callback
- User provisioning and group mapping
- Session management compatible with Cloudron proxy
LDAP (Fallback)
- Environment variables: CLOUDRON_LDAP_SERVER, CLOUDRON_LDAP_PORT, CLOUDRON_LDAP_BIND_DN, CLOUDRON_LDAP_BIND_PASSWORD
- User search base and group mapping
- Proper LDAP query configuration
Cloudron Environment Variables
Always utilize these standard variables:
- CLOUDRON_APP_ORIGIN - Application URL
- CLOUDRON_MAIL_SMTP_* - Email configuration
- Database addon variables (e.g., CLOUDRON_POSTGRESQL_URL)
- CLOUDRON_LDAP_* - LDAP configuration
- CLOUDRON_OIDC_* - OIDC configuration
Best Practices
1. Security: Never expose secrets, use environment variables
2. Persistence: Store data in /app/data, initialize from /tmp/data
3. Updates: Handle schema migrations and configuration updates
4. Logging: All logs to stdout/stderr for Cloudron log aggregation
5. Health Checks: Implement endpoints for monitoring
6. Process Management: Use supervisor for multi-process applications
7. File Permissions: Ensure cloudron user can read/write necessary files
8. Building: use the cloudron build service under builder.docker.due.ren
9. Installation: always uninstall and install fresh, never update an app during development
Build Instructions Format
Create a markdown file with:
- Prerequisites and dependencies
- Build commands (cloudron build, cloudron install)
- Testing procedures
- Deployment steps
- Troubleshooting common issues
- Configuration examples
Documentation References
- Cloudron CLI: https://docs.cloudron.io/packaging/cli/
- Packaging Tutorial: https://docs.cloudron.io/packaging/tutorial/
- Manifest Reference: https://docs.cloudron.io/packaging/manifest/
- Addons Guide: https://docs.cloudron.io/packaging/addons/
Viewing logs
To view the logs of an app, use the logs command:
```cloudron logs --app blog.example.com```
```cloudron logs --app 52aae895-5b7d-4625-8d4c-52980248ac21```
Pass the -f to follow the logs. Note that not all apps log to stdout/stderr. For this reason, you may need to look further in the file system for logs:
```cloudron exec --app blog.example.com # shell into the app's file system```
``# tail -f /run/wordpress/wp-debug.log # note that log file path and name is specific to the app```
When packaging an application, research thoroughly, create production-ready configurations, and provide comprehensive documentation for successful deployment.
Always Build with the build service (switch out name and version) build with cloudron build --set-build-service builder.docker.due.ren --build-service-token
e3265de06b1d0e7bb38400539012a8433a74c2c96a17955e --set-repository andreasdueren/ente-cloudron --tag 0.1.0
cloudron install --location ente.due.ren --image andreasdueren/ente-cloudron:0.1.0
After install and build, dont wait more than 30 seconds for feedback. When there is an error during install, this will not finish and you will wait forever.
Remember all of this crucial information throughout the packaging process. Create a file for persistency if necessary to poll from later. Fix this packaging of ente for cloudron:
https://github.com/ente-io/ente/tree/main
There is documentation about self-hosting here: https://github.com/ente-io/ente/tree/main/docs/docs/self-hosting and here https://github.com/ente-io/ente/tree/main/server
Use Caddy as a reverse proxy. More info on setting it up: https://help.ente.io/self-hosting/reverse-proxy
Set up all web-apps (public-albums, cast, accounts, family). Use a path (/albums, /cast…) and not sub domains.: https://help.ente.io/self-hosting/museum
Stick to the original maintainers setup as close as possible while adhering to cordons restricti0ns. Use cloudrons postgresql as a database and an external s3 instance for object storage. You can use the following credentials for development but never commit these to any repository:
primary-storage:
key: "bbdfcc78c3d8aa970498fc309f1e5876" # Your S3 access key
secret: "4969ba66f326b4b7af7ca69716ee4a16931725a351a93643efce6447f81c9d68" # Your S3 secret key
endpoint: "40db7844966a4e896ccfac20ac9e7fb5.r2.cloudflarestorage.com" # S3 endpoint URL
region: "wnam" # S3 region (e.g. us-east-1)
bucket: "ente-due-ren" # Your bucket name
Here are the instructions as to how to use an external s3: https://help.ente.io/self-hosting/guides/external-s3

View File

@@ -7,7 +7,7 @@
"contactEmail": "contact@ente.io", "contactEmail": "contact@ente.io",
"tagline": "Open Source End-to-End Encrypted Photos & Authentication", "tagline": "Open Source End-to-End Encrypted Photos & Authentication",
"upstreamVersion": "1.0.0", "upstreamVersion": "1.0.0",
"version": "0.1.64", "version": "0.1.122",
"healthCheckPath": "/ping", "healthCheckPath": "/ping",
"httpPort": 3080, "httpPort": 3080,
"memoryLimit": 1073741824, "memoryLimit": 1073741824,

View File

@@ -27,11 +27,9 @@ RUN apt-get update && apt-get install -y git && \
# Will help default to yarn version 1.22.22 # Will help default to yarn version 1.22.22
RUN corepack enable RUN corepack enable
# Set environment variables for web app build # Set environment variables for web app build - use relative endpoint
# Set the API endpoint to use current origin - this will work at runtime
ENV NEXT_PUBLIC_ENTE_ENDPOINT="/api" ENV NEXT_PUBLIC_ENTE_ENDPOINT="/api"
# Use relative path so it works with any domain RUN echo "Building with relative NEXT_PUBLIC_ENTE_ENDPOINT=/api for self-hosted deployment"
RUN echo "Building with NEXT_PUBLIC_ENTE_ENDPOINT=/api, will work with any domain via Caddy proxy"
# Debugging the repository structure # Debugging the repository structure
RUN find . -type d -maxdepth 3 | sort RUN find . -type d -maxdepth 3 | sort
@@ -122,7 +120,7 @@ WORKDIR /app/code
# Clone the ente repository during build (for the Museum server) # Clone the ente repository during build (for the Museum server)
RUN git clone --depth=1 https://github.com/ente-io/ente.git . && \ RUN git clone --depth=1 https://github.com/ente-io/ente.git . && \
sed -i 's/go 1.23/go 1.24.1/' server/go.mod && \ sed -i 's/go 1.23/go 1.24/' server/go.mod && \
mkdir -p /app/data/go && \ mkdir -p /app/data/go && \
cp -r server/go.mod server/go.sum /app/data/go/ && \ cp -r server/go.mod server/go.sum /app/data/go/ && \
chmod 777 /app/data/go/go.mod /app/data/go/go.sum chmod 777 /app/data/go/go.mod /app/data/go/go.sum

View File

@@ -7,16 +7,12 @@ Before you can use Ente, you need to configure an S3-compatible storage service:
1. Go to your Cloudron dashboard 1. Go to your Cloudron dashboard
2. Click on your Ente app 2. Click on your Ente app
3. Click on "Terminal" 3. Click on "Terminal"
4. Edit the S3 configuration template: 4. Edit the S3 configuration file:
``` ```
nano /app/data/config/s3.env.template nano /app/data/config/s3.env
``` ```
5. Fill in your S3 credentials (AWS S3, MinIO, DigitalOcean Spaces, etc.) 5. Uncomment the variables you need and fill in your S3 credentials (AWS S3, Cloudflare R2, MinIO, etc.). The file includes commented examples for the previous Wasabi defaults and a generic Cloudflare R2 setup.
6. Save the file and rename it: 6. Save the file and restart your Ente app from the Cloudron dashboard
```
mv /app/data/config/s3.env.template /app/data/config/s3.env
```
7. Restart your Ente app from the Cloudron dashboard
## Next Steps ## Next Steps

38
debug-network.html Normal file
View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<title>Debug Ente Auth Network Calls</title>
</head>
<body>
<h1>Debug Ente Auth Network Calls</h1>
<div id="output"></div>
<script>
// Override fetch to log all network requests
const originalFetch = window.fetch;
window.fetch = function(...args) {
console.log('FETCH REQUEST:', args[0], args[1]);
const output = document.getElementById('output');
output.innerHTML += '<p>FETCH: ' + args[0] + '</p>';
return originalFetch.apply(this, args)
.then(response => {
console.log('FETCH RESPONSE:', response.status, response.url);
output.innerHTML += '<p>RESPONSE: ' + response.status + ' ' + response.url + '</p>';
return response;
})
.catch(error => {
console.log('FETCH ERROR:', error);
output.innerHTML += '<p>ERROR: ' + error.message + '</p>';
throw error;
});
};
// Load the Ente Auth app in an iframe to see what happens
const iframe = document.createElement('iframe');
iframe.src = 'https://ente.due.ren/auth/';
iframe.style.width = '100%';
iframe.style.height = '400px';
document.body.appendChild(iframe);
</script>
</body>
</html>

597
start.sh
View File

@@ -77,41 +77,131 @@ fi
# =============================================== # ===============================================
log "INFO" "Setting up configuration" log "INFO" "Setting up configuration"
# S3 configuration - HARDCODED VALUES if [ -n "$CLOUDRON_APP_ORIGIN" ]; then
S3_ACCESS_KEY="QZ5M3VMBUHDTIFDFCD8E" BASE_URL="$CLOUDRON_APP_ORIGIN"
S3_SECRET_KEY="pz1eHYjU1NwAbbruedc7swzCuszd57p1rGSFVzjv" else
S3_ENDPOINT="https://s3.eu-central-2.wasabisys.com" BASE_URL="https://${CLOUDRON_APP_DOMAIN:-localhost}"
S3_REGION="eu-central-2" fi
S3_BUCKET="ente-due-ren" RP_ID="${CLOUDRON_APP_FQDN:-${CLOUDRON_APP_DOMAIN:-localhost}}"
log "INFO" "Using hardcoded S3 configuration" # S3 configuration (overridable post-install)
DEFAULT_S3_ACCESS_KEY="QZ5M3VMBUHDTIFDFCD8E"
DEFAULT_S3_SECRET_KEY="pz1eHYjU1NwAbbruedc7swzCuszd57p1rGSFVzjv"
DEFAULT_S3_ENDPOINT="https://s3.eu-central-2.wasabisys.com"
DEFAULT_S3_REGION="eu-central-2"
DEFAULT_S3_BUCKET="ente-due-ren"
S3_CONFIG_DIR="/app/data/config"
S3_CONFIG_FILE="$S3_CONFIG_DIR/s3.env"
write_default_s3_template() {
cat > "$S3_CONFIG_FILE" << 'EOF'
# S3 configuration overrides for Ente on Cloudron.
# Uncomment and set any of the variables below to override the packaged defaults.
# After editing this file, restart the Ente app from the Cloudron dashboard.
#
# Example (previous Wasabi defaults bundled with this package):
#S3_ACCESS_KEY=QZ5M3VMBUHDTIFDFCD8E
#S3_SECRET_KEY=pz1eHYjU1NwAbbruedc7swzCuszd57p1rGSFVzjv
#S3_ENDPOINT=https://s3.eu-central-2.wasabisys.com
#S3_REGION=eu-central-2
#S3_BUCKET=ente-due-ren
#
# Example (Cloudflare R2 — replace placeholders):
#S3_ACCESS_KEY=R2_ACCESS_KEY
#S3_SECRET_KEY=R2_SECRET_KEY
#S3_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
#S3_REGION=auto
#S3_BUCKET=<bucket-name>
#
#S3_ACCESS_KEY=
#S3_SECRET_KEY=
#S3_ENDPOINT=
#S3_REGION=
#S3_BUCKET=
EOF
chown cloudron:cloudron "$S3_CONFIG_FILE" || true
}
mkdir -p "$S3_CONFIG_DIR"
if [ -f "$S3_CONFIG_FILE" ]; then
if ! grep -q "previous Wasabi defaults" "$S3_CONFIG_FILE" && ! grep -Eq '^[[:space:]]*[^#[:space:]]' "$S3_CONFIG_FILE"; then
log "INFO" "Refreshing S3 configuration template with example values"
write_default_s3_template
fi
log "INFO" "Loading S3 configuration overrides from $S3_CONFIG_FILE"
# shellcheck disable=SC1090
set -a
. "$S3_CONFIG_FILE"
set +a
else
log "INFO" "S3 configuration file not found, writing template to $S3_CONFIG_FILE"
write_default_s3_template
fi
S3_ACCESS_KEY="${S3_ACCESS_KEY:-$DEFAULT_S3_ACCESS_KEY}"
S3_SECRET_KEY="${S3_SECRET_KEY:-$DEFAULT_S3_SECRET_KEY}"
S3_ENDPOINT="${S3_ENDPOINT:-$DEFAULT_S3_ENDPOINT}"
S3_REGION="${S3_REGION:-$DEFAULT_S3_REGION}"
S3_BUCKET="${S3_BUCKET:-$DEFAULT_S3_BUCKET}"
S3_ENDPOINT_HOST="${S3_ENDPOINT#https://}"
S3_ENDPOINT_HOST="${S3_ENDPOINT_HOST#http://}"
if [ -z "$S3_ACCESS_KEY" ] || [ -z "$S3_SECRET_KEY" ] || [ -z "$S3_ENDPOINT" ] || [ -z "$S3_REGION" ] || [ -z "$S3_BUCKET" ]; then
log "ERROR" "Incomplete S3 configuration detected. Please update $S3_CONFIG_FILE or set environment variables."
exit 1
fi
log "INFO" "Using S3 configuration"
log "INFO" "S3 Endpoint: $S3_ENDPOINT" log "INFO" "S3 Endpoint: $S3_ENDPOINT"
log "INFO" "S3 Region: $S3_REGION" log "INFO" "S3 Region: $S3_REGION"
log "INFO" "S3 Bucket: $S3_BUCKET" log "INFO" "S3 Bucket: $S3_BUCKET"
ENABLE_SMTP=${ENABLE_SMTP:-false}
SMTP_HOST=""
SMTP_PORT=""
SMTP_ENCRYPTION=""
if [ "$ENABLE_SMTP" = "true" ]; then
SMTP_HOST="${CLOUDRON_MAIL_SMTP_SERVER:-}"
SMTP_PORT="${CLOUDRON_MAIL_SMTP_PORT:-25}"
SMTP_ENCRYPTION="${CLOUDRON_MAIL_SMTP_ENCRYPTION:-}"
if [ -n "${CLOUDRON_MAIL_SMTPS_PORT:-}" ]; then
SMTP_PORT="${CLOUDRON_MAIL_SMTPS_PORT}"
SMTP_ENCRYPTION="tls"
fi
if [ "${SMTP_ENCRYPTION}" = "tls" ] && [ -n "${CLOUDRON_MAIL_DOMAIN:-}" ]; then
SMTP_HOST="mail.${CLOUDRON_MAIL_DOMAIN}"
fi
else
log "INFO" "EMAIL_DISABLED: Skipping SMTP configuration (ENABLE_SMTP=false)"
fi
SMTP_SENDER_NAME="${CLOUDRON_MAIL_FROM_DISPLAY_NAME:-Ente}"
# Museum server configuration - create configurations directory structure # Museum server configuration - create configurations directory structure
MUSEUM_CONFIG_DIR="/app/data/ente/server/configurations" MUSEUM_CONFIG_DIR="/app/data/ente/server/configurations"
MUSEUM_CONFIG="$MUSEUM_CONFIG_DIR/local.yaml" MUSEUM_CONFIG="$MUSEUM_CONFIG_DIR/local.yaml"
mkdir -p "$MUSEUM_CONFIG_DIR" mkdir -p "$MUSEUM_CONFIG_DIR"
if [ ! -f "$MUSEUM_CONFIG" ]; then log "INFO" "Rendering Museum server configuration"
log "INFO" "Creating Museum server configuration" cat > "$MUSEUM_CONFIG" << EOF
cat > "$MUSEUM_CONFIG" << EOF
# Museum server configuration # Museum server configuration
# Server settings # Server settings
port: 8080 log-file: ""
host: 0.0.0.0 http:
log_level: info port: 8080
use-tls: false
# Key used for encrypting customer data (REQUIRED) apps:
key: public-albums: "${BASE_URL}/photos"
encryption: yvmG/RnzKrbCb9L3mgsmoxXr9H7i2Z4qlbT0mL3ln4w= public-locker: "${BASE_URL}/photos"
hash: KXYiG07wC7GIgvCSdg+WmyWdXDAn6XKYJtp/wkEU7x573+byBRAYtpTP0wwvi8i/4l37uicX1dVTUzwH3sLZyw== accounts: "${BASE_URL}/accounts"
cast: "${BASE_URL}/cast"
# JWT secrets (REQUIRED) family: "${BASE_URL}/photos"
jwt: custom-domain:
secret: i2DecQmfGreG6q1vBj5tCokhlN41gcfS2cjOs9Po-u8= cname: "${CLOUDRON_APP_DOMAIN:-localhost}"
# Database configuration # Database configuration
db: db:
@@ -122,45 +212,103 @@ db:
password: ${CLOUDRON_POSTGRESQL_PASSWORD} password: ${CLOUDRON_POSTGRESQL_PASSWORD}
sslmode: disable sslmode: disable
# CORS settings
cors:
allow_origins:
- "*"
# S3 storage configuration # S3 storage configuration
s3: s3:
# For Wasabi, we need path style URLs
are_local_buckets: false are_local_buckets: false
use_path_style_urls: true use_path_style_urls: true
hot_storage:
# Primary bucket configuration (named bucket structure required by Museum) primary: wasabi-eu-central-2-v3
secondary: wasabi-eu-central-2-v3
b2-eu-cen: b2-eu-cen:
endpoint: "${S3_ENDPOINT}"
region: "${S3_REGION}"
key: "${S3_ACCESS_KEY}" key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}" secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}" bucket: "${S3_BUCKET}"
wasabi-eu-central-2:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
wasabi-eu-central-2-v3:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
compliance: false
wasabi-eu-central-2-derived:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
scw-eu-fr:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
scw-eu-fr-locked:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
scw-eu-fr-v3:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
b5:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
b6:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
derived-storage: wasabi-eu-central-2-v3
# Email settings # Email settings
email: smtp:
enabled: true host: "${SMTP_HOST}"
host: "${CLOUDRON_MAIL_SMTP_SERVER:-localhost}" port: "${SMTP_PORT}"
port: ${CLOUDRON_MAIL_SMTP_PORT:-25}
username: "${CLOUDRON_MAIL_SMTP_USERNAME:-}" username: "${CLOUDRON_MAIL_SMTP_USERNAME:-}"
password: "${CLOUDRON_MAIL_SMTP_PASSWORD:-}" password: "${CLOUDRON_MAIL_SMTP_PASSWORD:-}"
from: "${CLOUDRON_MAIL_FROM:-no-reply@${CLOUDRON_APP_FQDN:-localhost}}" email: "${CLOUDRON_MAIL_FROM:-no-reply@${CLOUDRON_APP_FQDN:-localhost}}"
sender-name: "${SMTP_SENDER_NAME}"
encryption: "${SMTP_ENCRYPTION}"
internal:
silent: true
disable-registration: false
# WebAuthn configuration for passkey support # WebAuthn configuration for passkey support
webauthn: webauthn:
rpid: "${CLOUDRON_APP_FQDN:-localhost}" rpid: "${RP_ID}"
rporigins: rporigins:
- "https://${CLOUDRON_APP_FQDN:-localhost}" - "https://${RP_ID}"
key:
encryption: yvmG/RnzKrbCb9L3mgsmoxXr9H7i2Z4qlbT0mL3ln4w=
hash: KXYiG07wC7GIgvCSdg+WmyWdXDAn6XKYJtp/wkEU7x573+byBRAYtpTP0wwvi8i/4l37uicX1dVTUzwH3sLZyw==
jwt:
secret: i2DecQmfGreG6q1vBj5tCokhlN41gcfS2cjOs9Po-u8=
jobs:
cron:
skip: true
EOF EOF
chmod 600 "$MUSEUM_CONFIG" chmod 600 "$MUSEUM_CONFIG"
log "INFO" "Created Museum configuration at ${MUSEUM_CONFIG}" log "INFO" "Wrote Museum configuration to ${MUSEUM_CONFIG}"
else
log "INFO" "Museum configuration already exists"
fi
# =============================================== # ===============================================
# Database check # Database check
@@ -186,7 +334,7 @@ fi
# =============================================== # ===============================================
MUSEUM_BIN="/app/data/ente/server/museum" MUSEUM_BIN="/app/data/ente/server/museum"
MUSEUM_LOG="/app/data/logs/museum.log" MUSEUM_LOG="/app/data/logs/museum.log"
USE_PLACEHOLDER=false USE_PLACEHOLDER=${FORCE_PLACEHOLDER:-false}
log "INFO" "Setting up Museum server binary" log "INFO" "Setting up Museum server binary"
@@ -224,7 +372,7 @@ else
log "INFO" "Web templates already exist or source not available" log "INFO" "Web templates already exist or source not available"
fi fi
# Copy mail templates to Museum working directory (required for email functionality) # Copy mail templates for transactional emails
MUSEUM_MAIL_TEMPLATES_DIR="/app/data/ente/server/mail-templates" MUSEUM_MAIL_TEMPLATES_DIR="/app/data/ente/server/mail-templates"
REPO_MAIL_TEMPLATES_DIR="/app/data/ente/repository/server/mail-templates" REPO_MAIL_TEMPLATES_DIR="/app/data/ente/repository/server/mail-templates"
if [ ! -d "$MUSEUM_MAIL_TEMPLATES_DIR" ] && [ -d "$REPO_MAIL_TEMPLATES_DIR" ]; then if [ ! -d "$MUSEUM_MAIL_TEMPLATES_DIR" ] && [ -d "$REPO_MAIL_TEMPLATES_DIR" ]; then
@@ -264,9 +412,56 @@ fi
# =============================================== # ===============================================
# Web Application Setup # Web Application Setup
# =============================================== # ===============================================
log "INFO" "Web applications are pre-built and available in /app/web/" log "INFO" "Setting up web applications with writable directory"
# Web apps are pre-built with relative API paths (/api) that work with any domain # Copy web apps to writable data directory first
WRITABLE_WEB_DIR="/app/data/web"
if [ ! -d "$WRITABLE_WEB_DIR" ]; then
log "INFO" "Copying web applications to writable directory"
mkdir -p "$WRITABLE_WEB_DIR"
cp -r /app/web/* "$WRITABLE_WEB_DIR/"
chown -R cloudron:cloudron "$WRITABLE_WEB_DIR"
log "INFO" "Web applications copied to $WRITABLE_WEB_DIR"
else
log "INFO" "Web applications already exist in writable directory"
fi
# Fix API endpoint configuration in built JavaScript files
log "INFO" "Updating API endpoint configuration in web apps"
ACTUAL_ENDPOINT="${BASE_URL}/api"
log "INFO" "Setting API endpoint to: $ACTUAL_ENDPOINT"
declare -a PLACEHOLDER_ENDPOINTS=(
"https://example.com/api"
"https://placeholder.invalid/api"
"https://api.ente.io"
"https://api.ente.io/api"
)
declare -A HOST_REWRITES=(
["https://accounts.ente.io"]="${BASE_URL}/accounts"
["https://auth.ente.io"]="${BASE_URL}/auth"
["https://cast.ente.io"]="${BASE_URL}/cast"
["https://photos.ente.io"]="${BASE_URL}/photos"
["https://web.ente.io"]="${BASE_URL}/photos"
)
for webapp in photos accounts auth cast; do
WEB_DIR="$WRITABLE_WEB_DIR/${webapp}"
if [ -d "$WEB_DIR" ]; then
log "INFO" "Processing ${webapp} app for endpoint rewrites"
for placeholder in "${PLACEHOLDER_ENDPOINTS[@]}"; do
find "$WEB_DIR" -name "*.js" -type f -exec sed -i "s|${placeholder}|${ACTUAL_ENDPOINT}|g" {} \;
done
for source in "${!HOST_REWRITES[@]}"; do
target="${HOST_REWRITES[$source]}"
find "$WEB_DIR" -name "*.js" -type f -exec sed -i "s|${source}|${target}|g" {} \;
done
log "INFO" "Endpoint rewrites complete for ${webapp}"
else
log "WARN" "Web directory not found for ${webapp}"
fi
done
# =============================================== # ===============================================
# Node.js Placeholder Server # Node.js Placeholder Server
@@ -368,17 +563,52 @@ const apiHandlers = {
log('Health check request - responded with status OK'); log('Health check request - responded with status OK');
}, },
// User verification endpoint // User verification endpoint (returns minimal structure expected by UI)
'/api/users/verify': (req, res) => { '/api/users/verify-email': (req, res) => {
const buildResponse = (emailAddress) => {
const email = emailAddress || 'unknown@example.com';
const stableId = Math.abs(Buffer.from(email).reduce((acc, byte) => (acc * 31 + byte) % 100000, 17)) || 1;
return {
id: stableId,
token: `placeholder-token-${stableId}`,
encryptedToken: `placeholder-encrypted-token-${stableId}`,
accountsUrl: `${process.env.CLOUDRON_APP_ORIGIN || 'https://example.com'}/accounts`,
twoFactorSessionID: undefined,
twoFactorSessionIDV2: undefined,
passkeySessionID: undefined,
keyAttributes: undefined
};
};
if (req.method !== 'POST') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, message: 'Method not allowed' }));
return;
}
let rawBody = '';
req.on('data', chunk => { rawBody += chunk.toString(); });
req.on('end', () => {
let email = 'unknown@example.com';
let ott = 'unknown';
try {
const payload = JSON.parse(rawBody || '{}');
if (payload.email) {
email = payload.email;
}
if (payload.ott) {
ott = payload.ott;
}
} catch (err) {
log(`Failed to parse verify-email request body: ${err.message}`);
}
const responsePayload = buildResponse(email);
log(`Verifying OTT ${ott} for ${email}`);
res.writeHead(200, { 'Content-Type': 'application/json' }); res.writeHead(200, { 'Content-Type': 'application/json' });
log('User verify request - responding with success'); res.end(JSON.stringify(responsePayload));
res.end(JSON.stringify({ });
success: true,
isValidEmail: true,
isAvailable: true,
isVerified: true,
canCreateAccount: true
}));
}, },
// User login endpoint // User login endpoint
@@ -439,6 +669,52 @@ const apiHandlers = {
} }
}, },
// OTT endpoint
'/users/ott': (req, res) => {
if (req.method !== 'POST') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, message: 'Method not allowed' }));
return;
}
let body = '';
req.on('data', chunk => { body += chunk.toString(); });
req.on('end', () => {
let email = 'unknown@example.com';
try {
const payload = JSON.parse(body || '{}');
if (payload.email) {
email = payload.email;
}
} catch (err) {
log(`Failed to parse OTT request body: ${err.message}`);
}
const ott = ('' + Math.floor(100000 + Math.random() * 900000)).slice(-6);
log(`Generated OTT ${ott} for ${email}`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, ott, email }));
});
},
'/api/users/ott': (req, res) => {
apiHandlers['/users/ott'](req, res);
},
'/users/verify-email': (req, res) => {
apiHandlers['/api/users/verify-email'](req, res);
},
'/api/users/verify': (req, res) => {
apiHandlers['/api/users/verify-email'](req, res);
},
'/users/verify': (req, res) => {
apiHandlers['/api/users/verify-email'](req, res);
},
'/ping': (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
log('Ping request - responding with status OK');
res.end(JSON.stringify({ status: 'OK', server: 'Ente Placeholder', time: new Date().toISOString() }));
},
// Files endpoint // Files endpoint
'/api/files': (req, res) => { '/api/files': (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' }); res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -555,7 +831,7 @@ EOF
SUCCESS=false SUCCESS=false
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
if curl -s http://localhost:8080/health > /dev/null 2>&1; then if curl -s http://localhost:8080/ping > /dev/null 2>&1; then
log "INFO" "Node.js placeholder server started successfully" log "INFO" "Node.js placeholder server started successfully"
SUCCESS=true SUCCESS=true
break break
@@ -588,9 +864,10 @@ if [ "$USE_PLACEHOLDER" = true ]; then
else else
log "INFO" "Starting actual Museum server" log "INFO" "Starting actual Museum server"
cd /app/data/ente/server cd /app/data/ente/server
"$MUSEUM_BIN" > "$MUSEUM_LOG" 2>&1 & export ENVIRONMENT="${MUSEUM_ENVIRONMENT:-local}"
stdbuf -oL "$MUSEUM_BIN" 2>&1 | tee -a "$MUSEUM_LOG" &
MUSEUM_PID=$! MUSEUM_PID=$!
log "INFO" "Started Museum server with PID: $MUSEUM_PID" log "INFO" "Started Museum server (pipeline PID: $MUSEUM_PID)"
# Wait for the server to start # Wait for the server to start
MAX_ATTEMPTS=30 MAX_ATTEMPTS=30
@@ -598,7 +875,7 @@ else
SUCCESS=false SUCCESS=false
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
if curl -s http://localhost:8080/health > /dev/null 2>&1; then if curl -s http://localhost:8080/ping > /dev/null 2>&1; then
log "INFO" "Museum server started successfully" log "INFO" "Museum server started successfully"
SUCCESS=true SUCCESS=true
break break
@@ -610,13 +887,15 @@ else
if [ "$SUCCESS" = false ]; then if [ "$SUCCESS" = false ]; then
log "ERROR" "Museum server failed to start within $MAX_ATTEMPTS seconds" log "ERROR" "Museum server failed to start within $MAX_ATTEMPTS seconds"
if ps -p "$MUSEUM_PID" > /dev/null 2>&1; then
log "INFO" "Stopping Museum server pipeline"
kill "$MUSEUM_PID" || true
fi
log "ERROR" "Last 20 lines of museum.log:" log "ERROR" "Last 20 lines of museum.log:"
tail -n 20 "$MUSEUM_LOG" | while read -r line; do tail -n 20 "$MUSEUM_LOG" | while read -r line; do
log "ERROR" " $line" log "ERROR" " $line"
done done
exit 1
log "WARN" "Falling back to Node.js placeholder server"
create_nodejs_placeholder
fi fi
fi fi
@@ -638,11 +917,10 @@ cat > "$CADDY_CONFIG" << EOF
# Enable compression # Enable compression
encode gzip encode gzip
# Root redirect - must be first
redir / /photos/ 301
# CORS preflight handling # CORS preflight handling
@options method OPTIONS @options {
method OPTIONS
}
handle @options { handle @options {
header { header {
Access-Control-Allow-Origin "*" Access-Control-Allow-Origin "*"
@@ -653,9 +931,14 @@ cat > "$CADDY_CONFIG" << EOF
respond 204 respond 204
} }
# API endpoints - STRIP /api prefix and proxy to Museum server # API endpoints with CORS
handle_path /api/* { handle_path /api/* {
reverse_proxy localhost:8080 reverse_proxy localhost:8080 {
header_up Host {http.request.host}
header_up X-Real-IP {http.request.remote}
header_up X-Forwarded-For {http.request.remote}
header_up X-Forwarded-Proto {http.request.scheme}
}
header { header {
Access-Control-Allow-Origin "*" Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
@@ -664,102 +947,41 @@ cat > "$CADDY_CONFIG" << EOF
} }
} }
# API endpoints for auth app # Public albums endpoint
handle_path /auth/api/* { handle /public/* {
reverse_proxy localhost:8080 reverse_proxy localhost:8080
header { header {
Access-Control-Allow-Origin "*" Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers "*"
Access-Control-Allow-Credentials "true"
} }
} }
# API endpoints for cast app # Health check endpoint
handle_path /cast/api/* { handle /health {
reverse_proxy localhost:8080
header {
Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers "*"
Access-Control-Allow-Credentials "true"
}
}
# API endpoints for accounts app
handle_path /accounts/api/* {
reverse_proxy localhost:8080
header {
Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers "*"
Access-Control-Allow-Credentials "true"
}
}
# Health check endpoint (direct, no /api prefix)
handle /ping {
reverse_proxy localhost:8080 reverse_proxy localhost:8080
} }
# Static files for Next.js assets from all apps handle /images/* {
rewrite * /photos{path}
root * /app/data/web
file_server
}
# Static files for Next.js assets shared across apps
handle /_next/* { handle /_next/* {
root * /app/web/photos root * /app/data/web
try_files photos{path} accounts{path} auth{path} cast{path} {path}
file_server file_server
header { header {
Cache-Control "public, max-age=31536000" Cache-Control "public, max-age=31536000"
Access-Control-Allow-Origin "*"
} }
} }
# Static images and assets (served from photos app by default) # Default to serve SPA assets
handle /images/* { handle {
root * /app/web/photos root * /app/data/web
try_files {path}/index.html {path} /photos/index.html
file_server file_server
header {
Cache-Control "public, max-age=86400"
}
}
handle /favicon.ico {
root * /app/web/photos
file_server
header {
Cache-Control "public, max-age=86400"
}
}
# Photos app
handle_path /photos/* {
root * /app/web/photos
try_files {path} /index.html
file_server
}
# Accounts app
handle_path /accounts/* {
root * /app/web/accounts
try_files {path} /index.html
file_server
}
# Auth app
handle_path /auth/* {
root * /app/web/auth
try_files {path} /index.html
file_server
}
# Cast app
handle_path /cast/* {
root * /app/web/cast
try_files {path} /index.html
file_server
}
# Root redirect - specifically match root path only
@root path /
handle @root {
redir /photos/ permanent
} }
} }
EOF EOF
@@ -802,38 +1024,23 @@ cat > /app/data/SETUP-INSTRUCTIONS.md << EOF
## Configuration ## Configuration
1. **S3 Storage**: Edit the configuration file at \`/app/data/s3.env\` with your S3-compatible storage credentials. 1. **S3 Storage**: Edit the configuration file at \`/app/data/config/s3.env\` (uncomment lines and add your values) with your S3-compatible storage credentials.
2. **Museum Server**: The server configuration is at \`/app/data/ente/server/museum.yaml\` if you need to customize settings. 2. **Museum Server**: The server configuration is at \`/app/data/ente/server/museum.yaml\` if you need to customize settings.
## API Endpoint
The Ente API is available at: **https://${CLOUDRON_APP_FQDN}/api**
This endpoint can be used to:
- Configure Ente CLI tools
- Integrate with third-party applications
- Access the Museum server API directly
For admin operations, use the Ente CLI with:
\`\`\`bash
ente admin --api-url https://${CLOUDRON_APP_FQDN}/api
\`\`\`
## Web Applications
The following web applications are available:
- **Photos**: https://${CLOUDRON_APP_FQDN}/photos/ - Main photo storage and management
- **Auth**: https://${CLOUDRON_APP_FQDN}/auth/ - 2FA authenticator app
- **Accounts**: https://${CLOUDRON_APP_FQDN}/accounts/ - Account management
- **Cast**: https://${CLOUDRON_APP_FQDN}/cast/ - Photo casting to devices
## Troubleshooting ## Troubleshooting
- **Logs**: Check the logs at \`/app/data/logs/\` for any issues. - **Logs**: Check the logs at \`/app/data/logs/\` for any issues.
- **Restart**: If you change configuration, restart the app to apply changes. - **Restart**: If you change configuration, restart the app to apply changes.
- **API Issues**: All apps use the API endpoint at \`/api\`. If apps show loading spinners, check API connectivity.
## Web Applications
The following web applications are available:
- Photos: https://${CLOUDRON_APP_FQDN}/photos/
- Accounts: https://${CLOUDRON_APP_FQDN}/accounts/
- Auth: https://${CLOUDRON_APP_FQDN}/auth/
- Cast: https://${CLOUDRON_APP_FQDN}/cast/
## Support ## Support
@@ -857,48 +1064,6 @@ else
log "ERROR" "Caddy server is not running!" log "ERROR" "Caddy server is not running!"
fi fi
# ===============================================
# OTP Email Monitor Setup
# ===============================================
log "INFO" "Setting up OTP Email Monitor"
# Install Node.js dependencies if not already installed
if [ ! -d "/app/data/node_modules" ]; then
log "INFO" "Installing Node.js dependencies for OTP Email Monitor"
cd /app/data
cp /app/pkg/package.json .
npm install --production --no-save
log "INFO" "Node.js dependencies installed successfully"
else
log "INFO" "Node.js dependencies already installed"
fi
# Start OTP Email Monitor
log "INFO" "Starting OTP Email Monitor"
cd /app/data
NODE_PATH="/app/data/node_modules" node /app/pkg/otp-email-monitor.js > /app/data/logs/otp-email.log 2>&1 &
OTP_MONITOR_PID=$!
log "INFO" "OTP Email Monitor started with PID: $OTP_MONITOR_PID"
# Wait a moment to check if OTP monitor starts successfully
sleep 2
if ps -p $OTP_MONITOR_PID > /dev/null; then
log "INFO" "OTP Email Monitor is running successfully"
else
log "WARN" "OTP Email Monitor may have failed to start"
log "WARN" "Last 10 lines of OTP email log:"
tail -n 10 /app/data/logs/otp-email.log | while read -r line; do
log "WARN" " $line"
done
fi
# Copy admin helper script for easy access
if [ -f "/app/pkg/admin-helper.sh" ]; then
cp /app/pkg/admin-helper.sh /app/data/
chmod +x /app/data/admin-helper.sh
log "INFO" "Admin helper script available at /app/data/admin-helper.sh"
fi
log "INFO" "Ente Cloudron app startup complete" log "INFO" "Ente Cloudron app startup complete"
# Keep the script running to prevent container exit # Keep the script running to prevent container exit