@cyaxis/ulvia 0.0.0-stage → 0.0.1-alpha.9

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 matthias-bernouy
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.
package/README.md CHANGED
@@ -1,3 +1,233 @@
1
- # Temporary Holding Version
1
+ # Ulvia CLI
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Ulvia installation and release CLI.
4
+
5
+ The workspace package contains private source code. Official releases are
6
+ bundled without workspace dependencies and published publicly as
7
+ `@cyaxis/ulvia`:
8
+
9
+ ```bash
10
+ bun add --global @cyaxis/ulvia@next
11
+ ulvia --help
12
+ ```
13
+
14
+ From the workspace, build and publish one exact release with:
15
+
16
+ ```bash
17
+ bun run cli:build --version 0.0.1-alpha.9
18
+ bun run cli:publish --version 0.0.1-alpha.9
19
+ ```
20
+
21
+ The build creates a checked npm archive and SHA-256 receipt below
22
+ `artifacts/cli`. Publishing accepts only that exact pair, targets the public npm
23
+ registry, and uses the `next` tag for prereleases or `latest` for stable versions.
24
+ It requires npm credentials configured outside the repository.
25
+
26
+ ```bash
27
+ bun run ulvia -- start
28
+ bun run ulvia -- configure network
29
+ bun run ulvia -- services status
30
+ bun run ulvia -- instance create preview --label "Preview site"
31
+ bun run ulvia -- instance list
32
+ bun run ulvia -- instance credentials preview
33
+ bun run ulvia -- instance stop preview
34
+ bun run ulvia -- instance delete preview --yes
35
+ bun run ulvia -- services stop
36
+ bun run ulvia -- upgrade kernel --version 0.0.1-alpha.9
37
+ bun run ulvia -- upgrade provider --version 0.0.1-alpha.9
38
+ bun run ulvia -- upgrade repository --version 0.0.1-alpha.9
39
+ bun run ulvia -- reset --yes
40
+ bun run ulvia -- release packages/official-repository/collections/ulvia-official
41
+ bun run ulvia -- release /path/to/contract-directory
42
+ bun run ulvia -- release /path/to/provider-directory
43
+ bun run ulvia -- push contract ulvia.official/ulvia.cms.installations@1.0.0 --repository https://repository.example
44
+ bun run ulvia -- pull collection ulvia.official/ulvia-official@1.0.0 --repository https://repository.example
45
+ bun run ulvia -- yank collection ulvia.official/ulvia-official@1.0.0 --repository https://repository.example --reason "Superseded"
46
+ bun run ulvia -- restore collection ulvia.official/ulvia-official@1.0.0 --repository https://repository.example
47
+ bun run ulvia -- prune
48
+ ```
49
+
50
+ The lifecycle commands manage the same Docker Compose projects for loopback and
51
+ public installations from any directory. Persistent
52
+ data lives below `$XDG_DATA_HOME/ulvia` or `~/.local/share/ulvia`;
53
+ `ULVIA_DATA_DIR` remains an explicit isolation override for tests and CI. The
54
+ CLI stores authored releases in `<data>/repository`. Shared Provider, Repository,
55
+ MongoDB, proxy, files, secrets and logs live below `<data>/services`; each
56
+ stateless Kernel has independent state and logs below `<data>/instances/<name>`.
57
+ The CLI never starts application servers, supervises their PIDs, or builds
58
+ software images. It pulls only the canonical public images under
59
+ `docker.io/cyaxis`, validates their Ulvia component and stack-API labels, and
60
+ stores the immutable repository digest selected for each component.
61
+
62
+ The first lifecycle command creates a safe installation configuration bound to
63
+ `127.0.0.1:5100`. Configure public exposure before creating an instance:
64
+
65
+ ```bash
66
+ ulvia configure network \
67
+ --bind 0.0.0.0 \
68
+ --http-port 80 \
69
+ --tls acme \
70
+ --https-port 443 \
71
+ --acme-email ops@example.com
72
+ ulvia start
73
+ ulvia instance create website --domain example.com
74
+ ```
75
+
76
+ The configuration is persisted under `<data>/services/installation.json` and
77
+ is not a per-start mode. ACME requires `0.0.0.0` and every instance in an ACME
78
+ installation requires an explicit DNS domain. Network configuration is locked
79
+ after the first instance because its canonical Kernel URL is part of Provider
80
+ provisioning. Repository and Provider management ports remain loopback-only.
81
+
82
+ `services start` starts only the shared proxy, MongoDB, Repository and Provider.
83
+ `instance create` provisions one isolated Provider tenant and immediately starts
84
+ its dedicated Kernel. `instance start` restarts only an already-provisioned
85
+ instance's Kernel. All Kernels use the shared proxy port: an instance named
86
+ `default` is available at `http://localhost:5100`, while any other instance such
87
+ as `preview` uses `http://preview.localhost:5100`.
88
+ `ULVIA_HTTP_PORT`, `ULVIA_REPOSITORY_PORT`, and `ULVIA_PROVIDER_PORT` provide
89
+ initial isolation defaults for tests and CI. Interactive configuration belongs
90
+ to `ulvia configure network`. The Provider still listens on its canonical port
91
+ 5103 inside the private Docker network; only its operator port on the host
92
+ changes.
93
+ `start` is the idempotent convenience command for shared services plus every
94
+ previously created local instance; it never creates an implicit instance.
95
+
96
+ The lifecycle boundary is deliberate:
97
+
98
+ | Command | Provider tenant | Kernel container |
99
+ | --- | --- | --- |
100
+ | `services start` | Does not create one | Does not start one |
101
+ | `instance create <name>` | Provisions one durable tenant | Creates and starts its Kernel |
102
+ | `instance start <name>` | Reuses the existing tenant | Starts only that Kernel |
103
+ | `start` | Never provisions | Starts every existing Kernel after shared services |
104
+
105
+ `instance credentials <name>` prints only that administrator's email and
106
+ password. Repository, Provider operator and instance bearer tokens remain in
107
+ private runtime files. Shared services and every instance have independent
108
+ `stop`, `status`, and `logs` boundaries. Software upgrades are explicit and
109
+ component-scoped through `upgrade <component> --version <exact-version>`.
110
+ Normal starts always reuse the installed digest and never follow a newer tag.
111
+ `reset --yes`
112
+ removes every Compose project, instance, shared state and repository
113
+ release. It does not inspect or modify the current working directory.
114
+ `release <resource-directory>` reads `definition.json` and routes by `kind`:
115
+ `collection`, `contract`, or `provider-manifest`. It validates and stores one
116
+ immutable release. A provider manifest must reference exact contract releases
117
+ already in the local repository. Release does not connect to or test a provider.
118
+ Contract fixture assets declared in `definition.json` are read from
119
+ `fixtures/<asset-id>` and verified by digest. Releasing the same coordinate with
120
+ different content fails. The local Repository serves stored resources
121
+ at `/v1/collections`, `/v1/contracts`, and `/v1/providers` on loopback port
122
+ 5102, with declared contract fixtures at
123
+ `/v1/contracts/<publisher>/<contract>/<version>/fixtures/<asset-id>`, and sees
124
+ new releases without restarting. The CMS consumes the collection and provider
125
+ repository endpoints. The Provider administration Page imports an exact
126
+ manifest, approves an account connection and replaces the site's exact contract
127
+ selections for Gateway calls. `prune` empties all local repository content
128
+ without deleting service or instance data.
129
+
130
+ Contract authors may keep only release metadata in `definition.json` and split
131
+ capabilities recursively below `capabilities/`. Each capability is one JSON
132
+ object. Optional `mocks/**/*.json` fragments contain one mock plus its explicit
133
+ `capabilityId`; mocks embedded in capability fragments reject. The compiler
134
+ sorts capabilities and mocks by their declared IDs, so paths never affect the
135
+ immutable digest. `definition.json` cannot contain an inline `capabilities`
136
+ array: the recursive source tree is the only authored contract format.
137
+
138
+ An optional conformance companion uses `conformance/definition.json`, recursive
139
+ `conformance/scenarios/**/*.json` scenario objects and recursive
140
+ `conformance/exemptions/**/*.json` coverage-exemption objects. Optional suite
141
+ fixture bytes live below `conformance/fixtures/<asset-id>`. The release command
142
+ assembles and admits a present suite, including exact locally released
143
+ dependencies, before storing the contract. Conformance publication and live
144
+ execution are separate Gateway/repository APIs; the CLI does not yet orchestrate
145
+ a disposable provider environment.
146
+
147
+ `push` publishes one exact local coordinate, and `pull` downloads and re-admits
148
+ one exact remote coordinate before storing it locally. Both support
149
+ `collection`, `contract`, and `provider`; dependencies must be transferred
150
+ first. `ULVIA_REPOSITORY_URL` can replace `--repository`.
151
+ Repository writes require `ULVIA_REPOSITORY_TOKEN`; the token is never accepted
152
+ as a command-line argument. Every mutation carries a timestamp, one-time nonce,
153
+ body digest, and HMAC-SHA-256 signature. The server rejects stale or replayed
154
+ requests, serializes writes across processes, re-runs admission and evolution
155
+ rules, and stages every declared asset as a separately signed raw stream. A final
156
+ idempotent commit makes the immutable artifact visible only after all byte lengths
157
+ and SHA-256 digests pass. Interrupted and rejected sessions never expose a partial
158
+ release. An exact republish is idempotent; different bytes at an existing coordinate
159
+ reject.
160
+
161
+ The protocol, exact-coordinate client and mutation endpoint live in
162
+ `@bernouy/cms-repository/repository/publication`; the CLI only composes them with
163
+ the reference filesystem adapter and its loopback listener. The filesystem
164
+ composition persists upload sessions and replay claims below the repository root.
165
+ A production repository can replace those ports without depending on this
166
+ executable package.
167
+
168
+ `yank` is reversible catalogue metadata: it hides a release from new catalogue
169
+ resolution without deleting its exact historical bytes. `restore` exposes it
170
+ again. Contract yanks block new dependent publications while previously
171
+ published provider manifests remain readable. The loopback Repository uses the
172
+ same write protocol and keeps its generated repository token in the private
173
+ runtime directory. The removed integration repository/Supabase bridge is not
174
+ run.
175
+
176
+ A collection keeps immutable administration copy in recursive locale
177
+ directories such as `translations/en/collection.json` and
178
+ `translations/en/theme/colors.json`. Every JSON object below
179
+ `translations/<locale>/` contributes to that locale's flat catalogue.
180
+ Collection, Bloc, setting, text, theme and Page metadata stores
181
+ reusable keys from those catalogues rather than inline labels. A key may occur
182
+ in only one fragment per locale. The collection locale must define every
183
+ referenced key; additional locale directories may be partial and fall back to
184
+ it.
185
+
186
+ A collection theme is authored as an ordered set of files. Its
187
+ `theme/definition.json` contains the theme label key and the ordered list of
188
+ category IDs. Category files may be organized recursively below `theme/`; the
189
+ scanner finds them by their internal `id`, while the manifest remains the only
190
+ source of display order. A category filename must match its ID, duplicate IDs
191
+ and orphan files reject, and category/token metadata uses the shared translation
192
+ keys. Categories use an `entries` array containing ordinary tokens and optional
193
+ typed `font-face` entries backed by local WOFF2 assets.
194
+
195
+ Collection assets are discovered recursively below `assets/`; their lowercase
196
+ relative paths are their stable IDs. They are not declared in the collection
197
+ `definition.json`. An optional root `assets/definition.json` accepts only a
198
+ `generations` object keyed by those paths. The compiler derives media type,
199
+ size and digest. Safe presentation-only SVG files receive the `inline-svg`
200
+ profile automatically; other SVGs remain downloadable assets but cannot be
201
+ expanded into authored Page DOM.
202
+
203
+ Collection texts use recursive `texts/definitions/**/*.json` arrays for stable
204
+ IDs and administration metadata, plus recursive
205
+ `texts/locales/<locale>/**/*.json` objects for content values. The release
206
+ command joins them into ordinary collection text records. Paths never prefix a
207
+ text ID; duplicate definitions, duplicate per-locale values and unknown value
208
+ IDs reject.
209
+
210
+ Bloc roots are discovered recursively below `blocs/`. A folder becomes a Bloc
211
+ when it contains `definition.json`, its basename must match the declared Bloc
212
+ ID, and discovery stops there so `settings/definition.json` is never mistaken
213
+ for a nested Bloc. Intermediate grouping directories contain only directories
214
+ and do not affect release identity.
215
+
216
+ The Official Repository image contains the admitted CMS contracts, provider
217
+ manifest, and administration collection. It installs that snapshot only into
218
+ empty persistent storage. To publish the same resources manually, release the
219
+ CMS contracts before the manifest:
220
+
221
+ ```bash
222
+ bun run ulvia -- release packages/official-repository/contracts/ulvia.cms.access
223
+ bun run ulvia -- release packages/official-repository/contracts/ulvia.cms.installations
224
+ bun run ulvia -- release packages/official-repository/contracts/ulvia.cms.content
225
+ bun run ulvia -- release packages/official-repository/contracts/ulvia.cms.files
226
+ bun run ulvia -- release packages/official-repository/contracts/ulvia.provider.cms-instances
227
+ bun run ulvia -- release packages/official-repository/providers/ulvia.official
228
+ bun run ulvia -- start
229
+ ```
230
+
231
+ `instance create` provisions the named instance through the Provider operator
232
+ API, then starts its Kernel. Connection review validates the runtime report and
233
+ exact manifest claims; it does not yet execute a live conformance suite.
@@ -0,0 +1,5 @@
1
+ services:
2
+ kernel:
3
+ environment:
4
+ ACME_HOST: ${ULVIA_INSTANCE_HOSTNAME}
5
+ LETSENCRYPT_HOST: ${ULVIA_INSTANCE_HOSTNAME}
@@ -0,0 +1,40 @@
1
+ services:
2
+ kernel:
3
+ image: ${KERNEL_IMAGE}
4
+ restart: unless-stopped
5
+ init: true
6
+ user: "${ULVIA_UID}:${ULVIA_GID}"
7
+ environment:
8
+ MODE: PROD
9
+ NODE_ENV: production
10
+ KERNEL_HOST: 0.0.0.0
11
+ KERNEL_PORT: "3000"
12
+ KERNEL_PUBLIC_URL: ${ULVIA_INSTANCE_URL}
13
+ CMS_INSTALLATIONS_PROVIDER_URL: http://ulvia-provider:5103
14
+ CMS_INSTALLATIONS_PROVIDER_TOKEN: ${CMS_INSTALLATIONS_PROVIDER_TOKEN}
15
+ ULVIA_LOG_FILE: /var/log/ulvia/kernel.ndjson
16
+ VIRTUAL_HOST: ${ULVIA_INSTANCE_HOSTNAME}
17
+ VIRTUAL_PORT: "3000"
18
+ volumes:
19
+ - type: bind
20
+ source: ${ULVIA_LOGS_DIR}
21
+ target: /var/log/ulvia
22
+ networks:
23
+ - instances
24
+ read_only: true
25
+ tmpfs:
26
+ - /tmp:rw,nosuid,nodev,noexec,size=256m
27
+ cap_drop: [ALL]
28
+ security_opt: [no-new-privileges:true]
29
+ healthcheck:
30
+ test: ["CMD", "bun", "-e", "const r=await fetch('http://127.0.0.1:3000/readyz'); if(!r.ok) process.exit(1); await r.body?.cancel()"]
31
+ interval: 30s
32
+ timeout: 5s
33
+ retries: 3
34
+ start_period: 10s
35
+ stop_grace_period: 30s
36
+
37
+ networks:
38
+ instances:
39
+ external: true
40
+ name: ${ULVIA_INSTANCE_NETWORK}
@@ -0,0 +1,21 @@
1
+ const environment = { ...process.env };
2
+
3
+ function required(name) {
4
+ const value = String(environment[name] ?? "").trim();
5
+ if (!value) {
6
+ throw new Error(`Missing ${name}`);
7
+ }
8
+ return value;
9
+ }
10
+
11
+ const username = required("MONGO_PROVIDER_USERNAME");
12
+ const password = required("MONGO_PROVIDER_PASSWORD");
13
+ const admin = db.getSiblingDB("admin");
14
+ const existing = admin.getUser(username);
15
+ const roles = [{ role: "readWriteAnyDatabase", db: "admin" }];
16
+
17
+ if (existing) {
18
+ admin.updateUser(username, { pwd: password, roles });
19
+ } else {
20
+ admin.createUser({ user: username, pwd: password, roles });
21
+ }
@@ -0,0 +1,13 @@
1
+ # Cache only generic capability routes. Provider Cache-Control headers remain
2
+ # authoritative, so private and no-store responses are never retained.
3
+ proxy_cache_path /var/cache/nginx/cms levels=1:2 keys_zone=cms_capabilities:16m max_size=1g inactive=1h use_temp_path=off;
4
+
5
+ map $uri $cms_skip_capability_cache {
6
+ default 1;
7
+ ~^/(?:[^/]+/)*\.cms/call/ 0;
8
+ }
9
+
10
+ map $request_method $cms_cache_method {
11
+ default $request_method;
12
+ HEAD GET;
13
+ }
@@ -0,0 +1,2 @@
1
+ # The application accepts 100 MiB files plus at most 1 MiB of multipart framing.
2
+ client_max_body_size 101m;
@@ -0,0 +1,8 @@
1
+ # Included inside every generated proxy location by nginx-proxy.
2
+ proxy_cache cms_capabilities;
3
+ proxy_cache_methods GET HEAD;
4
+ proxy_cache_key "$scheme|$cms_cache_method|$host|$uri|$is_args$args";
5
+ proxy_cache_bypass $cms_skip_capability_cache $http_authorization;
6
+ proxy_no_cache $cms_skip_capability_cache $http_authorization;
7
+ proxy_cache_valid 200 206 1h;
8
+ add_header X-Cache-Status $upstream_cache_status always;
@@ -0,0 +1,4 @@
1
+ # Never record query strings. Signed file access tokens are carried in queries.
2
+ log_format cms_query_safe '$remote_addr - $remote_user [$time_local] "$request_method $uri $server_protocol" '
3
+ '$status $body_bytes_sent "$http_referer" "$http_user_agent"';
4
+ access_log /var/log/nginx/access.log cms_query_safe;
@@ -0,0 +1,20 @@
1
+ services:
2
+ proxy:
3
+ ports:
4
+ - "${ULVIA_BIND_ADDRESS}:${ULVIA_HTTPS_PORT}:443"
5
+
6
+ acme:
7
+ image: nginxproxy/acme-companion:2.8.1@sha256:ccfceb9798b9f6586d47eeeadbb8e185c8897b34e011a2739a9b82d5e92a8b6c
8
+ restart: unless-stopped
9
+ depends_on:
10
+ proxy:
11
+ condition: service_healthy
12
+ volumes_from:
13
+ - proxy
14
+ volumes:
15
+ - /var/run/docker.sock:/var/run/docker.sock:ro
16
+ - ${ULVIA_PROXY_ACME_DIR}:/etc/acme.sh
17
+ environment:
18
+ DEFAULT_EMAIL: ${ULVIA_ACME_EMAIL}
19
+ networks:
20
+ - instances
@@ -0,0 +1,177 @@
1
+ services:
2
+ proxy:
3
+ image: nginxproxy/nginx-proxy:1.11.5@sha256:e06db5fb9586234e9c2bdef707c8b8d784caa4fb49fbe7285eb3730a501c3eaa
4
+ restart: unless-stopped
5
+ ports:
6
+ - "${ULVIA_BIND_ADDRESS}:${ULVIA_HTTP_PORT}:80"
7
+ volumes:
8
+ - /var/run/docker.sock:/tmp/docker.sock:ro
9
+ - ./nginx/client_max_body.conf:/etc/nginx/conf.d/zzz_client_max_body.conf:ro
10
+ - ./nginx/capability_cache.conf:/etc/nginx/conf.d/zzz_capability_cache.conf:ro
11
+ - ./nginx/query_safe_access_log.conf:/etc/nginx/conf.d/zzz_query_safe_access_log.conf:ro
12
+ - ./nginx/default_location:/etc/nginx/vhost.d/default_location:ro
13
+ - ${ULVIA_PROXY_CERTIFICATES_DIR}:/etc/nginx/certs
14
+ - ${ULVIA_PROXY_VHOST_DIR}:/etc/nginx/vhost.d
15
+ - ${ULVIA_PROXY_CHALLENGE_DIR}:/usr/share/nginx/html
16
+ - ${ULVIA_PROXY_ACME_DIR}:/etc/acme.sh
17
+ - proxy-cache:/var/cache/nginx
18
+ networks:
19
+ - instances
20
+ healthcheck:
21
+ test: ["CMD", "nginx", "-t"]
22
+ interval: 30s
23
+ timeout: 5s
24
+ retries: 3
25
+ start_period: 5s
26
+
27
+ mongo:
28
+ image: mongo:7.0.37@sha256:d5b3ca8c3f3cdce78d44870dc0871b76d5235e9b2ad4ea6bea5d1fbff8027703
29
+ restart: unless-stopped
30
+ user: "${ULVIA_UID}:${ULVIA_GID}"
31
+ environment:
32
+ MONGO_INITDB_ROOT_USERNAME: ${ULVIA_MONGO_USERNAME}
33
+ MONGO_INITDB_ROOT_PASSWORD: ${ULVIA_MONGO_PASSWORD}
34
+ volumes:
35
+ - type: bind
36
+ source: ${ULVIA_MONGO_DIR}
37
+ target: /data/db
38
+ networks:
39
+ - private
40
+ healthcheck:
41
+ test:
42
+ [
43
+ "CMD-SHELL",
44
+ "mongosh --quiet --username \"$$MONGO_INITDB_ROOT_USERNAME\" --password \"$$MONGO_INITDB_ROOT_PASSWORD\" --authenticationDatabase admin --eval 'quit(db.adminCommand({ ping: 1 }).ok ? 0 : 1)'",
45
+ ]
46
+ interval: 30s
47
+ timeout: 5s
48
+ retries: 3
49
+ start_period: 10s
50
+ stop_grace_period: 30s
51
+
52
+ repository:
53
+ image: ${OFFICIAL_REPOSITORY_IMAGE}
54
+ restart: unless-stopped
55
+ init: true
56
+ user: "${ULVIA_UID}:${ULVIA_GID}"
57
+ environment:
58
+ MODE: PROD
59
+ NODE_ENV: production
60
+ REPOSITORY_HOST: 0.0.0.0
61
+ REPOSITORY_PORT: "3000"
62
+ ULVIA_LOG_FILE: /var/log/ulvia/repository.ndjson
63
+ ULVIA_REPOSITORY_DIR: /var/lib/ulvia-repository
64
+ ULVIA_REPOSITORY_SEED_DIR: /opt/ulvia-repository-seed
65
+ ULVIA_REPOSITORY_TOKEN: ${ULVIA_REPOSITORY_TOKEN}
66
+ volumes:
67
+ - type: bind
68
+ source: ${ULVIA_REPOSITORY_DIR}
69
+ target: /var/lib/ulvia-repository
70
+ - type: bind
71
+ source: ${ULVIA_LOGS_DIR}
72
+ target: /var/log/ulvia
73
+ ports:
74
+ - "127.0.0.1:${ULVIA_REPOSITORY_PORT}:3000"
75
+ networks:
76
+ - private
77
+ read_only: true
78
+ tmpfs:
79
+ - /tmp:rw,nosuid,nodev,noexec,size=64m
80
+ cap_drop: [ALL]
81
+ security_opt: [no-new-privileges:true]
82
+ healthcheck:
83
+ test: ["CMD", "bun", "-e", "const r=await fetch('http://127.0.0.1:3000/readyz'); if(!r.ok) process.exit(1); await r.body?.cancel()"]
84
+ interval: 30s
85
+ timeout: 5s
86
+ retries: 3
87
+ start_period: 5s
88
+
89
+ mongo-provision:
90
+ image: mongo:7.0.37@sha256:d5b3ca8c3f3cdce78d44870dc0871b76d5235e9b2ad4ea6bea5d1fbff8027703
91
+ restart: "no"
92
+ environment:
93
+ MONGO_INITDB_ROOT_USERNAME: ${ULVIA_MONGO_USERNAME}
94
+ MONGO_INITDB_ROOT_PASSWORD: ${ULVIA_MONGO_PASSWORD}
95
+ MONGO_PROVIDER_USERNAME: ${ULVIA_MONGO_PROVIDER_USERNAME}
96
+ MONGO_PROVIDER_PASSWORD: ${ULVIA_MONGO_PROVIDER_PASSWORD}
97
+ entrypoint: ["/bin/sh", "-c"]
98
+ command:
99
+ - >-
100
+ exec mongosh --quiet --host mongo
101
+ --username "$$MONGO_INITDB_ROOT_USERNAME"
102
+ --password "$$MONGO_INITDB_ROOT_PASSWORD"
103
+ --authenticationDatabase admin
104
+ /opt/ulvia-mongo/provision-provider.js
105
+ volumes:
106
+ - ./mongo/provision-provider.js:/opt/ulvia-mongo/provision-provider.js:ro
107
+ networks:
108
+ - private
109
+ depends_on:
110
+ mongo:
111
+ condition: service_healthy
112
+ read_only: true
113
+ tmpfs:
114
+ - /tmp:rw,nosuid,nodev,noexec,size=32m
115
+ cap_drop: [ALL]
116
+ security_opt: [no-new-privileges:true]
117
+
118
+ provider:
119
+ image: ${PROVIDER_IMAGE}
120
+ restart: unless-stopped
121
+ init: true
122
+ user: "${ULVIA_UID}:${ULVIA_GID}"
123
+ environment:
124
+ MODE: PROD
125
+ NODE_ENV: production
126
+ CMS_FILES_DIR: /var/lib/cms/files
127
+ CMS_REPOSITORY_URL: http://repository:3000
128
+ CMS_REPOSITORY_ALLOW_INSECURE_HTTP: "true"
129
+ CMS_SESSION_SECRET: ${ULVIA_PROVIDER_SESSION_SECRET}
130
+ CMS_KEK_HEX: ${ULVIA_PROVIDER_KEK_HEX}
131
+ MONGO_URL: mongodb://${ULVIA_MONGO_PROVIDER_USERNAME}:${ULVIA_MONGO_PROVIDER_PASSWORD}@mongo:27017/ulvia_provider_control?authSource=admin&retryWrites=false
132
+ ULVIA_LOG_FILE: /var/log/ulvia/provider.ndjson
133
+ ULVIA_PROVIDER_HOST: 0.0.0.0
134
+ ULVIA_PROVIDER_PORT: "5103"
135
+ ULVIA_PROVIDER_PUBLIC_URL: http://ulvia-provider:5103
136
+ ULVIA_PROVIDER_OPERATOR_TOKEN: ${ULVIA_PROVIDER_OPERATOR_TOKEN}
137
+ volumes:
138
+ - type: bind
139
+ source: ${ULVIA_FILES_DIR}
140
+ target: /var/lib/cms/files
141
+ - type: bind
142
+ source: ${ULVIA_LOGS_DIR}
143
+ target: /var/log/ulvia
144
+ ports:
145
+ - "127.0.0.1:${ULVIA_PROVIDER_PORT}:5103"
146
+ networks:
147
+ private: {}
148
+ instances:
149
+ aliases:
150
+ - ulvia-provider
151
+ depends_on:
152
+ mongo-provision:
153
+ condition: service_completed_successfully
154
+ repository:
155
+ condition: service_healthy
156
+ read_only: true
157
+ tmpfs:
158
+ - /tmp:rw,nosuid,nodev,noexec,size=256m
159
+ cap_drop: [ALL]
160
+ security_opt: [no-new-privileges:true]
161
+ healthcheck:
162
+ test: ["CMD", "bun", "-e", "const r=await fetch('http://127.0.0.1:5103/ulvia/operator/instances',{headers:{authorization:'Bearer '+process.env.ULVIA_PROVIDER_OPERATOR_TOKEN}}); if(!r.ok) process.exit(1); await r.body?.cancel()"]
163
+ interval: 30s
164
+ timeout: 5s
165
+ retries: 3
166
+ start_period: 10s
167
+ stop_grace_period: 30s
168
+
169
+ networks:
170
+ private:
171
+ name: ${ULVIA_PRIVATE_NETWORK}
172
+ internal: true
173
+ instances:
174
+ name: ${ULVIA_INSTANCE_NETWORK}
175
+
176
+ volumes:
177
+ proxy-cache: