@or3/cloud 0.1.38 → 0.1.54

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 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
- before it writes deployment state.
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,6 +58,22 @@ 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`
@@ -60,14 +82,88 @@ 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.
61
83
  Updates also replace the generated Compose/Caddy files from the target CLI.
62
84
  Those files are checksummed into the pre-update backup and restored on a failed
63
- update, rollback, restore, or interrupted-operation recovery.
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.
64
115
  When selecting an exact version, run the matching CLI package, for example
65
- `npx --yes @or3/cloud@0.1.38 update --to 0.1.38`; the CLI refuses mismatched
116
+ `npx --yes @or3/cloud@0.1.39 update --to 0.1.39`; the CLI refuses mismatched
66
117
  package and image versions so their generated assets cannot drift.
67
118
  For a destructive purge, export a fresh backup to a new directory on another
68
119
  filesystem first; `remove --purge-data --yes` verifies that export before it
69
120
  will delete anything.
70
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
+
71
167
  Managed registration is invite-only: the bootstrap owner signs in first and
72
168
  invites additional users from the in-product admin flow. Guest access and
73
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; includeSubDomains"
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}
@@ -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}