diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3440976 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +.github +node_modules +tools +tests +Dockerfile +.dockerignore +fly.toml +*.log +.DS_Store +**/.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b09d256 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# SecureBit.chat is a static PWA (no backend). The committed build artifacts in +# dist/ are served as-is by nginx — matching the project's release workflow, +# where dist/ is rebuilt and committed for every release. +FROM nginx:1.27-alpine + +# Replace the default nginx config with our static-serving config. +COPY deploy/nginx.conf /etc/nginx/nginx.conf + +# Serve the repository (src/, assets/, libs/, dist/, config/, logo/, sw.js, ...). +COPY . /usr/share/nginx/html + +# Fly.io health checks and routing target this port. +EXPOSE 8080 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 0000000..d99f907 --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,66 @@ +# nginx config for serving SecureBit.chat (static PWA) on Fly.io. +# Mirrors the behavior of the Apache .htaccess: correct JS MIME for ES modules +# (.jsx/.mjs), no-cache for the app shell / service worker / versioning files, +# long-immutable cache for hashed/static assets, security headers, SPA fallback. + +worker_processes auto; +events { worker_connections 1024; } + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # ES modules must be served as JavaScript. nginx's default mime.types maps + # .js but not .mjs/.jsx — declare them explicitly (this overrides .js too). + types { + application/javascript js mjs jsx; + text/css css; + application/json json map; + application/manifest+json webmanifest; + font/woff2 woff2; + font/woff woff; + image/svg+xml svg; + } + + sendfile on; + tcp_nopush on; + server_tokens off; + + gzip on; + gzip_vary on; + gzip_min_length 256; + gzip_types text/plain text/css application/javascript application/json image/svg+xml font/woff2; + + # Decide Cache-Control from the request path. Keeping all add_header calls at + # one level avoids nginx's header-inheritance reset between blocks. + map $uri $sb_cache { + default "public, max-age=31536000, immutable"; + ~^/index\.html$ "no-cache, no-store, must-revalidate"; + ~^/$ "no-cache, no-store, must-revalidate"; + ~^/sw\.js$ "no-cache, no-store, must-revalidate"; + ~^/manifest\.json$ "no-cache, no-store, must-revalidate"; + ~^/meta\.json$ "no-cache, no-store, must-revalidate"; + ~^/dist/ "no-cache, no-store, must-revalidate"; + } + + server { + listen 8080; + listen [::]:8080; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Security headers (frame-ancestors complements the in-page CSP meta tag). + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header X-Frame-Options "DENY" always; + add_header Content-Security-Policy "frame-ancestors 'none';" always; + add_header Cache-Control $sb_cache always; + add_header Service-Worker-Allowed "/" always; + + # SPA-style fallback so unknown routes still load the app shell. + location / { + try_files $uri $uri/ /index.html; + } + } +} diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..8b5a61d --- /dev/null +++ b/fly.toml @@ -0,0 +1,26 @@ +# Fly.io configuration for SecureBit.chat (static PWA served by nginx). +# Set `app` to your chosen Fly app name and `primary_region` to the closest +# region (e.g. fra=Frankfurt, ams=Amsterdam, waw=Warsaw, iad=US East). +# Run `fly platform regions` to list them. + +app = "securebit-chat" +primary_region = "fra" + +[build] + dockerfile = "Dockerfile" + +[http_service] + internal_port = 8080 + force_https = true # matches the app's upgrade-insecure-requests CSP + auto_stop_machines = "stop" + auto_start_machines = true + min_machines_running = 0 # scale to zero when idle (free-tier friendly) + + [http_service.concurrency] + type = "requests" + soft_limit = 200 + hard_limit = 250 + +[[vm]] + size = "shared-cpu-1x" + memory = "256mb"