nanio-orchestrator 0.2.0__tar.gz
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.
- nanio_orchestrator-0.2.0/LICENSE +21 -0
- nanio_orchestrator-0.2.0/PKG-INFO +663 -0
- nanio_orchestrator-0.2.0/README.md +619 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/__init__.py +3 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/__main__.py +5 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/api/__init__.py +1 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/api/audit.py +48 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/api/buckets.py +512 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/api/config.py +661 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/api/credentials.py +142 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/api/health.py +41 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/api/migrations.py +469 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/api/pools.py +645 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/api/vhosts.py +663 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/app.py +160 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/audit_log.py +59 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/auth.py +100 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/backup.py +99 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/bucket_sync.py +270 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/cli.py +492 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/config.py +104 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/credentials.py +128 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/db.py +469 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/drift.py +95 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/install.py +331 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/migration_engine.py +1194 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/models.py +462 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/nginx/__init__.py +1 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/nginx/executor.py +107 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/nginx/generator.py +289 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/nginx/parser.py +255 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/nginx/templates/nanio_options.toml.j2 +13 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/nginx/templates/nanio_service.j2 +29 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/nginx/templates/node_nginx_http.conf.j2 +19 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/nginx/templates/node_nginx_nanio.conf.j2 +23 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/nginx/templates/upstream.conf.j2 +19 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/nginx/templates/vhost.conf.j2 +105 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/rebuild.py +472 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/s3client.py +445 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/sidecar.py +259 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/__init__.py +1 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/routes.py +278 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/static/app.js +911 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/static/style.css +232 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/templates/audit.html +70 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/templates/base.html +40 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/templates/buckets.html +459 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/templates/config.html +69 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/templates/dashboard.html +162 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/templates/login.html +93 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/templates/migrations.html +115 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/templates/pools.html +181 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/templates/settings.html +318 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator/web/templates/vhosts.html +202 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator.egg-info/PKG-INFO +663 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator.egg-info/SOURCES.txt +77 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator.egg-info/dependency_links.txt +1 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator.egg-info/entry_points.txt +2 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator.egg-info/requires.txt +16 -0
- nanio_orchestrator-0.2.0/nanio_orchestrator.egg-info/top_level.txt +1 -0
- nanio_orchestrator-0.2.0/pyproject.toml +79 -0
- nanio_orchestrator-0.2.0/setup.cfg +4 -0
- nanio_orchestrator-0.2.0/tests/test_audit.py +33 -0
- nanio_orchestrator-0.2.0/tests/test_auth.py +52 -0
- nanio_orchestrator-0.2.0/tests/test_buckets.py +290 -0
- nanio_orchestrator-0.2.0/tests/test_config.py +74 -0
- nanio_orchestrator-0.2.0/tests/test_credentials.py +112 -0
- nanio_orchestrator-0.2.0/tests/test_db.py +62 -0
- nanio_orchestrator-0.2.0/tests/test_drift.py +57 -0
- nanio_orchestrator-0.2.0/tests/test_encryption.py +68 -0
- nanio_orchestrator-0.2.0/tests/test_health.py +18 -0
- nanio_orchestrator-0.2.0/tests/test_live_migration.py +763 -0
- nanio_orchestrator-0.2.0/tests/test_migrations.py +600 -0
- nanio_orchestrator-0.2.0/tests/test_node_config.py +58 -0
- nanio_orchestrator-0.2.0/tests/test_pools.py +115 -0
- nanio_orchestrator-0.2.0/tests/test_rebuild.py +1056 -0
- nanio_orchestrator-0.2.0/tests/test_s3client.py +47 -0
- nanio_orchestrator-0.2.0/tests/test_vhosts.py +260 -0
- nanio_orchestrator-0.2.0/tests/test_web.py +40 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nuno Cardoso
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nanio-orchestrator
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Nginx configuration manager and gateway orchestrator for nanio S3-compatible storage clusters
|
|
5
|
+
Author-email: Nuno Kisc <nuno@kisc.pt>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nunokisc/nanio-orchestrator
|
|
8
|
+
Project-URL: Repository, https://github.com/nunokisc/nanio-orchestrator
|
|
9
|
+
Project-URL: Documentation, https://github.com/nunokisc/nanio-orchestrator/tree/main/docs
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/nunokisc/nanio-orchestrator/issues
|
|
11
|
+
Keywords: nginx,s3,storage,orchestrator,nanio,gateway
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: No Input/Output (Daemon)
|
|
14
|
+
Classifier: Framework :: FastAPI
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Internet :: Proxy Servers
|
|
24
|
+
Classifier: Topic :: System :: Systems Administration
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: fastapi>=0.110
|
|
29
|
+
Requires-Dist: uvicorn[standard]>=0.29
|
|
30
|
+
Requires-Dist: aiosqlite>=0.20
|
|
31
|
+
Requires-Dist: jinja2>=3.1
|
|
32
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
33
|
+
Requires-Dist: pydantic-settings>=2.2
|
|
34
|
+
Requires-Dist: click>=8.1
|
|
35
|
+
Requires-Dist: aiofiles>=23.2
|
|
36
|
+
Requires-Dist: cryptography>=42.0
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
39
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
41
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
42
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# nanio-orchestrator
|
|
46
|
+
|
|
47
|
+
Nginx configuration manager and gateway orchestrator for a distributed nanio S3-compatible storage cluster.
|
|
48
|
+
It is a **control plane tool only** — traffic never flows through it. If the orchestrator is stopped or
|
|
49
|
+
crashes, nginx keeps serving traffic exactly as configured.
|
|
50
|
+
|
|
51
|
+
## Architecture
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
CLIENT (S3 SDK / browser / aws-cli)
|
|
55
|
+
│ HTTPS
|
|
56
|
+
▼
|
|
57
|
+
NGINX (gateway machine)
|
|
58
|
+
│ proxy_pass only
|
|
59
|
+
├─► upstream pool-2025 → nanio instances → /data/2025/
|
|
60
|
+
├─► upstream pool-2026 → nanio instances → /data/2026/
|
|
61
|
+
└─► upstream pool-cdn → nginx instances → serve files via root/alias
|
|
62
|
+
|
|
63
|
+
ORCHESTRATOR (:8080, internal only)
|
|
64
|
+
│ writes config files + signals nginx
|
|
65
|
+
├─► /etc/nginx/nanio/pools/*.conf (upstream blocks)
|
|
66
|
+
├─► /etc/nginx/nanio/pools/*.meta.json (sidecar: pool type, description, encrypted credentials)
|
|
67
|
+
├─► /etc/nginx/nanio/vhosts/*.conf (server blocks, proxy_pass only)
|
|
68
|
+
├─► /etc/nginx/nanio/vhosts/*.meta.json (sidecar: default pool)
|
|
69
|
+
├─► SQLite at /opt/nanio-orchestrator/data/orchestrator.db
|
|
70
|
+
├─► SQLite backup at /opt/nanio-orchestrator/data/orchestrator.db.bak (+ rotated copies)
|
|
71
|
+
└─► /opt/nanio-orchestrator/data/migrations/*.state.json (in-progress migration state — alongside DB)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Quick Start — Production
|
|
75
|
+
|
|
76
|
+
### Method A: pipx (recommended — installs into an isolated env, exposes the CLI globally)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pipx install nanio-orchestrator
|
|
80
|
+
sudo nanio-orchestrator install
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Method B: uv tool
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
uv tool install nanio-orchestrator
|
|
87
|
+
sudo nanio-orchestrator install
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Method C: pip (into a venv)
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
python3 -m venv /opt/nanio-orchestrator/venv
|
|
94
|
+
/opt/nanio-orchestrator/venv/bin/pip install nanio-orchestrator
|
|
95
|
+
sudo /opt/nanio-orchestrator/venv/bin/nanio-orchestrator install
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
After install, follow the printed instructions to configure and start the service.
|
|
99
|
+
|
|
100
|
+
### Required nginx.conf Change
|
|
101
|
+
|
|
102
|
+
The orchestrator writes config files under `/etc/nginx/nanio/` but nginx only loads them if you
|
|
103
|
+
add the following includes to your nginx `http {}` block (e.g. `/etc/nginx/nginx.conf`):
|
|
104
|
+
|
|
105
|
+
```nginx
|
|
106
|
+
http {
|
|
107
|
+
# ... existing config ...
|
|
108
|
+
|
|
109
|
+
include /etc/nginx/nanio/pools/*.conf; # upstream blocks
|
|
110
|
+
include /etc/nginx/nanio/vhosts/*.conf; # server blocks
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
> The install command prints this reminder. Without these includes nginx serves no nanio traffic,
|
|
115
|
+
> and `nginx -t` will report "unknown upstream" errors for any vhost that references a pool.
|
|
116
|
+
|
|
117
|
+
## Quick Start — Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git clone https://github.com/nunokisc/nanio-orchestrator
|
|
121
|
+
cd nanio-orchestrator
|
|
122
|
+
make install-dev # creates .venv, installs deps
|
|
123
|
+
make run # dev server at http://localhost:8080
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Or manually:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
python3 -m venv .venv
|
|
130
|
+
source .venv/bin/activate
|
|
131
|
+
pip install -e ".[dev]"
|
|
132
|
+
python -m nanio_orchestrator
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Dev mode is auto-detected when `dev.env` exists or `DEV=true` is set. In dev mode:
|
|
136
|
+
- DB at `./dev-data/orchestrator.db`
|
|
137
|
+
- Nginx config at `./dev-data/nginx/`
|
|
138
|
+
- All nginx commands are **dry-run** (printed, not executed)
|
|
139
|
+
- uvicorn `--reload` enabled
|
|
140
|
+
- Default API key: `dev`
|
|
141
|
+
|
|
142
|
+
## Configuration Reference
|
|
143
|
+
|
|
144
|
+
All settings via `/etc/nanio-orchestrator/config.env` (production) or `dev.env` (development).
|
|
145
|
+
Every variable is prefixed with `NANIO_ORCHESTRATOR_`.
|
|
146
|
+
|
|
147
|
+
### Core
|
|
148
|
+
|
|
149
|
+
| Variable | Default | Description |
|
|
150
|
+
|----------|---------|-------------|
|
|
151
|
+
| `HOST` | `0.0.0.0` | Bind address |
|
|
152
|
+
| `PORT` | `8080` | Listen port |
|
|
153
|
+
| `API_KEY` | `changeme` | API authentication key |
|
|
154
|
+
| `DB_PATH` | `/opt/nanio-orchestrator/data/orchestrator.db` | SQLite database path |
|
|
155
|
+
| `NGINX_CONFIG_DIR` | `/etc/nginx/nanio` | Root directory for generated nginx configs |
|
|
156
|
+
| `LOG_LEVEL` | `info` | Log level (`debug`, `info`, `warning`, `error`) |
|
|
157
|
+
| `LOG_FILE` | _(unset)_ | Path to a rotating log file, e.g. `/var/log/nanio-orchestrator/nanio.log`. Up to 10 MB per file, 5 rotated copies. When unset, logs go to stderr only. |
|
|
158
|
+
| `SESSION_TTL` | `28800` | Web UI session duration in seconds (8 hours) |
|
|
159
|
+
| `SECRET` | _(unset)_ | Fernet key for credential encryption at rest. Generate with: `python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` |
|
|
160
|
+
|
|
161
|
+
### S3 / Bucket Sync
|
|
162
|
+
|
|
163
|
+
| Variable | Default | Description |
|
|
164
|
+
|----------|---------|-------------|
|
|
165
|
+
| `S3_ACCESS_KEY` | _(unset)_ | Global S3 access key (used when no per-pool credentials are set) |
|
|
166
|
+
| `S3_SECRET_KEY` | _(unset)_ | Global S3 secret key |
|
|
167
|
+
| `BUCKET_SYNC_INTERVAL` | `300` | Seconds between automatic bucket list syncs |
|
|
168
|
+
|
|
169
|
+
### Migrations (rclone)
|
|
170
|
+
|
|
171
|
+
| Variable | Default | Description |
|
|
172
|
+
|----------|---------|-------------|
|
|
173
|
+
| `RCLONE_PATH` | `rclone` | Path to the rclone binary |
|
|
174
|
+
| `MIGRATION_MAX_PARALLEL` | `2` | Maximum concurrent migrations |
|
|
175
|
+
| `MIGRATION_BANDWIDTH_LIMIT` | _(unset)_ | rclone `--bwlimit` value, e.g. `50M` |
|
|
176
|
+
| `MIGRATION_CHECKERS` | `8` | rclone `--checkers` value |
|
|
177
|
+
| `MIGRATION_TRANSFERS` | `4` | rclone `--transfers` value |
|
|
178
|
+
| `MIGRATION_MAX_COPY_PASSES` | `10` | Maximum convergence loop passes during the `copying` phase before entering `write_routing`. |
|
|
179
|
+
| `S3_REQUEST_TIMEOUT` | `3600` | Socket timeout in seconds for S3 HTTP requests. Increase for buckets with very large objects. |
|
|
180
|
+
|
|
181
|
+
### Drift Detection
|
|
182
|
+
|
|
183
|
+
| Variable | Default | Description |
|
|
184
|
+
|----------|---------|-------------|
|
|
185
|
+
| `DRIFT_INTERVAL` | `60` | Seconds between drift checks |
|
|
186
|
+
|
|
187
|
+
### Database Backup
|
|
188
|
+
|
|
189
|
+
| Variable | Default | Description |
|
|
190
|
+
|----------|---------|-------------|
|
|
191
|
+
| `DB_BACKUP_PATH` | `<DB_PATH>.bak` | Backup file path (defaults to DB path + `.bak`) |
|
|
192
|
+
| `DB_BACKUP_INTERVAL` | `300` | Seconds between timed backups |
|
|
193
|
+
| `DB_BACKUP_ROTATE` | `3` | Number of backup copies to keep (`.bak`, `.bak.2`, `.bak.3`) |
|
|
194
|
+
|
|
195
|
+
## Authentication
|
|
196
|
+
|
|
197
|
+
| Client | Method | Details |
|
|
198
|
+
|--------|--------|---------|
|
|
199
|
+
| **API** (`/api/*`) | `X-Orchestrator-Key` header | Missing/wrong key returns `401` |
|
|
200
|
+
| **Web UI** (`/web/*`, `/`) | Session cookie | Log in at `/login`; HMAC-signed `nanio_session` cookie issued with configurable TTL |
|
|
201
|
+
|
|
202
|
+
Public endpoints (no auth required): `/api/health`, `/api/docs`, `/api/redoc`, `/api/openapi.json`, `/login`, `/logout`, `/static/*`.
|
|
203
|
+
|
|
204
|
+
## API Reference
|
|
205
|
+
|
|
206
|
+
All endpoints under `/api/*` require the `X-Orchestrator-Key` header, except `/api/health`.
|
|
207
|
+
|
|
208
|
+
### Pools
|
|
209
|
+
|
|
210
|
+
| Method | Endpoint | Description |
|
|
211
|
+
|--------|----------|-------------|
|
|
212
|
+
| GET | `/api/pools` | List all pools |
|
|
213
|
+
| POST | `/api/pools` | Create pool |
|
|
214
|
+
| GET | `/api/pools/:id` | Get pool |
|
|
215
|
+
| PUT | `/api/pools/:id` | Update pool |
|
|
216
|
+
| DELETE | `/api/pools/:id` | Delete pool (rejected if routes reference it) |
|
|
217
|
+
| GET | `/api/pools/:id/members` | List pool members |
|
|
218
|
+
| POST | `/api/pools/:id/members` | Add member |
|
|
219
|
+
| PUT | `/api/pools/:id/members/:mid` | Update member |
|
|
220
|
+
| DELETE | `/api/pools/:id/members/:mid` | Remove member |
|
|
221
|
+
| GET | `/api/pools/:id/members/:mid/node-config` | Generate node config (query params) |
|
|
222
|
+
| POST | `/api/pools/:id/members/:mid/node-config` | Generate node config (body) |
|
|
223
|
+
| GET | `/api/pools/:id/node-config-summary` | Node config summary for all members |
|
|
224
|
+
| GET | `/api/pools/:id/buckets/status` | List all buckets on a nanio pool with routing status. Returns each bucket's status (`routed`, `via_default`, `orphaned`, `unrouted`) and which vhosts serve it. Only available for `nanio` pools. |
|
|
225
|
+
|
|
226
|
+
### Pool Credentials
|
|
227
|
+
|
|
228
|
+
Per-pool S3 credentials, encrypted at rest with Fernet. Requires `SECRET` to be set.
|
|
229
|
+
|
|
230
|
+
> **nanio pools only.** Credentials are only supported for pools of type `nanio`. Attempting to get, set, or remove credentials on an `http` pool returns HTTP 400.
|
|
231
|
+
|
|
232
|
+
| Method | Endpoint | Description |
|
|
233
|
+
|--------|----------|-------------|
|
|
234
|
+
| GET | `/api/pools/:id/credentials` | Get credentials (access key masked) |
|
|
235
|
+
| PUT | `/api/pools/:id/credentials` | Store or replace credentials |
|
|
236
|
+
| DELETE | `/api/pools/:id/credentials` | Remove credentials |
|
|
237
|
+
|
|
238
|
+
### Vhosts + Routes
|
|
239
|
+
|
|
240
|
+
| Method | Endpoint | Description |
|
|
241
|
+
|--------|----------|-------------|
|
|
242
|
+
| GET | `/api/vhosts` | List all vhosts |
|
|
243
|
+
| POST | `/api/vhosts` | Create vhost |
|
|
244
|
+
| GET | `/api/vhosts/:id` | Get vhost |
|
|
245
|
+
| PUT | `/api/vhosts/:id` | Update vhost |
|
|
246
|
+
| DELETE | `/api/vhosts/:id` | Delete vhost (rejected if routes exist) |
|
|
247
|
+
| GET | `/api/vhosts/:id/routes` | List routes |
|
|
248
|
+
| POST | `/api/vhosts/:id/routes` | Add route |
|
|
249
|
+
| PUT | `/api/vhosts/:id/routes/:rid` | Update route |
|
|
250
|
+
| DELETE | `/api/vhosts/:id/routes/:rid` | Delete route |
|
|
251
|
+
| GET | `/api/vhosts/:id/preview` | Preview rendered server block |
|
|
252
|
+
|
|
253
|
+
**SSL enforcement**: `ssl: true` requires both `ssl_certificate` and `ssl_certificate_key` to be provided. The API returns HTTP 422 if either is missing.
|
|
254
|
+
|
|
255
|
+
**Pool-type consistency**: If a vhost has a `nanio` default pool, all routes must also point to `nanio` pools. Mixed types within a vhost are rejected. Vhosts with no default pool are unrestricted.
|
|
256
|
+
|
|
257
|
+
**Additional configurations** (`extra_blocks`): Vhosts accept an optional `extra_blocks` array for injecting raw nginx directives into specific zones of the generated server block:
|
|
258
|
+
|
|
259
|
+
```json
|
|
260
|
+
"extra_blocks": [
|
|
261
|
+
{ "zone": "top", "content": "add_header X-Frame-Options SAMEORIGIN;" },
|
|
262
|
+
{ "zone": "ssl", "content": "ssl_stapling on;\nssl_stapling_verify on;" },
|
|
263
|
+
{ "zone": "proxy", "content": "proxy_read_timeout 300s;" },
|
|
264
|
+
{ "zone": "end", "content": "# custom footer" }
|
|
265
|
+
]
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
| Zone | Inserted after |
|
|
269
|
+
|------|----------------|
|
|
270
|
+
| `top` | `server_name` directive (and after IP rules, if any) |
|
|
271
|
+
| `ssl` | SSL certificate directives |
|
|
272
|
+
| `proxy` | Proxy buffering directives |
|
|
273
|
+
| `end` | Before the closing `}` of the server block |
|
|
274
|
+
|
|
275
|
+
This is intended for advanced per-vhost nginx settings that the orchestrator does not model natively. Content is injected verbatim — it must be valid nginx syntax or `nginx -t` will reject the config.
|
|
276
|
+
|
|
277
|
+
**IP Access Control** (`ip_rule_mode` + `ip_rule_ips`): Per-vhost IP allowlist or denylist, placed at server-block level (applies to all routes):
|
|
278
|
+
|
|
279
|
+
```json
|
|
280
|
+
{
|
|
281
|
+
"ip_rule_mode": "allow",
|
|
282
|
+
"ip_rule_ips": ["10.0.0.0/8", "192.168.1.5"]
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
| Mode | Generated nginx |
|
|
287
|
+
|------|----------------|
|
|
288
|
+
| `allow` | `allow <ip>; ... deny all;` — only listed IPs can access the vhost |
|
|
289
|
+
| `deny` | `deny <ip>;` — listed IPs are blocked, everything else is allowed |
|
|
290
|
+
| `null` / omitted | No IP restrictions |
|
|
291
|
+
|
|
292
|
+
Accepted formats: IPv4 (`1.2.3.4`), IPv4 CIDR (`10.0.0.0/8`), IPv6, IPv6 CIDR. Values are validated on write. Both fields are stored in the vhost sidecar `.meta.json` and are fully recovered by the rebuild-from-disk operation.
|
|
293
|
+
|
|
294
|
+
### Bucket Sync
|
|
295
|
+
|
|
296
|
+
Tracks buckets discovered on the default pool of each vhost. Background sync runs every `BUCKET_SYNC_INTERVAL` seconds.
|
|
297
|
+
|
|
298
|
+
| Method | Endpoint | Description |
|
|
299
|
+
|--------|----------|-------------|
|
|
300
|
+
| GET | `/api/vhosts/:id/buckets` | List buckets with routing status (`unrouted`, `routed`, `migrating`, `ignored`). Pass `?fetch_counts=true` to include object counts. |
|
|
301
|
+
| POST | `/api/vhosts/:id/buckets/sync` | Trigger an immediate bucket list sync |
|
|
302
|
+
| POST | `/api/vhosts/:id/buckets/:bucket/promote` | Promote a bucket: create it on the target pool, add an nginx route, optionally start migration. See promote request body below. |
|
|
303
|
+
| POST | `/api/vhosts/:id/buckets/:bucket/ignore` | Mark a bucket as ignored |
|
|
304
|
+
|
|
305
|
+
> **Bucket sync** only runs on vhosts whose default pool is of type `nanio`. Vhosts backed by an `http` pool are silently skipped — they have no S3 ListBuckets semantics.
|
|
306
|
+
|
|
307
|
+
**Promote request body** (`POST /api/vhosts/:id/buckets/:bucket/promote`):
|
|
308
|
+
|
|
309
|
+
| Field | Type | Default | Description |
|
|
310
|
+
|-------|------|---------|-------------|
|
|
311
|
+
| `pool_id` | int | — | Target pool for the new route |
|
|
312
|
+
| `migrate` | bool | `false` | Start rclone migration after creating the route |
|
|
313
|
+
| `allow_orphan` | bool | `false` | Allow routing to a different pool without migration when the source bucket already has objects. Existing objects remain on the source pool and will not be accessible via this route until migrated manually. |
|
|
314
|
+
|
|
315
|
+
**Conflict behaviour** when `migrate=false` and the source bucket has objects:
|
|
316
|
+
- Routing to **the same pool as the default** → allowed (just creates an explicit route, no data loss).
|
|
317
|
+
- Routing to **a different pool** → returns HTTP 400 with `allow_orphan` in the message. Re-submit with `allow_orphan=true` to acknowledge data will remain on the source. The Web UI shows an inline conflict box with "Enable migration & route" or "Route without migration" options.
|
|
318
|
+
|
|
319
|
+
| Method | Endpoint | Description |
|
|
320
|
+
|--------|----------|-------------|
|
|
321
|
+
| POST | `/api/vhosts/:id/buckets/:bucket/migrate` | Start (or restart) object migration for a routed bucket (uses rclone engine) |
|
|
322
|
+
| GET | `/api/vhosts/:id/buckets/orphans` | Scan routed buckets for orphan objects still on the default pool |
|
|
323
|
+
| POST | `/api/vhosts/:id/buckets/:bucket/purge-orphan` | Delete all objects from the default pool copy of a routed bucket |
|
|
324
|
+
|
|
325
|
+
### Migrations (rclone)
|
|
326
|
+
|
|
327
|
+
Full bucket migrations using rclone.
|
|
328
|
+
|
|
329
|
+
Phases: `pending → copying → write_routing → verifying → switching → done`
|
|
330
|
+
|
|
331
|
+
- **copying**: rclone copies data in a convergence loop (up to `MIGRATION_MAX_COPY_PASSES` passes). Ends early if counts stabilise across passes.
|
|
332
|
+
- **write_routing**: nginx is reconfigured so writes go directly to the destination pool while reads still come from the source (with 404-fallback to destination). Freezes new writes to the source.
|
|
333
|
+
- **verifying**: a copy→check convergence loop (up to `MIGRATION_MAX_COPY_PASSES` passes). Each pass does a final `rclone copy` followed by `rclone check`. If check passes cleanly the migration proceeds to switching. If differences are still found, the loop retries — this handles buckets that are still receiving uploads during migration. The loop aborts early if the diff count stops decreasing between passes (source diverging faster than rclone can copy).
|
|
334
|
+
- **switching**: the nginx route is flipped to the destination pool and the DB is updated atomically. Fails hard if the route cannot be found — no silent data loss.
|
|
335
|
+
- **done**: migration complete. Source data is **never deleted automatically**. The migration record tracks `orphaned_source_pool_id`, `orphaned_source_prefix`, and `orphaned_at` so operators can locate and clean up the source bucket at their own pace.
|
|
336
|
+
- **error** / **cancelled**: terminal failure states.
|
|
337
|
+
|
|
338
|
+
> **Source data is never purged automatically.** When a migration reaches `done`, the orchestrator records where the original data lives (pool + prefix + timestamp). Use `nanio-orchestrator orphaned list` or `GET /api/migrations/orphaned` to review, and delete the source objects manually when ready.
|
|
339
|
+
|
|
340
|
+
> **A route must exist before starting a migration.** Use `POST /api/vhosts/:id/buckets/:bucket/promote` (Buckets page) to create the bucket route first. The Migrations page only accepts buckets that are already routed — it validates that the route exists and points to `src_pool_id` before creating the migration record.
|
|
341
|
+
|
|
342
|
+
- **copy** mode (default): additive — only copies objects from source to destination, never deletes at the destination.
|
|
343
|
+
- **sync** mode: mirror — destination becomes identical to the source. A pre-flight guard aborts the migration if the source bucket is empty to prevent accidental data loss.
|
|
344
|
+
|
|
345
|
+
**Pre-flight validation** (at `POST /api/migrations` time):
|
|
346
|
+
- **Both pools must be of type `nanio`.** Migrations between `http` pools or from/to an `http` pool are rejected with HTTP 400.
|
|
347
|
+
- Both pools must have at least one enabled member.
|
|
348
|
+
- Source and destination must be different pools.
|
|
349
|
+
- A route `/{bucket}/` must exist in the vhost and point to `src_pool_id`.
|
|
350
|
+
- The source bucket must exist and contain at least one object on the source pool.
|
|
351
|
+
- No active migration for the same bucket can already be running.
|
|
352
|
+
|
|
353
|
+
**Destination bucket with existing objects**: In `copy` mode, if the destination bucket already has objects (e.g. from a previously failed migration attempt), the migration **proceeds with a warning** — rclone copy is additive and skips objects that already exist at the destination unchanged. In `sync` mode, the migration is **aborted** if the destination has objects to prevent accidental data loss.
|
|
354
|
+
|
|
355
|
+
**Audit log**: Every migration lifecycle event is written to the audit log — `start_migration` when created, `migration_done` on completion, `migration_cancelled` on cancellation, and `migration_error` on failure. Per-step details are also written to the migration's own log (`GET /api/migrations/:id/log`), including early-abort error messages.
|
|
356
|
+
|
|
357
|
+
| Method | Endpoint | Description |
|
|
358
|
+
|--------|----------|--------------|
|
|
359
|
+
| POST | `/api/migrations` | Start a new migration. Body: `{bucket, src_pool_id, dst_pool_id, mode}` where `mode` is `"copy"` (default) or `"sync"`. Requires an nginx route for the bucket pointing to `src_pool_id`. |
|
|
360
|
+
| GET | `/api/migrations` | List migrations (filter with `?phase=`) |
|
|
361
|
+
| GET | `/api/migrations/stale` | List active migrations that cannot proceed — source pool has no members, or source bucket has disappeared. |
|
|
362
|
+
| GET | `/api/migrations/orphaned` | List completed migrations that have orphaned source data pending manual cleanup |
|
|
363
|
+
| GET | `/api/migrations/source-buckets` | List buckets available to migrate from a given pool (`?pool_id=`). Returns buckets from `bucket_sync` and routed paths. Used by the Migrations UI to populate the bucket selector. |
|
|
364
|
+
| GET | `/api/migrations/:id` | Get migration details (includes `orphaned_source_pool_id`, `orphaned_source_prefix`, `orphaned_at`) |
|
|
365
|
+
| POST | `/api/migrations/:id/cancel` | Cancel a running migration |
|
|
366
|
+
| GET | `/api/migrations/:id/log` | Get migration log entries |
|
|
367
|
+
|
|
368
|
+
### Config Operations
|
|
369
|
+
|
|
370
|
+
| Method | Endpoint | Description |
|
|
371
|
+
|--------|----------|-------------|
|
|
372
|
+
| GET | `/api/config/status` | Drift status per file |
|
|
373
|
+
| POST | `/api/config/validate` | Run `nginx -t` |
|
|
374
|
+
| POST | `/api/config/reload` | Run `nginx -s reload` |
|
|
375
|
+
| POST | `/api/config/sync` | Re-import disk state → DB |
|
|
376
|
+
| POST | `/api/config/rebuild` | Rebuild all files from DB → disk → reload |
|
|
377
|
+
| POST | `/api/config/absorb-file` | Accept a drifted file: import disk state into DB |
|
|
378
|
+
| POST | `/api/config/rewrite-file` | Rewrite a single file from DB state + reload |
|
|
379
|
+
| GET | `/api/config/preview/pool/:id` | Preview upstream config (no apply) |
|
|
380
|
+
| GET | `/api/config/preview/vhost/:id` | Preview server block config (no apply) |
|
|
381
|
+
| POST | `/api/config/rebuild-from-disk` | Reconstruct DB from nginx configs + sidecar files (see [DB Resilience](#db-resilience)) |
|
|
382
|
+
| POST | `/api/config/backup` | Trigger an immediate database backup |
|
|
383
|
+
| GET | `/api/config/settings` | Current effective settings (secrets masked) |
|
|
384
|
+
| PUT | `/api/config/settings/:key` | Update a single setting in the config file (takes effect after restart) |
|
|
385
|
+
| POST | `/api/config/settings/restart` | Restart the service to apply pending config changes (requires sudoers rule installed by `nanio-orchestrator install`) |
|
|
386
|
+
|
|
387
|
+
### Health + Audit
|
|
388
|
+
|
|
389
|
+
| Method | Endpoint | Description |
|
|
390
|
+
|--------|----------|-------------|
|
|
391
|
+
| GET | `/api/health` | Health check (no auth required) |
|
|
392
|
+
| GET | `/api/audit` | Audit log (`?page=&entity_type=&from=&to=`) |
|
|
393
|
+
|
|
394
|
+
## How Nginx Config is Managed
|
|
395
|
+
|
|
396
|
+
### Write Path
|
|
397
|
+
|
|
398
|
+
Every config change follows this exact sequence:
|
|
399
|
+
|
|
400
|
+
1. Render new config from DB state (Jinja2 templates)
|
|
401
|
+
2. Write to `<file>.tmp`
|
|
402
|
+
3. Run `nginx -t` — if it fails: delete `.tmp`, return error, stop
|
|
403
|
+
4. `os.rename(<file>.tmp, <file>)` — atomic on POSIX
|
|
404
|
+
5. Run `nginx -s reload`
|
|
405
|
+
6. Update DB: sha256, content snapshot, audit log with nginx output
|
|
406
|
+
7. Trigger a DB backup
|
|
407
|
+
|
|
408
|
+
### Sidecar Files
|
|
409
|
+
|
|
410
|
+
Alongside each nginx config file the orchestrator writes a `.meta.json` sidecar containing
|
|
411
|
+
data that cannot be reconstructed from the nginx config alone:
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
/etc/nginx/nanio/
|
|
415
|
+
├── pools/
|
|
416
|
+
│ ├── pool-2025.conf # upstream block
|
|
417
|
+
│ └── pool-2025.meta.json # type, description, encrypted credentials
|
|
418
|
+
└── vhosts/
|
|
419
|
+
├── s3.xpto.pt.conf # server block
|
|
420
|
+
└── s3.xpto.pt.meta.json # default_pool_id + name
|
|
421
|
+
|
|
422
|
+
/opt/nanio-orchestrator/data/
|
|
423
|
+
├── orchestrator.db
|
|
424
|
+
├── orchestrator.db.bak
|
|
425
|
+
└── migrations/
|
|
426
|
+
├── migration-7.state.json # in-progress migration state (alongside DB, not in nginx dir)
|
|
427
|
+
└── migration-7.done.json # permanent completion record (written when migration reaches 'done')
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Sidecars are written atomically (`.tmp` → rename) and are the foundation for
|
|
431
|
+
[DB resilience](#db-resilience).
|
|
432
|
+
|
|
433
|
+
### Drift Detection
|
|
434
|
+
|
|
435
|
+
Background check every `DRIFT_INTERVAL` seconds:
|
|
436
|
+
- SHA256 each managed file on disk
|
|
437
|
+
- Compare with the last known hash in DB
|
|
438
|
+
- If mismatch: alert in dashboard and `GET /api/config/status`
|
|
439
|
+
- **Never auto-corrects** — the operator decides
|
|
440
|
+
|
|
441
|
+
### Pool Types
|
|
442
|
+
|
|
443
|
+
| Type | Members | Nginx `backup` flag | Credentials | Description |
|
|
444
|
+
|------|---------|---------------------|-------------|-------------|
|
|
445
|
+
| `nanio` | All `active` | Never | ✓ (S3 access/secret key) | Shared storage — any member handles any request |
|
|
446
|
+
| `http` | `primary` + `replica` | Yes, for replicas | ✗ | Read-only HTTP serve with failover |
|
|
447
|
+
|
|
448
|
+
Pool type also determines what is available in the Web UI:
|
|
449
|
+
- **S3 credentials** are only shown and editable for `nanio` pools.
|
|
450
|
+
- **Migrations** can only be created between two `nanio` pools.
|
|
451
|
+
- **Bucket sync** and the **Buckets** management page only operate on vhosts whose default pool is `nanio`.
|
|
452
|
+
|
|
453
|
+
### Node Config Generator
|
|
454
|
+
|
|
455
|
+
Generates config snippets for upstream nodes (rendered only, never deployed):
|
|
456
|
+
- **nanio-only**: nanio `options.toml` + systemd unit
|
|
457
|
+
- **nginx-only**: nginx server block for file serving
|
|
458
|
+
- **nginx-nanio**: both nanio config and nginx proxy config
|
|
459
|
+
|
|
460
|
+
Access via API or the "Node Setup" button in the Web UI.
|
|
461
|
+
|
|
462
|
+
## DB Resilience
|
|
463
|
+
|
|
464
|
+
The database is not the source of truth — the nginx config files and their sidecar files are.
|
|
465
|
+
The DB can be fully rebuilt from disk after loss or corruption.
|
|
466
|
+
|
|
467
|
+
### Automatic Backup
|
|
468
|
+
|
|
469
|
+
The DB is backed up automatically:
|
|
470
|
+
- After every successful nginx reload
|
|
471
|
+
- On a periodic timer (`DB_BACKUP_INTERVAL`, default 60 s)
|
|
472
|
+
- On demand via `POST /api/config/backup`
|
|
473
|
+
|
|
474
|
+
Backups are rotated: `.bak`, `.bak.2`, `.bak.3`, … up to `DB_BACKUP_ROTATE` copies.
|
|
475
|
+
|
|
476
|
+
### Rebuild from Disk
|
|
477
|
+
|
|
478
|
+
If the DB is lost or corrupted, reconstruct it without downtime (nginx keeps running):
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
# Preview what would be imported
|
|
482
|
+
nanio-orchestrator rebuild-db --dry-run
|
|
483
|
+
|
|
484
|
+
# Rebuild (safe — DB must be empty)
|
|
485
|
+
nanio-orchestrator rebuild-db
|
|
486
|
+
|
|
487
|
+
# Rebuild over existing data
|
|
488
|
+
nanio-orchestrator rebuild-db --force
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Or via API:
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
curl -X POST http://localhost:8080/api/config/rebuild-from-disk \
|
|
495
|
+
-H "X-Orchestrator-Key: <key>"
|
|
496
|
+
|
|
497
|
+
# Force over existing data
|
|
498
|
+
curl -X POST "http://localhost:8080/api/config/rebuild-from-disk?force=true" \
|
|
499
|
+
-H "X-Orchestrator-Key: <key>"
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
What is recovered:
|
|
503
|
+
|
|
504
|
+
| Data | Source | Recovered? |
|
|
505
|
+
|------|--------|-----------|
|
|
506
|
+
| Pools (name, lb_method, keepalive) | `pools/*.conf` | ✓ |
|
|
507
|
+
| Pool members | `pools/*.conf` | ✓ |
|
|
508
|
+
| Pool type, description | `pools/*.meta.json` | ✓ |
|
|
509
|
+
| Encrypted credentials | `pools/*.meta.json` | ✓ |
|
|
510
|
+
| Vhosts (server_name, SSL, ports) | `vhosts/*.conf` | ✓ |
|
|
511
|
+
| Routes | `vhosts/*.conf` | ✓ |
|
|
512
|
+
| Vhost default_pool_id | `vhosts/*.meta.json` | ✓ |
|
|
513
|
+
| In-progress migrations | `data/migrations/*.state.json` | ✓ (reset to pending, will auto-resume) |
|
|
514
|
+
| Completed migration records | `data/migrations/*.done.json` | ✓ (orphaned source info preserved) |
|
|
515
|
+
| config_files sha256 records | recomputed from disk | ✓ |
|
|
516
|
+
| bucket_sync | live `ListBuckets` call per pool member | ✓ (best-effort — requires pool members to be reachable) |
|
|
517
|
+
| audit_log | — | ✗ (historical only) |
|
|
518
|
+
|
|
519
|
+
After rebuild, restart the service so migrations resume:
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
systemctl restart nanio-orchestrator
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
## Web UI
|
|
526
|
+
|
|
527
|
+
The web UI is served at `/` and requires a session cookie obtained via `/login`.
|
|
528
|
+
|
|
529
|
+
| Page | URL | Description |
|
|
530
|
+
|------|-----|-------------|
|
|
531
|
+
| Dashboard | `/` | Overview: pools, vhosts, drift count, active migrations, unrouted buckets |
|
|
532
|
+
| Pools | `/web/pools` | Manage pools and members |
|
|
533
|
+
| Vhosts | `/web/vhosts` | Manage vhosts and routes |
|
|
534
|
+
| Buckets | `/web/buckets` or via Vhosts page | Bucket list, promote, migrate, orphan scan and purge per vhost |
|
|
535
|
+
| Config | `/web/config` | Config file drift status, per-file actions |
|
|
536
|
+
| Migrations | `/web/migrations` | Start (copy or sync mode) and monitor rclone migrations. Bucket selector is populated from the source pool's existing routes. Stale migrations (source pool lost members or source bucket disappeared) are flagged. |
|
|
537
|
+
| Audit | `/web/audit` | Last 100 audit log entries |
|
|
538
|
+
| Settings | `/web/settings` | View all current settings (secrets masked) |
|
|
539
|
+
|
|
540
|
+
## CLI Reference
|
|
541
|
+
|
|
542
|
+
```
|
|
543
|
+
nanio-orchestrator [serve] Start the server (default command)
|
|
544
|
+
nanio-orchestrator install Production install (run as root)
|
|
545
|
+
nanio-orchestrator rebuild-db Rebuild DB from disk
|
|
546
|
+
--dry-run Preview without writing
|
|
547
|
+
--force Overwrite existing DB data
|
|
548
|
+
|
|
549
|
+
nanio-orchestrator config show Print all settings grouped by category
|
|
550
|
+
nanio-orchestrator config get <key> Print the value of a single setting
|
|
551
|
+
nanio-orchestrator config set <key> <val> Write a setting to the config file
|
|
552
|
+
nanio-orchestrator config generate-secret Generate a Fernet key for SECRET
|
|
553
|
+
--set Also write it to the config file
|
|
554
|
+
nanio-orchestrator config edit Open the config file in $EDITOR
|
|
555
|
+
nanio-orchestrator config validate Run nginx -t
|
|
556
|
+
nanio-orchestrator config reload Run nginx -s reload
|
|
557
|
+
nanio-orchestrator config rebuild Regenerate all config files from DB + reload
|
|
558
|
+
|
|
559
|
+
nanio-orchestrator orphaned list List all completed migrations with orphaned source data
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### `config show` example
|
|
563
|
+
|
|
564
|
+
```
|
|
565
|
+
Core
|
|
566
|
+
host 0.0.0.0 Bind address
|
|
567
|
+
port 8080 Listen port
|
|
568
|
+
api_key chan**** API authentication key
|
|
569
|
+
log_level info Log level (debug/info/warning/error)
|
|
570
|
+
session_ttl 28800 Web UI session duration (seconds)
|
|
571
|
+
|
|
572
|
+
Database
|
|
573
|
+
db_path /opt/.../orchestrator.db SQLite database file path
|
|
574
|
+
db_backup_path /opt/.../orchestrator.db.bak Backup path (default: db_path + .bak)
|
|
575
|
+
...
|
|
576
|
+
|
|
577
|
+
Config file (production): /etc/nanio-orchestrator/config.env
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### `config set` example
|
|
581
|
+
|
|
582
|
+
```bash
|
|
583
|
+
nanio-orchestrator config set api_key mysecretkey
|
|
584
|
+
nanio-orchestrator config set log_level debug
|
|
585
|
+
nanio-orchestrator config set migration_max_parallel 4
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Accepts the short key name (without `NANIO_ORCHESTRATOR_` prefix). Updates the active config file in place, handling commented-out lines.
|
|
589
|
+
|
|
590
|
+
## Offline / Air-gapped Deployment
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
# On a machine with internet:
|
|
594
|
+
make build # produces dist/nanio_orchestrator-*.whl
|
|
595
|
+
|
|
596
|
+
# Copy the wheel to the target server, then:
|
|
597
|
+
python3 -m venv /opt/nanio-orchestrator/venv
|
|
598
|
+
/opt/nanio-orchestrator/venv/bin/pip install nanio_orchestrator-*.whl
|
|
599
|
+
/opt/nanio-orchestrator/venv/bin/nanio-orchestrator install
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
The wheel bundles all dependencies. No internet required on the target server.
|
|
603
|
+
|
|
604
|
+
## Troubleshooting
|
|
605
|
+
|
|
606
|
+
### `nginx -t` fails after config change
|
|
607
|
+
|
|
608
|
+
The orchestrator never applies a config that fails validation. Check the error output
|
|
609
|
+
in the API response or audit log. Common causes:
|
|
610
|
+
- Missing SSL certificates referenced in the vhost config
|
|
611
|
+
- Upstream pool name conflicts with an existing nginx config
|
|
612
|
+
- `include /etc/nginx/nanio/pools/*.conf;` and `include /etc/nginx/nanio/vhosts/*.conf;` not added to the `http {}` block in `nginx.conf`
|
|
613
|
+
|
|
614
|
+
### Drift detected
|
|
615
|
+
|
|
616
|
+
A file was modified outside the orchestrator. Options:
|
|
617
|
+
1. **Accept the change**: `POST /api/config/sync` to import disk state into DB
|
|
618
|
+
2. **Restore from DB**: `POST /api/config/rebuild` to overwrite disk with DB state
|
|
619
|
+
|
|
620
|
+
### Service won't start
|
|
621
|
+
|
|
622
|
+
```bash
|
|
623
|
+
journalctl -u nanio-orchestrator -f # check logs
|
|
624
|
+
nanio-orchestrator config validate # test nginx config
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Common causes:
|
|
628
|
+
- DB path not writable
|
|
629
|
+
- Port 8080 already in use (change `PORT` in config.env)
|
|
630
|
+
- Python version too old (requires 3.9+)
|
|
631
|
+
|
|
632
|
+
### Credentials API returns 500
|
|
633
|
+
|
|
634
|
+
`SECRET` is not set or is not a valid Fernet key. Generate and set one:
|
|
635
|
+
|
|
636
|
+
```bash
|
|
637
|
+
nanio-orchestrator config generate-secret --set
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
Or manually:
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
|
644
|
+
# Then: nanio-orchestrator config set secret <generated-key>
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
Restart the service after setting the key.
|
|
648
|
+
|
|
649
|
+
### API returns 401
|
|
650
|
+
|
|
651
|
+
All API endpoints (except `/api/health`) require `X-Orchestrator-Key` set to
|
|
652
|
+
`NANIO_ORCHESTRATOR_API_KEY`.
|
|
653
|
+
|
|
654
|
+
### Web UI keeps redirecting to /login
|
|
655
|
+
|
|
656
|
+
- Cookies blocked? Make sure the browser allows cookies for the host.
|
|
657
|
+
- Behind a TLS-terminating proxy? Ensure `X-Forwarded-Proto: https` is forwarded so the `Secure` cookie flag is set correctly.
|
|
658
|
+
- Session expired? Default TTL is 8 hours. Increase `SESSION_TTL` if needed.
|
|
659
|
+
- API key changed? Old cookies are immediately invalidated; re-login.
|
|
660
|
+
|
|
661
|
+
## License
|
|
662
|
+
|
|
663
|
+
MIT
|