Fixed filesystem access issues and network binding for dual-instance Ente setup

This commit is contained in:
Andreas Düren 2025-03-18 18:48:26 +01:00
parent 42c1374606
commit a73d2b4959

432
start.sh
View File

@ -1,7 +1,7 @@
#!/bin/bash
# Better signal handling - forward signals to child processes
trap 'kill -TERM $SERVER_PID; kill -TERM $CADDY_PID; exit' TERM INT
trap 'kill -TERM $SERVER_PID; kill -TERM $PUBLIC_SERVER_PID; kill -TERM $CADDY_PID; exit' TERM INT
set -eu
@ -231,6 +231,7 @@ mkdir -p /app/data/caddy
# Define ports
CADDY_PORT=3080
API_PORT=8080
PUBLIC_ALBUMS_PORT=8081 # New port for public albums service
# Check if ports are available
echo "==> Checking port availability"
@ -246,6 +247,12 @@ else
echo "==> Port $API_PORT is available for API server"
fi
if lsof -i:$PUBLIC_ALBUMS_PORT > /dev/null 2>&1; then
echo "==> WARNING: Port $PUBLIC_ALBUMS_PORT is already in use"
else
echo "==> Port $PUBLIC_ALBUMS_PORT is available for Public Albums server"
fi
# Determine available memory and set limits accordingly
if [[ -f /sys/fs/cgroup/cgroup.controllers ]]; then # cgroup v2
memory_limit=$(cat /sys/fs/cgroup/memory.max)
@ -343,11 +350,15 @@ if [ -d "$SERVER_DIR/cmd/museum" ]; then
if [ -f "$MAIN_GO" ]; then
echo "==> Patching main.go to force correct database host"
# Create a backup of the original file
cp "$MAIN_GO" "${MAIN_GO}.orig"
# Create writable directory for patched files
mkdir -p /app/data/patched
# Copy the file to a writable location before patching
cp "$MAIN_GO" "/app/data/patched/main.go"
WRITABLE_MAIN_GO="/app/data/patched/main.go"
# Look for setupDatabase function and patch it
DB_SETUP_LINE=$(grep -n "func setupDatabase" "$MAIN_GO" | cut -d: -f1)
DB_SETUP_LINE=$(grep -n "func setupDatabase" "$WRITABLE_MAIN_GO" | cut -d: -f1)
if [ -n "$DB_SETUP_LINE" ]; then
echo "==> Found setupDatabase function at line $DB_SETUP_LINE"
@ -356,25 +367,24 @@ if [ -d "$SERVER_DIR/cmd/museum" ]; then
sed -i "${DB_SETUP_LINE}a\\
\\tlog.Printf(\"Forcing database host to %s\", \"${CLOUDRON_POSTGRESQL_HOST}\")\\
\\tos.Setenv(\"PGHOST\", \"${CLOUDRON_POSTGRESQL_HOST}\")\\
\\tos.Setenv(\"PGHOSTADDR\", \"${CLOUDRON_POSTGRESQL_HOST}\")" "$MAIN_GO"
\\tos.Setenv(\"PGHOSTADDR\", \"${CLOUDRON_POSTGRESQL_HOST}\")" "$WRITABLE_MAIN_GO"
echo "==> Patched setupDatabase function"
fi
# If there's a connection string being built, patch that too
CONN_STR_LINE=$(grep -n "postgres://" "$MAIN_GO" | head -1 | cut -d: -f1)
CONN_STR_LINE=$(grep -n "postgres://" "$WRITABLE_MAIN_GO" | head -1 | cut -d: -f1)
if [ -n "$CONN_STR_LINE" ]; then
echo "==> Found connection string at line $CONN_STR_LINE"
# Backup again just to be safe
cp "$MAIN_GO" "${MAIN_GO}.conn_patch"
# Replace localhost or [::1] with the actual host
sed -i "s/localhost/${CLOUDRON_POSTGRESQL_HOST}/g" "$MAIN_GO"
sed -i "s/\[::1\]/${CLOUDRON_POSTGRESQL_HOST}/g" "$MAIN_GO"
sed -i "s/localhost/${CLOUDRON_POSTGRESQL_HOST}/g" "$WRITABLE_MAIN_GO"
sed -i "s/\[::1\]/${CLOUDRON_POSTGRESQL_HOST}/g" "$WRITABLE_MAIN_GO"
echo "==> Patched connection string"
fi
echo "==> Will use patched version at runtime"
fi
fi
@ -423,103 +433,8 @@ else
echo "==> Skipping migration state check: cmd/museum not found"
fi
# Set up Caddy web server
echo "==> Setting up Caddy web server"
# Create a Caddyfile for serving web apps and reverse proxy to API
cat > /app/data/caddy/Caddyfile <<EOT
# Global settings
{
admin off
auto_https off
http_port $CADDY_PORT
https_port 0
}
# Main site configuration
:$CADDY_PORT {
# Basic logging
log {
level INFO
output file /app/data/logs/caddy.log
}
# Root path serves the photos app
handle / {
root * /app/web/photos
try_files {path} /index.html
file_server
}
# Accounts app
handle /accounts/* {
root * /app/web/accounts
uri strip_prefix /accounts
try_files {path} /index.html
file_server
}
# Auth app
handle /auth/* {
root * /app/web/auth
uri strip_prefix /auth
try_files {path} /index.html
file_server
}
# Cast app
handle /cast/* {
root * /app/web/cast
uri strip_prefix /cast
try_files {path} /index.html
file_server
}
# API proxy
handle /api/* {
uri strip_prefix /api
reverse_proxy localhost:$API_PORT
}
# Health check endpoints
handle /health {
respond "OK"
}
handle /healthcheck {
respond "OK"
}
handle /api/health {
uri strip_prefix /api
reverse_proxy localhost:$API_PORT
}
# Configuration scripts
handle /config.js {
respond "
// Direct configuration for Ente
window.ENTE_CONFIG = {
API_URL: '${API_ENDPOINT}'
};
// Next.js environment variables
window.process = window.process || {};
window.process.env = window.process.env || {};
window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = '${API_ENDPOINT}';
console.log('Ente config loaded - API_URL:', window.ENTE_CONFIG.API_URL);
" 200 {
Content-Type "application/javascript"
}
}
}
EOT
echo "==> Created Caddy config at /app/data/caddy/Caddyfile"
# Start the Museum server with proper environment variables
echo "==> Starting Museum server"
echo "==> Starting main Museum server"
cd "$SERVER_DIR"
# Check if there's a pre-built binary
@ -560,8 +475,17 @@ CLOUDRON_POSTGRESQL_PORT="${CLOUDRON_POSTGRESQL_PORT}" \
CLOUDRON_POSTGRESQL_USERNAME="${CLOUDRON_POSTGRESQL_USERNAME}" \
CLOUDRON_POSTGRESQL_PASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" \
CLOUDRON_POSTGRESQL_DATABASE="${CLOUDRON_POSTGRESQL_DATABASE}" \
go run -ldflags "-X 'github.com/lib/pq.defaulthost=${MUSEUM_DB_HOST}'" overrides/db_override.go cmd/museum/main.go serve
EOF
# Check if we have a patched main.go to use
if [ -f "/app/data/patched/main.go" ]; then
echo "Using patched main.go from writable location"
echo "go run -ldflags \"-X 'github.com/lib/pq.defaulthost=${MUSEUM_DB_HOST}'\" overrides/db_override.go /app/data/patched/main.go serve" >> /tmp/run_server.sh
else
echo "Using original main.go from read-only location"
echo "go run -ldflags \"-X 'github.com/lib/pq.defaulthost=${MUSEUM_DB_HOST}'\" overrides/db_override.go cmd/museum/main.go serve" >> /tmp/run_server.sh
fi
chmod +x /tmp/run_server.sh
/usr/local/bin/gosu cloudron:cloudron env \
@ -672,7 +596,7 @@ echo "==> Server started with PID $SERVER_PID"
# Test if API is responding
echo "==> Testing API connectivity"
for i in {1..5}; do
if curl -s --max-time 2 --fail http://localhost:$API_PORT/health > /dev/null; then
if curl -s --max-time 2 --fail http://0.0.0.0:$API_PORT/health > /dev/null; then
echo "==> API is responding on port $API_PORT"
break
else
@ -687,6 +611,264 @@ for i in {1..5}; do
fi
done
# Start the Public Albums Museum server (second instance)
echo "==> Starting Public Albums Museum server"
# Create configuration for public albums instance
mkdir -p /app/data/config/public
cp /app/data/config/museum.yaml /app/data/config/public/museum.yaml
if [ -n "$MUSEUM_BIN" ]; then
echo "==> Starting Public Albums Museum from binary: $MUSEUM_BIN"
MUSEUM_CONFIG="/app/data/config/public/museum.yaml" $MUSEUM_BIN serve --port $PUBLIC_ALBUMS_PORT > /app/data/logs/public_museum.log 2>&1 &
PUBLIC_SERVER_PID=$!
elif [ -d "$SERVER_DIR/cmd/museum" ]; then
echo "==> Starting Public Albums Museum from source"
# Create a startup script
cat > /tmp/run_public_server.sh <<EOF
#!/bin/bash
cd "$SERVER_DIR" && \
PGHOST="${CLOUDRON_POSTGRESQL_HOST}" \
PGPORT="${CLOUDRON_POSTGRESQL_PORT}" \
PGUSER="${CLOUDRON_POSTGRESQL_USERNAME}" \
PGPASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" \
PGDATABASE="${CLOUDRON_POSTGRESQL_DATABASE}" \
PGSSLMODE="disable" \
ENTE_PG_HOST="${MUSEUM_DB_HOST}" \
ENTE_PG_PORT="${MUSEUM_DB_PORT}" \
ENTE_PG_USER="${MUSEUM_DB_USER}" \
ENTE_PG_PASSWORD="${MUSEUM_DB_PASSWORD}" \
ENTE_PG_DATABASE="${MUSEUM_DB_NAME}" \
ENTE_PG_DSN="postgres://${MUSEUM_DB_USER}:${MUSEUM_DB_PASSWORD}@${MUSEUM_DB_HOST}:${MUSEUM_DB_PORT}/${MUSEUM_DB_NAME}?sslmode=disable&host=${MUSEUM_DB_HOST}" \
CLOUDRON_POSTGRESQL_HOST="${CLOUDRON_POSTGRESQL_HOST}" \
CLOUDRON_POSTGRESQL_PORT="${CLOUDRON_POSTGRESQL_PORT}" \
CLOUDRON_POSTGRESQL_USERNAME="${CLOUDRON_POSTGRESQL_USERNAME}" \
CLOUDRON_POSTGRESQL_PASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" \
CLOUDRON_POSTGRESQL_DATABASE="${CLOUDRON_POSTGRESQL_DATABASE}" \
EOF
# Check if we have a patched main.go to use
if [ -f "/app/data/patched/main.go" ]; then
echo "Using patched main.go from writable location"
echo "go run -ldflags \"-X 'github.com/lib/pq.defaulthost=${MUSEUM_DB_HOST}'\" overrides/db_override.go /app/data/patched/main.go serve --port $PUBLIC_ALBUMS_PORT" >> /tmp/run_public_server.sh
else
echo "Using original main.go from read-only location"
echo "go run -ldflags \"-X 'github.com/lib/pq.defaulthost=${MUSEUM_DB_HOST}'\" overrides/db_override.go cmd/museum/main.go serve --port $PUBLIC_ALBUMS_PORT" >> /tmp/run_public_server.sh
fi
chmod +x /tmp/run_public_server.sh
/usr/local/bin/gosu cloudron:cloudron env \
GOCACHE="$GOCACHE" \
GOMODCACHE="$GOMODCACHE" \
GO111MODULE=on \
GOFLAGS="$GOFLAGS" \
MUSEUM_CONFIG="/app/data/config/public/museum.yaml" \
MUSEUM_DB_HOST="$MUSEUM_DB_HOST" \
MUSEUM_DB_PORT="$MUSEUM_DB_PORT" \
MUSEUM_DB_USER="$MUSEUM_DB_USER" \
MUSEUM_DB_PASSWORD="$MUSEUM_DB_PASSWORD" \
MUSEUM_DB_NAME="$MUSEUM_DB_NAME" \
ENTE_PG_DSN="postgres://${MUSEUM_DB_USER}:${MUSEUM_DB_PASSWORD}@${MUSEUM_DB_HOST}:${MUSEUM_DB_PORT}/${MUSEUM_DB_NAME}?sslmode=disable" \
ENTE_PG_HOST="$MUSEUM_DB_HOST" \
ENTE_PG_PORT="$MUSEUM_DB_PORT" \
ENTE_PG_USER="$MUSEUM_DB_USER" \
ENTE_PG_PASSWORD="$MUSEUM_DB_PASSWORD" \
ENTE_PG_DATABASE="$MUSEUM_DB_NAME" \
PGHOST="$PGHOST" \
PGPORT="$PGPORT" \
PGUSER="$PGUSER" \
PGPASSWORD="$PGPASSWORD" \
PGDATABASE="$PGDATABASE" \
PGSSLMODE="$PGSSLMODE" \
ENTE_LOG_LEVEL=debug \
bash /tmp/run_public_server.sh > /app/data/logs/public_museum.log 2>&1 &
PUBLIC_SERVER_PID=$!
else
echo "==> ERROR: Museum server not found for public albums"
echo "==> Starting a mock public albums server"
# Create a temporary directory for a simple Go server
mkdir -p /tmp/mock-public-server
cat > /tmp/mock-public-server/main.go.template <<EOT
package main
import (
"fmt"
"log"
"net/http"
"os"
"time"
)
func main() {
// Log environment variables
log.Println("Starting mock Public Albums API server")
log.Println("Running on port: PORT_NUMBER")
// Add a health endpoint
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"status":"ok","message":"Mock Public Albums server running","time":"` + time.Now().String() + `"}`)
})
// Handle all other requests
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Printf("Public Albums: Received request for %s via %s", r.URL.Path, r.Method)
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"status":"mock","service":"public_albums","endpoint":"%s","method":"%s","time":"%s"}`,
r.URL.Path, r.Method, time.Now().String())
})
// Start the server
log.Printf("Starting mock Public Albums server on port PORT_NUMBER\n")
if err := http.ListenAndServe(":PORT_NUMBER", nil); err != nil {
log.Fatalf("Failed to start Public Albums server: %v", err)
}
}
EOT
# Replace the port placeholder with the actual port number
sed "s/PORT_NUMBER/$PUBLIC_ALBUMS_PORT/g" /tmp/mock-public-server/main.go.template > /tmp/mock-public-server/main.go
# Run the mock server with environment variables
cd /tmp/mock-public-server
go run main.go > /app/data/logs/public_museum.log 2>&1 &
PUBLIC_SERVER_PID=$!
echo "==> Mock Public Albums server started with PID $PUBLIC_SERVER_PID"
fi
echo "==> Public Albums server started with PID $PUBLIC_SERVER_PID"
# Test if Public Albums API is responding
echo "==> Testing Public Albums API connectivity"
for i in {1..5}; do
if curl -s --max-time 2 --fail http://0.0.0.0:$PUBLIC_ALBUMS_PORT/health > /dev/null; then
echo "==> Public Albums API is responding on port $PUBLIC_ALBUMS_PORT"
break
else
if [ $i -eq 5 ]; then
echo "==> WARNING: Public Albums API is not responding after several attempts"
echo "==> Last 20 lines of public_museum.log:"
tail -20 /app/data/logs/public_museum.log || echo "==> No public_museum.log available"
else
echo "==> Attempt $i: Waiting for Public Albums API to start... (2 seconds)"
sleep 2
fi
fi
done
# Set up Caddy web server
echo "==> Setting up Caddy web server"
# Create a Caddyfile for serving web apps and reverse proxy to API
cat > /app/data/caddy/Caddyfile <<EOT
# Global settings
{
admin off
auto_https off
http_port $CADDY_PORT
https_port 0
}
# Main site configuration
:$CADDY_PORT {
# Basic logging
log {
level INFO
output file /app/data/logs/caddy.log
}
# Root path serves the photos app
handle / {
root * /app/web/photos
try_files {path} /index.html
file_server
}
# Accounts app
handle /accounts/* {
root * /app/web/accounts
uri strip_prefix /accounts
try_files {path} /index.html
file_server
}
# Auth app
handle /auth/* {
root * /app/web/auth
uri strip_prefix /auth
try_files {path} /index.html
file_server
}
# Cast app
handle /cast/* {
root * /app/web/cast
uri strip_prefix /cast
try_files {path} /index.html
file_server
}
# Main API proxy
handle /api/* {
uri strip_prefix /api
reverse_proxy 0.0.0.0:$API_PORT
}
# Public albums API proxy
handle /public/* {
uri strip_prefix /public
reverse_proxy 0.0.0.0:$PUBLIC_ALBUMS_PORT
}
# Health check endpoints
handle /health {
respond "OK"
}
handle /healthcheck {
respond "OK"
}
handle /api/health {
uri strip_prefix /api
reverse_proxy 0.0.0.0:$API_PORT
}
handle /public/health {
uri strip_prefix /public
reverse_proxy 0.0.0.0:$PUBLIC_ALBUMS_PORT
}
# Configuration scripts
handle /config.js {
respond "
// Direct configuration for Ente
window.ENTE_CONFIG = {
API_URL: '${API_ENDPOINT}',
PUBLIC_ALBUMS_URL: '${CLOUDRON_APP_ORIGIN}/public'
};
// Next.js environment variables
window.process = window.process || {};
window.process.env = window.process.env || {};
window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = '${API_ENDPOINT}';
window.process.env.NEXT_PUBLIC_ENTE_PUBLIC_ALBUMS_ENDPOINT = '${CLOUDRON_APP_ORIGIN}/public';
console.log('Ente config loaded - API_URL:', window.ENTE_CONFIG.API_URL);
console.log('Ente config loaded - PUBLIC_ALBUMS_URL:', window.ENTE_CONFIG.PUBLIC_ALBUMS_URL);
" 200 {
Content-Type "application/javascript"
}
}
}
EOT
echo "==> Created Caddy config at /app/data/caddy/Caddyfile"
# Start Caddy server
echo "==> Starting Caddy server"
caddy start --config /app/data/caddy/Caddyfile --adapter caddyfile &
@ -699,7 +881,7 @@ sleep 2
# Test Caddy connectivity
echo "==> Testing Caddy connectivity"
for i in {1..5}; do
if curl -s --max-time 2 --fail http://localhost:$CADDY_PORT/health > /dev/null; then
if curl -s --max-time 2 --fail http://0.0.0.0:$CADDY_PORT/health > /dev/null; then
echo "==> Caddy is responding on port $CADDY_PORT"
break
else
@ -716,9 +898,39 @@ done
echo "==> Application is now running"
echo "==> Access your Ente instance at: $CLOUDRON_APP_ORIGIN"
# Check communication between frontend and backend services
echo "==> Checking communication between frontend and backend services"
echo "==> Testing main API communication"
MAIN_API_TEST=$(curl -s --max-time 2 http://0.0.0.0:$CADDY_PORT/api/health)
if [ $? -eq 0 ]; then
echo "==> Main API communication via frontend is working"
else
echo "==> WARNING: Main API communication via frontend is NOT working"
echo "==> Response: $MAIN_API_TEST"
fi
echo "==> Testing public albums API communication"
PUBLIC_API_TEST=$(curl -s --max-time 2 http://0.0.0.0:$CADDY_PORT/public/health)
if [ $? -eq 0 ]; then
echo "==> Public Albums API communication via frontend is working"
else
echo "==> WARNING: Public Albums API communication via frontend is NOT working"
echo "==> Response: $PUBLIC_API_TEST"
fi
echo "==> Testing frontend config.js"
CONFIG_JS_TEST=$(curl -s --max-time 2 http://0.0.0.0:$CADDY_PORT/config.js)
if [[ "$CONFIG_JS_TEST" == *"API_URL"* && "$CONFIG_JS_TEST" == *"PUBLIC_ALBUMS_URL"* ]]; then
echo "==> Frontend configuration is properly loaded"
else
echo "==> WARNING: Frontend configuration is not properly loaded"
echo "==> Response: $CONFIG_JS_TEST"
fi
echo "==> Entering wait state - press Ctrl+C to stop"
# Wait for all background processes to complete (or for user to interrupt)
wait $SERVER_PID
wait $PUBLIC_SERVER_PID
wait $CADDY_PID
# Create a new go file to inject into the build that overrides the database connection