14 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
Your Name
b7fcf5c01d Add comprehensive API documentation to Cloudron setup instructions
- Added detailed API endpoint information in SETUP-INSTRUCTIONS.md
- Documented API usage with Ente CLI
- Enhanced routing configuration for auth/cast/accounts apps
- Updated to version 0.1.64
2025-07-25 11:02:06 -06:00
16 changed files with 1297 additions and 124 deletions

110
BUILD-INSTRUCTIONS.md Normal file
View File

@@ -0,0 +1,110 @@
# Ente Cloudron App Build and Installation Instructions
This document provides detailed instructions for building and installing the Ente Cloudron app, an open-source, end-to-end encrypted photo storage and authentication solution.
## Prerequisites
- **Cloudron CLI**: Ensure the Cloudron CLI is installed and configured on your system. Refer to [Cloudron CLI Documentation](https://docs.cloudron.io/packaging/cli/) for setup instructions.
- **Docker**: Required for local testing or custom builds if needed.
- **Git**: To clone or manage the repository.
- **Repository Access**: Ensure you have access to the Ente Cloudron repository at `andreasdueren/ente-cloudron`.
- **Build Service Token**: A token for the Cloudron build service is required (provided in the command below).
## Build Commands
1. **Clone the Repository** (if not already done):
```bash
git clone https://github.com/andreasdueren/ente-cloudron.git
cd ente-cloudron
```
2. **Build the App Using Cloudron Build Service**:
Use the provided build service and token to build the app. Replace `<version>` with the desired version tag (e.g., `0.1.0` or as per `CloudronManifest.json`).
```bash
cloudron build --set-build-service builder.docker.due.ren --build-service-token e3265de06b1d0e7bb38400539012a8433a74c2c96a17955e --set-repository andreasdueren/ente-cloudron --tag 1.0.1
```
**Note**: The build process should complete within a reasonable time. Monitor the output for any errors.
## Installation Commands
1. **Install the App on Cloudron**:
After a successful build, install the app on your Cloudron instance at the desired location (e.g., `ente.due.ren`).
```bash
cloudron install --location ente.due.ren --image andreasdueren/ente-cloudron:1.0.1
```
**Important**: Do not wait more than 30 seconds for feedback after running the install command. If there's an error, the process may hang, and you should terminate it to troubleshoot.
**Note**: Always uninstall and reinstall during development rather than updating an existing app to ensure a clean setup.
## Testing Procedures
1. **Verify Installation**:
- Access the app at `https://ente.due.ren` (or your configured domain).
- Ensure the Ente web interfaces (Photos, Accounts, Auth, Cast) load correctly.
2. **Check S3 Configuration**:
- Confirm that S3 environment variables are set in Cloudron app settings under the 'Environment Variables' section.
- Variables to check: `APP_S3_ENABLED`, `APP_S3_ENDPOINT`, `APP_S3_ACCESS_KEY_ID`, `APP_S3_SECRET_ACCESS_KEY`, `APP_S3_BUCKET`.
3. **Monitor Logs for Errors**:
- Use the Cloudron CLI to view logs:
```bash
cloudron logs --app ente.due.ren -f
```
- Alternatively, shell into the app for detailed log inspection:
```bash
cloudron exec --app ente.due.ren
tail -f /app/data/logs/*
```
- Look for S3 connection errors or other issues.
## Deployment Steps
1. **Post-Installation Configuration**:
- If S3 is not working, update the environment variables in Cloudron app settings and restart the app:
```bash
cloudron restart --app ente.due.ren
```
2. **User Authentication**:
- Ente uses its own authentication system. Ensure user registration and login work as expected.
- If OIDC integration is desired in the future, it can be configured using Cloudron's OIDC variables (`CLOUDRON_OIDC_IDENTIFIER`, `CLOUDRON_OIDC_CLIENT_ID`, `CLOUDRON_OIDC_CLIENT_SECRET`).
## Troubleshooting Common Issues
- **S3 Configuration Errors**:
- **Symptom**: App falls back to local storage or logs show S3 connection failures.
- **Solution**: Verify S3 environment variables in Cloudron settings. Test connectivity manually using AWS CLI (`aws s3 ls s3://<bucket> --endpoint-url <endpoint>`).
- **Build Failures**:
- **Symptom**: Build command errors out or hangs.
- **Solution**: Check network connectivity to the build service, ensure the token is correct, and review build logs for specific errors.
- **Installation Hangs**:
- **Symptom**: Install command does not complete within 30 seconds.
- **Solution**: Terminate the command and check Cloudron logs for errors (`cloudron logs --app ente.due.ren`). Reinstall if necessary.
- **App Not Starting**:
- **Symptom**: App shows as 'Stopped' or inaccessible after install.
- **Solution**: Check logs for startup errors (`cloudron logs --app ente.due.ren`). Ensure database connectivity and correct configuration.
## Configuration Examples
- **S3 Environment Variables** in Cloudron settings:
```
APP_S3_ENABLED=true
APP_S3_ENDPOINT=s3.amazonaws.com
APP_S3_ACCESS_KEY_ID=your_access_key
APP_S3_SECRET_ACCESS_KEY=your_secret_key
APP_S3_BUCKET=your_bucket_name
```
## Additional Resources
- **Cloudron Documentation**:
- [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/)
- [Cheat Sheet](https://docs.cloudron.io/packaging/cheat-sheet/)
For further assistance, contact the Ente team at `contact@ente.io` or refer to the GitHub repository at [https://github.com/ente-io/ente](https://github.com/ente-io/ente).

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

21
Caddyfile.simple Normal file
View File

@@ -0,0 +1,21 @@
{
admin off
auto_https off
}
:3080 {
log {
output stdout
level DEBUG
}
# Simple health check that always works
handle /health {
respond "{\"status\": \"OK\"}" 200
}
# Catch-all for debugging
handle {
respond "Caddy is running on port 3080" 200
}
}

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.62", "version": "0.1.122",
"healthCheckPath": "/ping", "healthCheckPath": "/ping",
"httpPort": 3080, "httpPort": 3080,
"memoryLimit": 1073741824, "memoryLimit": 1073741824,
@@ -35,4 +35,4 @@
"manifestVersion": 2, "manifestVersion": 2,
"minBoxVersion": "8.1.0", "minBoxVersion": "8.1.0",
"website": "https://ente.io" "website": "https://ente.io"
} }

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="https://example.com/api" RUN echo "Building with relative NEXT_PUBLIC_ENTE_ENDPOINT=/api for self-hosted deployment"
# Add a note for clarity
RUN echo "Building with placeholder NEXT_PUBLIC_ENTE_ENDPOINT, will be served by Caddy proxy at /api"
# 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
@@ -160,9 +158,13 @@ RUN chmod +x /app/museum-bin/museum
# Copy configuration and startup scripts # Copy configuration and startup scripts
ADD start.sh /app/pkg/ ADD start.sh /app/pkg/
ADD config.template.yaml /app/pkg/ ADD config.template.yaml /app/pkg/
ADD otp-email-monitor.js /app/pkg/
ADD package.json /app/pkg/
ADD admin-helper.sh /app/pkg/
ADD admin-helper-direct.sh /app/pkg/
# Set proper permissions # Set proper permissions
RUN chmod +x /app/pkg/start.sh RUN chmod +x /app/pkg/start.sh /app/pkg/admin-helper.sh /app/pkg/admin-helper-direct.sh
# Expose the web port (Cloudron expects port 3080) # Expose the web port (Cloudron expects port 3080)
EXPOSE 3080 EXPOSE 3080
@@ -170,4 +172,4 @@ EXPOSE 3080
EXPOSE 8080 EXPOSE 8080
# Start the application # Start the application
CMD ["/app/pkg/start.sh"] CMD ["/app/pkg/start.sh"]

View File

@@ -7,19 +7,15 @@ 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
1. Once S3 is configured, visit your app URL to create an admin account 1. Once S3 is configured, visit your app URL to create an admin account
2. Configure your mobile apps to use your custom self-hosted server (Settings → Advanced → Custom Server) 2. Configure your mobile apps to use your custom self-hosted server (Settings → Advanced → Custom Server)
3. Enjoy your private, end-to-end encrypted photo storage! 3. Enjoy your private, end-to-end encrypted photo storage!

133
admin-helper-direct.sh Normal file
View File

@@ -0,0 +1,133 @@
#!/bin/bash
# Direct Database Admin Helper for Ente Cloudron
# This script directly updates the database for admin operations
# Function to update user subscription directly in database
update_subscription() {
local user_email="$1"
local storage_gb="$2"
local valid_days="$3"
if [ -z "$user_email" ] || [ -z "$storage_gb" ] || [ -z "$valid_days" ]; then
echo "Usage: $0 update-subscription <user-email> <storage-gb> <valid-days>"
echo "Example: $0 update-subscription user@example.com 100 365"
return 1
fi
echo "Updating subscription for: $user_email"
echo "Storage: ${storage_gb}GB"
echo "Valid for: ${valid_days} days"
# Convert GB to bytes (1 GB = 1073741824 bytes)
local storage_bytes=$((storage_gb * 1073741824))
# Calculate expiry timestamp (current time + valid_days)
local current_timestamp=$(date +%s)
local expiry_timestamp=$((current_timestamp + (valid_days * 86400)))
# Convert to microseconds for the database
local expiry_microseconds="${expiry_timestamp}000000"
# Update the database directly
PGPASSWORD="$CLOUDRON_POSTGRESQL_PASSWORD" psql \
-h "$CLOUDRON_POSTGRESQL_HOST" \
-p "$CLOUDRON_POSTGRESQL_PORT" \
-U "$CLOUDRON_POSTGRESQL_USERNAME" \
-d "$CLOUDRON_POSTGRESQL_DATABASE" << EOF
-- Update user's storage and subscription
UPDATE users
SET
storage_bonus = $storage_bytes,
subscription_expiry = $expiry_microseconds
WHERE email = '$user_email';
-- Show the updated values
SELECT
email,
storage_bonus / 1073741824.0 as storage_gb,
to_timestamp(subscription_expiry / 1000000) as subscription_expires
FROM users
WHERE email = '$user_email';
EOF
if [ $? -eq 0 ]; then
echo "✓ Subscription updated successfully"
else
echo "✗ Failed to update subscription"
return 1
fi
}
# Function to get user details
get_user_details() {
local user_email="$1"
if [ -z "$user_email" ]; then
echo "Usage: $0 get-user <user-email>"
return 1
fi
PGPASSWORD="$CLOUDRON_POSTGRESQL_PASSWORD" psql \
-h "$CLOUDRON_POSTGRESQL_HOST" \
-p "$CLOUDRON_POSTGRESQL_PORT" \
-U "$CLOUDRON_POSTGRESQL_USERNAME" \
-d "$CLOUDRON_POSTGRESQL_DATABASE" << EOF
SELECT
email,
storage_bonus / 1073741824.0 as storage_gb,
storage_consumed / 1073741824.0 as used_gb,
to_timestamp(subscription_expiry / 1000000) as subscription_expires,
CASE
WHEN subscription_expiry > (EXTRACT(EPOCH FROM NOW()) * 1000000) THEN 'Active'
ELSE 'Expired'
END as status
FROM users
WHERE email = '$user_email';
EOF
}
# Function to list all users
list_users() {
PGPASSWORD="$CLOUDRON_POSTGRESQL_PASSWORD" psql \
-h "$CLOUDRON_POSTGRESQL_HOST" \
-p "$CLOUDRON_POSTGRESQL_PORT" \
-U "$CLOUDRON_POSTGRESQL_USERNAME" \
-d "$CLOUDRON_POSTGRESQL_DATABASE" << EOF
SELECT
email,
storage_bonus / 1073741824.0 as storage_gb,
storage_consumed / 1073741824.0 as used_gb,
to_timestamp(subscription_expiry / 1000000) as expires,
CASE
WHEN subscription_expiry > (EXTRACT(EPOCH FROM NOW()) * 1000000) THEN 'Active'
ELSE 'Expired'
END as status
FROM users
ORDER BY email;
EOF
}
# Main command handler
case "$1" in
"update-subscription")
update_subscription "$2" "$3" "$4"
;;
"get-user")
get_user_details "$2"
;;
"list-users")
list_users
;;
*)
echo "Ente Direct Admin Helper"
echo ""
echo "Usage:"
echo " $0 update-subscription <user-email> <storage-gb> <valid-days>"
echo " $0 get-user <user-email>"
echo " $0 list-users"
echo ""
echo "Examples:"
echo " $0 update-subscription user@example.com 100 365"
echo " $0 get-user user@example.com"
echo " $0 list-users"
;;
esac

