Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a4b549d9f | |||
| f3604b438b | |||
| 531831e84b | |||
| 90bb21e38b | |||
| 2f1a137342 | |||
| adf576fbe1 | |||
| 4264026bbe | |||
| 24b4a314a9 | |||
| ba825823a0 | |||
| fb16b7bbb8 | |||
| 8404125ff0 | |||
| 61ad2d61c1 | |||
| 9fd6b44487 | |||
| 9fe10bcce2 | |||
| f2abb40987 | |||
| 3697344cd0 | |||
| 4aa0ff705f | |||
| dc482e614a | |||
| 448882cfef | |||
| 7f9cb33a40 |
@@ -43,6 +43,8 @@ jobs:
|
||||
|
||||
- name: Log in to Container Registry
|
||||
if: github.event_name != 'pull_request' || github.event.inputs.push == 'true'
|
||||
id: login-ghcr
|
||||
continue-on-error: true
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
@@ -73,7 +75,8 @@ jobs:
|
||||
with:
|
||||
context: ./backend
|
||||
file: ./backend/Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' || github.event.inputs.push == 'true' }}
|
||||
# Always build; only push when registry login succeeded
|
||||
push: ${{ (github.event_name != 'pull_request' || github.event.inputs.push == 'true') && steps.login-ghcr.outcome == 'success' }}
|
||||
tags: ${{ steps.meta-backend.outputs.tags }}
|
||||
labels: ${{ steps.meta-backend.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -86,7 +89,7 @@ jobs:
|
||||
VERSION=${{ steps.meta-backend.outputs.version }}
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success'
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
image-ref: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:${{ steps.meta-backend.outputs.version }}
|
||||
@@ -96,7 +99,7 @@ jobs:
|
||||
timeout: '10m'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success'
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: 'trivy-backend.sarif'
|
||||
@@ -120,6 +123,8 @@ jobs:
|
||||
|
||||
- name: Log in to Container Registry
|
||||
if: github.event_name != 'pull_request' || github.event.inputs.push == 'true'
|
||||
id: login-ghcr
|
||||
continue-on-error: true
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
@@ -150,7 +155,8 @@ jobs:
|
||||
with:
|
||||
context: ./frontend
|
||||
file: ./frontend/Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' || github.event.inputs.push == 'true' }}
|
||||
# Always build; only push when registry login succeeded
|
||||
push: ${{ (github.event_name != 'pull_request' || github.event.inputs.push == 'true') && steps.login-ghcr.outcome == 'success' }}
|
||||
tags: ${{ steps.meta-frontend.outputs.tags }}
|
||||
labels: ${{ steps.meta-frontend.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -163,7 +169,7 @@ jobs:
|
||||
VERSION=${{ steps.meta-frontend.outputs.version }}
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success'
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
image-ref: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}:${{ steps.meta-frontend.outputs.version }}
|
||||
@@ -173,7 +179,7 @@ jobs:
|
||||
timeout: '10m'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success'
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: 'trivy-frontend.sarif'
|
||||
@@ -220,4 +226,4 @@ jobs:
|
||||
echo "- PR number (for pull requests)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Version tags (for releases)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Short SHA with branch prefix" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`latest\` (for main branch)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`latest\` (for main branch)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "picpeak-backend",
|
||||
"version": "1.0.112",
|
||||
"version": "1.0.121",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "picpeak-backend",
|
||||
"version": "1.0.112",
|
||||
"version": "1.0.121",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.850.0",
|
||||
"@aws-sdk/lib-storage": "^3.850.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "picpeak-backend",
|
||||
"version": "1.0.112",
|
||||
"version": "1.0.121",
|
||||
"description": "Backend for PicPeak event photo sharing platform",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
||||
+72
-19
@@ -12,6 +12,7 @@ logger.info('Server starting up', {
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
const helmet = require('helmet');
|
||||
const cors = require('cors');
|
||||
@@ -42,25 +43,36 @@ const PORT = process.env.PORT || 3000;
|
||||
app.set('trust proxy', 'loopback, linklocal, uniquelocal');
|
||||
|
||||
// Security middleware with custom CSP
|
||||
// In native HTTP installs, do NOT force HTTPS for subresources.
|
||||
const enableHsts = process.env.ENABLE_HSTS === 'true';
|
||||
const cspDirectives = {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'"], // Required for React
|
||||
styleSrc: ["'self'", "'unsafe-inline'", "https:"], // Required for styled components
|
||||
imgSrc: ["'self'", "data:", "https:", "blob:"], // Allow data URLs and external images
|
||||
connectSrc: ["'self'"], // API connections
|
||||
fontSrc: ["'self'", "https:", "data:"], // Web fonts
|
||||
objectSrc: ["'none'"], // Disable plugins
|
||||
mediaSrc: ["'self'"], // Audio/video
|
||||
frameSrc: ["'none'"], // Disable iframes
|
||||
};
|
||||
// Only upgrade insecure requests when HSTS explicitly enabled (HTTPS deployment)
|
||||
if (enableHsts) {
|
||||
// In helmet, an empty array enables the directive
|
||||
cspDirectives.upgradeInsecureRequests = [];
|
||||
}
|
||||
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'"], // Required for React
|
||||
styleSrc: ["'self'", "'unsafe-inline'", "https:"], // Required for styled components
|
||||
imgSrc: ["'self'", "data:", "https:", "blob:"], // Allow data URLs and external images
|
||||
connectSrc: ["'self'"], // API connections
|
||||
fontSrc: ["'self'", "https:", "data:"], // Web fonts
|
||||
objectSrc: ["'none'"], // Disable plugins
|
||||
mediaSrc: ["'self'"], // Audio/video
|
||||
frameSrc: ["'none'"], // Disable iframes
|
||||
},
|
||||
// Avoid helmet adding defaults like upgrade-insecure-requests when not desired
|
||||
useDefaults: false,
|
||||
directives: cspDirectives,
|
||||
},
|
||||
hsts: {
|
||||
hsts: enableHsts ? {
|
||||
maxAge: 31536000, // 1 year
|
||||
includeSubDomains: true,
|
||||
preload: true
|
||||
},
|
||||
} : false,
|
||||
permittedCrossDomainPolicies: false,
|
||||
referrerPolicy: { policy: "strict-origin-when-cross-origin" }
|
||||
}));
|
||||
@@ -72,14 +84,14 @@ app.use((req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
// CORS configuration
|
||||
// CORS configuration (apply only to API routes)
|
||||
const corsOptions = {
|
||||
origin: function (origin, callback) {
|
||||
const allowedOrigins = [
|
||||
process.env.FRONTEND_URL || 'http://localhost:3005',
|
||||
process.env.ADMIN_URL || 'http://localhost:3005'
|
||||
];
|
||||
|
||||
|
||||
// In development, also allow localhost origins
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
allowedOrigins.push(
|
||||
@@ -89,18 +101,22 @@ const corsOptions = {
|
||||
'http://localhost:3000' // Direct backend access
|
||||
);
|
||||
}
|
||||
|
||||
// Allow requests with no origin (like mobile apps or curl)
|
||||
|
||||
// Allow requests with no origin (like curl) and allow-listed origins
|
||||
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
// Do not error globally; just omit CORS headers on disallowed origins
|
||||
callback(null, false);
|
||||
}
|
||||
},
|
||||
credentials: true
|
||||
};
|
||||
|
||||
app.use(cors(corsOptions));
|
||||
// Only attach CORS to API endpoints, not static assets
|
||||
app.use('/api', cors(corsOptions));
|
||||
// Handle preflight explicitly for API paths
|
||||
app.options('/api/*', cors(corsOptions));
|
||||
|
||||
// Initialize rate limiters (they will be created dynamically)
|
||||
let generalRateLimiter;
|
||||
@@ -124,6 +140,22 @@ async function initializeRateLimiters() {
|
||||
app.use(express.json({ limit: '100mb' }));
|
||||
app.use(express.urlencoded({ extended: true, limit: '100mb' }));
|
||||
|
||||
// Request logging for API routes (with timestamps)
|
||||
const apiRequestLogger = (req, res, next) => {
|
||||
try {
|
||||
const started = Date.now();
|
||||
const ts = new Date().toISOString();
|
||||
logger.info(`[${ts}] ${req.method} ${req.originalUrl}`);
|
||||
res.on('finish', () => {
|
||||
const ms = Date.now() - started;
|
||||
const tsDone = new Date().toISOString();
|
||||
logger.info(`[${tsDone}] ${req.method} ${req.originalUrl} -> ${res.statusCode} (${ms}ms)`);
|
||||
});
|
||||
} catch (_) {}
|
||||
next();
|
||||
};
|
||||
app.use('/api', apiRequestLogger);
|
||||
|
||||
// Maintenance mode middleware - add after body parsing but before routes
|
||||
app.use(maintenanceMiddleware);
|
||||
|
||||
@@ -219,6 +251,27 @@ app.use('/api/public', require('./src/routes/publicCMS'));
|
||||
app.use('/api/images', require('./src/routes/protectedImages'));
|
||||
app.use('/api/secure-images', secureImagesRoutes);
|
||||
|
||||
// Optional: Serve built frontend (native installs)
|
||||
try {
|
||||
const serveFrontendEnv = process.env.SERVE_FRONTEND; // 'true' | 'false' | undefined
|
||||
const frontendDir = process.env.FRONTEND_DIR || path.join(__dirname, '../frontend/dist');
|
||||
const indexPath = path.join(frontendDir, 'index.html');
|
||||
// Auto-serve when dist exists unless explicitly disabled
|
||||
const shouldServe = (serveFrontendEnv === 'true') || ((serveFrontendEnv === undefined || serveFrontendEnv === 'auto') && fs.existsSync(indexPath));
|
||||
if (shouldServe) {
|
||||
logger.info(`Serving frontend from ${frontendDir}`);
|
||||
app.use(express.static(frontendDir));
|
||||
// SPA fallback for non-API routes
|
||||
app.get([ '/', '/admin', '/admin/*', '/gallery/*' ], (req, res) => {
|
||||
res.sendFile(indexPath);
|
||||
});
|
||||
} else {
|
||||
logger.info('Frontend static serving disabled or dist not found', { serveFrontendEnv, frontendDir });
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Failed to enable frontend static serving', { error: e.message });
|
||||
}
|
||||
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('EXPRESS ERROR HANDLER:', err);
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const knex = require('knex');
|
||||
const knexConfig = require('../../knexfile');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// Ensure SQLite directory exists when using file-based DB (native installs)
|
||||
try {
|
||||
const isPostgres = knexConfig && knexConfig.client === 'pg';
|
||||
if (!isPostgres && knexConfig && knexConfig.connection) {
|
||||
const filename = typeof knexConfig.connection === 'object'
|
||||
? knexConfig.connection.filename
|
||||
: (typeof knexConfig.connection === 'string' ? knexConfig.connection : null);
|
||||
if (filename && typeof filename === 'string') {
|
||||
const dir = path.dirname(filename);
|
||||
if (dir && dir !== '.') {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Non-fatal: log and continue; SQLite will fail later if still missing
|
||||
try { logger.warn('SQLite directory ensure failed', { error: e.message }); } catch (_) {}
|
||||
}
|
||||
|
||||
// Create database connection with built-in retry logic
|
||||
const db = knex(knexConfig);
|
||||
|
||||
@@ -312,4 +333,4 @@ async function logActivity(activityType, metadata = {}, eventId = null, actor =
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { db, initializeDatabase, logActivity, withRetry };
|
||||
module.exports = { db, initializeDatabase, logActivity, withRetry };
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "picpeak-frontend",
|
||||
"version": "1.0.112",
|
||||
"version": "1.0.121",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "picpeak-frontend",
|
||||
"version": "1.0.112",
|
||||
"version": "1.0.121",
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.0.0",
|
||||
"@tiptap/extension-character-count": "^2.26.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "picpeak-frontend",
|
||||
"private": true,
|
||||
"version": "1.0.112",
|
||||
"version": "1.0.121",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
+119
-21
@@ -61,6 +61,23 @@ UNINSTALL_MODE=false
|
||||
# Helper Functions
|
||||
################################################################################
|
||||
|
||||
# Run a command as the application user, even if sudo is not available
|
||||
run_as_user() {
|
||||
local cmd="$*"
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
# Already non-root; just run
|
||||
bash -lc "$cmd"
|
||||
return $?
|
||||
fi
|
||||
if command_exists sudo; then
|
||||
sudo -H -u "$NATIVE_APP_USER" bash -lc "$cmd"
|
||||
elif command_exists runuser; then
|
||||
runuser -u "$NATIVE_APP_USER" -- bash -lc "$cmd"
|
||||
else
|
||||
su -s /bin/bash - "$NATIVE_APP_USER" -c "$cmd"
|
||||
fi
|
||||
}
|
||||
|
||||
print_banner() {
|
||||
echo -e "${PURPLE}"
|
||||
echo "╔════════════════════════════════════════════════════════════════════════╗"
|
||||
@@ -110,11 +127,16 @@ generate_jwt_secret() {
|
||||
}
|
||||
|
||||
get_available_ram_mb() {
|
||||
if command_exists free; then
|
||||
free -m | awk '/^Mem:/{print $2}'
|
||||
else
|
||||
echo "0"
|
||||
# Prefer /proc/meminfo (always available on Linux), fallback to free(1)
|
||||
if [[ -r /proc/meminfo ]]; then
|
||||
awk '/^MemTotal:/ { printf "%d\n", $2/1024 }' /proc/meminfo
|
||||
return
|
||||
fi
|
||||
if command_exists free; then
|
||||
free -m | awk '/^Mem:/ {print $2}'
|
||||
return
|
||||
fi
|
||||
echo "0"
|
||||
}
|
||||
|
||||
get_available_disk_gb() {
|
||||
@@ -552,14 +574,26 @@ setup_native_installation() {
|
||||
# Create application directory
|
||||
log_step "Creating application directory..."
|
||||
mkdir -p "$NATIVE_APP_DIR"/{app,events/{active,archived},logs,config}
|
||||
chown -R $NATIVE_APP_USER:$NATIVE_APP_USER "$NATIVE_APP_DIR"
|
||||
|
||||
# Clone repository
|
||||
log_step "Downloading PicPeak..."
|
||||
if [[ -d "$NATIVE_APP_DIR/app/.git" ]]; then
|
||||
cd "$NATIVE_APP_DIR/app"
|
||||
git pull
|
||||
# Ensure correct remote and update even if history was rewritten
|
||||
run_as_user "git config --global --add safe.directory $NATIVE_APP_DIR/app" || true
|
||||
run_as_user "git remote set-url origin $REPO_URL" || true
|
||||
run_as_user "git fetch --all --prune" || true
|
||||
# Prefer checking out remote main and hard resetting to avoid merge prompts
|
||||
if ! run_as_user "git checkout -B main origin/main"; then
|
||||
run_as_user "git checkout main" || true
|
||||
run_as_user "git reset --hard origin/main"
|
||||
fi
|
||||
else
|
||||
git clone "$REPO_URL" "$NATIVE_APP_DIR/app"
|
||||
run_as_user "git clone $REPO_URL $NATIVE_APP_DIR/app" || {
|
||||
run_as_user "git config --global --add safe.directory $NATIVE_APP_DIR/app"
|
||||
run_as_user "git clone $REPO_URL $NATIVE_APP_DIR/app"
|
||||
}
|
||||
fi
|
||||
|
||||
# Install dependencies
|
||||
@@ -568,6 +602,19 @@ setup_native_installation() {
|
||||
# Install backend production dependencies
|
||||
cd "$NATIVE_APP_DIR/app/backend"
|
||||
npm install --production
|
||||
# Ensure SQLite data directory exists for native installs
|
||||
mkdir -p "$NATIVE_APP_DIR/app/backend/data"
|
||||
|
||||
# Build frontend for native serving
|
||||
log_step "Building frontend..."
|
||||
if [[ -d "$NATIVE_APP_DIR/app/frontend" ]]; then
|
||||
cd "$NATIVE_APP_DIR/app/frontend"
|
||||
# Try ci (faster/clean) then fallback to install
|
||||
run_as_user "npm ci --include=dev" || run_as_user "npm install"
|
||||
run_as_user "npm run build"
|
||||
else
|
||||
log_warn "Frontend directory not found; admin UI will not be served by backend"
|
||||
fi
|
||||
|
||||
# Generate secrets
|
||||
local jwt_secret=$(generate_jwt_secret)
|
||||
@@ -617,6 +664,10 @@ DEFAULT_EXPIRY_DAYS=30
|
||||
# Logging
|
||||
LOG_DIR=$NATIVE_APP_DIR/logs
|
||||
LOG_LEVEL=info
|
||||
|
||||
# Frontend serving (native installs)
|
||||
SERVE_FRONTEND=true
|
||||
FRONTEND_DIR=$NATIVE_APP_DIR/app/frontend/dist
|
||||
EOF
|
||||
|
||||
# Set permissions
|
||||
@@ -626,7 +677,7 @@ EOF
|
||||
# Run database migrations
|
||||
log_step "Initializing database..."
|
||||
cd "$NATIVE_APP_DIR/app/backend"
|
||||
sudo -u $NATIVE_APP_USER npm run migrate
|
||||
run_as_user "npm run migrate"
|
||||
|
||||
# Create systemd services
|
||||
create_systemd_services
|
||||
@@ -639,8 +690,15 @@ EOF
|
||||
# Start services
|
||||
log_step "Starting services..."
|
||||
systemctl daemon-reload
|
||||
systemctl enable picpeak-backend picpeak-workers
|
||||
systemctl start picpeak-backend picpeak-workers
|
||||
systemctl enable picpeak-backend
|
||||
# Stop/remove legacy workers service if present
|
||||
if systemctl list-unit-files | grep -q '^picpeak-workers.service'; then
|
||||
systemctl disable picpeak-workers || true
|
||||
systemctl stop picpeak-workers || true
|
||||
rm -f /etc/systemd/system/picpeak-workers.service
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
systemctl start picpeak-backend
|
||||
|
||||
log_success "Native installation completed!"
|
||||
}
|
||||
@@ -893,16 +951,31 @@ print_success_message() {
|
||||
|
||||
update_installation() {
|
||||
print_header "Updating PicPeak"
|
||||
|
||||
# Detect existing installation
|
||||
if [[ -d "$DOCKER_APP_DIR" ]] || [[ -d "/home/${SUDO_USER:-}/picpeak" ]]; then
|
||||
INSTALL_METHOD="docker"
|
||||
update_docker_installation
|
||||
elif [[ -d "$NATIVE_APP_DIR" ]]; then
|
||||
|
||||
# Prefer explicit native install detection first
|
||||
native_detected=false
|
||||
docker_detected=false
|
||||
|
||||
# Native detection: app/backend exists OR systemd unit present
|
||||
if [[ -d "$NATIVE_APP_DIR/app/backend" ]]; then
|
||||
native_detected=true
|
||||
elif command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files | grep -q '^picpeak-backend.service'; then
|
||||
native_detected=true
|
||||
fi
|
||||
|
||||
# Docker detection: docker app dir or user home picpeak dir exists
|
||||
if [[ -d "$DOCKER_APP_DIR" ]] || [[ -n "${SUDO_USER:-}" && -d "/home/${SUDO_USER}/picpeak" ]]; then
|
||||
docker_detected=true
|
||||
fi
|
||||
|
||||
if [[ "$native_detected" == true ]]; then
|
||||
INSTALL_METHOD="native"
|
||||
update_native_installation
|
||||
elif [[ "$docker_detected" == true ]]; then
|
||||
INSTALL_METHOD="docker"
|
||||
update_docker_installation
|
||||
else
|
||||
die "No existing PicPeak installation found"
|
||||
die "No existing PicPeak installation found (native dir $NATIVE_APP_DIR/app/backend or docker dir $DOCKER_APP_DIR not present)"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -935,7 +1008,10 @@ update_native_installation() {
|
||||
log_step "Updating native installation..."
|
||||
|
||||
# Stop services
|
||||
systemctl stop picpeak-backend picpeak-workers
|
||||
systemctl stop picpeak-backend || true
|
||||
if systemctl list-unit-files | grep -q '^picpeak-workers.service'; then
|
||||
systemctl stop picpeak-workers || true
|
||||
fi
|
||||
|
||||
# Backup current configuration
|
||||
if [[ -f "$NATIVE_APP_DIR/app/backend/.env" ]]; then
|
||||
@@ -944,17 +1020,39 @@ update_native_installation() {
|
||||
|
||||
# Pull latest code
|
||||
cd "$NATIVE_APP_DIR/app"
|
||||
sudo -u $NATIVE_APP_USER git pull
|
||||
run_as_user "git config --global --add safe.directory $NATIVE_APP_DIR/app" || true
|
||||
run_as_user "git remote set-url origin $REPO_URL" || true
|
||||
run_as_user "git fetch --all --prune"
|
||||
if ! run_as_user "git checkout -B main origin/main"; then
|
||||
run_as_user "git checkout main" || true
|
||||
run_as_user "git reset --hard origin/main"
|
||||
fi
|
||||
|
||||
# Update backend dependencies
|
||||
cd "$NATIVE_APP_DIR/app/backend"
|
||||
sudo -u $NATIVE_APP_USER npm install --production
|
||||
run_as_user "npm install --production"
|
||||
|
||||
# Run migrations
|
||||
sudo -u $NATIVE_APP_USER npm run migrate
|
||||
run_as_user "npm run migrate"
|
||||
|
||||
# Rebuild frontend (ensure admin UI for native installs)
|
||||
if [[ -d "$NATIVE_APP_DIR/app/frontend" ]]; then
|
||||
log_step "Rebuilding frontend..."
|
||||
cd "$NATIVE_APP_DIR/app/frontend"
|
||||
run_as_user "npm ci --include=dev" || run_as_user "npm install"
|
||||
run_as_user "npm run build"
|
||||
fi
|
||||
|
||||
# Ensure env has frontend serving flags
|
||||
if ! grep -q '^SERVE_FRONTEND=' "$NATIVE_APP_DIR/app/backend/.env"; then
|
||||
echo "SERVE_FRONTEND=true" >> "$NATIVE_APP_DIR/app/backend/.env"
|
||||
fi
|
||||
if ! grep -q '^FRONTEND_DIR=' "$NATIVE_APP_DIR/app/backend/.env"; then
|
||||
echo "FRONTEND_DIR=$NATIVE_APP_DIR/app/frontend/dist" >> "$NATIVE_APP_DIR/app/backend/.env"
|
||||
fi
|
||||
|
||||
# Restart services
|
||||
systemctl start picpeak-backend picpeak-workers
|
||||
systemctl restart picpeak-backend
|
||||
|
||||
log_success "Native installation updated successfully!"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user