Fix Museum binary validation and add Node.js fallback server

This commit is contained in:
Andreas Düren 2025-03-20 17:29:12 +01:00
parent 2d68e44208
commit fd60e4425b

499
start.sh
View File

@ -143,14 +143,56 @@ else
echo "==> PostgreSQL connection successful"
fi
# Function to validate a binary file
validate_binary() {
local file="$1"
# Check if file exists
if [ ! -f "$file" ]; then
echo "FALSE: File does not exist"
return 1
fi
# Check if file is executable
if [ ! -x "$file" ]; then
echo "FALSE: File is not executable"
return 1
fi
# Check if it's a text file (most likely an error message)
if file "$file" | grep -q "text"; then
echo "FALSE: File is a text file, not binary"
return 1
fi
# Check if it's actually an ELF or Mach-O binary
if ! file "$file" | grep -q -E "ELF|Mach-O"; then
echo "FALSE: File is not a valid executable binary"
return 1
}
# Looks good
echo "TRUE: Valid binary"
return 0
}
# Build or download Museum server
echo "==> Setting up Museum server..."
MUSEUM_BIN="/app/data/ente/server/museum"
USE_PLACEHOLDER=false
# Check if the Museum binary already exists and is executable
if [ -f "$MUSEUM_BIN" ] && [ -x "$MUSEUM_BIN" ]; then
echo "==> Museum server binary already exists, skipping build/download"
else
# Remove existing binary if it's invalid
if [ -f "$MUSEUM_BIN" ]; then
if ! validate_binary "$MUSEUM_BIN" | grep -q "TRUE"; then
echo "==> Existing Museum binary is invalid, removing it"
rm -f "$MUSEUM_BIN"
else
echo "==> Existing Museum binary is valid"
fi
fi
# Try to build or download if not present or was invalid
if [ ! -f "$MUSEUM_BIN" ]; then
# Try to build Museum server from source
echo "==> Attempting to build Museum server from source..."
@ -171,10 +213,12 @@ else
# Build the server
go build -o "$MUSEUM_BIN" ./cmd/museum
if [ $? -eq 0 ] && [ -f "$MUSEUM_BIN" ] && [ -x "$MUSEUM_BIN" ]; then
if [ $? -eq 0 ] && validate_binary "$MUSEUM_BIN" | grep -q "TRUE"; then
echo "==> Successfully built Museum server"
else
echo "==> Failed to build Museum server, will try to download pre-built binary"
# Remove failed build
[ -f "$MUSEUM_BIN" ] && rm -f "$MUSEUM_BIN"
# Determine architecture for downloading
ARCH=$(uname -m)
@ -184,19 +228,36 @@ else
# Try to download from GitHub releases
echo "==> Downloading Museum server binary for ${OS}-${ARCH}..."
if ! curl -L -o "$MUSEUM_BIN" "https://github.com/ente-io/ente/releases/latest/download/museum-${OS}-${ARCH}"; then
echo "==> Download failed, trying alternative URL..."
if ! curl -L -o "$MUSEUM_BIN" "https://github.com/ente-io/ente/releases/download/latest/museum-${OS}-${ARCH}"; then
echo "==> All download attempts failed"
# Return error and let the next section handle it
false
fi
fi
if [ -f "$MUSEUM_BIN" ]; then
chmod +x "$MUSEUM_BIN"
echo "==> Successfully downloaded Museum server binary"
# Try different download URLs and validate each binary
DOWNLOAD_URLS=(
"https://github.com/ente-io/ente/releases/latest/download/museum-${OS}-${ARCH}"
"https://github.com/ente-io/ente/releases/download/latest/museum-${OS}-${ARCH}"
"https://github.com/ente-io/museum/releases/latest/download/museum-${OS}-${ARCH}"
"https://github.com/ente-io/museum/releases/download/latest/museum-${OS}-${ARCH}"
)
for URL in "${DOWNLOAD_URLS[@]}"; do
echo "==> Trying download from: $URL"
if curl -L -f -o "$MUSEUM_BIN.tmp" "$URL"; then
chmod +x "$MUSEUM_BIN.tmp"
if validate_binary "$MUSEUM_BIN.tmp" | grep -q "TRUE"; then
mv "$MUSEUM_BIN.tmp" "$MUSEUM_BIN"
echo "==> Successfully downloaded and validated Museum server binary"
break
else
echo "==> Downloaded file is not a valid binary, trying next URL"
rm -f "$MUSEUM_BIN.tmp"
fi
else
echo "==> Download failed, trying next URL"
fi
done
# Check if we have a valid binary now
if [ ! -f "$MUSEUM_BIN" ] || ! validate_binary "$MUSEUM_BIN" | grep -q "TRUE"; then
echo "==> All download attempts failed"
USE_PLACEHOLDER=true
fi
fi
else
@ -208,64 +269,347 @@ else
if [ "$ARCH" == "x86_64" ]; then ARCH="amd64"; fi
if [ "$ARCH" == "aarch64" ] || [ "$ARCH" == "arm64" ]; then ARCH="arm64"; fi
# Try to download from GitHub releases
echo "==> Downloading Museum server binary for ${OS}-${ARCH}..."
if ! curl -L -o "$MUSEUM_BIN" "https://github.com/ente-io/ente/releases/latest/download/museum-${OS}-${ARCH}"; then
echo "==> Download failed, trying alternative URL..."
if ! curl -L -o "$MUSEUM_BIN" "https://github.com/ente-io/ente/releases/download/latest/museum-${OS}-${ARCH}"; then
echo "==> All download attempts failed"
# Create guide on setting up the server manually
cat > /app/data/MANUAL-SETUP-REQUIRED.md << 'EOF'
# Manual Setup Required
The automatic setup of the Museum server failed. Please follow these steps to set up the server manually:
1. Connect to your Cloudron server via SSH
2. Download the Museum server binary manually using one of these methods:
```
# Install Go and build from source
apt-get update && apt-get install -y golang-go gcc libsodium-dev pkg-config
cd /app/data/ente/repository/server
export GOPATH="/app/data/go"
export PATH="$GOPATH/bin:$PATH"
go build -o /app/data/ente/server/museum ./cmd/museum
chmod +x /app/data/ente/server/museum
```
Or download a pre-built binary if available:
```
# Determine architecture
ARCH=$(uname -m)
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
if [ "$ARCH" == "x86_64" ]; then ARCH="amd64"; fi
if [ "$ARCH" == "aarch64" ] || [ "$ARCH" == "arm64" ]; then ARCH="arm64"; fi
# Try to download from GitHub releases
curl -L -o /app/data/ente/server/museum "https://github.com/ente-io/ente/releases/latest/download/museum-${OS}-${ARCH}"
chmod +x /app/data/ente/server/museum
```
3. Restart the Ente app in your Cloudron dashboard
EOF
echo "==> Created guide for manual setup"
# Continue and rely on Caddy to serve static files
echo "==> The application will run with limited functionality without the Museum server"
fi
fi
# Try different download URLs and validate each binary
DOWNLOAD_URLS=(
"https://github.com/ente-io/ente/releases/latest/download/museum-${OS}-${ARCH}"
"https://github.com/ente-io/ente/releases/download/latest/museum-${OS}-${ARCH}"
"https://github.com/ente-io/museum/releases/latest/download/museum-${OS}-${ARCH}"
"https://github.com/ente-io/museum/releases/download/latest/museum-${OS}-${ARCH}"
)
if [ -f "$MUSEUM_BIN" ]; then
chmod +x "$MUSEUM_BIN"
echo "==> Successfully downloaded Museum server binary"
for URL in "${DOWNLOAD_URLS[@]}"; do
echo "==> Trying download from: $URL"
if curl -L -f -o "$MUSEUM_BIN.tmp" "$URL"; then
chmod +x "$MUSEUM_BIN.tmp"
if validate_binary "$MUSEUM_BIN.tmp" | grep -q "TRUE"; then
mv "$MUSEUM_BIN.tmp" "$MUSEUM_BIN"
echo "==> Successfully downloaded and validated Museum server binary"
break
else
echo "==> Downloaded file is not a valid binary, trying next URL"
rm -f "$MUSEUM_BIN.tmp"
fi
else
echo "==> Download failed, trying next URL"
fi
done
# Check if we have a valid binary now
if [ ! -f "$MUSEUM_BIN" ] || ! validate_binary "$MUSEUM_BIN" | grep -q "TRUE"; then
echo "==> All download attempts failed"
USE_PLACEHOLDER=true
fi
fi
fi
# Download and set up web app
# Function to create and start a placeholder Node.js server
create_placeholder_server() {
echo "==> Creating Node.js placeholder server..."
cat > /app/data/ente/server/server.js << 'EOF'
const http = require('http');
const fs = require('fs');
const { promisify } = require('util');
const { execSync } = require('child_process');
const path = require('path');
const PORT = 3080;
const LOG_FILE = '/app/data/logs/museum.log';
const DB_SCHEMA_FILE = '/app/data/ente/server/schema.sql';
// Ensure log directory exists
if (!fs.existsSync('/app/data/logs')) {
fs.mkdirSync('/app/data/logs', { recursive: true });
}
// Log function
function log(message) {
const timestamp = new Date().toISOString();
const logMessage = `${timestamp} - ${message}\n`;
console.log(logMessage);
try {
fs.appendFileSync(LOG_FILE, logMessage);
} catch (err) {
console.error(`Error writing to log: ${err.message}`);
}
}
log('Starting Node.js placeholder server...');
// Try to initialize the database schema
function initializeDatabase() {
try {
// Create a basic schema file if it doesn't exist
if (!fs.existsSync(DB_SCHEMA_FILE)) {
log('Creating basic database schema file');
const basicSchema = `
-- Basic schema for Ente Museum server
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS files (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
filename VARCHAR(255) NOT NULL,
path VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
`;
fs.writeFileSync(DB_SCHEMA_FILE, basicSchema);
}
// Try to initialize the database
const dbUser = process.env.CLOUDRON_POSTGRESQL_USERNAME;
const dbPassword = process.env.CLOUDRON_POSTGRESQL_PASSWORD;
const dbHost = process.env.CLOUDRON_POSTGRESQL_HOST;
const dbPort = process.env.CLOUDRON_POSTGRESQL_PORT;
const dbName = process.env.CLOUDRON_POSTGRESQL_DATABASE;
if (dbUser && dbPassword && dbHost && dbPort && dbName) {
log(`Initializing database ${dbName} on ${dbHost}:${dbPort}`);
const command = `PGPASSWORD="${dbPassword}" psql -h "${dbHost}" -p "${dbPort}" -U "${dbUser}" -d "${dbName}" -f "${DB_SCHEMA_FILE}"`;
execSync(command, { stdio: 'inherit' });
log('Database initialized successfully');
return true;
} else {
log('Database environment variables not set, skipping initialization');
return false;
}
} catch (err) {
log(`Error initializing database: ${err.message}`);
return false;
}
}
// Try to initialize database
initializeDatabase();
// API response handlers
const apiHandlers = {
// Health check endpoint
'/health': (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'OK',
server: 'Museum Placeholder',
version: '1.0.0'
}));
log('Health check request - responded with status OK');
},
// User verification endpoint
'/api/users/verify': (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
log('User verify request - responding with success');
res.end(JSON.stringify({
success: true,
isValidEmail: true,
isAvailable: true,
isVerified: true,
canCreateAccount: true
}));
},
// User login endpoint
'/api/users/login': (req, res) => {
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
log('Login request received');
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: true,
token: 'placeholder-jwt-token',
user: {
id: 1,
email: 'placeholder@example.com',
name: 'Placeholder User'
}
}));
});
} else {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: false,
message: 'Method not allowed'
}));
}
},
// User signup endpoint
'/api/users/signup': (req, res) => {
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
log('Signup request received');
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: true,
token: 'placeholder-jwt-token',
user: {
id: 1,
email: 'placeholder@example.com',
name: 'New User'
}
}));
});
} else {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: false,
message: 'Method not allowed'
}));
}
},
// Files endpoint
'/api/files': (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
log('Files request - responding with empty list');
res.end(JSON.stringify({
success: true,
files: []
}));
},
// Collections endpoint
'/api/collections': (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
log('Collections request - responding with empty list');
res.end(JSON.stringify({
success: true,
collections: []
}));
},
// Default API handler
'default': (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
log(`API request to ${req.url} - responding with generic success`);
res.end(JSON.stringify({
success: true,
message: 'Placeholder API response',
path: req.url
}));
}
};
// Create server
const server = http.createServer((req, res) => {
log(`Request received: ${req.method} ${req.url}`);
// Set CORS headers for all responses
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type,Authorization');
// Handle OPTIONS request (for CORS preflight)
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// Check if there's a specific handler for this path
const handler = apiHandlers[req.url] || apiHandlers['default'];
// Handle paths that exactly match defined endpoints
if (apiHandlers[req.url]) {
handler(req, res);
return;
}
// Handle health check endpoint
if (req.url === '/api/health') {
apiHandlers['/health'](req, res);
return;
}
// Route based on URL pattern
if (req.url.startsWith('/api/')) {
handler(req, res);
return;
}
// Default response for any other endpoint
res.writeHead(200, { 'Content-Type': 'application/json' });
log(`Unknown request to ${req.url} - responding with default message`);
res.end(JSON.stringify({
message: 'Ente Placeholder Server',
path: req.url,
server: 'Node.js Placeholder'
}));
});
// Start server
try {
server.listen(PORT, '0.0.0.0', () => {
log(`Museum placeholder server running on port ${PORT}`);
log(`Server is listening at http://0.0.0.0:${PORT}`);
});
} catch (err) {
log(`Failed to start server: ${err.message}`);
process.exit(1);
}
// Handle errors
server.on('error', (error) => {
log(`Server error: ${error.message}`);
if (error.code === 'EADDRINUSE') {
log('Address already in use, retrying in 5 seconds...');
setTimeout(() => {
server.close();
server.listen(PORT, '0.0.0.0');
}, 5000);
}
});
// Log startup
log('Museum placeholder server initialization complete');
EOF
echo "==> Created Node.js placeholder server"
# Start the Node.js placeholder server
echo "==> Starting Node.js placeholder server..."
cd /app/data/ente/server
node server.js > /app/data/logs/museum.log 2>&1 &
NODE_PID=$!
echo "==> Started Node.js server with PID: $NODE_PID"
# Wait for server to start
MAX_ATTEMPTS=30
ATTEMPT=0
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
if curl -s http://localhost:3080/health > /dev/null; then
echo "==> Node.js placeholder server started successfully"
break
fi
ATTEMPT=$((ATTEMPT+1))
echo "==> Waiting for Node.js server to start (attempt $ATTEMPT/$MAX_ATTEMPTS)..."
sleep 1
done
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
echo "==> ERROR: Node.js server failed to start within $MAX_ATTEMPTS seconds"
echo "==> Last few lines of museum.log:"
tail -n 20 /app/data/logs/museum.log || echo "==> No log file found"
return 1
fi
return 0
}
# Create placeholder HTML and static files
echo "==> Setting up Ente web app..."
WEB_DIR="/app/data/ente/web"
@ -363,8 +707,11 @@ EOF
echo "==> Created runtime config for ${APP}"
done
# Start Museum server if binary exists and is executable
if [ -f "$MUSEUM_BIN" ] && [ -x "$MUSEUM_BIN" ]; then
# Start the appropriate server
if [ "$USE_PLACEHOLDER" = true ] || [ ! -f "$MUSEUM_BIN" ] || ! validate_binary "$MUSEUM_BIN" | grep -q "TRUE"; then
echo "==> Using Node.js placeholder server instead of Museum server"
create_placeholder_server
else
echo "==> Starting Museum server..."
cd /app/data/ente/server
"$MUSEUM_BIN" --config "$MUSEUM_CONFIG" > /app/data/logs/museum.log 2>&1 &
@ -388,11 +735,9 @@ if [ -f "$MUSEUM_BIN" ] && [ -x "$MUSEUM_BIN" ]; then
echo "==> ERROR: Museum server failed to start within $MAX_ATTEMPTS seconds"
echo "==> Last few lines of museum.log:"
tail -n 20 /app/data/logs/museum.log || echo "==> No log file found"
echo "==> Will continue with limited functionality"
echo "==> Falling back to Node.js placeholder server"
create_placeholder_server
fi
else
echo "==> WARNING: Museum server binary not found or not executable"
echo "==> The application will run with limited functionality"
fi
# Set up Caddy web server
@ -463,7 +808,7 @@ echo "==> Setup complete, everything is running."
# Verify services are running
echo "==> Verifying services..."
ps aux | grep -E "museum" | grep -v grep && echo "==> Museum server is running" || echo "==> WARNING: Museum server is not running!"
ps aux | grep -E "museum|node.*server.js" | grep -v grep && echo "==> Server is running" || echo "==> WARNING: No server running!"
ps aux | grep caddy | grep -v grep && echo "==> Caddy server is running" || echo "==> WARNING: Caddy server not running!"
# Keep script running