93
admin-helper.sh Normal file
View File

@@ -0,0 +1,93 @@
#!/bin/bash
# Ente Admin Helper Script for Cloudron
# This script simplifies admin operations in the Cloudron terminal
MUSEUM_BIN="/app/data/ente/server/museum"
# Check if museum binary exists
if [ ! -f "$MUSEUM_BIN" ]; then
echo "Error: Museum binary not found at $MUSEUM_BIN"
exit 1
fi
# Function to update user subscription
update_subscription() {
local user_email="$1"
local storage_gb="$2"
local valid_days="$3"
if [ -z "$user_email" ] || [ -z "$storage_gb" ] || [ -z "$valid_days" ]; then
echo "Usage: $0 update-subscription <user-email> <storage-gb> <valid-days>"
echo "Example: $0 update-subscription user@example.com 100 365"
return 1
fi
echo "Updating subscription for: $user_email"
echo "Storage: ${storage_gb}GB"
echo "Valid for: ${valid_days} days"
cd /app/data/ente/server
# Use environment variables for database connection
export DB_HOST="$CLOUDRON_POSTGRESQL_HOST"
export DB_PORT="$CLOUDRON_POSTGRESQL_PORT"
export DB_NAME="$CLOUDRON_POSTGRESQL_DATABASE"
export DB_USERNAME="$CLOUDRON_POSTGRESQL_USERNAME"
export DB_PASSWORD="$CLOUDRON_POSTGRESQL_PASSWORD"
# Museum admin commands need specific syntax
"$MUSEUM_BIN" admin update-subscription "$user_email" "$storage_gb" "$valid_days"
}
# Function to get user details
get_user_details() {
local user_email="$1"
if [ -z "$user_email" ]; then
echo "Usage: $0 get-user <user-email>"
return 1
fi
cd /app/data/ente/server
"$MUSEUM_BIN" admin get-user-details --user "$user_email"
}
# Function to list all users
list_users() {
cd /app/data/ente/server
# Connect to PostgreSQL and list users
PGPASSWORD="$CLOUDRON_POSTGRESQL_PASSWORD" psql \
-h "$CLOUDRON_POSTGRESQL_HOST" \
-p "$CLOUDRON_POSTGRESQL_PORT" \
-U "$CLOUDRON_POSTGRESQL_USERNAME" \
-d "$CLOUDRON_POSTGRESQL_DATABASE" \
-c "SELECT email, storage_bonus, subscription_expiry FROM users ORDER BY email;"
}
# Main command handler
case "$1" in
"update-subscription")
update_subscription "$2" "$3" "$4"
;;
"get-user")
get_user_details "$2"
;;
"list-users")
list_users
;;
*)
echo "Ente Admin Helper"
echo ""
echo "Usage:"
echo " $0 update-subscription <user-email> <storage-gb> <valid-days>"
echo " $0 get-user <user-email>"
echo " $0 list-users"
echo ""
echo "Examples:"
echo " $0 update-subscription user@example.com 100 365"
echo " $0 get-user user@example.com"
echo " $0 list-users"
;;
esac

