@naviyra/cloud-panel 0.0.0-stage → 0.0.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/.env.example +40 -0
- package/README.md +134 -2
- package/apps/agent/package.json +13 -0
- package/apps/agent/src/cloudflare.mjs +733 -0
- package/apps/agent/src/native-worker.mjs +52 -0
- package/apps/agent/src/native.mjs +457 -0
- package/apps/agent/src/runtime.mjs +760 -0
- package/apps/agent/src/server.mjs +195 -0
- package/apps/agent/src/static-server.mjs +53 -0
- package/apps/panel/AGENTS.md +9 -0
- package/apps/panel/CLAUDE.md +1 -0
- package/apps/panel/app/api/[...path]/route.js +445 -0
- package/apps/panel/app/globals.css +2057 -0
- package/apps/panel/app/icon.png +0 -0
- package/apps/panel/app/layout.jsx +18 -0
- package/apps/panel/app/login/page.jsx +108 -0
- package/apps/panel/app/page.jsx +33 -0
- package/apps/panel/app/ui/cloudflare.jsx +926 -0
- package/apps/panel/app/ui/dashboard.jsx +1487 -0
- package/apps/panel/app/ui/databases.jsx +293 -0
- package/apps/panel/app/ui/skeleton.jsx +196 -0
- package/apps/panel/app/ui/theme.jsx +35 -0
- package/apps/panel/lib/agent.mjs +83 -0
- package/apps/panel/lib/auth.mjs +48 -0
- package/apps/panel/lib/database-runtime.mjs +243 -0
- package/apps/panel/lib/databases.mjs +292 -0
- package/apps/panel/lib/db.mjs +6 -0
- package/apps/panel/next.config.mjs +8 -0
- package/apps/panel/package.json +22 -0
- package/apps/panel/public/naviyra-logo.png +0 -0
- package/bin/naviyra.mjs +177 -0
- package/db/001_init.sql +28 -0
- package/db/002_native_runtimes.sql +3 -0
- package/db/003_databases.sql +8 -0
- package/db/004_database_engines.sql +4 -0
- package/deploy/naviyra-agent.service +17 -0
- package/docker-compose.yml +18 -0
- package/docs/managed-runtime.md +52 -0
- package/docs/native-hosts.md +48 -0
- package/package.json +61 -4
- package/packages/runtime/artifacts.json +32 -0
- package/packages/runtime/index.cjs +526 -0
- package/packages/runtime/index.mjs +10 -0
- package/packages/runtime/package.json +6 -0
- package/packages/shared/index.mjs +85 -0
- package/packages/shared/package.json +9 -0
- package/scripts/migrate.mjs +18 -0
- package/scripts/postgres-local.mjs +97 -0
- package/scripts/runtime-download-manifest.mjs +30 -0
- package/scripts/runtime.mjs +20 -0
- package/scripts/seed.mjs +35 -0
- package/scripts/service-supervisor.mjs +164 -0
- package/scripts/setup-local.mjs +215 -0
- package/scripts/start-naviyra.mjs +303 -0
- package/scripts/windows-boot.mjs +259 -0
package/bin/naviyra.mjs
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { cp, readdir, readFile, mkdir, access } from "node:fs/promises";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
const source = path.resolve(
|
|
8
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
9
|
+
"..",
|
|
10
|
+
);
|
|
11
|
+
const skip = new Set([
|
|
12
|
+
"node_modules",
|
|
13
|
+
".next",
|
|
14
|
+
".git",
|
|
15
|
+
".runtime",
|
|
16
|
+
"coverage",
|
|
17
|
+
"test-results",
|
|
18
|
+
"playwright-report",
|
|
19
|
+
".env",
|
|
20
|
+
".env.local",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
async function exists(file) {
|
|
24
|
+
try {
|
|
25
|
+
await access(file);
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function isPanel(dir) {
|
|
32
|
+
try {
|
|
33
|
+
const pkg = JSON.parse(
|
|
34
|
+
await readFile(path.join(dir, "package.json"), "utf8"),
|
|
35
|
+
);
|
|
36
|
+
return (
|
|
37
|
+
pkg.name === "@naviyra/cloud-panel" &&
|
|
38
|
+
(await exists(path.join(dir, "apps", "panel", "package.json")))
|
|
39
|
+
);
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function isEmpty(dir) {
|
|
45
|
+
try {
|
|
46
|
+
const entries = await readdir(dir);
|
|
47
|
+
return entries.length === 0;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error.code === "ENOENT") return true;
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function copyTree(from, to) {
|
|
54
|
+
await mkdir(to, { recursive: true });
|
|
55
|
+
for (const entry of await readdir(from, { withFileTypes: true })) {
|
|
56
|
+
if (skip.has(entry.name) || entry.name.endsWith(".log")) continue;
|
|
57
|
+
const src = path.join(from, entry.name);
|
|
58
|
+
const dest = path.join(to, entry.name);
|
|
59
|
+
if (entry.isDirectory()) await copyTree(src, dest);
|
|
60
|
+
else if (entry.isFile()) await cp(src, dest);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function run(command, args, cwd) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const child = spawn(command, args, {
|
|
66
|
+
cwd,
|
|
67
|
+
stdio: "inherit",
|
|
68
|
+
shell: process.platform === "win32" && command === "npm",
|
|
69
|
+
windowsHide: true,
|
|
70
|
+
});
|
|
71
|
+
child.once("error", reject);
|
|
72
|
+
child.once("exit", (code) =>
|
|
73
|
+
code === 0 ? resolve() : reject(Error(`${command} exited ${code}`)),
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function start(cwd, args) {
|
|
78
|
+
const child = spawn(process.execPath, ["scripts/start-naviyra.mjs", ...args], {
|
|
79
|
+
cwd,
|
|
80
|
+
stdio: "inherit",
|
|
81
|
+
windowsHide: true,
|
|
82
|
+
});
|
|
83
|
+
child.once("error", (error) => {
|
|
84
|
+
console.error(`Could not start Naviyra: ${error.message}`);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
});
|
|
87
|
+
child.once("exit", (code, signal) => {
|
|
88
|
+
process.exit(code ?? (signal ? 1 : 0));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function enableBackground(target) {
|
|
93
|
+
if (process.platform !== "win32" || process.env.NAVIYRA_SKIP_SERVICE === "1")
|
|
94
|
+
return null;
|
|
95
|
+
const registrar = path.join(target, "scripts", "windows-boot.mjs");
|
|
96
|
+
if (!(await exists(registrar))) return null;
|
|
97
|
+
try {
|
|
98
|
+
const boot = await import(pathToFileURL(registrar).href);
|
|
99
|
+
const installed = await boot.installBootTask({ root: target });
|
|
100
|
+
if (!installed.ok) {
|
|
101
|
+
console.error(
|
|
102
|
+
`Could not register the Windows startup task. ${installed.message}`,
|
|
103
|
+
);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
console.log(installed.message);
|
|
107
|
+
const started = await boot.startBootTask();
|
|
108
|
+
if (!started.ok) {
|
|
109
|
+
if (started.message) console.error(started.message);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return boot;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error(
|
|
115
|
+
`Could not register the Windows startup task. ${error.message}`,
|
|
116
|
+
);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const cwd = process.cwd();
|
|
122
|
+
try {
|
|
123
|
+
const args = process.argv.slice(2);
|
|
124
|
+
for (const arg of args)
|
|
125
|
+
if (!["--help", "-h", "--install-only", "--no-browser"].includes(arg))
|
|
126
|
+
throw Error(`Unknown option: ${arg}. Run with --help for usage.`);
|
|
127
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
128
|
+
console.log(`Usage: npx @naviyra/cloud-panel [--install-only] [--no-browser]
|
|
129
|
+
|
|
130
|
+
Installs into the current empty folder, or a naviyra subfolder.
|
|
131
|
+
In a terminal, asks for the administrator email, password, and agent port.
|
|
132
|
+
On Windows, also registers a scheduled task that keeps the panel running
|
|
133
|
+
and restarts it if it stops. The task uses the account that runs the
|
|
134
|
+
installer, starts again at the next sign-in, and starts at boot when the
|
|
135
|
+
installer itself is elevated.
|
|
136
|
+
--install-only Install, register the startup task, and leave it running.
|
|
137
|
+
--no-browser Do not open a browser after the panel is ready.
|
|
138
|
+
|
|
139
|
+
Startup requires PostgreSQL and the private runtime's OS prerequisites.
|
|
140
|
+
See docs/managed-runtime.md in the installed folder.`);
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
let target = cwd;
|
|
144
|
+
if (path.resolve(cwd) !== source && !(await isPanel(cwd))) {
|
|
145
|
+
if (!(await isEmpty(cwd))) {
|
|
146
|
+
target = path.join(cwd, "naviyra");
|
|
147
|
+
if (!(await isPanel(target)) && !(await isEmpty(target)))
|
|
148
|
+
throw Error(
|
|
149
|
+
"This folder is not empty. Run npx @naviyra/cloud-panel in an empty folder.",
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (!(await isPanel(target))) {
|
|
153
|
+
console.log(`Installing Naviyra into ${target}`);
|
|
154
|
+
await copyTree(source, target);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Always let npm validate the complete workspace. A failed install may have
|
|
158
|
+
// created pg already while leaving Next.js or the agent dependencies missing.
|
|
159
|
+
console.log("Checking and installing dependencies…");
|
|
160
|
+
await run("npm", ["install", "--omit=dev"], target);
|
|
161
|
+
await run(process.execPath, ["scripts/setup-local.mjs"], target);
|
|
162
|
+
const background = await enableBackground(target);
|
|
163
|
+
if (background) {
|
|
164
|
+
console.log(
|
|
165
|
+
"Naviyra will keep running in the background. Stop it with: node scripts/windows-boot.mjs stop",
|
|
166
|
+
);
|
|
167
|
+
if (!args.includes("--install-only") && !args.includes("--no-browser"))
|
|
168
|
+
await background.openPanelWhenReady(target);
|
|
169
|
+
} else if (args.includes("--install-only")) {
|
|
170
|
+
console.log(`Naviyra installed in ${target}. Run npm run services:start there to start it.`);
|
|
171
|
+
} else {
|
|
172
|
+
start(target, args.filter((arg) => arg === "--no-browser"));
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error(error.message);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
package/db/001_init.sql
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
2
|
+
id uuid PRIMARY KEY, email text NOT NULL UNIQUE, password_hash text NOT NULL,
|
|
3
|
+
role text NOT NULL DEFAULT 'user' CHECK (role IN ('admin','user')), created_at timestamptz NOT NULL DEFAULT now()
|
|
4
|
+
);
|
|
5
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
6
|
+
id uuid PRIMARY KEY, name text NOT NULL UNIQUE, token_hash text NOT NULL, last_seen timestamptz
|
|
7
|
+
);
|
|
8
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
9
|
+
id uuid PRIMARY KEY, user_id uuid NOT NULL REFERENCES users(id), agent_id uuid NOT NULL REFERENCES agents(id),
|
|
10
|
+
name text NOT NULL UNIQUE, type text NOT NULL DEFAULT 'nodejs' CHECK(type='nodejs'), subdomain text NOT NULL UNIQUE,
|
|
11
|
+
port integer UNIQUE, status text NOT NULL DEFAULT 'created', folder_path text,
|
|
12
|
+
source_type text NOT NULL CHECK(source_type IN ('zip','git')), git_url text NOT NULL DEFAULT '',
|
|
13
|
+
created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now()
|
|
14
|
+
);
|
|
15
|
+
CREATE TABLE IF NOT EXISTS tunnels (
|
|
16
|
+
project_id uuid PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
|
|
17
|
+
tunnel_id text NOT NULL, tunnel_name text NOT NULL, credentials_path text, config_path text
|
|
18
|
+
);
|
|
19
|
+
CREATE TABLE IF NOT EXISTS deployments (
|
|
20
|
+
id uuid PRIMARY KEY, project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
21
|
+
status text NOT NULL, source_type text NOT NULL, logs_ref text, error text,
|
|
22
|
+
created_at timestamptz NOT NULL DEFAULT now(), finished_at timestamptz
|
|
23
|
+
);
|
|
24
|
+
CREATE UNIQUE INDEX IF NOT EXISTS one_active_deployment ON deployments(project_id) WHERE status IN ('queued','deploying');
|
|
25
|
+
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
26
|
+
id bigserial PRIMARY KEY, user_id uuid REFERENCES users(id), project_id uuid,
|
|
27
|
+
action text NOT NULL, created_at timestamptz NOT NULL DEFAULT now()
|
|
28
|
+
);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
ALTER TABLE projects DROP CONSTRAINT IF EXISTS projects_type_check;
|
|
2
|
+
ALTER TABLE projects ADD CONSTRAINT projects_type_check CHECK(type IN ('nodejs','python','php','go','static','custom'));
|
|
3
|
+
ALTER TABLE projects ADD COLUMN IF NOT EXISTS runtime_config jsonb NOT NULL DEFAULT '{}';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
ALTER TABLE app_databases ADD COLUMN IF NOT EXISTS engine text NOT NULL DEFAULT 'postgresql';
|
|
2
|
+
ALTER TABLE app_databases ADD COLUMN IF NOT EXISTS runtime_id text;
|
|
3
|
+
ALTER TABLE app_databases ADD COLUMN IF NOT EXISTS host text;
|
|
4
|
+
ALTER TABLE app_databases ADD COLUMN IF NOT EXISTS port integer;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Naviyra Host Agent
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
User=root
|
|
9
|
+
WorkingDirectory=/opt/naviyra-panel
|
|
10
|
+
EnvironmentFile=/opt/naviyra-panel/.env
|
|
11
|
+
ExecStart=/usr/bin/node /opt/naviyra-panel/apps/agent/src/server.mjs
|
|
12
|
+
Restart=on-failure
|
|
13
|
+
RestartSec=5
|
|
14
|
+
UMask=0077
|
|
15
|
+
|
|
16
|
+
[Install]
|
|
17
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:17-alpine
|
|
4
|
+
environment:
|
|
5
|
+
POSTGRES_USER: naviyra
|
|
6
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-naviyra}
|
|
7
|
+
POSTGRES_DB: naviyra
|
|
8
|
+
ports:
|
|
9
|
+
- "127.0.0.1:${POSTGRES_PORT:-55432}:5432"
|
|
10
|
+
volumes:
|
|
11
|
+
- postgres_data:/var/lib/postgresql/data
|
|
12
|
+
healthcheck:
|
|
13
|
+
test: ["CMD-SHELL", "pg_isready -U naviyra"]
|
|
14
|
+
interval: 5s
|
|
15
|
+
timeout: 5s
|
|
16
|
+
retries: 10
|
|
17
|
+
volumes:
|
|
18
|
+
postgres_data:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Naviyra-managed Docker
|
|
2
|
+
|
|
3
|
+
Naviyra includes its own Docker runtime manager. Users do not install Docker Desktop or Docker Engine separately. Choosing PostgreSQL, MySQL, or MariaDB automatically prepares the private runtime, downloads the chosen database image when missing, and starts a database with credentials and a loopback port.
|
|
4
|
+
|
|
5
|
+
## Setup and lifecycle
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install
|
|
9
|
+
npm run runtime:setup
|
|
10
|
+
npm run db:migrate
|
|
11
|
+
npm run runtime:status
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`runtime:setup` can be run before opening the panel to complete downloads without a browser or reverse-proxy timeout. Database creation also invokes setup automatically. `runtime:start` starts the existing engine or installs it if missing. `runtime:stop` stops the engine and its databases while retaining their data. The runtime survives panel process exit. Database operations also start a stopped engine on demand. On Windows, `npx @naviyra/cloud-panel` registers a scheduled task for the installing account. That task starts the panel at sign-in, including after a reboot, and the panel starts this runtime. An elevated install can also start it at boot, before sign-in. On Linux, include `runtime:start` in the host's startup supervisor.
|
|
15
|
+
|
|
16
|
+
The default runtime directory is `<repository>/.runtime/docker`, independent of the current working directory. `NAVIYRA_RUNTIME_ROOT` can select a different absolute directory; changing it selects a different runtime, so keep it stable. The archive cache is `<repository>/.runtime/runtime-downloads`. Do not delete the runtime directory: it contains database data (or the WSL virtual disk).
|
|
17
|
+
|
|
18
|
+
## Windows x64 / ARM64
|
|
19
|
+
|
|
20
|
+
Windows must support WSL2 and have hardware virtualization enabled. When WSL2 is unavailable, Naviyra reports this command:
|
|
21
|
+
|
|
22
|
+
```powershell
|
|
23
|
+
wsl --install --no-distribution
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Run it as Administrator and restart Windows if requested. Naviyra does not force a restart or enable Windows features from a web request. These are OS prerequisites, not a separate Docker installation.
|
|
27
|
+
|
|
28
|
+
Naviyra imports a small Alpine root filesystem into a dedicated `Naviyra-<installation-id>` WSL2 distribution, installs its OS dependencies, copies the verified Docker binaries, and starts a private daemon there. Other WSL distributions and Docker Desktop are not used or modified. Keep setup and the panel under the same Windows account, because WSL registrations are per-user. WSL's localhost forwarding must be enabled for native Windows applications to reach database ports. The private engine log is `/opt/naviyra/daemon.log` inside that distribution.
|
|
29
|
+
|
|
30
|
+
## Linux x64 / ARM64
|
|
31
|
+
|
|
32
|
+
An ordinary service account uses Naviyra's downloaded rootless Docker binaries. The OS must provide `tar`, `sh`, `nohup`, `newuidmap` and `newgidmap` (usually the `uidmap` package), subordinate UID/GID ranges in `/etc/subuid` and `/etc/subgid`, and support unprivileged user namespaces. Distribution security policies may require rootlesskit configuration. Set up those prerequisites for the panel service account; Naviyra reports missing prerequisites rather than installing system packages with an assumed sudo password. See [Docker rootless prerequisites](https://docs.docker.com/engine/security/rootless/).
|
|
33
|
+
|
|
34
|
+
When deliberately run as root, setup uses a private rootful daemon, requiring the standard Docker kernel/network prerequisites including iptables. Use the same account for runtime setup and panel execution. Prefer an unprivileged service account with rootless support. The daemon log is `<runtime-root>/daemon.log`.
|
|
35
|
+
|
|
36
|
+
## Isolation, upgrades, and recovery
|
|
37
|
+
|
|
38
|
+
The client always targets the private Unix socket, checks the installation's daemon label, and ignores external Docker environment settings and contexts. Database containers use a dedicated bridge network, private loopback port bindings, and persistent volumes. Operations never fall back to a system Docker executable or the Docker Desktop socket.
|
|
39
|
+
|
|
40
|
+
Docker 29.8.2 and Alpine 3.23.6 archives are pinned in `packages/runtime/artifacts.json`. Downloads are bounded, checksum-verified, and atomically cached. Runtime updates require an explicit Naviyra release; OS package updates do not update these binaries. The maintainer utility `scripts/runtime-download-manifest.mjs` resolves release hashes from official HTTPS distribution endpoints. Review updated versions and hashes before shipping them. [Docker documents the maintenance requirements of static binaries](https://docs.docker.com/engine/install/binaries/).
|
|
41
|
+
|
|
42
|
+
Setup is serialized across panel workers and the CLI. A failed setup can be retried; the installed marker is written only after extraction and configuration finish. Do not change runtime directories to repair an existing database. If a panel process is killed during database creation, inspect the private engine for a `naviyra-db-<id>` container before retrying. Container deletion also deletes its database volume; runtime stop preserves it.
|
|
43
|
+
|
|
44
|
+
The panel's own metadata database still uses `DATABASE_URL` and must be initialized before signing in. Existing native PostgreSQL databases and the native PostgreSQL creation option are retained.
|
|
45
|
+
|
|
46
|
+
## Verification
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
node --test tests/database-runtime.test.mjs tests/managed-runtime.test.mjs
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Set `NAVIYRA_RUNTIME_INTEGRATION=1` and run `node --test tests/managed-databases.integration.test.mjs` to download images and test real database creation, host port access, authentication, password reset, and deletion. This test creates and deletes only its own randomly named databases.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Native Windows and Linux hosting
|
|
2
|
+
|
|
3
|
+
Application deployment does not require Docker. Node.js runs the panel and agent; PostgreSQL can be a native installation or a remote PostgreSQL service. The existing Docker Compose file is only an optional local database shortcut.
|
|
4
|
+
|
|
5
|
+
For a dedicated local database using an installed PostgreSQL distribution, set `POSTGRES_BIN` to its bin directory if needed and run `npm run db:local`. On Windows the default is `C:/Program Files/PostgreSQL/18/bin`. This creates a separate cluster under `.runtime/postgres`, bound only to loopback using the port and credentials in `DATABASE_URL`; it does not import or modify other PostgreSQL installations or Docker volumes. Stop it with `npm run db:local:stop`. On Linux, put PostgreSQL tools on PATH or set `POSTGRES_BIN`, and run as an ordinary user (PostgreSQL refuses root).
|
|
6
|
+
|
|
7
|
+
Set `AGENT_MODE=native` in `.env`, run `npm run db:migrate`, then start the agent (`npm run agent`) and panel (`npm run dev`). Set `NATIVE_TUNNELS=false` for real local applications without Cloudflare credentials. Project details show the assigned loopback URL. A loopback link opens on the computer using the browser; use it on the agent host. To publish domains, set `NATIVE_TUNNELS=true`, install cloudflared, run `cloudflared tunnel login` as the account running the agent, and configure `CLOUDFLARE_CERT`. Use the full hostname from a Cloudflare-managed zone. DNS deletion also requires `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ZONE_ID`.
|
|
8
|
+
|
|
9
|
+
## Host prerequisites
|
|
10
|
+
|
|
11
|
+
| Selection | Required on the host | Source requirements |
|
|
12
|
+
| ----------------- | ---------------------------------- | ---------------------------------------------------------------------- |
|
|
13
|
+
| Node.js | Node.js + npm | `package.json` with `start`; optional `build` |
|
|
14
|
+
| Python | Python 3 with venv/pip | Optional `requirements.txt`; ASGI or WSGI `module:object` |
|
|
15
|
+
| PHP | PHP CLI, PHP-CGI, Caddy | Web root containing `index.php`; Composer PHAR if composer.json exists |
|
|
16
|
+
| Go | Go toolchain | Buildable main package at repository root |
|
|
17
|
+
| Static / frontend | Node.js; npm if building | `index.html` in selected public directory, e.g. `dist` |
|
|
18
|
+
| Other | Owner-configured native executable | Prebuilt application; executable alias and JSON argument array |
|
|
19
|
+
|
|
20
|
+
Executables are discovered through PATH; override their paths with the variables in `.env.example`. Restart the agent after installing a runtime or changing PATH. No runtimes are silently installed system-wide. Python dependencies are installed in a project-specific virtual environment; the agent adds Uvicorn for ASGI or Waitress for WSGI. Go is built to a project-specific executable. PHP runs through Caddy and PHP-CGI, not PHP's development web server. PHP extensions, application databases, framework secrets, migrations, and writable storage configuration remain host-owner responsibilities.
|
|
21
|
+
|
|
22
|
+
All apps must bind to `127.0.0.1` and use the assigned `PORT`. Python and PHP bindings are generated automatically. Custom argument values may include `{PORT}`, which is replaced with the allocated port. Commands are invoked with argument arrays, without shell interpretation. Set an executable alias map on the host, for example:
|
|
23
|
+
|
|
24
|
+
```dotenv
|
|
25
|
+
NATIVE_EXECUTABLES={"dotnet":"C:/Program Files/dotnet/dotnet.exe","java":"C:/Java/bin/java.exe"}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A prebuilt .NET app can use alias `dotnet` and arguments `["MyApp.dll", "--urls", "http://127.0.0.1:{PORT}"]`. Build these custom applications before upload; arbitrary custom build commands are not supported.
|
|
29
|
+
|
|
30
|
+
## Windows
|
|
31
|
+
|
|
32
|
+
Use Node.js and native PostgreSQL (or a remote PostgreSQL URL). Install the runtimes you want to deploy and cloudflared if publishing. For PHP, add both PHP-CGI and Caddy. Run the commands from PowerShell at the repository root. Paths with spaces are supported in native mode. Use an ordinary dedicated account with access to the application directory.
|
|
33
|
+
|
|
34
|
+
`npx @naviyra/cloud-panel` registers one startup task that runs the panel and the agent together, under the account that performed the install, and restarts them if they stop. Keep that account: the private runtime and Cloudflare certificate are per-user. Remove the task with `node scripts/windows-boot.mjs remove` from the installed folder. Running the panel under another account changes PATH and the Cloudflare certificate location; configure absolute paths.
|
|
35
|
+
|
|
36
|
+
## Linux
|
|
37
|
+
|
|
38
|
+
Native mode uses the same process supervisor as Windows and does not require privileged per-project systemd operations. Run the agent as a dedicated unprivileged user with access to its project directory and installed tools. To start it on boot, adapt `deploy/naviyra-agent.service`: change `User=root` to your dedicated user, set `AGENT_MODE=native`, and use directories owned by that user. The existing `host` backend remains a Linux-only legacy Node.js/systemd deployment option.
|
|
39
|
+
|
|
40
|
+
## Lifecycle and limits
|
|
41
|
+
|
|
42
|
+
The agent supervises application processes and tunnels, captures bounded logs, and retries unexpected exits three times. IPC watchdogs stop child process trees when the agent disconnects, including on Windows. On a normal agent restart, previously running native projects restart from their saved launch specifications. Interrupted builds are marked failed; redeploy to retry. Restarting/redeploying is not zero-downtime. Only one agent process may manage a given state directory.
|
|
43
|
+
|
|
44
|
+
Native applications execute as the agent account and are intended for trusted source code. Native mode is not tenant isolation. Child environments exclude panel, database, and Cloudflare secrets, but applications running as the same OS user can access files that user owns. Native per-process CPU/RAM counters are not yet collected; the UI shows unavailable instead of invented values. Host memory/load remain available. Static serving does not provide SPA fallback routing; build separate HTML routes or use a Node.js server for history fallback.
|
|
45
|
+
|
|
46
|
+
Examples are included under `examples/hello-node`, `hello-python`, `hello-php`, `hello-go`, and `hello-static`. ZIP each example's contents, choose its runtime, create, and deploy. Python uses `app:app` with ASGI; PHP/static use `.` as the public directory.
|
|
47
|
+
|
|
48
|
+
References: [Uvicorn settings](https://www.uvicorn.org/settings/), [Waitress runner](https://docs.pylonsproject.org/projects/waitress/en/latest/runner.html), [Caddy PHP hosting](https://caddyserver.com/docs/caddyfile/directives/php_fastcgi).
|
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naviyra/cloud-panel",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Self-hosted Naviyra panel for projects, databases, DNS, and email routing.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cloud-panel": "./bin/naviyra.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"apps",
|
|
12
|
+
"!apps/**/.next/**",
|
|
13
|
+
"!apps/**/.runtime/**",
|
|
14
|
+
"packages",
|
|
15
|
+
"scripts",
|
|
16
|
+
"db",
|
|
17
|
+
"docs",
|
|
18
|
+
"deploy",
|
|
19
|
+
".env.example",
|
|
20
|
+
"docker-compose.yml",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"workspaces": [
|
|
27
|
+
"apps/*",
|
|
28
|
+
"packages/*"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"services:start": "node scripts/start-naviyra.mjs",
|
|
32
|
+
"service:install": "node scripts/windows-boot.mjs install",
|
|
33
|
+
"service:stop": "node scripts/windows-boot.mjs stop",
|
|
34
|
+
"service:remove": "node scripts/windows-boot.mjs remove",
|
|
35
|
+
"runtime:setup": "node --env-file-if-exists=.env scripts/runtime.mjs setup",
|
|
36
|
+
"runtime:start": "node --env-file-if-exists=.env scripts/runtime.mjs start",
|
|
37
|
+
"runtime:status": "node --env-file-if-exists=.env scripts/runtime.mjs status",
|
|
38
|
+
"runtime:stop": "node --env-file-if-exists=.env scripts/runtime.mjs stop",
|
|
39
|
+
"db:local": "node --env-file-if-exists=.env scripts/postgres-local.mjs",
|
|
40
|
+
"db:local:stop": "node --env-file-if-exists=.env scripts/postgres-local.mjs --stop",
|
|
41
|
+
"setup:local": "node scripts/setup-local.mjs",
|
|
42
|
+
"dev": "npm run dev -w @naviyra/panel",
|
|
43
|
+
"agent": "npm run start -w @naviyra/agent",
|
|
44
|
+
"build": "npm run build -w @naviyra/panel",
|
|
45
|
+
"start": "npm run start -w @naviyra/panel",
|
|
46
|
+
"db:migrate": "node --env-file-if-exists=.env scripts/migrate.mjs",
|
|
47
|
+
"db:seed": "node --env-file-if-exists=.env scripts/seed.mjs",
|
|
48
|
+
"test": "node --test tests/*.test.mjs",
|
|
49
|
+
"test:e2e": "playwright test"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"bcryptjs": "^3.0.2",
|
|
53
|
+
"dotenv": "^17.2.3",
|
|
54
|
+
"pg": "^8.16.3"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=22.9.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@playwright/test": "^1.63.0",
|
|
61
|
+
"prettier": "^3.9.9"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"docker-x86_64": {
|
|
3
|
+
"file": "docker-29.8.2-x86_64.tgz",
|
|
4
|
+
"url": "https://download.docker.com/linux/static/stable/x86_64/docker-29.8.2.tgz",
|
|
5
|
+
"sha256": "995d1ef289677f74fd58d8d2c35727b6a4ee389c69db8638a3e42d0487aa5b0f"
|
|
6
|
+
},
|
|
7
|
+
"docker-rootless-extras-x86_64": {
|
|
8
|
+
"file": "docker-rootless-extras-29.8.2-x86_64.tgz",
|
|
9
|
+
"url": "https://download.docker.com/linux/static/stable/x86_64/docker-rootless-extras-29.8.2.tgz",
|
|
10
|
+
"sha256": "707ebf6a5afd88104086e7b6749997b2366e816aeaf2c3ef2305b08fde9ee007"
|
|
11
|
+
},
|
|
12
|
+
"alpine-x86_64": {
|
|
13
|
+
"file": "alpine-minirootfs-3.23.6-x86_64.tar.gz",
|
|
14
|
+
"url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/releases/x86_64/alpine-minirootfs-3.23.6-x86_64.tar.gz",
|
|
15
|
+
"sha256": "6fc0e3639a1c01f156970d7626aab90bc90697117069dbc39ad880b84efa319a"
|
|
16
|
+
},
|
|
17
|
+
"docker-aarch64": {
|
|
18
|
+
"file": "docker-29.8.2-aarch64.tgz",
|
|
19
|
+
"url": "https://download.docker.com/linux/static/stable/aarch64/docker-29.8.2.tgz",
|
|
20
|
+
"sha256": "76a624e4a8e5da654d1150e808175125efb5a6f1b6aa1cbd9caee18f51047a50"
|
|
21
|
+
},
|
|
22
|
+
"docker-rootless-extras-aarch64": {
|
|
23
|
+
"file": "docker-rootless-extras-29.8.2-aarch64.tgz",
|
|
24
|
+
"url": "https://download.docker.com/linux/static/stable/aarch64/docker-rootless-extras-29.8.2.tgz",
|
|
25
|
+
"sha256": "f8f759dfeecb5bbe2c963232a1b9380e133f677dc0579263e4ca04c69f40aa47"
|
|
26
|
+
},
|
|
27
|
+
"alpine-aarch64": {
|
|
28
|
+
"file": "alpine-minirootfs-3.23.6-aarch64.tar.gz",
|
|
29
|
+
"url": "https://dl-cdn.alpinelinux.org/alpine/v3.23/releases/aarch64/alpine-minirootfs-3.23.6-aarch64.tar.gz",
|
|
30
|
+
"sha256": "b17a57958e29735ff0e6e64254d958e65903687a70ca30cc430b33a965ad49d7"
|
|
31
|
+
}
|
|
32
|
+
}
|