nibula 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eleventy.js +1 -1
- package/CHANGELOG.md +45 -0
- package/README.md +1 -1
- package/docs/Backend.md +1 -1
- package/docs/Deploy.md +58 -113
- package/docs/Nginx.md +133 -0
- package/nginx.conf +123 -61
- package/package.json +1 -1
package/.eleventy.js
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,51 @@ All notable changes to Nibula are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.1] - 2026-08-03
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `nginx.conf` header now documents the full setup: package install,
|
|
12
|
+
certificate issuance with `certbot --cert-name`, auto-renew, and the
|
|
13
|
+
`sites-available` → `sites-enabled` steps.
|
|
14
|
+
- Troubleshooting table covering the errors whose message points elsewhere
|
|
15
|
+
than the actual cause: `nginx -t` without sudo, wrong certificate name,
|
|
16
|
+
certificate not covering the domain, nginx < 1.25, missing `404.html`,
|
|
17
|
+
DNS not pointing to the server.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Certificates are now issued with `--cert-name SITE_NAME`, so the directory
|
|
21
|
+
under `/etc/letsencrypt/live/` matches the site name and `YOUR_CERTIFICATE`
|
|
22
|
+
is easier to fill in.
|
|
23
|
+
|
|
24
|
+
## [1.4.0] - 2026-08-03
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- `nginx.conf` is now a complete, ready-to-use site file instead of a set of
|
|
28
|
+
directives to paste into your own `server` block. Replace three placeholders
|
|
29
|
+
(`YOUR_DOMAIN`, `SITE_FOLDER`, `YOUR_CERTIFICATE`), symlink it into
|
|
30
|
+
`sites-enabled/`, reload.
|
|
31
|
+
- Nginx documentation moved from `docs/Deploy.md` to `docs/Nginx.md`;
|
|
32
|
+
`Deploy.md` now covers only where to publish, one section per server.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- HTTP → HTTPS redirect on port 80, with an optional ACME webroot block.
|
|
36
|
+
- TLS setup via Let's Encrypt, with placeholders for the certificate path.
|
|
37
|
+
- Security headers: `X-Content-Type-Options`, `X-Frame-Options`,
|
|
38
|
+
`Referrer-Policy`, `Strict-Transport-Security` (`max-age=300` by default).
|
|
39
|
+
- `.php` requests return 404, preventing source-code leaks when php-fpm is
|
|
40
|
+
not configured.
|
|
41
|
+
|
|
42
|
+
### Removed
|
|
43
|
+
- **Breaking:** the automatic Node → PHP fallback on Nginx. `/api` is now
|
|
44
|
+
proxied to Node only; if the process is down, Nginx returns 502. For a PHP
|
|
45
|
+
deployment, edit the `.php` block and `try_files` as described in
|
|
46
|
+
`docs/Nginx.md`.
|
|
47
|
+
|
|
48
|
+
### Migration
|
|
49
|
+
Existing Nginx deployments: replace your `server` block with the new
|
|
50
|
+
`nginx.conf` and fill in the three placeholders. If you relied on the PHP
|
|
51
|
+
fallback, follow *Using the PHP backend* in `docs/Nginx.md`.
|
|
52
|
+
|
|
8
53
|
## [1.3.0] - 2026-08-01
|
|
9
54
|
|
|
10
55
|
### Added
|
package/README.md
CHANGED
|
@@ -43,10 +43,10 @@ Nibula ships with a clean, opinionated layout so beginners are never lost. But i
|
|
|
43
43
|
|
|
44
44
|
* **Node.js**: v18.0.0 or higher
|
|
45
45
|
* **Composer** *(only if you choose the PHP backend)*: latest version
|
|
46
|
+
* **Better Nunjucks** — VS Code extension by **Ed Heltzel**
|
|
46
47
|
|
|
47
48
|
### Recommended
|
|
48
49
|
|
|
49
|
-
* **Better Nunjucks** — VS Code extension by **Ed Heltzel**
|
|
50
50
|
* **Material Icon Theme** — VS Code extension by **Philipp Kief**
|
|
51
51
|
|
|
52
52
|
## 📦 Installation
|
package/docs/Backend.md
CHANGED
|
@@ -11,7 +11,7 @@ Only the language differs.
|
|
|
11
11
|
> | Host | PHP | Node.js |
|
|
12
12
|
> |---|---|---|
|
|
13
13
|
> | Apache (shared hosting, VPS) | ✅ | ⚠️ needs a persistent process (VPS only) |
|
|
14
|
-
> | Nginx
|
|
14
|
+
> | Nginx (VPS) | ⚠️ works, but the shipped `nginx.conf` targets Node — needs a manual `fastcgi_pass` block | ✅ (Nginx reverse-proxies to Node) |
|
|
15
15
|
> | IIS (Windows Server) | ✅ FastCGI | ✅ via ARR reverse proxy |
|
|
16
16
|
> | Static hosting (Netlify, Vercel, GitHub Pages, Cloudflare Pages) | ❌ | ❌ |
|
|
17
17
|
>
|
package/docs/Deploy.md
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Deploy
|
|
2
2
|
|
|
3
|
-
Nibula builds a static site into
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Node.js — see `docs/Backend.md`).
|
|
7
|
-
|
|
8
|
-
The shipped `.htaccess`, `web.config` and `nginx.conf` are written to cover
|
|
9
|
-
**both** backends, so you don't swap config files based on your choice. There is
|
|
10
|
-
one caveat on Apache/IIS, explained at the end.
|
|
3
|
+
Nibula builds a static site into `out`. To publish it you upload that folder to a
|
|
4
|
+
web server. What you need depends on **where** you publish and **which backend**
|
|
5
|
+
you chose at scaffold time (PHP or Node.js — see `docs/Backend.md`).
|
|
11
6
|
|
|
12
7
|
## Prerequisites
|
|
13
8
|
|
|
@@ -15,101 +10,56 @@ one caveat on Apache/IIS, explained at the end.
|
|
|
15
10
|
FastCGI) and Composer for the backend dependencies. Works on typical shared
|
|
16
11
|
hosting.
|
|
17
12
|
|
|
18
|
-
**Node backend:** Node.js 18+
|
|
19
|
-
|
|
20
|
-
or Composer required.
|
|
21
|
-
|
|
22
|
-
## Where to publish
|
|
23
|
-
|
|
24
|
-
### Shared hosting (Apache — e.g. Aruba, most cPanel providers)
|
|
25
|
-
|
|
26
|
-
The most common and beginner-friendly option, and the natural home for the
|
|
27
|
-
**PHP** backend (shared hosting can't keep a Node process alive). You rent space
|
|
28
|
-
on a server that's already set up and just upload your files.
|
|
29
|
-
|
|
30
|
-
Take the **contents of the `out` folder** and upload them to the hosting's public
|
|
31
|
-
web root (often `htdocs`, `public_html`, or `www`). That's it — the site is live.
|
|
32
|
-
`.htaccess` (Apache) or `web.config` (IIS) are already inside `out` and get picked
|
|
33
|
-
up automatically, handling security and routing for you.
|
|
34
|
-
|
|
35
|
-
### Your own server (Nginx — e.g. an Ubuntu VPS)
|
|
36
|
-
|
|
37
|
-
If you rent a bare server (a VPS) and install everything yourself, it typically
|
|
38
|
-
runs **Nginx** on **Ubuntu Server**. Here nothing is pre-configured: you set up
|
|
39
|
-
Nginx, the backend, and the security rules by hand. This is the setup for the
|
|
40
|
-
**Node** backend (and also works for PHP + PHP-FPM). The Nginx section below
|
|
41
|
-
gives you the essential rules to start from.
|
|
13
|
+
**Node backend:** Node.js 18+ and the ability to run a long-running process —
|
|
14
|
+
i.e. a VPS you control, not typical shared hosting. No PHP or Composer required.
|
|
42
15
|
|
|
43
16
|
---
|
|
44
17
|
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
`.htaccess` files at `src/frontend/` and `src/backend/` are copied into the build
|
|
48
|
-
output by Eleventy. No setup required — upload `out` and Apache reads them.
|
|
49
|
-
|
|
50
|
-
They cover:
|
|
51
|
-
- Directory listing disabled
|
|
52
|
-
- 403 / 404 → `/404.html`
|
|
53
|
-
- Sensitive files blocked (`web.config`, dotfiles, etc.)
|
|
54
|
-
- Backend source directory sealed — all access funnels through the front controller
|
|
55
|
-
- `/api/*` routing (see the dual-backend note below)
|
|
18
|
+
## Shared hosting (Apache)
|
|
56
19
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
rewritten to the PHP front controller. To force PHP on a server that *has*
|
|
60
|
-
`mod_proxy`, comment the `mod_proxy` block in `.htaccess`.
|
|
20
|
+
The most common option, and the natural home for the **PHP** backend — shared
|
|
21
|
+
hosting can't keep a Node process alive.
|
|
61
22
|
|
|
62
|
-
|
|
23
|
+
Upload the **contents of `out`** to the hosting's web root (often `htdocs`,
|
|
24
|
+
`public_html`, or `www`). Done. `.htaccess` is already inside `out` and Apache
|
|
25
|
+
reads it automatically: directory listing off, 403/404 → `/404.html`, sensitive
|
|
26
|
+
files blocked, backend source sealed, `/api/*` routed.
|
|
63
27
|
|
|
64
|
-
|
|
65
|
-
`
|
|
66
|
-
|
|
28
|
+
**Backend routing:** if `mod_proxy` is available, `/api` is reverse-proxied to
|
|
29
|
+
Node on `127.0.0.1:3000`; if not, `/api` is rewritten to the PHP front
|
|
30
|
+
controller. To force PHP on a proxy-enabled server, comment the `mod_proxy`
|
|
31
|
+
block in `.htaccess`.
|
|
67
32
|
|
|
68
|
-
|
|
69
|
-
modules) reverse-proxies `/api` to Node and wins if present. To use PHP instead,
|
|
70
|
-
remove the `ApiToNode` rule; the `ApiToPhp` fallback then rewrites `/api` to the
|
|
71
|
-
PHP front controller.
|
|
33
|
+
## Windows hosting (IIS)
|
|
72
34
|
|
|
73
|
-
|
|
35
|
+
Same idea, different server. Upload the contents of `out`; `web.config` is
|
|
36
|
+
already there and read automatically.
|
|
74
37
|
|
|
75
|
-
|
|
76
|
-
|
|
38
|
+
**Backend routing:** the `ApiToNode` rewrite rule (requires the ARR + URL Rewrite
|
|
39
|
+
modules) reverse-proxies `/api` to Node and wins if present. Remove it to fall
|
|
40
|
+
back to `ApiToPhp`, which rewrites `/api` to the PHP front controller.
|
|
77
41
|
|
|
78
|
-
|
|
79
|
-
directives you adapt into a complete Nginx `server { }` block — the one that
|
|
80
|
-
defines your domain (`server_name`), ports (`listen`), and SSL certificates.
|
|
81
|
-
Paste these rules inside your own server block and set `root` to your `out`
|
|
82
|
-
folder.
|
|
42
|
+
## Your own server (Nginx on a VPS)
|
|
83
43
|
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
- `autoindex off` — disables directory listing
|
|
87
|
-
- 403 / 404 → `/404.html`
|
|
88
|
-
- Backend source directory (`/backend/`) sealed with `return 404`
|
|
89
|
-
- Server config files (`.htaccess`, `web.config`) blocked anywhere in the tree
|
|
90
|
-
- `/api/*` routed to the backend (see below)
|
|
91
|
-
- Security headers on static responses
|
|
44
|
+
The setup for the **Node** backend. Unlike Apache and IIS, Nginx doesn't read
|
|
45
|
+
per-directory config files — the whole site is defined in one file.
|
|
92
46
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
back automatically to the PHP front controller. So the same config works whether
|
|
96
|
-
you deployed Node or PHP — Nginx is the only server that can do this fallback
|
|
97
|
-
automatically.
|
|
47
|
+
Nibula ships `nginx.conf` in the project root as a **ready-to-use site file**:
|
|
48
|
+
replace three placeholders, symlink it, reload.
|
|
98
49
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
50
|
+
```bash
|
|
51
|
+
sudo cp nginx.conf /etc/nginx/sites-available/my-site
|
|
52
|
+
sudo nano /etc/nginx/sites-available/my-site # YOUR_DOMAIN, SITE_FOLDER, YOUR_CERTIFICATE
|
|
53
|
+
sudo ln -s /etc/nginx/sites-available/my-site /etc/nginx/sites-enabled/
|
|
54
|
+
sudo nginx -t && sudo systemctl reload nginx
|
|
55
|
+
```
|
|
102
56
|
|
|
103
|
-
|
|
57
|
+
Full reference, requirements and troubleshooting: **`docs/Nginx.md`**.
|
|
104
58
|
|
|
105
|
-
|
|
106
|
-
by default. Check with `ls /run/php/` and adjust:
|
|
59
|
+
## Static hosting (Netlify, Vercel, GitHub Pages)
|
|
107
60
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
| Debian / Ubuntu | `unix:/run/php/php8.3-fpm.sock` |
|
|
111
|
-
| RHEL / Fedora | `unix:/run/php-fpm/php-fpm.sock` |
|
|
112
|
-
| TCP fallback | `127.0.0.1:9000` |
|
|
61
|
+
Frontend only. Neither backend runs there — no PHP, no persistent process. Fine
|
|
62
|
+
if your site has no `/api` endpoints.
|
|
113
63
|
|
|
114
64
|
---
|
|
115
65
|
|
|
@@ -119,49 +69,48 @@ Unlike PHP, the Node backend is **not** executed by the web server per request
|
|
|
119
69
|
it is a process you start and keep alive. After uploading your built `out` folder:
|
|
120
70
|
|
|
121
71
|
1. Install the backend's runtime deps (only needed if an endpoint uses the DB):
|
|
122
|
-
|
|
72
|
+
```bash
|
|
123
73
|
cd /var/www/your-site/out/backend
|
|
124
74
|
npm install
|
|
125
|
-
|
|
75
|
+
```
|
|
126
76
|
|
|
127
77
|
2. Make sure `config.js` exists in `out/backend/` (copy `example.config.js` to
|
|
128
78
|
`config.js` if missing) and fill in your values.
|
|
129
79
|
|
|
130
80
|
3. Start it — quick test first:
|
|
131
|
-
|
|
81
|
+
```bash
|
|
132
82
|
node _core/index.js
|
|
133
83
|
# -> [backend-node] listening on http://127.0.0.1:3000 (env: production)
|
|
134
|
-
|
|
84
|
+
```
|
|
135
85
|
Then, from the server, confirm it answers:
|
|
136
|
-
|
|
86
|
+
```bash
|
|
137
87
|
curl http://127.0.0.1:3000/api/example-public
|
|
138
|
-
|
|
88
|
+
```
|
|
139
89
|
|
|
140
90
|
> Running `node _core/index.js` directly ties the process to your SSH session —
|
|
141
|
-
> close the terminal and the backend dies.
|
|
142
|
-
>
|
|
91
|
+
> close the terminal and the backend dies. To try it out without staying stuck
|
|
92
|
+
> there, use **`screen`** (a detachable terminal):
|
|
143
93
|
> ```bash
|
|
144
94
|
> screen -S backend # open a named session
|
|
145
95
|
> node _core/index.js # start the backend inside it
|
|
146
96
|
> # press Ctrl+A then D to detach — the backend keeps running
|
|
147
97
|
> # reattach later with: screen -r backend
|
|
148
98
|
> ```
|
|
149
|
-
> `screen` is
|
|
150
|
-
> deployment that also restarts on crash/reboot, prefer **systemd** (step 4).
|
|
99
|
+
> `screen` is fine for testing. For production, prefer **systemd** (step 4).
|
|
151
100
|
|
|
152
101
|
4. To keep it running after logout, use **systemd**. An example unit ships at
|
|
153
102
|
`backend/backend-node.service.example` — copy it to
|
|
154
103
|
`/etc/systemd/system/backend-node.service`, adjust `WorkingDirectory` and the
|
|
155
104
|
`node` path (`which node`), then:
|
|
156
|
-
|
|
105
|
+
```bash
|
|
157
106
|
sudo systemctl daemon-reload
|
|
158
107
|
sudo systemctl enable --now backend-node
|
|
159
108
|
sudo systemctl status backend-node
|
|
160
|
-
|
|
109
|
+
```
|
|
161
110
|
(`pm2` also works: `pm2 start _core/index.js --name backend-node`.)
|
|
162
111
|
|
|
163
|
-
The web server then reverse-proxies `/api` to this process
|
|
164
|
-
|
|
112
|
+
The web server then reverse-proxies `/api` to this process. If Node is down the
|
|
113
|
+
proxy returns 502 — there is no automatic PHP fallback.
|
|
165
114
|
|
|
166
115
|
### Environment variables
|
|
167
116
|
|
|
@@ -174,17 +123,13 @@ The web server then reverse-proxies `/api` to this process; with the shipped
|
|
|
174
123
|
|
|
175
124
|
---
|
|
176
125
|
|
|
177
|
-
##
|
|
126
|
+
## One config file, one backend
|
|
178
127
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
can't know whether Node is up. So there the routing is decided by configuration,
|
|
182
|
-
not at runtime:
|
|
128
|
+
Each server config targets a specific backend, decided at setup time rather than
|
|
129
|
+
at runtime:
|
|
183
130
|
|
|
184
|
-
- **Apache:** `mod_proxy` present → Node; absent → PHP
|
|
185
|
-
|
|
186
|
-
- **
|
|
187
|
-
`ApiToPhp` fallback.
|
|
131
|
+
- **Apache:** `mod_proxy` present → Node; absent → PHP
|
|
132
|
+
- **IIS:** `ApiToNode` rule present (needs ARR) → Node; remove it for `ApiToPhp`
|
|
133
|
+
- **Nginx:** ships configured for Node; see `docs/Nginx.md` for PHP
|
|
188
134
|
|
|
189
|
-
Since a given deployment runs only one backend, this is a one-time setup
|
|
190
|
-
per-request concern.
|
|
135
|
+
Since a given deployment runs only one backend, this is a one-time setup.
|
package/docs/Nginx.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Nginx
|
|
2
|
+
|
|
3
|
+
`nginx.conf` in the project root is a **ready-to-use site file** for a Nibula
|
|
4
|
+
site on your own server (a VPS). Replace three placeholders, symlink it, reload.
|
|
5
|
+
|
|
6
|
+
It targets the **Node** backend; see *Using the PHP backend* below for PHP.
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
| Placeholder | Value |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `YOUR_DOMAIN` | your domain — appears twice (port 80 and 443) |
|
|
13
|
+
| `SITE_FOLDER` | document root under `/var/www/` — your uploaded `out` folder |
|
|
14
|
+
| `YOUR_CERTIFICATE` | dir name under `/etc/letsencrypt/live/` — **not always equal to the domain**: a cert may be issued for a different hostname (common with DDNS) or cover several domains. Check with `sudo certbot certificates` |
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
sudo cp nginx.conf /etc/nginx/sites-available/my-site
|
|
18
|
+
sudo nano /etc/nginx/sites-available/my-site # replace the placeholders
|
|
19
|
+
sudo ln -s /etc/nginx/sites-available/my-site /etc/nginx/sites-enabled/
|
|
20
|
+
sudo nginx -t && sudo systemctl reload nginx
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Verify:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
curl -I http://YOUR_DOMAIN/ # 301 to https
|
|
27
|
+
curl -I https://YOUR_DOMAIN/ # 200
|
|
28
|
+
curl -I https://YOUR_DOMAIN/nonexistent # 404, not 200 or 500
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
On RHEL/Alpine there is no `sites-available`; put the file in
|
|
32
|
+
`/etc/nginx/conf.d/` instead.
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
- A valid certificate covering `YOUR_DOMAIN`
|
|
37
|
+
- `404.html` present in the document root and readable by nginx
|
|
38
|
+
- Nginx ≥ 1.25 for `http2 on;` — on older versions use `listen 443 ssl http2;`
|
|
39
|
+
|
|
40
|
+
## What it provides
|
|
41
|
+
|
|
42
|
+
- HTTP → HTTPS redirect on port 80
|
|
43
|
+
- TLS via Let's Encrypt, with the Certbot SSL options included
|
|
44
|
+
- `server_tokens off` and `autoindex off`
|
|
45
|
+
- 403 / 404 → `/404.html`, preserving the original status code
|
|
46
|
+
- Backend source directory (`/backend/`) sealed with `return 404`
|
|
47
|
+
- `web.config`, dotfiles and `.php` blocked
|
|
48
|
+
- Security headers: `X-Content-Type-Options`, `X-Frame-Options`,
|
|
49
|
+
`Referrer-Policy`, `Strict-Transport-Security`
|
|
50
|
+
- `/api/*` reverse-proxied to the Node backend on `127.0.0.1:3000`
|
|
51
|
+
|
|
52
|
+
## Certificate renewal
|
|
53
|
+
|
|
54
|
+
The `/.well-known/acme-challenge/` block is only needed with the **webroot**
|
|
55
|
+
authenticator. If you obtained the certificate with `certbot --nginx`, remove it.
|
|
56
|
+
Check with:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
grep authenticator /etc/letsencrypt/renewal/YOUR_DOMAIN.conf
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
When adding a domain to an existing certificate, always pass `--cert-name` **and
|
|
63
|
+
list every domain already covered** — omitted ones are dropped, and without the
|
|
64
|
+
flag Certbot creates a separate certificate instead of expanding.
|
|
65
|
+
|
|
66
|
+
## HSTS
|
|
67
|
+
|
|
68
|
+
The file ships with `max-age=300` deliberately. Browsers cache the HSTS policy
|
|
69
|
+
for the whole `max-age` **even after you remove the header**, so a long value
|
|
70
|
+
combined with a broken certificate locks visitors out with no way back.
|
|
71
|
+
|
|
72
|
+
Raise it to `31536000` once HTTPS is confirmed stable. Add `; includeSubDomains`
|
|
73
|
+
only if every current and future subdomain is served over HTTPS with a valid
|
|
74
|
+
certificate.
|
|
75
|
+
|
|
76
|
+
## WebSocket
|
|
77
|
+
|
|
78
|
+
Not enabled by default. If your backend uses socket.io or similar, add a map at
|
|
79
|
+
`http` level (e.g. `/etc/nginx/conf.d/websocket.conf`):
|
|
80
|
+
|
|
81
|
+
```nginx
|
|
82
|
+
map $http_upgrade $connection_upgrade {
|
|
83
|
+
default upgrade;
|
|
84
|
+
'' close;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
and inside `location ^~ /api/`:
|
|
89
|
+
|
|
90
|
+
```nginx
|
|
91
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
92
|
+
proxy_set_header Connection $connection_upgrade;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
For long-lived connections also raise `proxy_read_timeout`, ideally in a
|
|
96
|
+
dedicated location for the socket path so normal API requests keep the 60s
|
|
97
|
+
default.
|
|
98
|
+
|
|
99
|
+
## Using the PHP backend
|
|
100
|
+
|
|
101
|
+
This file targets Node. `.php` requests return 404 by design: without a
|
|
102
|
+
`fastcgi_pass` block Nginx would serve them as plain text and leak source code,
|
|
103
|
+
database credentials included.
|
|
104
|
+
|
|
105
|
+
For a PHP deployment, replace the `.php` block with:
|
|
106
|
+
|
|
107
|
+
```nginx
|
|
108
|
+
location ~ \.php$ {
|
|
109
|
+
include snippets/fastcgi-php.conf;
|
|
110
|
+
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
and change `try_files` in `location /` to `$uri $uri/ /index.php?$query_string`.
|
|
115
|
+
|
|
116
|
+
Check the socket path with `ls /run/php/`:
|
|
117
|
+
|
|
118
|
+
| Distro | Path |
|
|
119
|
+
|---|---|
|
|
120
|
+
| Debian / Ubuntu | `unix:/run/php/php8.3-fpm.sock` |
|
|
121
|
+
| RHEL / Fedora | `unix:/run/php-fpm/php-fpm.sock` |
|
|
122
|
+
| TCP fallback | `127.0.0.1:9000` |
|
|
123
|
+
|
|
124
|
+
## Troubleshooting
|
|
125
|
+
|
|
126
|
+
| Symptom | Cause |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `cannot load certificate ... Permission denied` | `nginx -t` run without `sudo` — the certs are root-only |
|
|
129
|
+
| `no alternative certificate subject name matches` | the cert doesn't cover `YOUR_DOMAIN`; check `sudo certbot certificates` |
|
|
130
|
+
| 500 instead of the 404 page | `404.html` missing or unreadable by nginx |
|
|
131
|
+
| `unknown directive "http2"` | Nginx < 1.25 — use `listen 443 ssl http2;` |
|
|
132
|
+
| 502 on `/api/*` | the Node process isn't running; see `docs/Deploy.md` |
|
|
133
|
+
| Site not responding at all | symlink missing in `sites-enabled/`, or nginx not reloaded |
|
package/nginx.conf
CHANGED
|
@@ -1,75 +1,137 @@
|
|
|
1
|
-
#
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Nibula — default nginx site configuration
|
|
3
|
+
# Static site + optional HTTP backend on 127.0.0.1:3000. PHP not handled.
|
|
4
|
+
# -----------------------------------------------------------------------------
|
|
5
|
+
# REPLACE
|
|
2
6
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# So the same config works whether you deployed the Node backend or the PHP one:
|
|
6
|
-
# - Node backend running -> Node answers /api
|
|
7
|
-
# - only PHP deployed -> Node is down -> falls back to PHP-FPM
|
|
7
|
+
# YOUR_DOMAIN server_name, twice (port 80 and 443)
|
|
8
|
+
# SITE_FOLDER document root under /var/www/
|
|
8
9
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
10
|
+
# YOUR_CERTIFICATE dir under /etc/letsencrypt/live/ — not always the sam
|
|
11
|
+
# as YOUR_DOMAIN. Check: sudo certbot certificates
|
|
12
|
+
#
|
|
13
|
+
# SITE_NAME (commands below) name of this file and of the
|
|
14
|
+
# certificate; any short name, never appears in URLs
|
|
15
|
+
#
|
|
16
|
+
#
|
|
17
|
+
# 1. INSTALL (skip if already present)
|
|
18
|
+
# sudo apt update
|
|
19
|
+
# sudo apt install -y nginx certbot python3-certbot-nginx
|
|
20
|
+
# nginx -v # >= 1.25 for the http2 directive
|
|
21
|
+
#
|
|
22
|
+
# 2. CERTIFICATE (before enabling the site — nginx won't start without it)
|
|
23
|
+
# dig +short YOUR_DOMAIN A # must return this server's public IP
|
|
24
|
+
# sudo certbot certonly --nginx --cert-name SITE_NAME -d YOUR_DOMAIN
|
|
25
|
+
# sudo systemctl enable --now certbot.timer # auto-renew every 90 days
|
|
26
|
+
#
|
|
27
|
+
# 3. INSTALL THE SITE
|
|
28
|
+
# sudo cp nginx.conf /etc/nginx/sites-available/SITE_NAME
|
|
29
|
+
# sudo nano /etc/nginx/sites-available/SITE_NAME
|
|
30
|
+
# sudo ln -s /etc/nginx/sites-available/SITE_NAME /etc/nginx/sites-enabled/
|
|
31
|
+
# sudo nginx -t && sudo systemctl reload nginx
|
|
32
|
+
#
|
|
33
|
+
# IF IT FAILS
|
|
34
|
+
# cannot load certificate ... Permission denied → nginx -t needs sudo
|
|
35
|
+
# cannot load certificate ... No such file → wrong certificate name
|
|
36
|
+
# no alternative certificate subject name → cert doesn't cover domain
|
|
37
|
+
# unknown directive "http2" → nginx < 1.25
|
|
38
|
+
# 500 instead of the 404 page → 404.html missing
|
|
39
|
+
# NXDOMAIN / timeout during certbot → DNS not pointing here
|
|
40
|
+
# =============================================================================
|
|
12
41
|
|
|
13
|
-
#
|
|
14
|
-
|
|
42
|
+
# HTTP (80) configuration
|
|
43
|
+
server {
|
|
15
44
|
|
|
16
|
-
|
|
17
|
-
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
45
|
+
server_name YOUR_DOMAIN;
|
|
18
46
|
|
|
19
|
-
|
|
20
|
-
|
|
47
|
+
listen 80;
|
|
48
|
+
listen [::]:80;
|
|
21
49
|
|
|
22
|
-
#
|
|
23
|
-
|
|
50
|
+
# Webroot ACME renewal; drop if using the nginx authenticator.
|
|
51
|
+
location ^~ /.well-known/acme-challenge/ {
|
|
52
|
+
root /var/www/certbot;
|
|
53
|
+
}
|
|
24
54
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
55
|
+
location / {
|
|
56
|
+
return 301 https://$host$request_uri;
|
|
57
|
+
}
|
|
28
58
|
}
|
|
29
59
|
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
60
|
+
# HTTPS (443) configuration
|
|
61
|
+
server {
|
|
62
|
+
### Essentials
|
|
63
|
+
listen 443 ssl;
|
|
64
|
+
listen [::]:443 ssl;
|
|
34
65
|
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
return 404;
|
|
38
|
-
}
|
|
66
|
+
# nginx >= 1.25; older: "listen 443 ssl http2;"
|
|
67
|
+
http2 on;
|
|
39
68
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
location ^~ /api/ {
|
|
44
|
-
proxy_pass http://127.0.0.1:3000;
|
|
45
|
-
proxy_http_version 1.1;
|
|
46
|
-
proxy_set_header Host $host;
|
|
47
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
48
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
49
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
50
|
-
|
|
51
|
-
proxy_intercept_errors on;
|
|
52
|
-
error_page 502 504 = @php_backend;
|
|
53
|
-
}
|
|
69
|
+
server_name YOUR_DOMAIN;
|
|
70
|
+
root /var/www/SITE_FOLDER;
|
|
71
|
+
index index.html;
|
|
54
72
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
73
|
+
### Certificate
|
|
74
|
+
ssl_certificate /etc/letsencrypt/live/YOUR_CERTIFICATE/fullchain.pem;
|
|
75
|
+
ssl_certificate_key /etc/letsencrypt/live/YOUR_CERTIFICATE/privkey.pem;
|
|
76
|
+
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
77
|
+
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
58
78
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
|
|
64
|
-
}
|
|
79
|
+
### Security headers
|
|
80
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
81
|
+
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
82
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
65
83
|
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
# HSTS persists in browsers for max-age even if removed here. Raise to
|
|
85
|
+
# 31536000 once stable; "; includeSubDomains" needs HTTPS on all subdomains.
|
|
86
|
+
add_header Strict-Transport-Security "max-age=300" always;
|
|
87
|
+
|
|
88
|
+
server_tokens off;
|
|
89
|
+
autoindex off;
|
|
90
|
+
|
|
91
|
+
### Blocked paths
|
|
92
|
+
error_page 403 404 /404.html;
|
|
93
|
+
|
|
94
|
+
location ^~ /backend/ {
|
|
95
|
+
return 404;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
location ~* ^/(web\.config)$ {
|
|
99
|
+
return 404;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
location ~ /\. {
|
|
103
|
+
return 404;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# PHP not handled: without php-fpm these would leak source code.
|
|
107
|
+
# For PHP sites, replace this block with fastcgi_pass and change
|
|
108
|
+
# try_files below to: $uri $uri/ /index.php?$query_string
|
|
109
|
+
location ~ \.php$ {
|
|
110
|
+
return 404;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
### API backend
|
|
114
|
+
location ^~ /api/ {
|
|
115
|
+
proxy_pass http://127.0.0.1:3000;
|
|
116
|
+
proxy_http_version 1.1;
|
|
117
|
+
proxy_set_header Host $host;
|
|
118
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
119
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
120
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
121
|
+
|
|
122
|
+
# proxy_read_timeout defaults to 60s; raise only for slow endpoints.
|
|
123
|
+
proxy_connect_timeout 5s;
|
|
124
|
+
|
|
125
|
+
# WebSocket is not enabled; needs a $connection_upgrade map at http level.
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
### Static files
|
|
129
|
+
location / {
|
|
130
|
+
try_files $uri $uri/ $uri.html =404;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
### Error page
|
|
134
|
+
location = /404.html {
|
|
135
|
+
internal;
|
|
136
|
+
}
|
|
137
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nibula",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "A beginner-friendly static site generator built on Eleventy that stays close to vanilla HTML, CSS and JS — with a page-management CLI, an optional PHP backend, and SEO files generated for you.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"static-site-generator",
|