35
debug-headers.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
echo "==> Debugging Caddy MIME type headers"
echo "==> Testing various file types..."
BASE_URL="${1:-https://ente.due.ren}"
echo
echo "Testing HTML files:"
curl -I "$BASE_URL/" 2>/dev/null | grep -i content-type || echo "No Content-Type header found"
curl -I "$BASE_URL/index.html" 2>/dev/null | grep -i content-type || echo "No Content-Type header found"
echo
echo "Testing JavaScript files:"
curl -I "$BASE_URL/config.js" 2>/dev/null | grep -i content-type || echo "No Content-Type header found"
echo
echo "Testing CSS files (if any):"
curl -I "$BASE_URL/styles.css" 2>/dev/null | grep -i content-type || echo "File not found or no Content-Type header"
echo
echo "Testing JSON files (if any):"
curl -I "$BASE_URL/manifest.json" 2>/dev/null | grep -i content-type || echo "File not found or no Content-Type header"
echo
echo "==> Full response headers for main page:"
curl -I "$BASE_URL/" 2>/dev/null || echo "Failed to connect to $BASE_URL"
echo
echo "==> To test from inside a container:"
echo "docker exec -it <container-name> curl -I http://localhost:3080/"
echo
echo "==> To view Caddy logs:"
echo "docker exec -it <container-name> tail -f /app/data/logs/caddy.log"

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>

