nibula 1.4.1 → 1.5.0
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/CHANGELOG.md +25 -11
- package/docs/Backend.md +97 -224
- package/docs/Deploy.md +74 -95
- package/nginx.conf +40 -26
- package/package.json +1 -1
- package/docs/Nginx.md +0 -133
- package/src/backend/backend-node.service.example +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -5,21 +5,35 @@ 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.
|
|
8
|
+
## [1.5.0] - 2026-08-04
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
|
-
- `nginx.conf`
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
DNS not pointing to the server.
|
|
11
|
+
- `nginx.conf` now covers **both backends** with no manual edits. `/api` is
|
|
12
|
+
proxied to Node on `127.0.0.1:3000`; if Node is unreachable, the request is
|
|
13
|
+
retried through the PHP front controller via the internal `@php_backend`
|
|
14
|
+
location. The same file works for a Node deployment and a PHP one.
|
|
15
|
+
- `nginx.conf` header now documents the PHP-FPM requirement, the version-agnostic
|
|
16
|
+
socket, and the errors specific to a PHP deployment.
|
|
18
17
|
|
|
19
18
|
### Changed
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
- PHP is invoked through `SCRIPT_FILENAME` instead of by URL, so `.php` requests
|
|
20
|
+
keep returning 404 even on a PHP deployment and no source file is ever
|
|
21
|
+
reachable by name.
|
|
22
|
+
- `fastcgi_pass` targets the version-agnostic socket `unix:/run/php/php-fpm.sock`
|
|
23
|
+
instead of a versioned path, so the config survives PHP upgrades.
|
|
24
|
+
- `web.config` is now blocked anywhere in the tree, not only at the document root.
|
|
25
|
+
- `docs/Backend.md` is limited to writing endpoints: routing, `public` vs
|
|
26
|
+
`protected`, the `Response` helper and configuration. Examples are Node-only,
|
|
27
|
+
with a note that PHP behaves identically. Hosting and server requirements
|
|
28
|
+
moved to `docs/Deploy.md`.
|
|
29
|
+
- `docs/Deploy.md` rewritten around where a site can be published and what each
|
|
30
|
+
target requires, including why shared hosting cannot run the Node backend.
|
|
31
|
+
|
|
32
|
+
### Removed
|
|
33
|
+
- `docs/Nginx.md` — the setup steps live in the `nginx.conf` header, where they
|
|
34
|
+
are read at the moment they are needed.
|
|
35
|
+
- `src/backend/backend-node.service.example` — the systemd unit is documented
|
|
36
|
+
in `docs/Deploy.md` instead of shipped as a file.
|
|
23
37
|
|
|
24
38
|
## [1.4.0] - 2026-08-03
|
|
25
39
|
|
package/docs/Backend.md
CHANGED
|
@@ -1,46 +1,24 @@
|
|
|
1
1
|
# Backend
|
|
2
2
|
|
|
3
|
-
Nibula ships **two interchangeable backends**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
envelope, same `X-Api-Key` auth, same CORS model, same file-based rate limiter.
|
|
7
|
-
Only the language differs.
|
|
8
|
-
|
|
9
|
-
> **Server requirement** — where each backend can run:
|
|
10
|
-
>
|
|
11
|
-
> | Host | PHP | Node.js |
|
|
12
|
-
> |---|---|---|
|
|
13
|
-
> | Apache (shared hosting, VPS) | ✅ | ⚠️ needs a persistent process (VPS only) |
|
|
14
|
-
> | Nginx (VPS) | ⚠️ works, but the shipped `nginx.conf` targets Node — needs a manual `fastcgi_pass` block | ✅ (Nginx reverse-proxies to Node) |
|
|
15
|
-
> | IIS (Windows Server) | ✅ FastCGI | ✅ via ARR reverse proxy |
|
|
16
|
-
> | Static hosting (Netlify, Vercel, GitHub Pages, Cloudflare Pages) | ❌ | ❌ |
|
|
17
|
-
>
|
|
18
|
-
> Typical shared hosting can run **PHP** but not a long-running Node process, so
|
|
19
|
-
> choose PHP there. On a VPS you control, either works.
|
|
20
|
-
|
|
21
|
-
The backend lives in `src/backend/` and is copied to the output directory
|
|
22
|
-
automatically at build time.
|
|
23
|
-
|
|
24
|
-
## Choosing a backend
|
|
25
|
-
|
|
26
|
-
When you run `nib new`, the scaffolder asks three questions: language, CSS
|
|
27
|
-
framework, and **backend**. Pick Node.js or PHP.
|
|
28
|
-
|
|
29
|
-
- **If you pick Node**, Composer is never run — you get an npm-only setup, and
|
|
30
|
-
the backend's dependency (`mysql2`, used only if you touch the database) is
|
|
31
|
-
added to the **root** `package.json` and installed into the root `node_modules`.
|
|
32
|
-
- **If you pick PHP**, Composer dependencies are installed as before, and no Node
|
|
33
|
-
backend files are added.
|
|
34
|
-
|
|
35
|
-
**Only the backend you choose is copied into the project.** A Node project
|
|
36
|
-
contains no PHP files; a PHP project contains no `.js` backend files. Shared,
|
|
3
|
+
Nibula ships **two interchangeable backends** — PHP and Node.js. You pick one
|
|
4
|
+
when you scaffold a project with `nib new`, and **only that one is copied into
|
|
5
|
+
your project**: a Node project contains no PHP files and vice versa. Shared,
|
|
37
6
|
backend-agnostic files (SQL migrations, `.htaccess`, `web.config`) are always
|
|
38
7
|
included.
|
|
39
8
|
|
|
9
|
+
Both provide the same REST API: same routing, same `Response` envelope, same
|
|
10
|
+
`X-Api-Key` auth, same CORS model, same rate limiter. Only the syntax differs.
|
|
11
|
+
|
|
12
|
+
> **The examples below are in JavaScript (the Node backend).** If you chose PHP,
|
|
13
|
+
> everything works exactly the same way — same routing, same variables, same
|
|
14
|
+
> `Response` helper, same behaviour. Only the syntax differs.
|
|
15
|
+
|
|
16
|
+
For **where each backend can run** and how to deploy it, see `docs/Deploy.md`.
|
|
17
|
+
|
|
40
18
|
## Structure
|
|
41
19
|
|
|
42
|
-
The
|
|
43
|
-
|
|
20
|
+
The tree below shows both backends for reference; your project will have only
|
|
21
|
+
one side of each pair.
|
|
44
22
|
|
|
45
23
|
```
|
|
46
24
|
src/backend/
|
|
@@ -48,152 +26,56 @@ src/backend/
|
|
|
48
26
|
│ ├── public/ # Endpoints accessible without an API key
|
|
49
27
|
│ └── protected/ # Endpoints requiring the X-Api-Key header
|
|
50
28
|
├── _core/ # Framework internals (routing, modules) — do not edit
|
|
51
|
-
│ ├── index.php # PHP front controller
|
|
52
|
-
│ ├── index.js # Node front controller + server
|
|
29
|
+
│ ├── index.php # PHP front controller (PHP project)
|
|
30
|
+
│ ├── index.js # Node front controller + server (Node project)
|
|
53
31
|
│ ├── init.php / init.js
|
|
54
32
|
│ └── modules/ # Response, RateLimiter
|
|
55
33
|
├── database/
|
|
56
|
-
│ ├── Database.php # PDO singleton
|
|
57
|
-
│ ├── Database.js # mysql2 pool singleton
|
|
34
|
+
│ ├── Database.php # PDO singleton (PHP project)
|
|
35
|
+
│ ├── Database.js # mysql2 pool singleton (Node project)
|
|
58
36
|
│ └── migrations/ # shared
|
|
59
|
-
├──
|
|
60
|
-
|
|
61
|
-
├── example.config.js # Node project only — versioned template
|
|
62
|
-
├── config.php # PHP project only — generated on setup, never commit
|
|
63
|
-
└── config.js # Node project only — generated on setup, never commit
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
> `config.php` / `config.js` are generated automatically by `nib new` (a copy of
|
|
67
|
-
> the matching `example.config.*`) and are git-ignored, so your secrets stay
|
|
68
|
-
> local. The `example.config.*` file is versioned as a secret-free reference. If
|
|
69
|
-
> `config.*` is ever missing (e.g. after a fresh clone), copy the example to it.
|
|
70
|
-
|
|
71
|
-
> **Where do the Node backend's dependencies live?** In the **root**
|
|
72
|
-
> `node_modules`, not in `src/backend` — so your source tree stays clean (no
|
|
73
|
-
> `src/backend/node_modules` locally). On the server you install them into
|
|
74
|
-
> `out/backend` instead; see `docs/Deploy.md`.
|
|
75
|
-
|
|
76
|
-
## Key difference: how each backend runs
|
|
77
|
-
|
|
78
|
-
- **PHP** is executed **per request** by the web server through PHP-FPM (or
|
|
79
|
-
FastCGI/mod_php). There is no process to keep alive.
|
|
80
|
-
- **Node** is a **long-running process** that *is* the server, listening on
|
|
81
|
-
`127.0.0.1:3000`. The web server (Nginx/Apache/IIS) **reverse-proxies** `/api`
|
|
82
|
-
to it. You start it once and keep it alive (systemd/pm2). See `docs/Deploy.md`.
|
|
83
|
-
|
|
84
|
-
## Configuration
|
|
85
|
-
|
|
86
|
-
`config.php` / `config.js` work like a `.env` file — they hold secrets and
|
|
87
|
-
environment settings that stay local and out of version control. Same keys in
|
|
88
|
-
both, just PHP-array vs JS-object syntax.
|
|
89
|
-
|
|
90
|
-
### config.php <small>(`src/backend/`)</small>
|
|
91
|
-
```php
|
|
92
|
-
// Default key for protected endpoints without a specific key in CUSTOM_ENDPOINT_KEYS
|
|
93
|
-
'GENERAL_API_KEY' => 'DEFAULT_KEY',
|
|
94
|
-
|
|
95
|
-
// Per-endpoint keys. For subfolder endpoints use the relative path ('subfolder/endpoint')
|
|
96
|
-
'CUSTOM_ENDPOINT_KEYS' => [
|
|
97
|
-
'subfolder/endpoint' => 'custom-key',
|
|
98
|
-
],
|
|
99
|
-
|
|
100
|
-
'GENERAL_ALLOWED_ORIGINS' => [
|
|
101
|
-
'*',
|
|
102
|
-
// 'https://example.com',
|
|
103
|
-
],
|
|
104
|
-
|
|
105
|
-
'CUSTOM_ENDPOINT_ORIGINS' => [
|
|
106
|
-
'subfolder/endpoint' => ['https://app.example.com'],
|
|
107
|
-
],
|
|
108
|
-
|
|
109
|
-
// Database configuration
|
|
110
|
-
'DB_HOST' => '127.0.0.1',
|
|
111
|
-
'DB_NAME' => 'example_db',
|
|
112
|
-
'DB_USER' => 'root',
|
|
113
|
-
'DB_PASS' => '',
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### config.js <small>(`src/backend/`)</small>
|
|
117
|
-
```js
|
|
118
|
-
module.exports = {
|
|
119
|
-
GENERAL_API_KEY: 'DEFAULT_KEY',
|
|
120
|
-
CUSTOM_ENDPOINT_KEYS: {
|
|
121
|
-
'subfolder/endpoint': 'custom-key',
|
|
122
|
-
},
|
|
123
|
-
GENERAL_ALLOWED_ORIGINS: ['*' /*, 'https://example.com' */],
|
|
124
|
-
CUSTOM_ENDPOINT_ORIGINS: {
|
|
125
|
-
'subfolder/endpoint': ['https://app.example.com'],
|
|
126
|
-
},
|
|
127
|
-
DB_HOST: '127.0.0.1',
|
|
128
|
-
DB_NAME: 'example_db',
|
|
129
|
-
DB_USER: 'root',
|
|
130
|
-
DB_PASS: '',
|
|
131
|
-
APP_ENV: 'production', // anything other than 'production' = verbose errors
|
|
132
|
-
};
|
|
37
|
+
├── example.config.php / example.config.js # versioned template
|
|
38
|
+
└── config.php / config.js # generated on setup, never commit
|
|
133
39
|
```
|
|
134
40
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
`
|
|
138
|
-
`CUSTOM_ENDPOINT_KEYS` to assign a different key to a specific endpoint — for
|
|
139
|
-
subfolder endpoints, use the relative path as the key.
|
|
140
|
-
|
|
141
|
-
> ⚠️ The API key travels in the `X-Api-Key` header on every request. Use it only
|
|
142
|
-
> for server-to-server calls over HTTPS. Never embed it in frontend code, where
|
|
143
|
-
> it would be publicly visible.
|
|
144
|
-
|
|
145
|
-
### CORS (allowed origins)
|
|
146
|
-
|
|
147
|
-
Cross-origin access mirrors the API-key model:
|
|
148
|
-
|
|
149
|
-
- `GENERAL_ALLOWED_ORIGINS` — the default list of origins allowed to call any
|
|
150
|
-
endpoint from a browser.
|
|
151
|
-
- `CUSTOM_ENDPOINT_ORIGINS` — overrides the default for a specific endpoint,
|
|
152
|
-
keyed by the same relative path used in `CUSTOM_ENDPOINT_KEYS`.
|
|
153
|
-
|
|
154
|
-
Origins must be exact (scheme + host, no trailing slash), e.g.
|
|
155
|
-
`https://example.com`. When a request's `Origin` is in the resolved list, it is
|
|
156
|
-
reflected back in `Access-Control-Allow-Origin` (with `Vary: Origin`). An empty
|
|
157
|
-
list sends no CORS header — the most restrictive setting; same-origin requests
|
|
158
|
-
still work. Use `'*'` as the only element to allow any origin (not recommended
|
|
159
|
-
for protected endpoints).
|
|
160
|
-
|
|
161
|
-
Resolution order for a given endpoint: `CUSTOM_ENDPOINT_ORIGINS[path]` if
|
|
162
|
-
present, otherwise `GENERAL_ALLOWED_ORIGINS`.
|
|
41
|
+
> `config.php` / `config.js` are created automatically by `nib new` as a copy of
|
|
42
|
+
> the matching `example.config.*`, and are git-ignored so your secrets stay
|
|
43
|
+
> local. If `config.*` is missing (e.g. after a fresh clone), copy the example.
|
|
163
44
|
|
|
164
45
|
## How routing works
|
|
165
46
|
|
|
166
|
-
The file path inside `api/` maps directly to the URL. The `public
|
|
47
|
+
The file path inside `api/` maps directly to the URL. The `public` / `protected`
|
|
167
48
|
folder does **not** appear in the URL — it only decides whether the `X-Api-Key`
|
|
168
|
-
check applies
|
|
169
|
-
are both reached at `/api/<endpoint>`:
|
|
49
|
+
check applies:
|
|
170
50
|
|
|
171
51
|
- `api/public/example-public` → `/api/example-public` (no key)
|
|
172
52
|
- `api/protected/example-protected` → `/api/example-protected` (requires key)
|
|
173
53
|
|
|
174
|
-
Subfolders **do** appear: `api/public/users/list` → `/api/users/list`.
|
|
175
|
-
segments become route parameters. Every endpoint has access to:
|
|
54
|
+
Subfolders **do** appear: `api/public/users/list` → `/api/users/list`.
|
|
176
55
|
|
|
177
|
-
|
|
56
|
+
Extra URL segments become route parameters, resolved from the deepest match
|
|
57
|
+
backwards: `/api/posts/42` looks for `posts/42` first, then falls back to
|
|
58
|
+
`posts` with `42` as a parameter.
|
|
59
|
+
|
|
60
|
+
Every endpoint receives:
|
|
61
|
+
|
|
62
|
+
| Field | Description |
|
|
178
63
|
|---|---|
|
|
179
64
|
| `method` | HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`) |
|
|
180
65
|
| `requestParams` | Extra URL segments (e.g. `/api/posts/42` → `['42']`) |
|
|
181
66
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
> authentication. Don't duplicate names across the two folders.
|
|
67
|
+
The Node handler also gets `query`, `body` (parsed JSON), `rawBody`, `headers`,
|
|
68
|
+
`config`, `req`, `res`.
|
|
185
69
|
|
|
186
|
-
>
|
|
187
|
-
>
|
|
188
|
-
>
|
|
189
|
-
> same `Response` helper and the same behaviour — only the syntax differs. Each
|
|
190
|
-
> section shows the JS version first, with the equivalent PHP right after.
|
|
70
|
+
> ⚠️ If two files share the same path — one in `public/`, one in `protected/` —
|
|
71
|
+
> the public one wins, so the endpoint ends up **without** authentication. Don't
|
|
72
|
+
> duplicate names across the two folders.
|
|
191
73
|
|
|
192
|
-
## Creating
|
|
74
|
+
## Creating an endpoint
|
|
193
75
|
|
|
194
|
-
Create a file anywhere inside `api/public/`.
|
|
76
|
+
Create a file anywhere inside `api/public/` or `api/protected/`. For protected
|
|
77
|
+
endpoints the API-key check runs automatically before your file does.
|
|
195
78
|
|
|
196
|
-
### `api/public/example.js` (Node — reference)
|
|
197
79
|
```js
|
|
198
80
|
module.exports = ({ method, requestParams, Response }) => {
|
|
199
81
|
if (method !== 'GET') Response.error('Method not allowed', 405);
|
|
@@ -202,95 +84,86 @@ module.exports = ({ method, requestParams, Response }) => {
|
|
|
202
84
|
};
|
|
203
85
|
```
|
|
204
86
|
|
|
205
|
-
The
|
|
206
|
-
`Response` (`.success` / `.error` / `.noContent`), plus `query`, `body` (parsed
|
|
207
|
-
JSON), `rawBody`, `headers`, `config`, `req`, `res`.
|
|
208
|
-
|
|
209
|
-
The same endpoint in PHP — identical logic, PHP syntax:
|
|
210
|
-
```php
|
|
211
|
-
<?php
|
|
212
|
-
declare(strict_types=1);
|
|
87
|
+
## The Response helper
|
|
213
88
|
|
|
214
|
-
|
|
89
|
+
Same envelope in both backends.
|
|
215
90
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
91
|
+
```js
|
|
92
|
+
Response.success(data, code); // default 200
|
|
93
|
+
Response.error(message, code, details); // default 400
|
|
94
|
+
Response.noContent(); // 204
|
|
95
|
+
```
|
|
219
96
|
|
|
220
|
-
|
|
97
|
+
Shapes:
|
|
221
98
|
|
|
222
|
-
|
|
223
|
-
|
|
99
|
+
- success → `{ "status": "success", "data": ... }`
|
|
100
|
+
- error → `{ "status": "error", "message": "...", "code": ... }` (+ optional `details`)
|
|
224
101
|
|
|
225
|
-
##
|
|
102
|
+
## Configuration
|
|
226
103
|
|
|
227
|
-
|
|
228
|
-
|
|
104
|
+
`config.php` / `config.js` work like a `.env` file: they hold secrets and
|
|
105
|
+
environment settings that stay local and out of version control. Same keys in
|
|
106
|
+
both, PHP-array vs JS-object syntax.
|
|
229
107
|
|
|
230
|
-
### `api/protected/example.js` (Node — reference)
|
|
231
108
|
```js
|
|
232
|
-
module.exports =
|
|
233
|
-
|
|
234
|
-
|
|
109
|
+
module.exports = {
|
|
110
|
+
GENERAL_API_KEY: 'DEFAULT_KEY',
|
|
111
|
+
CUSTOM_ENDPOINT_KEYS: {
|
|
112
|
+
'subfolder/endpoint': 'custom-key',
|
|
113
|
+
},
|
|
114
|
+
GENERAL_ALLOWED_ORIGINS: ['*' /*, 'https://example.com' */],
|
|
115
|
+
CUSTOM_ENDPOINT_ORIGINS: {
|
|
116
|
+
'subfolder/endpoint': ['https://app.example.com'],
|
|
117
|
+
},
|
|
118
|
+
DB_HOST: '127.0.0.1',
|
|
119
|
+
DB_NAME: 'example_db',
|
|
120
|
+
DB_USER: 'root',
|
|
121
|
+
DB_PASS: '',
|
|
122
|
+
APP_ENV: 'production', // anything else = verbose errors
|
|
235
123
|
};
|
|
236
124
|
```
|
|
237
125
|
|
|
238
|
-
|
|
239
|
-
```php
|
|
240
|
-
<?php
|
|
241
|
-
declare(strict_types=1);
|
|
126
|
+
### API keys
|
|
242
127
|
|
|
243
|
-
|
|
128
|
+
`GENERAL_API_KEY` is the fallback for all protected endpoints.
|
|
129
|
+
`CUSTOM_ENDPOINT_KEYS` assigns a different key to a specific endpoint, keyed by
|
|
130
|
+
its relative path (`'subfolder/endpoint'`).
|
|
244
131
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
132
|
+
> ⚠️ The key travels in the `X-Api-Key` header on every request. Use it only for
|
|
133
|
+
> server-to-server calls over HTTPS. Never embed it in frontend code, where it
|
|
134
|
+
> would be publicly visible.
|
|
248
135
|
|
|
249
|
-
|
|
250
|
-
```
|
|
136
|
+
### CORS
|
|
251
137
|
|
|
252
|
-
|
|
138
|
+
Mirrors the API-key model: `GENERAL_ALLOWED_ORIGINS` is the default list,
|
|
139
|
+
`CUSTOM_ENDPOINT_ORIGINS` overrides it per endpoint using the same relative path.
|
|
253
140
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
```
|
|
141
|
+
Origins must be exact (scheme + host, no trailing slash). A matching `Origin` is
|
|
142
|
+
reflected in `Access-Control-Allow-Origin` with `Vary: Origin`. An empty list
|
|
143
|
+
sends no CORS header — the most restrictive setting; same-origin requests still
|
|
144
|
+
work. `'*'` as the only element allows any origin (not recommended for protected
|
|
145
|
+
endpoints).
|
|
260
146
|
|
|
261
|
-
|
|
147
|
+
Resolution order: `CUSTOM_ENDPOINT_ORIGINS[path]` if present, otherwise
|
|
148
|
+
`GENERAL_ALLOWED_ORIGINS`.
|
|
149
|
+
|
|
150
|
+
## Database
|
|
262
151
|
|
|
263
|
-
|
|
152
|
+
Both backends expose a connection singleton reading the same config —
|
|
153
|
+
`database/Database.js` (a `mysql2` pool) or `database/Database.php` (a PDO
|
|
154
|
+
singleton).
|
|
264
155
|
|
|
265
156
|
```js
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
Response.noContent(); // 204
|
|
157
|
+
const pool = Database.getInstance();
|
|
158
|
+
const [rows] = await pool.execute(sql, params);
|
|
269
159
|
```
|
|
270
|
-
```php
|
|
271
|
-
Response::success($data, $code); // default 200
|
|
272
|
-
Response::error($message, $code, $details); // default 400
|
|
273
|
-
Response::noContent(); // 204
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
Response shapes:
|
|
277
|
-
- success → `{ "status": "success", "data": ... }`
|
|
278
|
-
- error → `{ "status": "error", "message": "...", "code": ... }` (+ optional `details`)
|
|
279
|
-
|
|
280
|
-
## Database
|
|
281
|
-
|
|
282
|
-
Both backends expose a connection singleton reading the same config.
|
|
283
160
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
The `mysql2` driver is installed at scaffold time when you choose Node (it is
|
|
287
|
-
loaded lazily, so the backend also boots fine without a database).
|
|
288
|
-
- **PHP** — `database/Database.php`, a PDO singleton:
|
|
289
|
-
`$pdo = Database::getInstance();`
|
|
161
|
+
`mysql2` is installed at scaffold time and loaded lazily, so the backend boots
|
|
162
|
+
fine without a database.
|
|
290
163
|
|
|
291
164
|
## Pre-built endpoints
|
|
292
165
|
|
|
293
166
|
| Route | Method | Description |
|
|
294
167
|
|---|---|---|
|
|
295
|
-
| `/api/example-public` | `GET` | Example endpoint
|
|
296
|
-
| `/api/example-protected` | `GET` | Example endpoint
|
|
168
|
+
| `/api/example-public` | `GET` | Example endpoint requiring no key |
|
|
169
|
+
| `/api/example-protected` | `GET` | Example endpoint requiring `X-Api-Key` |
|
package/docs/Deploy.md
CHANGED
|
@@ -1,135 +1,114 @@
|
|
|
1
1
|
# Deploy
|
|
2
2
|
|
|
3
|
-
Nibula builds a static site into `out`.
|
|
4
|
-
web server. What you need depends on **where** you publish and **which
|
|
5
|
-
you chose
|
|
3
|
+
Nibula builds a static site into `out`. Publishing means uploading that folder to
|
|
4
|
+
a web server. What you need depends on **where** you publish and **which
|
|
5
|
+
backend** you chose (see `docs/Backend.md`).
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## The one constraint that decides everything
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
FastCGI) and Composer for the backend dependencies. Works on typical shared
|
|
11
|
-
hosting.
|
|
9
|
+
The two backends run in fundamentally different ways:
|
|
12
10
|
|
|
13
|
-
**
|
|
14
|
-
|
|
11
|
+
- **PHP** is executed **per request** by the web server through PHP-FPM. There is
|
|
12
|
+
no process to keep alive — the server starts it, it answers, it exits.
|
|
13
|
+
- **Node** **is** the server: a long-running process listening on
|
|
14
|
+
`127.0.0.1:3000`, which the web server reverse-proxies `/api` to. Something has
|
|
15
|
+
to start it and keep it alive across reboots and crashes.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
This is why **shared hosting cannot run the Node backend**. On Aruba, cPanel and
|
|
18
|
+
similar, you upload files into a web root — you have no shell to start a daemon,
|
|
19
|
+
no systemd, and any process you did manage to launch would be killed. If your
|
|
20
|
+
host is shared hosting, choose PHP. It is not a limitation of Nibula.
|
|
17
21
|
|
|
18
|
-
##
|
|
22
|
+
## Where you can publish
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
| Target | Frontend | PHP backend | Node backend |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| Shared hosting (Apache — Aruba, cPanel) | ✅ | ✅ | ❌ no persistent process |
|
|
27
|
+
| Windows hosting (IIS) | ✅ | ✅ FastCGI | ⚠️ needs ARR reverse proxy |
|
|
28
|
+
| Your own VPS (Nginx on Ubuntu) | ✅ | ✅ | ✅ |
|
|
29
|
+
| Static hosting (Netlify, Vercel, GitHub Pages) | ✅ | ❌ | ❌ |
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
reads it automatically: directory listing off, 403/404 → `/404.html`, sensitive
|
|
26
|
-
files blocked, backend source sealed, `/api/*` routed.
|
|
31
|
+
Static hosting serves files and nothing else — fine if your site has no `/api`
|
|
32
|
+
endpoints, unusable otherwise.
|
|
27
33
|
|
|
28
|
-
|
|
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`.
|
|
34
|
+
## Requirements
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
**PHP backend:** a server with PHP support and Composer for the backend
|
|
37
|
+
dependencies.
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
**Node backend:** Node.js 18+ and shell access to run a persistent process — a
|
|
40
|
+
VPS you control.
|
|
37
41
|
|
|
38
|
-
|
|
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.
|
|
42
|
+
---
|
|
41
43
|
|
|
42
|
-
##
|
|
44
|
+
## Shared hosting (Apache)
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
Upload the **contents of `out`** to the web root (often `htdocs`, `public_html`
|
|
47
|
+
or `www`). Done — `.htaccess` is already inside `out` and Apache reads it
|
|
48
|
+
automatically: directory listing off, 403/404 → `/404.html`, sensitive files
|
|
49
|
+
blocked, backend source sealed, `/api/*` routed.
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
**Backend routing:** if `mod_proxy` is available, `/api` is proxied to Node on
|
|
52
|
+
`127.0.0.1:3000`; if not — the usual case on shared hosting — `/api` is
|
|
53
|
+
rewritten to the PHP front controller. To force PHP on a proxy-enabled server,
|
|
54
|
+
comment the `mod_proxy` block in `.htaccess`.
|
|
49
55
|
|
|
50
|
-
|
|
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
|
-
```
|
|
56
|
+
## Windows hosting (IIS)
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
Same idea: upload the contents of `out`, `web.config` is already there and read
|
|
59
|
+
automatically.
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
**Backend routing:** the `ApiToNode` rule (requires the ARR + URL Rewrite
|
|
62
|
+
modules) proxies `/api` to Node and wins if present. Remove it to fall through
|
|
63
|
+
to `ApiToPhp`, which rewrites `/api` to the PHP front controller.
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
## Your own server (Nginx on a VPS)
|
|
66
|
+
|
|
67
|
+
Nginx doesn't read per-directory config files — the whole site lives in one file.
|
|
68
|
+
Nibula ships `nginx.conf` in the project root as a ready-to-use site file that
|
|
69
|
+
**covers both backends without edits**: `/api` goes to Node, and if Node is
|
|
70
|
+
unreachable the request falls through to PHP automatically.
|
|
63
71
|
|
|
64
72
|
---
|
|
65
73
|
|
|
66
74
|
## Starting the Node backend on the server
|
|
67
75
|
|
|
68
|
-
|
|
69
|
-
it is a process you start and keep alive. After uploading your built `out` folder:
|
|
76
|
+
After uploading `out`:
|
|
70
77
|
|
|
71
|
-
1. Install the backend's runtime deps
|
|
72
|
-
```bash
|
|
73
|
-
cd /var/www/
|
|
78
|
+
1. Install the backend's runtime deps:
|
|
79
|
+
```bash
|
|
80
|
+
cd /var/www/backend/SITE_FOLDER
|
|
74
81
|
npm install
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
2. Make sure `config.js` exists in `out/backend/` (copy `example.config.js`
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
3. Start it
|
|
81
|
-
```bash
|
|
82
|
-
node
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
Then, from the server, confirm it answers:
|
|
86
|
-
```bash
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
2. Make sure `config.js` exists in `out/backend/` (copy `example.config.js` if
|
|
85
|
+
missing) and fill in your values.
|
|
86
|
+
|
|
87
|
+
3. Start it in a new screen:
|
|
88
|
+
```bash
|
|
89
|
+
screen -S node-backend
|
|
90
|
+
# In the new screen terminal:
|
|
91
|
+
sudo node /var/www/SITE_FOLDER/backend/_core/index.js
|
|
87
92
|
curl http://127.0.0.1:3000/api/example-public
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
> ```bash
|
|
94
|
-
> screen -S backend # open a named session
|
|
95
|
-
> node _core/index.js # start the backend inside it
|
|
96
|
-
> # press Ctrl+A then D to detach — the backend keeps running
|
|
97
|
-
> # reattach later with: screen -r backend
|
|
98
|
-
> ```
|
|
99
|
-
> `screen` is fine for testing. For production, prefer **systemd** (step 4).
|
|
100
|
-
|
|
101
|
-
4. To keep it running after logout, use **systemd**. An example unit ships at
|
|
102
|
-
`backend/backend-node.service.example` — copy it to
|
|
93
|
+
```
|
|
94
|
+
4. Exit the screen without closing the process with `CTRL + A`, `D`. To resume the screen type `screen -r node-backend`.
|
|
95
|
+
|
|
96
|
+
5. For production use **systemd**, which also restarts on crash and reboot. An
|
|
97
|
+
example unit ships at `backend/backend-node.service.example` — copy it to
|
|
103
98
|
`/etc/systemd/system/backend-node.service`, adjust `WorkingDirectory` and the
|
|
104
99
|
`node` path (`which node`), then:
|
|
105
|
-
```bash
|
|
100
|
+
```bash
|
|
106
101
|
sudo systemctl daemon-reload
|
|
107
102
|
sudo systemctl enable --now backend-node
|
|
108
103
|
sudo systemctl status backend-node
|
|
109
|
-
```
|
|
110
|
-
(`pm2`
|
|
111
|
-
|
|
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.
|
|
104
|
+
```
|
|
105
|
+
(`pm2` works too: `pm2 start _core/index.js --name backend-node`.)
|
|
114
106
|
|
|
115
107
|
### Environment variables
|
|
116
108
|
|
|
117
109
|
| Variable | Default | Purpose |
|
|
118
110
|
|---|---|---|
|
|
119
111
|
| `PORT` | `3000` | Port Node listens on (match your proxy target) |
|
|
120
|
-
| `HOST` | `127.0.0.1` | Bind address
|
|
112
|
+
| `HOST` | `127.0.0.1` | Bind address — keep it local; the web server is the public face |
|
|
121
113
|
| `APP_ENV` | `production` | `production` hides error details; anything else = verbose 500s |
|
|
122
|
-
| `DOCUMENT_ROOT` | auto (`
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## One config file, one backend
|
|
127
|
-
|
|
128
|
-
Each server config targets a specific backend, decided at setup time rather than
|
|
129
|
-
at runtime:
|
|
130
|
-
|
|
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
|
|
134
|
-
|
|
135
|
-
Since a given deployment runs only one backend, this is a one-time setup.
|
|
114
|
+
| `DOCUMENT_ROOT` | auto (`SITE_FOLDER/`) | Where `404.html` lives; detected as the folder above `backend/` |
|
package/nginx.conf
CHANGED
|
@@ -1,42 +1,49 @@
|
|
|
1
1
|
# =============================================================================
|
|
2
2
|
# Nibula — default nginx site configuration
|
|
3
|
-
# Static site + optional HTTP backend on 127.0.0.1:3000. PHP not handled.
|
|
4
3
|
# -----------------------------------------------------------------------------
|
|
5
|
-
#
|
|
4
|
+
# 1. REPLACE
|
|
6
5
|
#
|
|
7
6
|
# YOUR_DOMAIN server_name, twice (port 80 and 443)
|
|
8
7
|
# SITE_FOLDER document root under /var/www/
|
|
8
|
+
# YOUR_CERTIFICATE dir under /etc/letsencrypt/live/
|
|
9
|
+
# SITE_NAME commands below file name
|
|
9
10
|
#
|
|
10
|
-
#
|
|
11
|
-
# as YOUR_DOMAIN. Check: sudo certbot certificates
|
|
11
|
+
# 2. INSTALL (skip if already present)
|
|
12
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
13
|
# sudo apt update
|
|
19
14
|
# sudo apt install -y nginx certbot python3-certbot-nginx
|
|
20
|
-
# nginx -v # >= 1.25 for the http2 directive
|
|
21
15
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
16
|
+
# If you are using PHP as backend:
|
|
17
|
+
# sudo apt install -y php-fpm
|
|
18
|
+
#
|
|
19
|
+
# 3. CERTIFICATE
|
|
20
|
+
#
|
|
24
21
|
# sudo certbot certonly --nginx --cert-name SITE_NAME -d YOUR_DOMAIN
|
|
25
22
|
# sudo systemctl enable --now certbot.timer # auto-renew every 90 days
|
|
26
23
|
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
24
|
+
# 4. INSTALL THE SITE
|
|
25
|
+
#
|
|
26
|
+
# Move to nginx folders
|
|
27
|
+
# cd /etc/nginx/sites-available
|
|
28
|
+
#
|
|
29
|
+
# Create and write a new file
|
|
30
|
+
# sudo nano SITE_NAME
|
|
31
|
+
# CTRL + O, ENTER, CTRL + X
|
|
32
32
|
#
|
|
33
|
-
#
|
|
33
|
+
# Copy the config to sites-enabled
|
|
34
|
+
# sudo cp SITE_NAME ../sites-enabled/
|
|
35
|
+
#
|
|
36
|
+
# Test and reload the nginx configuration
|
|
37
|
+
# sudo nginx -t && sudo systemctl reload nginx
|
|
38
|
+
#
|
|
39
|
+
# IF IT FAILS
|
|
34
40
|
# cannot load certificate ... Permission denied → nginx -t needs sudo
|
|
35
41
|
# cannot load certificate ... No such file → wrong certificate name
|
|
36
|
-
# no alternative certificate subject name →
|
|
37
|
-
# unknown directive "http2" → nginx < 1.25
|
|
42
|
+
# no alternative certificate subject name → certificate doesn't cover domain
|
|
43
|
+
# unknown directive "http2" → nginx < 1.25, check with nginx -v
|
|
38
44
|
# 500 instead of the 404 page → 404.html missing
|
|
39
45
|
# NXDOMAIN / timeout during certbot → DNS not pointing here
|
|
46
|
+
# 502 on /api with PHP deployed → wrong php-fpm socket path
|
|
40
47
|
# =============================================================================
|
|
41
48
|
|
|
42
49
|
# HTTP (80) configuration
|
|
@@ -63,7 +70,7 @@ server {
|
|
|
63
70
|
listen 443 ssl;
|
|
64
71
|
listen [::]:443 ssl;
|
|
65
72
|
|
|
66
|
-
# nginx >= 1.25;
|
|
73
|
+
# nginx >= 1.25;
|
|
67
74
|
http2 on;
|
|
68
75
|
|
|
69
76
|
server_name YOUR_DOMAIN;
|
|
@@ -95,7 +102,7 @@ server {
|
|
|
95
102
|
return 404;
|
|
96
103
|
}
|
|
97
104
|
|
|
98
|
-
location ~*
|
|
105
|
+
location ~* (^|/)web\.config$ {
|
|
99
106
|
return 404;
|
|
100
107
|
}
|
|
101
108
|
|
|
@@ -103,14 +110,11 @@ server {
|
|
|
103
110
|
return 404;
|
|
104
111
|
}
|
|
105
112
|
|
|
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
113
|
location ~ \.php$ {
|
|
110
114
|
return 404;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
### API backend
|
|
117
|
+
### API backend — Node primary, PHP fallback
|
|
114
118
|
location ^~ /api/ {
|
|
115
119
|
proxy_pass http://127.0.0.1:3000;
|
|
116
120
|
proxy_http_version 1.1;
|
|
@@ -122,9 +126,19 @@ server {
|
|
|
122
126
|
# proxy_read_timeout defaults to 60s; raise only for slow endpoints.
|
|
123
127
|
proxy_connect_timeout 5s;
|
|
124
128
|
|
|
129
|
+
proxy_intercept_errors on;
|
|
130
|
+
error_page 502 504 = @php_backend;
|
|
131
|
+
|
|
125
132
|
# WebSocket is not enabled; needs a $connection_upgrade map at http level.
|
|
126
133
|
}
|
|
127
134
|
|
|
135
|
+
location @php_backend {
|
|
136
|
+
include fastcgi_params;
|
|
137
|
+
|
|
138
|
+
fastcgi_param SCRIPT_FILENAME $document_root/backend/_core/index.php;
|
|
139
|
+
fastcgi_pass unix:/run/php/php-fpm.sock;
|
|
140
|
+
}
|
|
141
|
+
|
|
128
142
|
### Static files
|
|
129
143
|
location / {
|
|
130
144
|
try_files $uri $uri/ $uri.html =404;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nibula",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
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",
|
package/docs/Nginx.md
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
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 |
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Example systemd unit to keep the Node backend running on your server.
|
|
2
|
-
#
|
|
3
|
-
# 1. Copy your built site to the server (the `out` folder).
|
|
4
|
-
# 2. Adjust WorkingDirectory below to point at out/backend on your server.
|
|
5
|
-
# 3. Copy this file to /etc/systemd/system/backend-node.service (drop the .example).
|
|
6
|
-
# 4. Enable and start it:
|
|
7
|
-
# sudo systemctl daemon-reload
|
|
8
|
-
# sudo systemctl enable --now backend-node
|
|
9
|
-
# sudo systemctl status backend-node
|
|
10
|
-
#
|
|
11
|
-
# Check the Node path with `which node` and update ExecStart if it isn't /usr/bin/node
|
|
12
|
-
# (nvm installs live elsewhere, e.g. /home/<user>/.nvm/versions/node/<v>/bin/node).
|
|
13
|
-
|
|
14
|
-
[Unit]
|
|
15
|
-
Description=Nibula Node backend
|
|
16
|
-
After=network.target
|
|
17
|
-
|
|
18
|
-
[Service]
|
|
19
|
-
Type=simple
|
|
20
|
-
WorkingDirectory=/var/www/your-site/out/backend
|
|
21
|
-
ExecStart=/usr/bin/node _core/index.js
|
|
22
|
-
Environment=PORT=3000
|
|
23
|
-
Environment=HOST=127.0.0.1
|
|
24
|
-
Environment=APP_ENV=production
|
|
25
|
-
Restart=on-failure
|
|
26
|
-
RestartSec=2
|
|
27
|
-
User=www-data
|
|
28
|
-
|
|
29
|
-
[Install]
|
|
30
|
-
WantedBy=multi-user.target
|