@@ -292,6 +292,224 @@ sentinelcore/
292292
293293---
294294
295+
296+ ## 🚀 Binary Deployment
297+
298+ Pre-compiled binaries are distributed in the release package. No Rust toolchain is needed on the server.
299+
300+ ** Release package contents:**
301+ ```
302+ vulnerability-manager # backend binary (x86_64 Linux)
303+ migrations/ # SQL migration files (56 files)
304+ vulnerability-manager-frontend/build/ # React production build
305+ ```
306+
307+ ### System Requirements
308+
309+ - Debian 12/13 or Ubuntu 22.04+ (x86_64)
310+ - 2+ CPU cores, 4 GB+ RAM
311+ - PostgreSQL 15+, Nginx
312+ - ` nmap ` , ` arp-scan ` packages
313+
314+ ### 1. Install Dependencies
315+
316+ ``` bash
317+ sudo apt update
318+ sudo apt install -y postgresql nginx nmap arp-scan libssl3
319+ ```
320+
321+ ---
322+
323+ ### 2. PostgreSQL Setup
324+
325+ ``` bash
326+ sudo systemctl start postgresql
327+ sudo systemctl enable postgresql
328+
329+ sudo -u postgres psql -c " CREATE USER vlnman WITH PASSWORD 'your-strong-password';"
330+ sudo -u postgres psql -c " CREATE DATABASE vulnerability_manager OWNER vlnman;"
331+ ```
332+
333+ > ** Important:** The backend connects via TCP (` 127.0.0.1:5432 ` ), not via Unix socket.
334+ > Always use ` 127.0.0.1 ` (not ` localhost ` ) in the connection URL — using ` localhost `
335+ > can cause libpq to fall back to the Unix socket, triggering peer authentication
336+ > failure even though the pg_hba.conf allows scram-sha-256 for TCP connections.
337+
338+ ---
339+
340+ ### 3. Apply Database Migrations
341+
342+ Migrations are ** not** applied automatically at startup. Run them manually before
343+ the first start, and again after any update that adds new migration files.
344+
345+ ``` bash
346+ # Copy migrations to the server first, then:
347+ for f in $( ls /opt/sentinelcore/migrations/* .sql | sort) ; do
348+ PGPASSWORD=your-password psql -h 127.0.0.1 -U vlnman -d vulnerability_manager -f " $f "
349+ done
350+ ```
351+
352+ > The migrations directory contains 56 SQL files numbered sequentially.
353+ > They must be applied in alphabetical/numerical order.
354+
355+ ---
356+
357+ ### 4. Install Application Files
358+
359+ ``` bash
360+ sudo mkdir -p /opt/sentinelcore/{migrations,uploads}
361+ sudo chown -R $USER :$USER /opt/sentinelcore
362+
363+ # Backend binary
364+ cp vulnerability-manager /opt/sentinelcore/
365+ chmod +x /opt/sentinelcore/vulnerability-manager
366+
367+ # Migrations (keep for future upgrades)
368+ cp -r migrations/ /opt/sentinelcore/
369+ ```
370+
371+ ---
372+
373+ ### 5. Environment Configuration
374+
375+ Create ` /opt/sentinelcore/.env ` :
376+
377+ ``` bash
378+ # Database — use 127.0.0.1, not localhost (see section 2)
379+ VULN_DATABASE_URL=postgresql://vlnman:your-password@127.0.0.1:5432/vulnerability_manager
380+
381+ # JWT secret — generate a strong random key (minimum 32 characters)
382+ JWT_SECRET_KEY=replace-with-a-long-random-secret-key
383+
384+ # Logging
385+ RUST_LOG=info,vulnerability_manager=info
386+ ```
387+
388+ > ** Config system note:** The default configuration is embedded in the binary at
389+ > compile time via ` include_str! ` . Runtime overrides require the ` VULN_ ` prefix.
390+ > The separator is ` _ ` , so ` VULN_DATABASE_URL ` maps to ` database.url ` ,
391+ > ` VULN_SERVER_PORT ` maps to ` server.port ` , and so on.
392+ >
393+ > Alternatively, place a ` config/development.yaml ` (or ` config/production.yaml `
394+ > with ` APP_ENV=production ` ) in the working directory ` /opt/sentinelcore/ ` .
395+ > This file is loaded at runtime and merged on top of compiled-in defaults.
396+
397+ ---
398+
399+ ### 6. Systemd Service
400+
401+ Create ` /etc/systemd/system/sentinelcore.service ` :
402+
403+ ``` ini
404+ [Unit]
405+ Description =SentinelCore Vulnerability Manager Backend
406+ After =network.target postgresql.service
407+ Requires =postgresql.service
408+
409+ [Service]
410+ Type =simple
411+ User =your-user
412+ WorkingDirectory =/opt/sentinelcore
413+ EnvironmentFile =/opt/sentinelcore/.env
414+ ExecStart =/opt/sentinelcore/vulnerability-manager
415+ Restart =on-failure
416+ RestartSec =5
417+ StandardOutput =journal
418+ StandardError =journal
419+ SyslogIdentifier =sentinelcore
420+
421+ [Install]
422+ WantedBy =multi-user.target
423+ ```
424+
425+ ``` bash
426+ sudo systemctl daemon-reload
427+ sudo systemctl enable sentinelcore
428+ sudo systemctl start sentinelcore
429+
430+ # Verify
431+ sudo systemctl status sentinelcore
432+ sudo journalctl -u sentinelcore -f
433+ ```
434+
435+ ---
436+
437+ ### 7. Nginx Configuration
438+
439+ Deploy the React build and install the site config:
440+
441+ ``` bash
442+ # Copy frontend static files
443+ sudo cp -r build/* /usr/share/nginx/html/
444+ ```
445+
446+ Create ` /etc/nginx/sites-available/sentinelcore ` :
447+
448+ ``` nginx
449+ server {
450+ listen 80;
451+ listen [::]:80;
452+ server_name _;
453+
454+ root /usr/share/nginx/html;
455+ index index.html;
456+
457+ add_header X-Frame-Options "DENY" always;
458+ add_header X-Content-Type-Options "nosniff" always;
459+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
460+
461+ gzip on;
462+ gzip_vary on;
463+ gzip_min_length 1024;
464+ gzip_types text/plain text/css text/xml text/javascript application/json application/javascript;
465+
466+ # Cache static assets (filenames are content-hashed by the build tool)
467+ location ~* \.(js|css|woff2?|ttf|eot|svg|ico|png|jpg)$ {
468+ expires 1y;
469+ add_header Cache-Control "public, immutable";
470+ }
471+
472+ # Proxy API requests to the backend
473+ location /api/ {
474+ proxy_pass http://127.0.0.1:8080;
475+ proxy_http_version 1.1;
476+ proxy_set_header Host $host;
477+ proxy_set_header X-Real-IP $remote_addr;
478+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
479+ proxy_set_header X-Forwarded-Proto $scheme;
480+ }
481+
482+ # Proxy file uploads served by the backend
483+ location /uploads/ {
484+ proxy_pass http://127.0.0.1:8080;
485+ proxy_set_header Host $host;
486+ proxy_set_header X-Real-IP $remote_addr;
487+ }
488+
489+ # SPA fallback — all unmatched routes return index.html
490+ location / {
491+ try_files $uri $uri/ /index.html;
492+ }
493+
494+ location ~ /\. {
495+ deny all;
496+ }
497+ }
498+ ```
499+
500+ ``` bash
501+ sudo ln -sf /etc/nginx/sites-available/sentinelcore /etc/nginx/sites-enabled/sentinelcore
502+ sudo rm -f /etc/nginx/sites-enabled/default # remove default nginx page
503+ sudo nginx -t
504+ sudo systemctl reload nginx
505+ ```
506+
507+ > ** Why this nginx layout matters:** The frontend must be served as static files by
508+ > nginx, with only ` /api/ ` and ` /uploads/ ` proxied to the backend. Proxying
509+ > everything through the backend would bypass the React router (SPA fallback) and
510+ > break client-side navigation.
511+
512+ ---
295513## Development
296514
297515``` bash
0 commit comments