32
debug-start.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
# Add this debugging section to your start.sh after line 350
# Start Caddy with more verbose logging
echo "==> Starting Caddy web server with debug logging"
echo "==> Validating Caddyfile first..."
caddy validate --config /app/data/Caddyfile --adapter caddyfile || {
echo "==> ERROR: Caddyfile validation failed!"
cat /app/data/Caddyfile
exit 1
}
echo "==> Starting Caddy..."
# Run Caddy in foreground first to see errors
timeout 10 caddy run --config /app/data/Caddyfile --adapter caddyfile 2>&1 | tee /app/data/logs/caddy-debug.log || {
echo "==> ERROR: Caddy failed to start"
echo "==> Last 50 lines of Caddy debug log:"
tail -50 /app/data/logs/caddy-debug.log
}
# Check if port is actually listening
echo "==> Checking if port 3080 is listening..."
netstat -tlnp | grep 3080 || lsof -i :3080 || {
echo "==> ERROR: Nothing listening on port 3080"
}
# Test the health endpoint
echo "==> Testing health endpoint..."
curl -v http://localhost:3080/health || {
echo "==> ERROR: Health check failed"
}

64
ente-cli-config.md Normal file
View File

@@ -0,0 +1,64 @@
# Ente CLI Configuration for Custom Server
The Ente CLI expects configuration in `~/.ente/config.yaml`. Here's how to set it up:
## Method 1: Direct Configuration
1. Create the config file:
```bash
mkdir -p ~/.ente
cat > ~/.ente/config.yaml << EOF
api:
url: https://ente.due.ren
EOF
```
2. Add your account interactively:
```bash
ente account add
# It will ask for:
# - Export directory: /tmp/ente-export (or any directory)
# - Email: your-admin@email.com
# - Password: your-password
```
## Method 2: Using the Admin Commands Directly
If the interactive setup is problematic, you can use the admin commands with explicit parameters:
```bash
# Set the API endpoint
export ENTE_API_URL="https://ente.due.ren"
# Or pass it directly in the command
ente admin update-subscription \
--api-url https://ente.due.ren \
--admin-user admin@due.ren \
--user user@example.com \
--storage 1000 \
--valid-for 365
```
## Method 3: Direct Database Update (Fallback)
Since the CLI setup seems problematic, you can update the database directly in the Cloudron terminal:
```bash
# In Cloudron terminal
PGPASSWORD="$CLOUDRON_POSTGRESQL_PASSWORD" psql \
-h "$CLOUDRON_POSTGRESQL_HOST" \
-U "$CLOUDRON_POSTGRESQL_USERNAME" \
-d "$CLOUDRON_POSTGRESQL_DATABASE" << EOF
-- Update user to 1TB for 1 year
UPDATE users
SET storage_bonus = 1073741824000, -- 1000 GB in bytes
subscription_expiry = EXTRACT(EPOCH FROM NOW() + INTERVAL '365 days') * 1000000
WHERE email = 'andreas@due.ren';
-- Show the result
SELECT email,
storage_bonus / 1073741824.0 as storage_gb,
to_timestamp(subscription_expiry / 1000000) as expires
FROM users WHERE email = 'andreas@due.ren';
EOF
```

