@or3/cloud 0.1.37 → 0.1.49
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/LICENSE +17 -0
- package/README.md +104 -2
- package/assets/Caddyfile +4 -1
- package/assets/compose.operator.yaml +61 -0
- package/assets/compose.public.yaml +6 -0
- package/assets/compose.yaml +4 -0
- package/assets/dashboard-operator.mjs +874 -0
- package/dist/cli.mjs +2178 -331
- package/package.json +8 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2025 @Saluana
|
|
5
|
+
|
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU General Public License
|
|
17
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
package/README.md
CHANGED
|
@@ -16,10 +16,15 @@ saves both values in the mode-`0600`
|
|
|
16
16
|
`or3-cloud/.or3-initial-credentials` file. It does not print the password.
|
|
17
17
|
Save the file in a password manager, then delete it. Open
|
|
18
18
|
[http://127.0.0.1:3000](http://127.0.0.1:3000) and sign in.
|
|
19
|
+
During initialization the CLI proves both account logins, persists their bcrypt
|
|
20
|
+
hashes in the managed data volume, removes the plaintext provisioning passwords
|
|
21
|
+
from `.env`, and recreates the container without them. The `0600` handoff file
|
|
22
|
+
is the only remaining plaintext copy managed by the CLI.
|
|
19
23
|
|
|
20
24
|
The published Cloud image supports `linux/amd64` and `linux/arm64` and uses
|
|
21
|
-
Basic Auth + SQLite + filesystem storage. The CLI checks the image manifest
|
|
22
|
-
|
|
25
|
+
Basic Auth + SQLite + filesystem storage. The CLI checks the image manifest,
|
|
26
|
+
source/version labels, and then writes a digest-qualified image reference; it
|
|
27
|
+
also verifies the image of the running container before committing state.
|
|
23
28
|
|
|
24
29
|
## Run it publicly on a VPS
|
|
25
30
|
|
|
@@ -41,6 +46,7 @@ Run these from the deployment directory created by `init`:
|
|
|
41
46
|
|
|
42
47
|
```sh
|
|
43
48
|
npx @or3/cloud doctor
|
|
49
|
+
npx @or3/cloud verify
|
|
44
50
|
npx @or3/cloud backup
|
|
45
51
|
npx @or3/cloud update
|
|
46
52
|
npx @or3/cloud rollback --yes
|
|
@@ -52,16 +58,112 @@ npx @or3/cloud stop
|
|
|
52
58
|
npx @or3/cloud restart
|
|
53
59
|
```
|
|
54
60
|
|
|
61
|
+
Run `verify` after every update and before declaring a deployment healthy. It
|
|
62
|
+
checks the managed image digest, deep provider health, Basic Auth sign-in,
|
|
63
|
+
session hydration, SQLite sync, a disposable filesystem upload/download/delete
|
|
64
|
+
cycle, both SQLite databases, volume ownership, proxy runtime settings, and a
|
|
65
|
+
bounded window of serious container logs. On a public VPS, require the real
|
|
66
|
+
HTTPS path (with no redirects) using `npx @or3/cloud verify --public`.
|
|
67
|
+
After changing the owner password in the app, pass the current credential only
|
|
68
|
+
through an owner-only file:
|
|
69
|
+
`npx @or3/cloud verify --public --verification-email owner@example.com --verification-password-file /secure/current-owner-password`.
|
|
70
|
+
Until the one-time handoff file is deleted, `verify` may read its initial owner
|
|
71
|
+
credential from that protected file; it is never restored to `.env` or Docker
|
|
72
|
+
container metadata.
|
|
73
|
+
Verification rejects cross-origin filesystem grants and unexpected methods or
|
|
74
|
+
headers, validates the storage ID before uploading, deletes its probe, and
|
|
75
|
+
revokes its temporary session even when a later check fails.
|
|
76
|
+
|
|
55
77
|
Do not run `docker compose down --volumes` on a normal deployment: it deletes
|
|
56
78
|
the application data. Use `backup` before an update or any destructive action.
|
|
57
79
|
When an adopted legacy deployment used a different container UID, `update`
|
|
58
80
|
rebuilds the data from its checksummed pre-update backup as the hardened
|
|
59
81
|
runtime user. It does not recursively rewrite file ownership in place, and a
|
|
60
82
|
failed migration restores the recorded legacy root owner and snapshot.
|
|
83
|
+
Updates also replace the generated Compose/Caddy files from the target CLI.
|
|
84
|
+
Those files are checksummed into the pre-update backup and restored on a failed
|
|
85
|
+
update, rollback, restore, or interrupted-operation recovery. Backups also
|
|
86
|
+
carry a deployment-local authentication tag, so an altered or foreign archive
|
|
87
|
+
cannot supply configuration or executable Compose assets. Every mutating
|
|
88
|
+
command holds one deployment-wide lease; do not delete `.or3-cloud` lock or
|
|
89
|
+
recovery files by hand.
|
|
90
|
+
Backup artifacts are allocated in that journal before archiving, fully
|
|
91
|
+
revalidated before success is reported, and safely removed by `recover` if a
|
|
92
|
+
hard interruption leaves an incomplete artifact. A standalone backup and a
|
|
93
|
+
failed adoption preserve whether the original app was intentionally stopped.
|
|
94
|
+
Initialization and adoption also fail closed unless Docker explicitly confirms
|
|
95
|
+
that the target volumes, project containers, and networks do not exist.
|
|
96
|
+
Docker architecture checks query the selected daemon, remote daemons skip
|
|
97
|
+
misleading client-host port probes, every Docker command has a deadline, and
|
|
98
|
+
streaming archive children are terminated as a process group on timeout.
|
|
99
|
+
Lifecycle commits fsync files and parent directories before/after rename.
|
|
100
|
+
Restore refuses pre-authenticated-asset backups instead of mixing an old image
|
|
101
|
+
with current Compose/Caddy files, and failed asset rollback retains any
|
|
102
|
+
recovery copies it could not reinstall.
|
|
103
|
+
Use owner/admin password files for noninteractive credential rotation. TTY
|
|
104
|
+
prompts suppress input, Docker receives only environment variable names in its
|
|
105
|
+
argument vector, and the admin credential file is committed with
|
|
106
|
+
write/fsync/rename so journal replay cannot encounter a truncated JSON file.
|
|
107
|
+
A successful rotation removes the obsolete `.or3-initial-credentials` copy.
|
|
108
|
+
It also removes the plaintext provisioning passwords from `.env` and the
|
|
109
|
+
recreated container; older deployments migrate to this layout on reset.
|
|
110
|
+
|
|
111
|
+
The authentication key is intentionally not embedded in an exported archive.
|
|
112
|
+
Escrow an owner-only copy of `.or3-cloud/backup-auth.key` separately in an
|
|
113
|
+
encrypted secret store; put that key back in the recovered deployment before
|
|
114
|
+
restoring an off-host archive.
|
|
115
|
+
When selecting an exact version, run the matching CLI package, for example
|
|
116
|
+
`npx --yes @or3/cloud@0.1.39 update --to 0.1.39`; the CLI refuses mismatched
|
|
117
|
+
package and image versions so their generated assets cannot drift.
|
|
61
118
|
For a destructive purge, export a fresh backup to a new directory on another
|
|
62
119
|
filesystem first; `remove --purge-data --yes` verifies that export before it
|
|
63
120
|
will delete anything.
|
|
64
121
|
|
|
122
|
+
## Dashboard updates
|
|
123
|
+
|
|
124
|
+
New managed Linux deployments expose a **Dashboard Update** card only when a
|
|
125
|
+
local Linux Docker socket passes the CLI's disposable, exact-mount bridge
|
|
126
|
+
probe. Click **Check for updates**, then approve the exact latest release. The
|
|
127
|
+
card uses the same managed updater as the CLI: it makes a verified backup,
|
|
128
|
+
waits for deep health, and restores the prior release and data if the update
|
|
129
|
+
fails. Hosts that do not pass the probe remain CLI-only.
|
|
130
|
+
|
|
131
|
+
Release-check results, including the last successful latest version and a
|
|
132
|
+
bounded failure or compatibility reason, persist across page reloads and
|
|
133
|
+
operator restarts. Accepted asynchronous starts return HTTP 202; corrupt or
|
|
134
|
+
unavailable operator responses are reported separately from unsupported hosts.
|
|
135
|
+
While an update is active, the card announces state changes and polls every two
|
|
136
|
+
seconds, backing off on connection failures and stopping after 15 minutes or a
|
|
137
|
+
terminal result.
|
|
138
|
+
|
|
139
|
+
Once an earlier protocol-compatible release exists, the tagged release gate
|
|
140
|
+
also upgrades a disposable installation from it through this Unix API, proves
|
|
141
|
+
concurrent starts serialize, checks persisted data and deep health, and rolls
|
|
142
|
+
the deployment back.
|
|
143
|
+
|
|
144
|
+
The application container never receives the Docker socket. A separate,
|
|
145
|
+
socket-only operator sidecar is the only container with Docker access, and it
|
|
146
|
+
accepts only status, release-check, and exact-version update requests. It runs
|
|
147
|
+
in a dedicated digest-pinned operator image—not the web application image.
|
|
148
|
+
Before running privileged release code, it installs with lifecycle scripts
|
|
149
|
+
disabled, verifies the npm registry signature and the exact SLSA bundle, and
|
|
150
|
+
requires that the signed package came from this repository's tagged
|
|
151
|
+
`release-cloud.yml` workflow. That authenticated package pins both qualified
|
|
152
|
+
GHCR image digests and the exact source revision carried by both images, so
|
|
153
|
+
moving or replacing a version tag—or mixing artifacts from different
|
|
154
|
+
commits—is rejected before an image can run. A stale dashboard-owned update is
|
|
155
|
+
recovered automatically with its exact target CLI; unrelated/manual operations
|
|
156
|
+
remain locked for host-side `recover`. Existing deployments gain the card after
|
|
157
|
+
one normal exact-version CLI update. Remote Docker hosts remain CLI-only.
|
|
158
|
+
|
|
159
|
+
The operator socket is deployment-group-only, mutation requests are bounded,
|
|
160
|
+
and accepted/rejected operations are recorded without secrets in
|
|
161
|
+
`.or3-cloud/dashboard-update-audit.jsonl`. These are operational controls, not
|
|
162
|
+
a host sandbox: the dedicated operator has read-write Docker API access and is
|
|
163
|
+
therefore a host-root trust component. Application-process code execution is
|
|
164
|
+
inside the dashboard-update trust boundary; disable the operator overlay and
|
|
165
|
+
use host CLI updates when that tradeoff is unacceptable.
|
|
166
|
+
|
|
65
167
|
Managed registration is invite-only: the bootstrap owner signs in first and
|
|
66
168
|
invites additional users from the in-product admin flow. Guest access and
|
|
67
169
|
anonymous registration stay disabled. Uploading/installing custom plugins or
|
package/assets/Caddyfile
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
# site handler runs, so Strict-Transport-Security is only ever emitted on
|
|
4
4
|
# HTTPS responses (it is meaningless on plain HTTP).
|
|
5
5
|
header {
|
|
6
|
-
Strict-Transport-Security "max-age=31536000
|
|
6
|
+
Strict-Transport-Security "max-age=31536000"
|
|
7
7
|
X-Content-Type-Options "nosniff"
|
|
8
8
|
X-Frame-Options "DENY"
|
|
9
9
|
Referrer-Policy "strict-origin-when-cross-origin"
|
|
10
10
|
Permissions-Policy "camera=(), microphone=(), geolocation=()"
|
|
11
|
+
# Nuxt needs same-origin scripts plus inline hydration styles/scripts; the
|
|
12
|
+
# fixed Cloud profile has no browser-side third-party providers.
|
|
13
|
+
Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; media-src 'self' blob:; worker-src 'self' blob:; frame-src 'none'; upgrade-insecure-requests"
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
encode zstd gzip
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# This overlay is included only after the CLI proves the local Docker socket
|
|
2
|
+
# can be mounted and used by the recorded deployment owner. Keeping it out of
|
|
3
|
+
# compose.yaml is intentional: Compose interpolates variables in inactive
|
|
4
|
+
# profiles too, which would otherwise break CLI-only installs.
|
|
5
|
+
services:
|
|
6
|
+
or3:
|
|
7
|
+
# The app needs only group write access to the operator socket; it never
|
|
8
|
+
# receives the Docker socket or the deployment bind mount.
|
|
9
|
+
group_add:
|
|
10
|
+
- "${OR3_OPERATOR_GID}"
|
|
11
|
+
volumes:
|
|
12
|
+
# The web service can connect only to the narrow Unix-socket API. It does
|
|
13
|
+
# not receive the Docker socket or managed deployment directory.
|
|
14
|
+
- type: bind
|
|
15
|
+
source: ./.or3-cloud/operator-ipc
|
|
16
|
+
target: /run/or3-operator
|
|
17
|
+
read_only: true
|
|
18
|
+
bind:
|
|
19
|
+
create_host_path: false
|
|
20
|
+
|
|
21
|
+
or3-operator:
|
|
22
|
+
# SECURITY TRUST BOUNDARY: read-write Docker API access is
|
|
23
|
+
# host-root-equivalent. The controls below reduce accidental surface; they
|
|
24
|
+
# do not sandbox a compromised operator from the host Docker daemon.
|
|
25
|
+
image: ${OR3_OPERATOR_IMAGE}
|
|
26
|
+
labels:
|
|
27
|
+
io.or3.cloud.deployment-id: ${OR3_DEPLOYMENT_ID}
|
|
28
|
+
entrypoint: ["/usr/local/bin/node", "/operator/dashboard-operator.mjs"]
|
|
29
|
+
working_dir: ${OR3_DEPLOYMENT_DIR}
|
|
30
|
+
environment:
|
|
31
|
+
DOCKER_HOST: unix:///var/run/docker.sock
|
|
32
|
+
OR3_DASHBOARD_OPERATOR_SOCKET: /run/or3-operator/operator.sock
|
|
33
|
+
user: "${OR3_OPERATOR_UID}:${OR3_OPERATOR_GID}"
|
|
34
|
+
group_add:
|
|
35
|
+
- "${OR3_DOCKER_GID}"
|
|
36
|
+
volumes:
|
|
37
|
+
- type: bind
|
|
38
|
+
source: ${OR3_DEPLOYMENT_DIR}
|
|
39
|
+
target: ${OR3_DEPLOYMENT_DIR}
|
|
40
|
+
- type: bind
|
|
41
|
+
source: ./dashboard-operator.mjs
|
|
42
|
+
target: /operator/dashboard-operator.mjs
|
|
43
|
+
read_only: true
|
|
44
|
+
bind:
|
|
45
|
+
create_host_path: false
|
|
46
|
+
- type: bind
|
|
47
|
+
source: ${OR3_DOCKER_SOCKET}
|
|
48
|
+
target: /var/run/docker.sock
|
|
49
|
+
- type: bind
|
|
50
|
+
source: ./.or3-cloud/operator-ipc
|
|
51
|
+
target: /run/or3-operator
|
|
52
|
+
bind:
|
|
53
|
+
create_host_path: false
|
|
54
|
+
restart: on-failure
|
|
55
|
+
security_opt:
|
|
56
|
+
- no-new-privileges:true
|
|
57
|
+
cap_drop:
|
|
58
|
+
- ALL
|
|
59
|
+
read_only: true
|
|
60
|
+
tmpfs:
|
|
61
|
+
- /tmp:size=128m
|
|
@@ -3,6 +3,8 @@ services:
|
|
|
3
3
|
# Pinned to the exact patch release and image digest so a floating tag
|
|
4
4
|
# cannot silently change proxy behavior or the base image.
|
|
5
5
|
image: caddy:2.10.2-alpine@sha256:4c6e91c6ed0e2fa03efd5b44747b625fec79bc9cd06ac5235a779726618e530d
|
|
6
|
+
labels:
|
|
7
|
+
io.or3.cloud.deployment-id: ${OR3_DEPLOYMENT_ID}
|
|
6
8
|
depends_on:
|
|
7
9
|
or3:
|
|
8
10
|
# This gates Caddy's initial startup only. A later unhealthy OR3
|
|
@@ -44,5 +46,9 @@ services:
|
|
|
44
46
|
volumes:
|
|
45
47
|
caddy-data:
|
|
46
48
|
name: ${OR3_CADDY_DATA_VOLUME}
|
|
49
|
+
labels:
|
|
50
|
+
io.or3.cloud.deployment-id: ${OR3_DEPLOYMENT_ID}
|
|
47
51
|
caddy-config:
|
|
48
52
|
name: ${OR3_CADDY_CONFIG_VOLUME}
|
|
53
|
+
labels:
|
|
54
|
+
io.or3.cloud.deployment-id: ${OR3_DEPLOYMENT_ID}
|
package/assets/compose.yaml
CHANGED
|
@@ -3,6 +3,8 @@ name: ${OR3_COMPOSE_PROJECT}
|
|
|
3
3
|
services:
|
|
4
4
|
or3:
|
|
5
5
|
image: ${OR3_IMAGE}
|
|
6
|
+
labels:
|
|
7
|
+
io.or3.cloud.deployment-id: ${OR3_DEPLOYMENT_ID}
|
|
6
8
|
env_file:
|
|
7
9
|
- .env
|
|
8
10
|
environment:
|
|
@@ -70,3 +72,5 @@ services:
|
|
|
70
72
|
volumes:
|
|
71
73
|
or3-data:
|
|
72
74
|
name: ${OR3_VOLUME_NAME}
|
|
75
|
+
labels:
|
|
76
|
+
io.or3.cloud.deployment-id: ${OR3_DEPLOYMENT_ID}
|