diff --git a/README.md b/README.md index 0be59d2..b6c8069 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,16 @@ Deploy the Wikideck production container to an existing Kubernetes cluster using volume claim, a dedicated ServiceAccount, a ClusterIP Service, and a NetworkPolicy. An HTTPS Ingress is optional and disabled by default. -**Wikideck is an experimental RandomX testnet, not a production-ready public -mainnet.** The updated application has independent validating nodes, fork choice, -native NFTs, and encrypted wallet backup/recovery. It supplies no public bootnodes -or deployed public network. Use test wallets and no valuable assets. This role +**Wikideck is mainnet-first in code but remains an experimental, launch-gated +candidate; the current production decision is NO-GO.** No public network, +bootnodes, independent audit, or production readiness is established. The +application includes validating nodes, fork choice, native NFTs, and encrypted +local-wallet backup/recovery. Use test wallets and no valuable assets. This role deploys a **validator**, never a miner: mining is an explicitly started native CLI process outside the browser. Private keys, backups and passwords must stay off -validator pods and out of Ansible variables. +validator pods and out of Ansible variables. See the upstream +[mainnet launch gates](https://github.com/ericomeehan/wikideck/blob/main/docs/mainnet-launch.md) +and [operations/recovery guidance](https://github.com/ericomeehan/wikideck/blob/main/docs/operations-recovery.md). ## Requirements @@ -24,13 +27,16 @@ validator pods and out of Ansible variables. - Kubeconfig access to create/update/read the resources in the target namespace, delete the role's Ingress/NetworkPolicy when disabled, and optionally create a Namespace. No cluster-admin access or in-cluster workload RBAC is needed. -- **A published image built from Wikideck's production Dockerfile.** There is no +- **A published, immutable image built from Wikideck's production Dockerfile.** There is no assumed official registry image. Set `wikideck_image` to your actual tagged or - digest-pinned image. The role does not build/push images or install a cluster. + preferably digest-pinned image. The role does not build/push images or install a cluster. Rebuild from the current source: the image must include Node 24, libstdc++, and the native RandomX executable, with `RANDOMX_BINARY` set by the image. An old browser-mining image is incompatible. Build and verify for your worker node - architecture; do not assume a locally built image is multi-architecture. + architecture; do not assume a locally built image is multi-architecture. The + production Dockerfile defaults to mainnet and pins its profile/genesis into the + image manifest; runtime settings cannot change that identity. Testnet is an + explicit development fixture, not a public deployment profile. - For ingress: an ingress controller, DNS, and a trusted TLS Secret in the app namespace. Alternatively, configure cert-manager annotations and an existing issuer. The role does not install cert-manager or create an issuer. @@ -43,30 +49,23 @@ environment. Windows controllers must run Ansible through WSL or Linux container Install this checkout into your playbook's role search path as `roles/wikideck`, or install it using an Ansible Galaxy Git role requirement named `wikideck`. -[examples/playbook.yml](examples/playbook.yml) shows an HTTPS deployment with -ingress-nginx and cert-manager. Replace the image, hostname, peer URLs, class, -issuer, and controller namespace with real values before running it. - -```yaml -- name: Deploy a cluster-internal Wikideck instance - hosts: localhost - connection: local - gather_facts: false - roles: - - role: wikideck - vars: - wikideck_image: registry.example.com/games/wikideck:1.0.0 -``` +[examples/playbook.yml](examples/playbook.yml) is the gated HTTPS deployment +example with ingress-nginx and cert-manager. It requires an approved image +digest, the image's verified manifest/genesis, launch gates, and real peers before +it will change the cluster. There is intentionally no ungated, copy-and-run +mainnet snippet: use a pre-task gate like the example. Empty gates cause the +application to exit before listening, but do not by themselves prevent Kubernetes +resources from being created. Run the playbook on the controller, using its normal kubeconfig discovery or `wikideck_kubeconfig` / `wikideck_kube_context` overrides. Do not target every Kubernetes node: the role operates on the Kubernetes API, not over node SSH. -For a private local preview, forward the Service with +For an explicitly built testnet development image, forward the Service with `kubectl -n wikideck port-forward service/wikideck 3001:80` and open . Keep the forwarding bind address at its loopback default. -Wikideck already allows this local origin. Different hosts, ports, schemes, and -browser profiles use different browser wallets. +The app allows this local origin. Different hosts, ports, schemes, and browser +profiles use different browser wallets. This does not bypass mainnet startup gates. For remote browsers, enable ingress and set `wikideck_public_origin` to exactly `https://` followed by `wikideck_ingress_host` (no slash or path). HTTPS is needed @@ -81,6 +80,11 @@ Set `wikideck_peers` to a list of up to 16 compatible peer HTTPS origins, such a An empty list deliberately starts an **isolated validator**. There is no automatic discovery, bootnode list, or automatic reciprocal configuration: configure the other operators' nodes too. All nodes must run the same network/genesis/consensus. +For mainnet, application startup additionally requires at least two distinct +public HTTPS peer origins, an arrived image-pinned genesis time, the exact chain-ID +approval, and explicit unaudited-risk acceptance. These switches are not launch +approval from this role; the project currently records a NO-GO decision. The role +does not discover or verify peer independence. Peer RPC uses `/p2p/*` on the **same port 3001** as the UI/API; no separate TCP/UDP P2P Service or host networking is required. The existing `/` ingress path forwards @@ -149,6 +153,8 @@ All defaults are in [defaults/main.yml](defaults/main.yml). | `wikideck_image_pull_secrets` | `[]` | Names of existing registry Secrets in this namespace. | | `wikideck_peers` | `[]` | Up to 16 static peer origins; empty means isolated. | | `wikideck_allow_private_peers` | `false` | Explicit test-only private/HTTP peer permission. | +| `wikideck_mainnet_launch` | empty | Exact 64-character chain-ID startup approval; leave empty unless a launch is formally approved. | +| `wikideck_mainnet_risk_ack` | empty | Must equal `operator-accepts-unaudited-risk` to acknowledge residual risk; not a certification. | | `wikideck_public_origin` | empty | Additional allowed HTTPS browser origin; required with ingress. | | `wikideck_ingress_enabled` | `false` | Create TLS ingress; disabling removes the role's named ingress. | | `wikideck_ingress_host` | empty | DNS hostname, without scheme, port, or path. | @@ -161,6 +167,8 @@ All defaults are in [defaults/main.yml](defaults/main.yml). | `wikideck_storage_access_modes` | `[ReadWriteOnce]` | `ReadWriteOnce` or `ReadWriteOncePod`; never `ReadWriteMany`. | | `wikideck_resources` | see defaults | CPU/memory requests `250m`/`512Mi`, limits `1`/`1Gi`; small-validator starting point, not a capacity guarantee. | | `wikideck_node_selector` / `wikideck_tolerations` / `wikideck_affinity` | `{}` / `[]` / `{}` | Pod placement settings. | +| `wikideck_trusted_proxies` | `[]` | Up to 16 literal proxy IPs/CIDRs; only trust actual edge source addresses after the edge overwrites forwarded headers. | +| `wikideck_ops_port` | empty | Optional port for a diagnostics listener bound only to container loopback; never add it to the Service, Ingress, or NetworkPolicy. | | `wikideck_network_policy_enabled` | `true` | Manage network isolation; disabling deletes this role's policy. | | `wikideck_network_policy_ingress_from` | same-namespace pods | List of NetworkPolicy peers allowed to reach TCP 3001; empty list denies all ingress. | | `wikideck_network_policy_dns_to` | `kube-system` namespace | DNS peers allowed on UDP/TCP 53; adapt for custom/NodeLocal DNS. | @@ -182,10 +190,14 @@ entrypoint are deliberately preserved; no production fake-PoW fallback is added. `ReadWriteOncePod` if supported by your CSI driver. Do not force-delete an old pod on a partitioned node until it has been fenced; Recreate is not a distributed storage lock. -- `/app/data/testnet/ledger.json` stores the block DAG and pending transfers; - `/app/data/testnet-node.lock` exclusively locks the entire data directory. - The old demo chain is separate, not imported, and has **no migration** to the - new testnet. Back it up first; preferably use a fresh claim for the new network. +- The active ledger is SQLite at `/app/data//ledger.sqlite` (with WAL + sidecars); `/app/data/-node.lock` exclusively locks that profile's data. + A matching legacy `ledger.json` may be imported once and is retained as a + recovery copy. The import path accepts at most 128 MiB and 10,000 blocks; these + are legacy-import limits, not live SQLite disk bounds. Different profiles and + chain identities must not share a writable claim. Preserve and back up existing + data before changing image profile or genesis; incompatible chain data fails + closed rather than resetting automatically. The process runs as UID/GID 1000 with `fsGroup: 1000`. Your storage driver must honor ownership settings, or the existing volume must already be writable by this identity. @@ -199,15 +211,18 @@ entrypoint are deliberately preserved; no production fake-PoW fallback is added. backup or use your CSI provider's documented snapshot procedure. Encrypt disks and backups, define retention, and test restores. Deleting a namespace or PVC can still destroy data depending on the underlying reclaim policy. +- The current `node:backup` utility creates/verifies/restores ledger-only packages + while the node is stopped. It does not contain browser wallets, secrets, or + configuration, and it does not encrypt the package; store it in restricted, + encrypted off-host storage and verify a restore before relying on it. - Full native RandomX revalidation runs before the server listens. Increase `wikideck_startup_probe_failure_threshold` and `wikideck_wait_timeout` together for your measured replay time, leaving scheduling/image-pull margin. A healthy empty node does not exercise the native cache or prove peer synchronization. -- The app bounds the ledger to 128 MiB and 10,000 total DAG blocks, with 256 - pending transfers. Complete NFT states are retained per DAG node, so RAM may - grow quadratically despite disk bounds. Whole-ledger rewrites and full replay - are **not suitable for a long-lived public chain**; a larger PVC does not fix - those application limits. +- SQLite avoids whole-ledger JSON rewrites, but the node retains DAG records in + memory, rebuilds state roots over the full NFT set, and revalidates proofs at + startup. Long-chain capacity and archival-scale operation remain unresolved; + a larger PVC does not fix memory, replay, or consensus-capacity limits. ### Crash recovery and resource sizing @@ -268,12 +283,15 @@ your worker architecture and runtime security profile; HTTP health alone cannot detect every native engine failure. Do not grant privileged mode or disable seccomp as an untested performance workaround. -Wikideck does not currently configure Express `trust proxy`. Clients behind one -proxy can therefore share an IP-based application rate limit. Review the -application before public exposure; do not try to fix this by blindly trusting -forwarded headers. The current app limits `/api` and `/p2p` together; default -forwarded-header behavior can also produce express-rate-limit validation errors -in logs. The role adds no authentication, WAF, or abuse protection. Eclipse, +`WIKIDECK_TRUSTED_PROXIES` is empty/untrusted by default. Populate +`wikideck_trusted_proxies` only with the literal source addresses/CIDRs actually +used by the trusted edge; the app validates them at startup. The edge must +overwrite forwarded headers, and network controls must prevent direct backend +access. Do not copy a generic proxy range without verifying the cluster path. +The optional `WIKIDECK_OPS_PORT` listener is loopback-only inside the pod and is +not exposed by role resources. It is intended for local/container-exec checks, +not an ingress endpoint. The role adds no authentication, WAF, or abuse +protection. Eclipse, majority-hashpower, spam/DoS and reorganization risks remain application/protocol issues; deployment hardening does not make a public mainnet safe. @@ -282,8 +300,8 @@ issues; deployment hardening does not make a public mainnet safe. [tests/render.yml](tests/render.yml) runs entirely offline: it validates inputs, renders/parses every template across default, ingress, existing-claim, storage class, isolated/public/private peers, custom HTTPS ports, deny-all, longer startup, -and digest-image variants, and checks wiring/security -invariants. It also checks that unsafe/invalid settings are rejected. Run it with +digest-image, launch-gate, proxy-trust and diagnostics variants, and checks +wiring/security invariants. It also checks that unsafe/invalid settings are rejected. Run it with `ansible-playbook -i tests/inventory tests/render.yml`. [tests/test.yml](tests/test.yml) is a real-cluster smoke test using the local diff --git a/defaults/main.yml b/defaults/main.yml index eb29912..b55a151 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -21,6 +21,9 @@ wikideck_image_pull_secrets: [] wikideck_peers: [] # Test/private networks only; permits private addresses AND HTTP in the app. wikideck_allow_private_peers: false +# Explicit mainnet candidate startup gates. Never set these before launch approval. +wikideck_mainnet_launch: "" +wikideck_mainnet_risk_ack: "" # Exact browser origin, without a trailing slash. Required when using ingress. wikideck_public_origin: "" wikideck_ingress_enabled: false @@ -48,6 +51,10 @@ wikideck_resources: wikideck_node_selector: {} wikideck_tolerations: [] wikideck_affinity: {} +# Trust only literal proxy source IPs/CIDRs whose edge overwrites forwarded headers. +wikideck_trusted_proxies: [] +# Optional private operations listener; application binds it to container loopback. +wikideck_ops_port: "" # Requires a NetworkPolicy-capable CNI. Same-namespace clients only by default. wikideck_network_policy_enabled: true diff --git a/examples/playbook.yml b/examples/playbook.yml index 242a45b..1356fd9 100644 --- a/examples/playbook.yml +++ b/examples/playbook.yml @@ -2,16 +2,32 @@ - name: Deploy Wikideck hosts: localhost connection: local - gather_facts: false + gather_facts: true + pre_tasks: + - name: Require an approved and ready mainnet candidate release + ansible.builtin.assert: + that: + - wikideck_mainnet_deployment_approved | default(false) | bool + - wikideck_release_image is defined and wikideck_release_image is string and wikideck_release_image is match('^.+@sha256:[a-f0-9]{64}$') + - wikideck_release_peers is defined and wikideck_release_peers is sequence and wikideck_release_peers is not string and wikideck_release_peers | length >= 2 + - wikideck_release_peers | unique | list | length == wikideck_release_peers | length + - wikideck_candidate_chain_id is defined and wikideck_candidate_chain_id is match('^[a-f0-9]{64}$') + - wikideck_candidate_risk_ack | default('') == 'operator-accepts-unaudited-risk' + - wikideck_release_genesis_time is defined and (wikideck_release_genesis_time | string) is match('^[1-9][0-9]{8,10}$') + - wikideck_release_genesis_time | int >= 1700000000 and wikideck_release_genesis_time | int <= 4102444800 + - wikideck_release_genesis_time | int <= ansible_date_time.epoch | int + fail_msg: >- + Mainnet is currently NO-GO. Supply only an approved digest-pinned image, + verified frozen manifest and arrived genesis, independent public HTTPS + peers, and explicit unaudited-risk acceptance. roles: - role: wikideck vars: - # Replace with your published, scanned image; preferably use @sha256:... - wikideck_image: registry.example.com/games/wikideck:1.0.0 - # Replace with real compatible testnet peers. No bootnodes are supplied. - wikideck_peers: - - https://peer-a.example.org - - https://peer-b.example.org + wikideck_image: "{{ wikideck_release_image }}" + wikideck_image_pull_policy: IfNotPresent + wikideck_peers: "{{ wikideck_release_peers }}" + wikideck_mainnet_launch: "{{ wikideck_candidate_chain_id }}" + wikideck_mainnet_risk_ack: "{{ wikideck_candidate_risk_ack }}" wikideck_ingress_enabled: true wikideck_ingress_host: wikideck.example.com wikideck_public_origin: https://wikideck.example.com diff --git a/meta/main.yml b/meta/main.yml index 2a7d15b..d9d55f8 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -3,7 +3,7 @@ galaxy_info: role_name: wikideck author: Eric Meehan - description: Deploy Wikideck to Kubernetes with persistent storage and optional TLS ingress. + description: Deploy the gated Wikideck mainnet candidate or an explicit development node to Kubernetes. license: MIT-0 min_ansible_version: "2.16" galaxy_tags: diff --git a/playbooks/wikideck.yaml b/playbooks/wikideck.yaml new file mode 100644 index 0000000..0ab3ab9 --- /dev/null +++ b/playbooks/wikideck.yaml @@ -0,0 +1,45 @@ +- name: Deploy Wikideck + hosts: alpha-control-plane + become: true + vars_files: + - ~/Desktop/secrets.yaml + pre_tasks: + - name: Require an explicitly approved, immutable mainnet release + ansible.builtin.assert: + that: + - wikideck_mainnet_deployment_approved | default(false) | bool + - wikideck_release_image is defined and wikideck_release_image is string and wikideck_release_image is match('^.+@sha256:[a-f0-9]{64}$') + - wikideck_mainnet_peers is defined and wikideck_mainnet_peers is sequence and wikideck_mainnet_peers is not string and wikideck_mainnet_peers | length >= 2 + - wikideck_mainnet_peers | unique | list | length == wikideck_mainnet_peers | length + - wikideck_candidate_chain_id is defined and wikideck_candidate_chain_id is match('^[a-f0-9]{64}$') + - wikideck_candidate_risk_ack | default('') == 'operator-accepts-unaudited-risk' + - wikideck_release_genesis_time is defined and (wikideck_release_genesis_time | string) is match('^[1-9][0-9]{8,10}$') + - wikideck_release_genesis_time | int >= 1700000000 and wikideck_release_genesis_time | int <= 4102444800 + - wikideck_release_genesis_time | int <= ansible_date_time.epoch | int + fail_msg: >- + Mainnet is currently NO-GO. Do not deploy until the operator has approved + a digest-pinned image with a verified frozen manifest, arrived genesis, + at least two independent public HTTPS peers, and explicit risk acceptance. + roles: + - role: ericomeehan.wikideck + vars: + wikideck_image: "{{ wikideck_release_image }}" + wikideck_image_pull_policy: IfNotPresent + wikideck_peers: "{{ wikideck_mainnet_peers }}" + wikideck_mainnet_launch: "{{ wikideck_candidate_chain_id }}" + wikideck_mainnet_risk_ack: "{{ wikideck_candidate_risk_ack }}" + wikideck_public_origin: https://wikideck.eom.dev + wikideck_ingress_enabled: true + wikideck_ingress_host: wikideck.eom.dev + wikideck_ingress_class_name: nginx + wikideck_ingress_annotations: + cert-manager.io/cluster-issuer: ca-issuer + # Match the actual ingress controller namespace; add its podSelector if known. + wikideck_network_policy_ingress_from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + wikideck_storage_size: 2Ti + wikideck_storage_class: nfs-client + # Populate only after confirming the real proxy source IPs/CIDRs. + wikideck_trusted_proxies: "{{ wikideck_ingress_proxy_sources | default([]) }}" diff --git a/tasks/validate.yml b/tasks/validate.yml index c44a1e4..1f9f4d8 100644 --- a/tasks/validate.yml +++ b/tasks/validate.yml @@ -28,6 +28,16 @@ - wikideck_existing_claim | length == 0 or wikideck_existing_claim is match('^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$') - wikideck_public_origin is string - wikideck_public_origin | length == 0 or wikideck_public_origin is match('^https://[a-z0-9.-]+(:[0-9]+)?$') + - wikideck_mainnet_launch is string + - wikideck_mainnet_launch | length == 0 or wikideck_mainnet_launch is match('^[a-f0-9]{64}$') + - wikideck_mainnet_risk_ack is string + - wikideck_mainnet_risk_ack in ['', 'operator-accepts-unaudited-risk'] + - wikideck_trusted_proxies is sequence and wikideck_trusted_proxies is not string and wikideck_trusted_proxies is not mapping + - wikideck_trusted_proxies | length <= 16 + - wikideck_trusted_proxies | select('string') | list | length == wikideck_trusted_proxies | length + - wikideck_trusted_proxies | select('match', '^[0-9a-fA-F:.]+(/[0-9]{1,3})?$') | list | length == wikideck_trusted_proxies | length + - wikideck_ops_port is string or wikideck_ops_port is integer + - wikideck_ops_port | string | length == 0 or ((wikideck_ops_port | string) is match('^[1-9][0-9]{0,4}$') and wikideck_ops_port | int <= 65535) - wikideck_ingress_enabled is boolean - wikideck_network_policy_enabled is boolean - wikideck_allow_private_peers is boolean @@ -68,6 +78,7 @@ - wikideck_network_policy_https_ports - wikideck_network_policy_peer_egress - wikideck_peers + - wikideck_trusted_proxies - name: Validate the static peer allowlist ansible.builtin.assert: diff --git a/templates/deployment.yml.j2 b/templates/deployment.yml.j2 index 59fa9a8..d0744cc 100644 --- a/templates/deployment.yml.j2 +++ b/templates/deployment.yml.j2 @@ -71,6 +71,18 @@ spec: value: {{ (wikideck_peers | join(',')) | to_json }} - name: WIKIDECK_ALLOW_PRIVATE_PEERS value: {{ ('1' if wikideck_allow_private_peers else '0') | to_json }} + - name: WIKIDECK_MAINNET_LAUNCH + value: {{ wikideck_mainnet_launch | to_json }} + - name: WIKIDECK_MAINNET_RISK_ACK + value: {{ wikideck_mainnet_risk_ack | to_json }} +{% if wikideck_trusted_proxies | length > 0 %} + - name: WIKIDECK_TRUSTED_PROXIES + value: {{ (wikideck_trusted_proxies | join(',')) | to_json }} +{% endif %} +{% if wikideck_ops_port | string | length > 0 %} + - name: WIKIDECK_OPS_PORT + value: {{ (wikideck_ops_port | string) | to_json }} +{% endif %} {% if wikideck_public_origin | length > 0 %} - name: PUBLIC_ORIGIN value: {{ wikideck_public_origin | to_json }} diff --git a/tests/invalid_case.yml b/tests/invalid_case.yml index f458c46..11ced54 100644 --- a/tests/invalid_case.yml +++ b/tests/invalid_case.yml @@ -19,6 +19,10 @@ wikideck_network_policy_peer_egress: "{{ invalid_case.peer_egress | default([]) }}" wikideck_image_pull_secrets: "{{ invalid_case.pull_secrets | default([]) }}" wikideck_network_policy_ingress_from: "{{ invalid_case.ingress_from | default([{'podSelector': {}}]) }}" + wikideck_trusted_proxies: "{{ invalid_case.trusted_proxies | default([]) }}" + wikideck_ops_port: "{{ invalid_case.ops_port | default('') }}" + wikideck_mainnet_launch: "{{ invalid_case.mainnet_launch | default('') }}" + wikideck_mainnet_risk_ack: "{{ invalid_case.risk_ack | default('') }}" block: - name: Run role input validation ansible.builtin.include_tasks: ../tasks/validate.yml diff --git a/tests/render.yml b/tests/render.yml index 5faebda..0b40c4a 100644 --- a/tests/render.yml +++ b/tests/render.yml @@ -15,6 +15,10 @@ wikideck_namespace: "{{ render_case.namespace | default('wikideck') }}" wikideck_peers: "{{ render_case.peers | default([]) }}" wikideck_allow_private_peers: "{{ render_case.allow_private | default(false) }}" + wikideck_mainnet_launch: "{{ render_case.mainnet_launch | default('') }}" + wikideck_mainnet_risk_ack: "{{ render_case.risk_ack | default('') }}" + wikideck_trusted_proxies: "{{ render_case.trusted_proxies | default([]) }}" + wikideck_ops_port: "{{ render_case.ops_port | default('') }}" wikideck_startup_probe_failure_threshold: "{{ render_case.startup_threshold | default(180) }}" wikideck_ingress_enabled: "{{ render_case.ingress | default(false) }}" wikideck_ingress_host: wikideck.example.com @@ -73,6 +77,13 @@ https_to: [] - label: digest-pinned image image: registry.example.com/wikideck@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - label: proxy trust and private diagnostics + peers: [https://peer-a.example.org, https://peer-b.example.org] + trusted_proxies: [10.20.30.40, 2001:db8::/64] + ops_port: "9090" + - label: exact mainnet launch configuration + mainnet_launch: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + risk_ack: operator-accepts-unaudited-risk loop_control: loop_var: render_case label: "{{ render_case.label }}" @@ -146,6 +157,14 @@ peer_egress: - to: [{podSelector: {}}] ports: [{protocol: UDP, port: 3001}] + - label: malformed mainnet chain approval + mainnet_launch: not-a-chain-id + - label: unsupported mainnet risk acknowledgement + risk_ack: operator-accepts-risk + - label: proxy entry is not an IP or CIDR + trusted_proxies: [proxy.example.org] + - label: operations port out of range + ops_port: "70000" loop_control: loop_var: invalid_case label: "{{ invalid_case.label }}" diff --git a/tests/render_case.yml b/tests/render_case.yml index 52ba6c4..6995d6e 100644 --- a/tests/render_case.yml +++ b/tests/render_case.yml @@ -48,6 +48,10 @@ - env.WIKIDECK_DATA_DIR == '/app/data' and env.NODE_ENV == 'production' - env.WIKIDECK_PEERS == wikideck_peers | join(',') - env.WIKIDECK_ALLOW_PRIVATE_PEERS == ('1' if wikideck_allow_private_peers else '0') + - env.WIKIDECK_MAINNET_LAUNCH == wikideck_mainnet_launch + - env.WIKIDECK_MAINNET_RISK_ACK == wikideck_mainnet_risk_ack + - (env.WIKIDECK_TRUSTED_PROXIES | default('')) == wikideck_trusted_proxies | join(',') + - (env.WIKIDECK_OPS_PORT | default('')) == (wikideck_ops_port | string) - env.WIKIDECK_OFFLINE is not defined - env.RANDOMX_BINARY is not defined - container.command is not defined and container.args is not defined diff --git a/tests/runtime-smoke.mjs b/tests/runtime-smoke.mjs index 50174f6..20196c4 100644 --- a/tests/runtime-smoke.mjs +++ b/tests/runtime-smoke.mjs @@ -2,7 +2,8 @@ import assert from 'node:assert/strict'; import { spawn, execFileSync } from 'node:child_process'; import { once } from 'node:events'; -import { readFile, access } from 'node:fs/promises'; +import { access } from 'node:fs/promises'; +import { DatabaseSync } from 'node:sqlite'; assert.equal(process.env.WIKIDECK_DATA_DIR, '/app/data'); assert.equal(process.env.WIKIDECK_PEERS, ''); @@ -39,17 +40,23 @@ async function runNode() { const body = await response.json(); if (path === '/healthz') { assert.equal(body.status, 'ok'); - assert.equal(body.network, 'wikideck-randomx-testnet-2'); + assert.equal(body.network, 'wikideck-randomx-testnet-6'); } else if (path === '/p2p/status') { - assert.equal(body.network, 'wikideck-randomx-testnet-2'); + assert.equal(body.network, 'wikideck-randomx-testnet-6'); assert.equal(body.size, 0); assert.match(body.chainId, /^[a-f0-9]{64}$/); } else assert.deepEqual(body.peers, []); } await access('/app/data/testnet-node.lock'); - const ledger = JSON.parse(await readFile('/app/data/testnet/ledger.json', 'utf8')); - assert.equal(ledger.network, 'wikideck-randomx-testnet-2'); - assert.deepEqual(ledger.blocks, []); + const ledger = new DatabaseSync('/app/data/testnet/ledger.sqlite'); + try { + const metadata = ledger.prepare('SELECT value FROM metadata WHERE key = ?').get('network'); + assert.equal(metadata.value, 'wikideck-randomx-testnet-6'); + assert.equal(ledger.prepare('SELECT COUNT(*) AS count FROM blocks').get().count, 0); + assert.equal(ledger.prepare('SELECT COUNT(*) AS count FROM pending_transfers').get().count, 0); + } finally { + ledger.close(); + } child.kill('SIGTERM'); assert.deepEqual(await exited, [0, null], output); await assert.rejects(access('/app/data/testnet-node.lock'), { code: 'ENOENT' });