20
setup-ente-cli.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Setup Ente CLI for custom server
echo "Setting up Ente CLI for custom server..."
# Create config directory
mkdir -p ~/.ente
# Create the CLI config with custom endpoint
cat > ~/.ente/config.yaml << EOF
host: https://ente.due.ren
EOF
echo "Configuration created at ~/.ente/config.yaml"
echo ""
echo "Now you can add your account:"
echo " ente account add"
echo ""
echo "Then use admin commands:"
echo " ente admin update-subscription --admin-user admin@due.ren --user user@example.com --storage 1000 --valid-for 365"

150
start-debug.sh Executable file
View File

@@ -0,0 +1,150 @@
#!/bin/bash
# Better signal handling - forward signals to child processes
trap 'kill -TERM $SERVER_PID; kill -TERM $CADDY_PID; exit' TERM INT
set -eu
echo "==> Starting Ente Cloudron app (DEBUG MODE)..."
# Create necessary directories
mkdir -p /app/data/config /app/data/logs /app/data/caddy
# Check if web directories exist
echo "==> Checking web app directories:"
for app in photos accounts auth cast; do
if [ -d "/app/web/$app" ]; then
echo "==> Found: /app/web/$app"
ls -la "/app/web/$app" | head -5
else
echo "==> WARNING: Missing /app/web/$app - creating placeholder"
mkdir -p "/app/web/$app"
echo "<html><body><h1>$app app placeholder</h1></body></html>" > "/app/web/$app/index.html"
fi
done
# Create a simple test Caddyfile first
echo "==> Creating simple test Caddyfile"
cat > /app/data/Caddyfile <<'EOT'
{
admin off
auto_https off
}
:3080 {
log {
output stdout
format console
level DEBUG
}
# Health check endpoint
handle /health {
header Content-Type "application/json"
respond "{\"status\": \"OK\", \"timestamp\": \"{{now | date \"2006-01-02T15:04:05Z07:00\"}}\"}" 200
}
# Test endpoint
handle /test {
respond "Caddy is working on port 3080!" 200
}
# API proxy to Museum server
handle /api/* {
uri strip_prefix /api
reverse_proxy localhost:8080 {
transport http {
read_timeout 60s
write_timeout 60s
}
# Add error handling
handle_errors {
respond "{\"error\": \"Museum server not available\"}" 503
}
}
}
# Serve web apps with fallback
handle {
root * /app/web/photos
try_files {path} {path}/ /index.html
file_server {
browse
}
}
}
EOT
# Start a simple Museum mock server for testing
echo "==> Starting mock Museum server on port 8080"
cat > /tmp/museum-mock.js <<'EOF'
const http = require('http');
const server = http.createServer((req, res) => {
console.log(`Museum mock: ${req.method} ${req.url}`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', path: req.url, timestamp: new Date().toISOString() }));
});
server.listen(8080, '127.0.0.1', () => {
console.log('Museum mock server running on http://127.0.0.1:8080');
});
EOF
node /tmp/museum-mock.js > /app/data/logs/museum-mock.log 2>&1 &
SERVER_PID=$!
echo "==> Mock Museum server started (PID: $SERVER_PID)"
# Wait for Museum mock to be ready
sleep 2
# Test Museum mock
echo "==> Testing Museum mock server..."
curl -s http://localhost:8080/test || echo "WARNING: Museum mock not responding"
# Validate Caddyfile
echo "==> Validating Caddyfile..."
caddy validate --config /app/data/Caddyfile --adapter caddyfile || {
echo "==> ERROR: Caddyfile validation failed!"
exit 1
}
# Start Caddy with explicit environment
echo "==> Starting Caddy web server..."
CADDY_FORMAT=console caddy run --config /app/data/Caddyfile --adapter caddyfile 2>&1 | tee /app/data/logs/caddy-combined.log &
CADDY_PID=$!
echo "==> Caddy started (PID: $CADDY_PID)"
# Wait for Caddy to start
echo "==> Waiting for Caddy to start..."
for i in {1..30}; do
if curl -s http://localhost:3080/health > /dev/null; then
echo "==> Caddy is responding!"
break
fi
echo -n "."
sleep 1
done
echo
# Check process status
echo "==> Process status:"
ps aux | grep -E "(caddy|node)" | grep -v grep || echo "No processes found"
# Check port status
echo "==> Port status:"
netstat -tlnp 2>/dev/null | grep -E "(3080|8080)" || lsof -i :3080 -i :8080 2>/dev/null || echo "Cannot check port status"
# Test endpoints
echo "==> Testing endpoints:"
echo "Health check:"
curl -s http://localhost:3080/health | jq . || echo "Failed"
echo -e "\nTest endpoint:"
curl -s http://localhost:3080/test || echo "Failed"
echo -e "\nAPI proxy:"
curl -s http://localhost:3080/api/status | jq . || echo "Failed"
echo "==> Startup complete. Services:"
echo " - Caddy PID: $CADDY_PID"
echo " - Museum Mock PID: $SERVER_PID"
echo "==> Logs: /app/data/logs/"
# Keep running
wait $SERVER_PID $CADDY_PID

474
start.sh
View File

@@ -77,32 +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
apps:
public-albums: "${BASE_URL}/photos"
public-locker: "${BASE_URL}/photos"
accounts: "${BASE_URL}/accounts"
cast: "${BASE_URL}/cast"
family: "${BASE_URL}/photos"
custom-domain:
cname: "${CLOUDRON_APP_DOMAIN:-localhost}"
# Database configuration # Database configuration
db: db:
@@ -113,42 +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:
endpoint: "${S3_ENDPOINT}"
region: "${S3_REGION}"
access_key: "${S3_ACCESS_KEY}"
secret_key: "${S3_SECRET_KEY}"
bucket: "${S3_BUCKET}"
# For Wasabi, we need path style URLs
use_path_style_urls: true
are_local_buckets: false are_local_buckets: false
use_path_style_urls: true
hot_storage:
primary: wasabi-eu-central-2-v3
secondary: wasabi-eu-central-2-v3
b2-eu-cen:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
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
@@ -174,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"
@@ -201,7 +361,7 @@ else
log "INFO" "Migration files already exist or source not available" log "INFO" "Migration files already exist or source not available"
fi fi
# Copy web templates to Museum working directory # Copy web templates to Museum working directory
MUSEUM_WEB_TEMPLATES_DIR="/app/data/ente/server/web-templates" MUSEUM_WEB_TEMPLATES_DIR="/app/data/ente/server/web-templates"
REPO_WEB_TEMPLATES_DIR="/app/data/ente/repository/server/web-templates" REPO_WEB_TEMPLATES_DIR="/app/data/ente/repository/server/web-templates"
if [ ! -d "$MUSEUM_WEB_TEMPLATES_DIR" ] && [ -d "$REPO_WEB_TEMPLATES_DIR" ]; then if [ ! -d "$MUSEUM_WEB_TEMPLATES_DIR" ] && [ -d "$REPO_WEB_TEMPLATES_DIR" ]; then
@@ -212,6 +372,17 @@ 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 for transactional emails
MUSEUM_MAIL_TEMPLATES_DIR="/app/data/ente/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
log "INFO" "Copying mail templates"
cp -r "$REPO_MAIL_TEMPLATES_DIR" "$MUSEUM_MAIL_TEMPLATES_DIR"
log "INFO" "Copied mail templates to $MUSEUM_MAIL_TEMPLATES_DIR"
else
log "INFO" "Mail templates already exist or source not available"
fi
# Check if Museum binary exists and is valid # Check if Museum binary exists and is valid
log "INFO" "Checking for Museum binary at: $MUSEUM_BIN" log "INFO" "Checking for Museum binary at: $MUSEUM_BIN"
if [ -f "$MUSEUM_BIN" ]; then if [ -f "$MUSEUM_BIN" ]; then
@@ -241,21 +412,52 @@ 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"
# 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 # Fix API endpoint configuration in built JavaScript files
log "INFO" "Updating API endpoint configuration in web apps" log "INFO" "Updating API endpoint configuration in web apps"
ACTUAL_ENDPOINT="https://${CLOUDRON_APP_DOMAIN}/api" ACTUAL_ENDPOINT="${BASE_URL}/api"
log "INFO" "Setting API endpoint to: $ACTUAL_ENDPOINT" log "INFO" "Setting API endpoint to: $ACTUAL_ENDPOINT"
# Replace placeholder endpoint in all JavaScript files 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 for webapp in photos accounts auth cast; do
WEB_DIR="/app/web/${webapp}" WEB_DIR="$WRITABLE_WEB_DIR/${webapp}"
if [ -d "$WEB_DIR" ]; then if [ -d "$WEB_DIR" ]; then
log "INFO" "Processing ${webapp} app" log "INFO" "Processing ${webapp} app for endpoint rewrites"
# Find and replace the placeholder endpoint in all JS files for placeholder in "${PLACEHOLDER_ENDPOINTS[@]}"; do
find "$WEB_DIR" -name "*.js" -type f -exec sed -i "s|https://example.com/api|${ACTUAL_ENDPOINT}|g" {} \; find "$WEB_DIR" -name "*.js" -type f -exec sed -i "s|${placeholder}|${ACTUAL_ENDPOINT}|g" {} \;
log "INFO" "Updated endpoint configuration for ${webapp}" 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 else
log "WARN" "Web directory not found for ${webapp}" log "WARN" "Web directory not found for ${webapp}"
fi fi
@@ -360,18 +562,53 @@ 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) => {
res.writeHead(200, { 'Content-Type': 'application/json' }); const buildResponse = (emailAddress) => {
log('User verify request - responding with success'); const email = emailAddress || 'unknown@example.com';
res.end(JSON.stringify({ const stableId = Math.abs(Buffer.from(email).reduce((acc, byte) => (acc * 31 + byte) % 100000, 17)) || 1;
success: true, return {
isValidEmail: true, id: stableId,
isAvailable: true, token: `placeholder-token-${stableId}`,
isVerified: true, encryptedToken: `placeholder-encrypted-token-${stableId}`,
canCreateAccount: true 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.end(JSON.stringify(responsePayload));
});
}, },
// User login endpoint // User login endpoint
@@ -431,6 +668,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) => {
@@ -548,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
@@ -581,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
@@ -591,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
@@ -603,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
@@ -646,7 +932,7 @@ cat > "$CADDY_CONFIG" << EOF
} }
# API endpoints with CORS # API endpoints with CORS
handle /api/* { handle_path /api/* {
reverse_proxy localhost:8080 { reverse_proxy localhost:8080 {
header_up Host {http.request.host} header_up Host {http.request.host}
header_up X-Real-IP {http.request.remote} header_up X-Real-IP {http.request.remote}
@@ -674,51 +960,29 @@ cat > "$CADDY_CONFIG" << EOF
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/* {
@photosNext path /_next/* root * /app/data/web
handle @photosNext { try_files photos{path} accounts{path} auth{path} cast{path} {path}
root * /app/web/photos file_server
file_server
}
header { header {
Cache-Control "public, max-age=31536000" Cache-Control "public, max-age=31536000"
Access-Control-Allow-Origin "*" Access-Control-Allow-Origin "*"
} }
} }
# Photos app
handle_path /photos/* {
root * /app/web/photos
try_files {path} /index.html
file_server
}
# Accounts app # Default to serve SPA assets
handle_path /accounts/* { handle {
root * /app/web/accounts root * /app/data/web
try_files {path} /index.html try_files {path}/index.html {path} /photos/index.html
file_server 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
handle / {
redir /photos/ permanent
}
} }
EOF EOF
@@ -760,7 +1024,7 @@ 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.
@@ -803,4 +1067,4 @@ 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
exec tail -f "$MUSEUM_LOG" exec tail -f "$MUSEUM_LOG"

57
update-storage.sh Executable file
View File

@@ -0,0 +1,57 @@
#!/bin/bash
# Script to update Ente user storage using the Ente CLI
# Run this from your local machine (not inside Cloudron)
# Check if ente CLI is installed
if ! command -v ente &> /dev/null; then
echo "Ente CLI is not installed. Please install it first:"
echo ""
echo "For macOS:"
echo " brew tap ente-io/ente"
echo " brew install ente-cli"
echo ""
echo "For other systems, download from:"
echo " https://github.com/ente-io/ente/releases"
exit 1
fi
# Your Ente instance
ENTE_ENDPOINT="https://ente.due.ren"
# Function to update subscription
update_subscription() {
local admin_email="$1"
local user_email="$2"
local storage_gb="$3"
local valid_days="$4"
echo "Updating subscription for: $user_email"
echo "Storage: ${storage_gb}GB"
echo "Valid for: ${valid_days} days"
echo "Using admin account: $admin_email"
echo ""
# Run the ente CLI command
ente admin update-subscription \
--host "$ENTE_ENDPOINT" \
--admin-user "$admin_email" \
--user "$user_email" \
--storage "$storage_gb" \
--valid-for "$valid_days"
}
# Check arguments
if [ $# -lt 4 ]; then
echo "Usage: $0 <admin-email> <user-email> <storage-gb> <valid-days>"
echo ""
echo "Example:"
echo " $0 admin@due.ren andreas@due.ren 1000 365"
echo ""
echo "Make sure you're logged in to the Ente CLI first:"
echo " ente account add"
echo " API endpoint: $ENTE_ENDPOINT"
exit 1
fi
# Run the update
update_subscription "$1" "$2" "$3" "$4"