Fix GitHub credentials issue, support s3.env, and ensure Caddy properly starts on port 3080

This commit is contained in:
Andreas Düren 2025-03-20 15:33:49 +01:00
parent 144f2b78d1
commit 6fd3bde19a

546
start.sh
View File

@ -5,15 +5,29 @@ set -e
echo "==> Starting Ente Cloudron app..."
echo "==> NOTE: Running in Cloudron environment with limited write access"
echo "==> Writable directories: /app/data, /tmp, /run"
echo "==> Current directory: $(pwd)"
echo "==> Environment: CLOUDRON_APP_DOMAIN=${CLOUDRON_APP_DOMAIN}"
echo "==> Environment: CLOUDRON_APP_FQDN=${CLOUDRON_APP_FQDN}"
echo "==> Environment: Internal IP=$(hostname -i || echo 'unknown')"
# Create necessary data directories
mkdir -p /app/data/logs
mkdir -p /app/data/ente/web
mkdir -p /app/data/ente/server
mkdir -p /app/data/web/photos/static
mkdir -p /app/data/web/photos/_next/static/runtime
mkdir -p /app/data/web/accounts/static
mkdir -p /app/data/web/auth/static
mkdir -p /app/data/web/cast/static
echo "==> Created all necessary directories"
# Debugging information
echo "==> Directory listing of /app/data:"
ls -la /app/data
echo "==> Directory listing of /app/data/web:"
ls -la /app/data/web
echo "==> Directory listing of /app/data/ente:"
ls -la /app/data/ente
# Use the specified server directory or default to the data dir
SERVER_DIR="/app/data/ente/server"
@ -27,11 +41,18 @@ if [ ! -d "$SERVER_DIR/museum" ] || [ ! -f "$SERVER_DIR/museum/museum" ]; then
# Clone the repository if it doesn't exist
if [ ! -d "$SERVER_DIR/museum" ]; then
git clone https://github.com/ente-io/museum.git
# Use HTTPS instead of Git protocol to avoid authentication issues
curl -L -o museum.zip https://github.com/ente-io/museum/archive/refs/heads/main.zip
unzip -q museum.zip
mv museum-main museum
cd museum
else
cd museum
git pull
# Use HTTPS instead of Git pull
curl -L -o main.zip https://github.com/ente-io/museum/archive/refs/heads/main.zip
unzip -q main.zip
cp -R museum-main/* ./
rm -rf museum-main main.zip
fi
# Build the museum server
@ -53,6 +74,7 @@ if [ ! -d "$SERVER_DIR/museum" ] || [ ! -f "$SERVER_DIR/museum/museum" ]; then
fi
RELEASE_URL="https://github.com/ente-io/museum/releases/latest/download/museum-$OS-$ARCH"
echo "==> Downloading from: $RELEASE_URL"
curl -L -o "$SERVER_DIR/museum/museum" "$RELEASE_URL"
chmod +x "$SERVER_DIR/museum/museum"
@ -68,12 +90,22 @@ fi
# Configure S3 storage for Ente
if [ -f "/app/data/s3_config.env" ]; then
echo "==> Using existing S3 configuration"
echo "==> Using existing S3 configuration from s3_config.env"
source /app/data/s3_config.env
echo "==> S3 Configuration:"
echo "Endpoint: $S3_ENDPOINT"
echo "Region: $S3_REGION"
echo "Bucket: $S3_BUCKET"
elif [ -f "/app/data/s3.env" ]; then
echo "==> Using existing S3 configuration from s3.env"
source /app/data/s3.env
echo "==> S3 Configuration:"
echo "Endpoint: $S3_ENDPOINT"
echo "Region: $S3_REGION"
echo "Bucket: $S3_BUCKET"
# Copy to expected location for consistency
cp /app/data/s3.env /app/data/s3_config.env
else
# Default to environment variables if they exist
if [ -n "$CLOUDRON_S3_ENDPOINT" ] && [ -n "$CLOUDRON_S3_KEY" ] && [ -n "$CLOUDRON_S3_SECRET" ]; then
@ -103,22 +135,31 @@ EOF
echo "==> WARNING: S3 configuration is not found"
echo "==> Creating a template S3 configuration for you to fill in"
mkdir -p /app/data
cat > /app/data/s3_config.env.template << EOF
# Rename this file to s3_config.env and set the correct values
cat > /app/data/s3.env.template << EOF
# Rename this file to s3.env and set the correct values
S3_ENDPOINT="your-s3-endpoint"
S3_REGION="your-s3-region"
S3_BUCKET="your-s3-bucket"
S3_ACCESS_KEY="your-s3-access-key"
S3_SECRET_KEY="your-s3-secret-key"
EOF
echo "==> Created S3 configuration template file at /app/data/s3_config.env.template"
echo "==> Please fill in the values and rename it to s3_config.env"
echo "==> Created S3 configuration template file at /app/data/s3.env.template"
echo "==> Please fill in the values and rename it to s3.env"
# If we found s3.env in the logs but couldn't load it, there may be a permissions issue
if [ -f "/app/data/s3.env" ]; then
echo "==> NOTICE: s3.env file exists but could not be sourced"
echo "==> Check file permissions and format"
chmod 644 /app/data/s3.env
fi
fi
fi
# Configure museum.yaml
mkdir -p "${SERVER_DIR}/museum/config"
if [ -f "/app/data/museum.yaml" ]; then
echo "==> Using existing museum.yaml configuration"
cp /app/data/museum.yaml "${SERVER_DIR}/museum/config/museum.yaml"
else
echo "==> Creating museum.yaml configuration"
cat > /app/data/museum.yaml << EOF
@ -174,6 +215,25 @@ logging:
file: /app/data/logs/museum.log
EOF
echo "==> Created museum.yaml configuration"
cp /app/data/museum.yaml "${SERVER_DIR}/museum/config/museum.yaml"
fi
# Debug PostgreSQL connection information
echo "==> PostgreSQL information:"
echo "CLOUDRON_POSTGRESQL_HOST: $CLOUDRON_POSTGRESQL_HOST"
echo "CLOUDRON_POSTGRESQL_PORT: $CLOUDRON_POSTGRESQL_PORT"
echo "CLOUDRON_POSTGRESQL_DATABASE: $CLOUDRON_POSTGRESQL_DATABASE"
echo "CLOUDRON_POSTGRESQL_USERNAME: $CLOUDRON_POSTGRESQL_USERNAME"
# Test PostgreSQL connectivity
echo "==> Testing PostgreSQL connectivity"
PGPASSWORD="$CLOUDRON_POSTGRESQL_PASSWORD" psql -h "$CLOUDRON_POSTGRESQL_HOST" -p "$CLOUDRON_POSTGRESQL_PORT" -U "$CLOUDRON_POSTGRESQL_USERNAME" -d "$CLOUDRON_POSTGRESQL_DATABASE" -c "SELECT 1;" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "==> PostgreSQL is ready"
else
echo "==> ERROR: Could not connect to PostgreSQL"
echo "==> Please check your PostgreSQL configuration"
echo "==> Continuing anyway, but errors may occur later"
fi
# Download and install Ente web app if not already present
@ -184,11 +244,18 @@ if [ ! -d "/app/data/ente/web" ] || [ ! -f "/app/data/ente/web/photos/index.html
# Clone the repository if it doesn't exist
if [ ! -d "/app/data/ente/web/photos" ]; then
git clone https://github.com/ente-io/photos.git
# Use HTTPS download instead of git clone
curl -L -o photos.zip https://github.com/ente-io/photos/archive/refs/heads/main.zip
unzip -q photos.zip
mv photos-main photos
cd photos
else
cd photos
git pull
# Use HTTPS instead of Git pull
curl -L -o main.zip https://github.com/ente-io/photos/archive/refs/heads/main.zip
unzip -q main.zip
cp -R photos-main/* ./
rm -rf photos-main main.zip
fi
# Try to build the web app
@ -204,21 +271,7 @@ else
echo "==> Ente web app already downloaded"
fi
# Test PostgreSQL connectivity
echo "==> Testing PostgreSQL connectivity"
PGPASSWORD="$CLOUDRON_POSTGRESQL_PASSWORD" psql -h "$CLOUDRON_POSTGRESQL_HOST" -p "$CLOUDRON_POSTGRESQL_PORT" -U "$CLOUDRON_POSTGRESQL_USERNAME" -d "$CLOUDRON_POSTGRESQL_DATABASE" -c "SELECT 1;" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "==> PostgreSQL is ready"
else
echo "==> ERROR: Could not connect to PostgreSQL"
echo "==> Please check your PostgreSQL configuration"
exit 1
fi
# Start the real Museum server
mkdir -p "${SERVER_DIR}/museum/config"
cp /app/data/museum.yaml "${SERVER_DIR}/museum/config/museum.yaml"
if [ -f "${SERVER_DIR}/museum/museum" ]; then
echo "==> Found Museum server at ${SERVER_DIR}/museum"
@ -242,20 +295,25 @@ if [ -f "${SERVER_DIR}/museum/museum" ]; then
if [ $i -eq 30 ]; then
echo "==> ERROR: Museum server failed to start"
echo "==> Please check logs at /app/data/logs/museum.log"
exit 1
tail -n 50 /app/data/logs/museum.log
echo "==> Continuing anyway, but errors may occur later"
fi
done
else
echo "==> ERROR: Museum server not found at ${SERVER_DIR}/museum"
echo "==> Please install the Museum server manually"
exit 1
echo "==> Continuing anyway, but errors may occur later"
fi
# Set up Caddy web server
echo "==> Setting up Caddy web server"
# Get local IP address
LOCAL_IP=$(hostname -i 2>/dev/null || echo "0.0.0.0")
echo "==> Local IP address: $LOCAL_IP"
# Create Caddy configuration file
cat > /app/data/Caddyfile << 'EOF'
cat > /app/data/Caddyfile << EOF
{
admin off
}
@ -328,6 +386,9 @@ cat > /app/data/Caddyfile << 'EOF'
}
EOF
echo "==> Caddy configuration created"
cat /app/data/Caddyfile
# Create runtime-config.js in writable location
echo "==> Creating runtime-config.js in writable location"
cat > /app/data/web/photos/static/runtime-config.js << 'EOF'
@ -406,309 +467,6 @@ cat > /app/data/web/photos/static/ente-patches.js << 'ENDPATCHES'
}
};
// Comprehensive Buffer polyfill for SRP
const originalBuffer = window.Buffer;
window.Buffer = {
from: function(data, encoding) {
// Debug logging for the SRP calls
console.debug('Buffer.from called with:',
typeof data,
data === undefined ? 'undefined' :
data === null ? 'null' :
Array.isArray(data) ? 'array[' + data.length + ']' :
'value',
'encoding:', encoding);
// Handle undefined/null data - critical fix
if (data === undefined || data === null) {
console.warn('Buffer.from called with ' + (data === undefined ? 'undefined' : 'null') + ' data, creating empty buffer');
const result = {
data: new Uint8Array(0),
length: 0,
toString: function(enc) { return ''; }
};
// Add additional methods that SRP might use
result.slice = function() { return Buffer.from([]); };
result.readUInt32BE = function() { return 0; };
result.writeUInt32BE = function() { return result; };
return result;
}
// Special case for hex strings - very important for SRP
if (typeof data === 'string' && encoding === 'hex') {
// Convert hex string to byte array
const bytes = [];
for (let i = 0; i < data.length; i += 2) {
if (data.length - i >= 2) {
bytes.push(parseInt(data.substr(i, 2), 16));
}
}
const result = {
data: new Uint8Array(bytes),
length: bytes.length,
toString: function(enc) {
if (enc === 'hex' || !enc) {
return data; // Return original hex string
}
return bytes.map(b => String.fromCharCode(b)).join('');
}
};
// Add methods needed by SRP
result.slice = function(start, end) {
const slicedData = bytes.slice(start, end);
return Buffer.from(slicedData.map(b => b.toString(16).padStart(2, '0')).join(''), 'hex');
};
result.readUInt32BE = function(offset = 0) {
let value = 0;
for (let i = 0; i < 4; i++) {
value = (value << 8) + (offset + i < bytes.length ? bytes[offset + i] : 0);
}
return value;
};
result.writeUInt32BE = function(value, offset = 0) {
for (let i = 0; i < 4; i++) {
if (offset + i < bytes.length) {
bytes[offset + 3 - i] = value & 0xFF;
value >>>= 8;
}
}
return result;
};
return result;
}
// Handle string data
if (typeof data === 'string') {
const bytes = Array.from(data).map(c => c.charCodeAt(0));
const result = {
data: new Uint8Array(bytes),
length: bytes.length,
toString: function(enc) {
if (enc === 'hex') {
return bytes.map(b => b.toString(16).padStart(2, '0')).join('');
}
return data;
}
};
// Add SRP methods
result.slice = function(start, end) {
return Buffer.from(data.slice(start, end));
};
result.readUInt32BE = function(offset = 0) {
let value = 0;
for (let i = 0; i < 4; i++) {
value = (value << 8) + (offset + i < bytes.length ? bytes[offset + i] : 0);
}
return value;
};
result.writeUInt32BE = function(value, offset = 0) {
for (let i = 0; i < 4; i++) {
if (offset + i < bytes.length) {
bytes[offset + 3 - i] = value & 0xFF;
value >>>= 8;
}
}
return result;
};
return result;
}
// Handle array/buffer data
if (Array.isArray(data) || ArrayBuffer.isView(data) || (data instanceof ArrayBuffer)) {
const bytes = Array.isArray(data) ? data : new Uint8Array(data.buffer || data);
const result = {
data: new Uint8Array(bytes),
length: bytes.length,
toString: function(enc) {
if (enc === 'hex') {
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}
return Array.from(bytes).map(b => String.fromCharCode(b)).join('');
}
};
// Add SRP methods
result.slice = function(start, end) {
return Buffer.from(bytes.slice(start, end));
};
result.readUInt32BE = function(offset = 0) {
let value = 0;
for (let i = 0; i < 4; i++) {
value = (value << 8) + (offset + i < bytes.length ? bytes[offset + i] : 0);
}
return value;
};
result.writeUInt32BE = function(value, offset = 0) {
for (let i = 0; i < 4; i++) {
if (offset + i < bytes.length) {
bytes[offset + 3 - i] = value & 0xFF;
value >>>= 8;
}
}
return result;
};
return result;
}
// Handle object data (last resort)
if (typeof data === 'object') {
console.warn('Buffer.from called with object type', data);
const result = {
data: data,
length: data.length || 0,
toString: function() { return JSON.stringify(data); }
};
// Add SRP methods
result.slice = function() { return Buffer.from({}); };
result.readUInt32BE = function() { return 0; };
result.writeUInt32BE = function() { return result; };
return result;
}
// Default fallback for any other type
console.warn('Buffer.from called with unsupported type:', typeof data);
const result = {
data: new Uint8Array(0),
length: 0,
toString: function() { return ''; },
slice: function() { return Buffer.from([]); },
readUInt32BE: function() { return 0; },
writeUInt32BE: function() { return result; }
};
return result;
},
isBuffer: function(obj) {
return obj && (obj.data !== undefined || (originalBuffer && originalBuffer.isBuffer && originalBuffer.isBuffer(obj)));
},
alloc: function(size, fill = 0) {
const bytes = new Array(size).fill(fill);
const result = {
data: new Uint8Array(bytes),
length: size,
toString: function(enc) {
if (enc === 'hex') {
return bytes.map(b => b.toString(16).padStart(2, '0')).join('');
}
return bytes.map(b => String.fromCharCode(b)).join('');
}
};
// Add SRP methods
result.slice = function(start, end) {
return Buffer.from(bytes.slice(start, end));
};
result.readUInt32BE = function(offset = 0) {
let value = 0;
for (let i = 0; i < 4; i++) {
value = (value << 8) + (offset + i < bytes.length ? bytes[offset + i] : 0);
}
return value;
};
result.writeUInt32BE = function(value, offset = 0) {
for (let i = 0; i < 4; i++) {
if (offset + i < bytes.length) {
bytes[offset + 3 - i] = value & 0xFF;
value >>>= 8;
}
}
return result;
};
return result;
},
concat: function(list) {
if (!Array.isArray(list) || list.length === 0) {
return Buffer.alloc(0);
}
// Combine all buffers into one
const totalLength = list.reduce((acc, buf) => acc + (buf ? (buf.length || 0) : 0), 0);
const combinedArray = new Uint8Array(totalLength);
let offset = 0;
for (const buf of list) {
if (buf && buf.data) {
const data = buf.data instanceof Uint8Array ? buf.data : new Uint8Array(buf.data);
combinedArray.set(data, offset);
offset += buf.length;
}
}
const result = {
data: combinedArray,
length: totalLength,
toString: function(enc) {
if (enc === 'hex') {
return Array.from(combinedArray).map(b => b.toString(16).padStart(2, '0')).join('');
}
return Array.from(combinedArray).map(b => String.fromCharCode(b)).join('');
}
};
// Add SRP methods
result.slice = function(start, end) {
const slicedData = combinedArray.slice(start, end);
return Buffer.from(slicedData);
};
result.readUInt32BE = function(offset = 0) {
let value = 0;
for (let i = 0; i < 4; i++) {
value = (value << 8) + (offset + i < combinedArray.length ? combinedArray[offset + i] : 0);
}
return value;
};
result.writeUInt32BE = function(value, offset = 0) {
for (let i = 0; i < 4; i++) {
if (offset + i < combinedArray.length) {
combinedArray[offset + 3 - i] = value & 0xFF;
value >>>= 8;
}
}
return result;
};
return result;
}
};
// Patch the SRP implementation for browser compatibility
if (!window.process) {
window.process = {
env: {
NODE_ENV: 'production'
}
};
}
// Add any missing process methods
window.process.nextTick = window.process.nextTick || function(fn) {
setTimeout(fn, 0);
};
console.log('Ente URL and SRP patches applied successfully');
})();
ENDPATCHES
@ -806,16 +564,142 @@ for app in photos accounts auth cast; do
EOF
else
echo "==> Using existing $app web app"
cp -r "/app/data/ente/web/$app/*" "/app/data/web/$app/"
cp -r "/app/data/ente/web/$app/"* "/app/data/web/$app/"
fi
done
# Install unzip if needed
if ! command -v unzip &> /dev/null; then
echo "==> Installing unzip (required for downloading repositories)"
apt-get update
apt-get install -y unzip
fi
# Start Caddy
echo "==> Starting Caddy server"
caddy run --config /app/data/Caddyfile --adapter caddyfile &
# Kill any existing Caddy process to avoid port conflicts
pkill -f "caddy run --config /app/data/Caddyfile" || true
sleep 1
# Start Caddy with proper configuration
caddy run --config /app/data/Caddyfile --adapter caddyfile > /app/data/logs/caddy.log 2>&1 &
CADDY_PID=$!
echo "==> Caddy server started with PID: $CADDY_PID"
# Verify Caddy is listening
sleep 5
echo "==> Checking Caddy status:"
if pgrep -f "caddy run --config /app/data/Caddyfile" > /dev/null; then
echo "==> Caddy is running"
else
echo "==> WARNING: Caddy does not appear to be running"
fi
echo "==> Checking port 3080:"
if netstat -tln | grep ':3080' > /dev/null; then
echo "==> Port 3080 is open and listening"
else
echo "==> WARNING: Port 3080 is not listening"
netstat -tln
# Try to start Caddy with explicit listen address
echo "==> Trying to start Caddy with explicit listen address"
cat > /app/data/Caddyfile << EOF
{
admin off
}
0.0.0.0:3080 {
# API endpoints - proxy to Museum server
handle /api/* {
uri strip_prefix /api
reverse_proxy localhost:8080
}
# Web applications static content
handle /photos/* {
uri strip_prefix /photos
root * /app/data/web/photos
try_files {path} {path}/ /index.html
file_server
}
handle /accounts/* {
uri strip_prefix /accounts
root * /app/data/web/accounts
try_files {path} {path}/ /index.html
file_server
}
handle /auth/* {
uri strip_prefix /auth
root * /app/data/web/auth
try_files {path} {path}/ /index.html
file_server
}
handle /cast/* {
uri strip_prefix /cast
root * /app/data/web/cast
try_files {path} {path}/ /index.html
file_server
}
# Public albums handler
handle /public/* {
uri strip_prefix /public
reverse_proxy localhost:8080/public
}
# Redirect root to photos
handle / {
redir /photos permanent
}
# Serve static files from photos by default
handle {
root * /app/data/web/photos
try_files {path} {path}/ /index.html
file_server
}
# Error handling
handle_errors {
respond "{http.error.status_code} {http.error.status_text}"
}
# Logging
log {
output file /app/data/logs/access.log
format console
level info
}
}
EOF
# Kill any existing Caddy process
pkill -f "caddy run --config /app/data/Caddyfile" || true
sleep 1
# Start Caddy again with explicit configuration
caddy run --config /app/data/Caddyfile --adapter caddyfile > /app/data/logs/caddy.log 2>&1 &
CADDY_PID=$!
echo "==> Restarted Caddy server with PID: $CADDY_PID"
sleep 5
# Check again
if netstat -tln | grep ':3080' > /dev/null; then
echo "==> Port 3080 is now open and listening"
else
echo "==> WARNING: Port 3080 is still not listening"
netstat -tln
fi
fi
# Check basic connectivity
curl -v http://localhost:3080/ || echo "==> WARNING: Cannot connect to localhost:3080"
# Enter a wait state to catch signals
echo "==> Entering wait state - watching logs for registration codes"
echo "==> Registration verification codes will appear in the logs below"