Compare commits
2
Commits
d038fe3bab
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc194b8168
|
||
|
|
885f4160a7
|
@@ -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
|
||||
<http://127.0.0.1:3001>. 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
|
||||
peer origins (public HTTPS by default), 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
|
||||
@@ -98,7 +102,8 @@ HTTPS peer origins on another port, add that port to
|
||||
rules. Browser Wikimedia image fetches are client-side; validators no longer
|
||||
fetch random Wikipedia articles.
|
||||
|
||||
For an explicitly private testnet only, set `wikideck_allow_private_peers: true`
|
||||
For an explicitly private testnet or operator-controlled mainnet candidate cluster,
|
||||
set `wikideck_allow_private_peers: true`
|
||||
and provide `wikideck_network_policy_peer_egress` rules with destination selectors
|
||||
and numeric TCP ports. This opt-in permits **both private addresses and HTTP** in
|
||||
the application; it does not automatically open network policy. For example:
|
||||
@@ -124,7 +129,12 @@ wikideck_network_policy_peer_egress:
|
||||
The receiving node must separately allow the sender through its ingress policy.
|
||||
Service DNAT handling depends on the CNI; the example selects the destination pod
|
||||
and its target port 3001 rather than opening all destinations on Service port 80.
|
||||
Do not enable private-peer exceptions for public peer configuration.
|
||||
Do not enable private-peer exceptions for public peer configuration. Updated
|
||||
mainnet candidate images advertise `privatePeersSupported: true` in their manifest;
|
||||
older images reject the private opt-in. Browser HTTPS, chain approval, risk
|
||||
acknowledgment and arrived genesis remain mandatory. Private HTTP RPC is not
|
||||
authenticated or encrypted; keep it within a trusted cluster boundary or add
|
||||
independently configured mesh transport protection.
|
||||
|
||||
Deploy multiple validators as **separate role instances with different names and
|
||||
PVCs** (and separate hostnames if using ingress), not by scaling one Deployment.
|
||||
@@ -132,6 +142,45 @@ Sharing a machine/cluster/storage failure domain does not provide independent
|
||||
operator security. Empty local chains can synchronize from configured peers;
|
||||
never seed a second running node by sharing its writable PVC.
|
||||
|
||||
### Three-node latest candidate playbook
|
||||
|
||||
[playbooks/wikideck.yaml](playbooks/wikideck.yaml) configures the public `wikideck`
|
||||
node plus `wikideck-peer-a` and `wikideck-peer-b` within the same namespace. Each
|
||||
uses its own Service and 2 TiB PVC (6 TiB requested in total), reciprocal internal
|
||||
DNS peer URLs, scoped validator-pod ingress/egress, and public TCP 443 egress for
|
||||
Wikipedia article and Wikimedia statistics requests used by mining templates.
|
||||
Standard NetworkPolicy cannot select FQDNs; private address ranges remain excluded
|
||||
from that public HTTPS rule. Only the public node gets
|
||||
the `wikideck.eom.dev` TLS ingress. All three are created before readiness waits.
|
||||
This deploys validators, not miners, and does not establish independent operators.
|
||||
|
||||
The playbook uses this role checkout directly, not a separately installed older
|
||||
Galaxy role. It defaults to `ericomeehan/wikideck:latest` with pull policy `Always`
|
||||
and `wikideck_candidate_risk_ack: operator-accepts-unaudited-risk`. This explicit
|
||||
candidate workflow is not the digest-pinned public-launch example. Before creating
|
||||
ledger resources, a short-lived hardened Kubernetes Job runs the image's
|
||||
`--network-manifest` command. The playbook generates `wikideck_candidate_chain_id`
|
||||
from its output and rejects older images without private-peer support, non-mainnet
|
||||
images, or a future image-baked genesis. The Job is removed even on failure; it
|
||||
mounts no ledger, wallet secrets, or service-account token.
|
||||
|
||||
Rebuild and publish `latest` from the updated application before running this
|
||||
playbook. The default Dockerfile genesis is November 1, 2026, 00:00 UTC
|
||||
(`1793491200`), so a normal rebuild can start now without custom genesis arguments.
|
||||
Older images retain their old genesis; publishing updated source is required.
|
||||
Never change the chain identity of an existing ledger without a deliberate backup
|
||||
and migration plan. Do not move `latest` while inspection/deployment is in progress:
|
||||
all nodes must pull the same image. For reproducible public releases use the
|
||||
digest-pinned [examples/playbook.yml](examples/playbook.yml) instead. A mutable tag
|
||||
with `Always` is pulled when pods start. The playbook records the inspected
|
||||
container image ID in a pod-template annotation, so rerunning after publishing a
|
||||
new `latest` triggers a rollout; unchanged image IDs do not force a restart.
|
||||
Preserve existing data and keep the same genesis for updates to an operating chain.
|
||||
|
||||
The controller needs Job creation/deletion and Pod/log reads in addition to the
|
||||
role's permissions. Manifest inspection needs a live cluster, so this playbook
|
||||
rejects check mode; the role's offline render tests require no cluster credentials.
|
||||
|
||||
## Variables
|
||||
|
||||
All defaults are in [defaults/main.yml](defaults/main.yml).
|
||||
@@ -149,6 +198,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 +212,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 +235,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/<profile>/ledger.sqlite` (with WAL
|
||||
sidecars); `/app/data/<profile>-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 +256,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 +328,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 +345,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
|
||||
|
||||
+10
-1
@@ -19,8 +19,11 @@ wikideck_image_pull_secrets: []
|
||||
|
||||
# Static peer origins, at most 16. Empty means an isolated validator, not a network.
|
||||
wikideck_peers: []
|
||||
# Test/private networks only; permits private addresses AND HTTP in the app.
|
||||
# Operator-controlled test/private candidate clusters; permits private addresses AND HTTP.
|
||||
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,12 @@ wikideck_resources:
|
||||
wikideck_node_selector: {}
|
||||
wikideck_tolerations: []
|
||||
wikideck_affinity: {}
|
||||
# Pod-template annotations can trigger a rollout when a mutable image tag changes.
|
||||
wikideck_pod_annotations: {}
|
||||
# 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
|
||||
|
||||
+23
-7
@@ -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
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
- name: Deploy a three-node private Wikideck mainnet candidate
|
||||
hosts: alpha-control-plane
|
||||
become: true
|
||||
vars_files:
|
||||
- ~/Desktop/secrets.yaml
|
||||
vars:
|
||||
# This is an operator-controlled candidate, not an independent public network.
|
||||
wikideck_release_image: ericomeehan/wikideck:latest
|
||||
wikideck_candidate_risk_ack: operator-accepts-unaudited-risk
|
||||
wikideck_cluster_nodes:
|
||||
- wikideck
|
||||
- wikideck-peer-a
|
||||
- wikideck-peer-b
|
||||
wikideck_namespace: wikideck
|
||||
wikideck_image: "{{ wikideck_release_image }}"
|
||||
wikideck_image_pull_policy: Always
|
||||
# Roll out latest only when the inspected image changes, not on every run.
|
||||
wikideck_pod_annotations:
|
||||
wikideck.eom.dev/image-id: "{{ wikideck_manifest_image_id }}"
|
||||
wikideck_allow_private_peers: true
|
||||
wikideck_mainnet_launch: "{{ wikideck_candidate_chain_id }}"
|
||||
wikideck_mainnet_risk_ack: "{{ wikideck_candidate_risk_ack }}"
|
||||
# Internal validators share the browser origin, but only the first has ingress.
|
||||
wikideck_public_origin: https://wikideck.eom.dev
|
||||
wikideck_ingress_host: wikideck.eom.dev
|
||||
wikideck_ingress_class_name: nginx
|
||||
wikideck_ingress_annotations:
|
||||
cert-manager.io/cluster-issuer: ca-issuer
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
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([]) }}"
|
||||
# Start every node before waiting; the first must not wait for absent peers.
|
||||
wikideck_wait: false
|
||||
wikideck_wait_timeout: 900
|
||||
wikideck_network_policy_ingress_from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: wikideck
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: ingress-nginx
|
||||
# Mining templates fetch Wikipedia articles and Wikimedia statistics over HTTPS.
|
||||
# Standard NetworkPolicy cannot select FQDNs; permit public TCP 443, not private ranges.
|
||||
wikideck_network_policy_https_to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
except:
|
||||
- 10.0.0.0/8
|
||||
- 100.64.0.0/10
|
||||
- 127.0.0.0/8
|
||||
- 169.254.0.0/16
|
||||
- 172.16.0.0/12
|
||||
- 192.168.0.0/16
|
||||
- ipBlock:
|
||||
cidr: "::/0"
|
||||
except:
|
||||
- "::1/128"
|
||||
- "fc00::/7"
|
||||
- "fe80::/10"
|
||||
wikideck_network_policy_peer_egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: wikideck
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 3001
|
||||
pre_tasks:
|
||||
- name: Validate candidate cluster inputs
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- not ansible_check_mode
|
||||
- wikideck_release_image is string and wikideck_release_image is match('^[^\s@]+:latest$')
|
||||
- wikideck_cluster_nodes is sequence and wikideck_cluster_nodes is not string and wikideck_cluster_nodes is not mapping
|
||||
- wikideck_cluster_nodes | length == 3
|
||||
- wikideck_cluster_nodes | unique | list | length == 3
|
||||
- wikideck_cluster_nodes | select('match', '^[a-z]([a-z0-9-]*[a-z0-9])?$') | list | length == 3
|
||||
- wikideck_cluster_nodes | map('length') | max <= 50
|
||||
- wikideck_candidate_risk_ack | default('') == 'operator-accepts-unaudited-risk'
|
||||
fail_msg: >-
|
||||
This candidate playbook requires a latest-tag image, three unique node
|
||||
names, and operator-accepts-unaudited-risk. Check mode cannot inspect the
|
||||
image manifest; use the offline render tests instead.
|
||||
run_once: true
|
||||
|
||||
- name: Initialize manifest registry Secret references
|
||||
ansible.builtin.set_fact:
|
||||
wikideck_manifest_pull_secrets: []
|
||||
run_once: true
|
||||
|
||||
- name: Convert registry Secret names to Kubernetes references
|
||||
ansible.builtin.set_fact:
|
||||
wikideck_manifest_pull_secrets: "{{ wikideck_manifest_pull_secrets + [{'name': item}] }}"
|
||||
loop: "{{ wikideck_image_pull_secrets | default([]) }}"
|
||||
run_once: true
|
||||
|
||||
- name: Create namespace for read-only image inspection
|
||||
kubernetes.core.k8s:
|
||||
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
||||
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
||||
state: present
|
||||
apply: true
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ wikideck_namespace }}"
|
||||
run_once: true
|
||||
|
||||
- name: Inspect latest without opening a ledger or starting a validator
|
||||
run_once: true
|
||||
block:
|
||||
- name: Remove a stale manifest inspection Job
|
||||
kubernetes.core.k8s:
|
||||
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
||||
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
||||
state: absent
|
||||
api_version: batch/v1
|
||||
kind: Job
|
||||
namespace: "{{ wikideck_namespace }}"
|
||||
name: wikideck-candidate-manifest
|
||||
wait: true
|
||||
|
||||
- name: Run the image manifest command
|
||||
kubernetes.core.k8s:
|
||||
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
||||
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
||||
state: present
|
||||
wait: true
|
||||
wait_timeout: 300
|
||||
wait_condition:
|
||||
type: Complete
|
||||
status: "True"
|
||||
definition:
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: wikideck-candidate-manifest
|
||||
namespace: "{{ wikideck_namespace }}"
|
||||
spec:
|
||||
backoffLimit: 0
|
||||
activeDeadlineSeconds: 240
|
||||
ttlSecondsAfterFinished: 600
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
automountServiceAccountToken: false
|
||||
enableServiceLinks: false
|
||||
imagePullSecrets: "{{ wikideck_manifest_pull_secrets }}"
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: manifest
|
||||
image: "{{ wikideck_release_image }}"
|
||||
imagePullPolicy: Always
|
||||
command:
|
||||
- node
|
||||
- --experimental-strip-types
|
||||
- server/index.ts
|
||||
- --network-manifest
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
resources:
|
||||
requests: {cpu: 100m, memory: 128Mi}
|
||||
limits: {cpu: "1", memory: 512Mi}
|
||||
|
||||
- name: Locate the completed manifest pod
|
||||
kubernetes.core.k8s_info:
|
||||
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
||||
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
||||
kind: Pod
|
||||
namespace: "{{ wikideck_namespace }}"
|
||||
label_selectors:
|
||||
- job-name=wikideck-candidate-manifest
|
||||
register: wikideck_manifest_pods
|
||||
|
||||
- name: Require exactly one completed manifest pod
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- wikideck_manifest_pods.resources | length == 1
|
||||
- wikideck_manifest_pods.resources[0].status.phase == 'Succeeded'
|
||||
|
||||
- name: Read the public image manifest
|
||||
kubernetes.core.k8s_log:
|
||||
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
||||
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
||||
namespace: "{{ wikideck_namespace }}"
|
||||
name: "{{ wikideck_manifest_pods.resources[0].metadata.name }}"
|
||||
container: manifest
|
||||
register: wikideck_manifest_log
|
||||
|
||||
- name: Parse the image identity
|
||||
ansible.builtin.set_fact:
|
||||
wikideck_candidate_manifest: "{{ wikideck_manifest_log.log | from_json }}"
|
||||
|
||||
- name: Require a compatible image and an arrived image-baked genesis
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- wikideck_candidate_manifest.profile == 'mainnet'
|
||||
- wikideck_candidate_manifest.network == 'wikideck-randomx-mainnet-1'
|
||||
- wikideck_candidate_manifest.privatePeersSupported | default(false) == true
|
||||
- wikideck_candidate_manifest.chainId is string and wikideck_candidate_manifest.chainId is match('^[a-f0-9]{64}$')
|
||||
- wikideck_candidate_manifest.genesisTime is integer
|
||||
- wikideck_candidate_manifest.genesisTime >= 1700000000 and wikideck_candidate_manifest.genesisTime <= 4102444800
|
||||
- wikideck_candidate_manifest.genesisTime <= ansible_date_time.epoch | int
|
||||
fail_msg: >-
|
||||
Rebuild and publish latest from code supporting private candidate
|
||||
peers, with a deliberately chosen, arrived mainnet genesis frozen
|
||||
into the image. Runtime genesis overrides are not supported.
|
||||
|
||||
- name: Generate the candidate chain approval from this image
|
||||
ansible.builtin.set_fact:
|
||||
wikideck_candidate_chain_id: "{{ wikideck_candidate_manifest.chainId }}"
|
||||
wikideck_release_genesis_time: "{{ wikideck_candidate_manifest.genesisTime }}"
|
||||
wikideck_manifest_image_id: "{{ wikideck_manifest_pods.resources[0].status.containerStatuses[0].imageID }}"
|
||||
always:
|
||||
- name: Remove the manifest inspection Job and its pod
|
||||
kubernetes.core.k8s:
|
||||
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
||||
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
||||
state: absent
|
||||
api_version: batch/v1
|
||||
kind: Job
|
||||
namespace: "{{ wikideck_namespace }}"
|
||||
name: wikideck-candidate-manifest
|
||||
wait: true
|
||||
tasks:
|
||||
- name: Deploy each validator with its own service and persistent ledger
|
||||
ansible.builtin.include_role:
|
||||
# Use this updated checkout rather than an older installed Galaxy role.
|
||||
name: "{{ playbook_dir }}/.." # noqa: role-name[path]
|
||||
vars:
|
||||
wikideck_name: "{{ wikideck_cluster_node }}"
|
||||
wikideck_existing_claim: ""
|
||||
wikideck_ingress_enabled: "{{ wikideck_cluster_node == wikideck_cluster_nodes[0] }}"
|
||||
wikideck_peers: >-
|
||||
{{ wikideck_cluster_nodes | reject('equalto', wikideck_cluster_node)
|
||||
| map('regex_replace', '^', 'http://')
|
||||
| map('regex_replace', '$', '.' ~ wikideck_namespace ~ '.svc.cluster.local:80') | list }}
|
||||
loop: "{{ wikideck_cluster_nodes }}"
|
||||
loop_control:
|
||||
loop_var: wikideck_cluster_node
|
||||
run_once: true
|
||||
|
||||
- name: Wait for all three validators after creating the full peer topology
|
||||
kubernetes.core.k8s_info:
|
||||
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
||||
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
||||
api_version: apps/v1
|
||||
kind: Deployment
|
||||
namespace: "{{ wikideck_namespace }}"
|
||||
name: "{{ item }}"
|
||||
wait: true
|
||||
wait_timeout: "{{ wikideck_wait_timeout | int }}"
|
||||
wait_condition:
|
||||
type: Available
|
||||
status: "True"
|
||||
loop: "{{ wikideck_cluster_nodes }}"
|
||||
run_once: true
|
||||
+13
-1
@@ -19,6 +19,7 @@
|
||||
- wikideck_resources.limits.cpu is defined and wikideck_resources.limits.memory is defined
|
||||
- wikideck_node_selector is mapping
|
||||
- wikideck_affinity is mapping
|
||||
- wikideck_pod_annotations is mapping
|
||||
- wikideck_ingress_annotations is mapping
|
||||
- wikideck_storage_size is string and wikideck_storage_size | length > 0
|
||||
- wikideck_storage_access_modes | length > 0
|
||||
@@ -28,6 +29,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 +79,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:
|
||||
@@ -84,7 +96,7 @@
|
||||
- wikideck_allow_private_peers or item is match('^https://')
|
||||
fail_msg: >-
|
||||
Peers must be HTTPS origins without credentials, paths, commas, queries or
|
||||
fragments. Private/HTTP test peers require wikideck_allow_private_peers=true.
|
||||
fragments. Private/HTTP cluster peers require wikideck_allow_private_peers=true.
|
||||
Wikideck additionally validates ports and public DNS/IP addresses at runtime.
|
||||
loop: "{{ wikideck_peers }}"
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ spec:
|
||||
app.kubernetes.io/instance: {{ wikideck_name | to_json }}
|
||||
template:
|
||||
metadata:
|
||||
annotations: {{ wikideck_pod_annotations | to_json }}
|
||||
labels:
|
||||
app.kubernetes.io/name: wikideck
|
||||
app.kubernetes.io/instance: {{ wikideck_name | to_json }}
|
||||
@@ -71,6 +72,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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,8 +13,14 @@
|
||||
wikideck_image: "{{ render_case.image | default('registry.example.com/wikideck:1.0.0') }}"
|
||||
wikideck_name: "{{ render_case.name | default('wikideck') }}"
|
||||
wikideck_namespace: "{{ render_case.namespace | default('wikideck') }}"
|
||||
wikideck_image_pull_policy: "{{ render_case.pull_policy | default('IfNotPresent') }}"
|
||||
wikideck_pod_annotations: "{{ render_case.pod_annotations | default({}) }}"
|
||||
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 +79,59 @@
|
||||
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
|
||||
- label: latest private mainnet candidate public node
|
||||
image: ericomeehan/wikideck:latest
|
||||
pull_policy: Always
|
||||
pod_annotations:
|
||||
wikideck.eom.dev/image-id: docker-pullable://ericomeehan/wikideck@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
peers: [http://wikideck-peer-a.wikideck.svc.cluster.local:80, http://wikideck-peer-b.wikideck.svc.cluster.local:80]
|
||||
allow_private: true
|
||||
ingress: true
|
||||
mainnet_launch: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
risk_ack: operator-accepts-unaudited-risk
|
||||
# Public HTTPS is needed for Wikipedia mining templates and statistics.
|
||||
https_to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
except: [10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16]
|
||||
- ipBlock:
|
||||
cidr: "::/0"
|
||||
except: ["::1/128", "fc00::/7", "fe80::/10"]
|
||||
ingress_from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: wikideck
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: ingress-nginx
|
||||
peer_egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: wikideck
|
||||
ports: [{protocol: TCP, port: 3001}]
|
||||
- label: latest private mainnet candidate internal peer
|
||||
image: ericomeehan/wikideck:latest
|
||||
pull_policy: Always
|
||||
name: wikideck-peer-a
|
||||
peers: [http://wikideck.wikideck.svc.cluster.local:80, http://wikideck-peer-b.wikideck.svc.cluster.local:80]
|
||||
allow_private: true
|
||||
mainnet_launch: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
risk_ack: operator-accepts-unaudited-risk
|
||||
https_to: []
|
||||
peer_egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: wikideck
|
||||
ports: [{protocol: TCP, port: 3001}]
|
||||
loop_control:
|
||||
loop_var: render_case
|
||||
label: "{{ render_case.label }}"
|
||||
@@ -146,6 +205,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 }}"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
that:
|
||||
- deployment.spec.replicas == 1
|
||||
- deployment.spec.strategy.type == 'Recreate'
|
||||
- deployment.spec.template.metadata.annotations == wikideck_pod_annotations
|
||||
- deployment.spec.selector.matchLabels == deployment.spec.template.metadata.labels
|
||||
- wikideck_test_manifests.service.spec.selector == deployment.spec.selector.matchLabels
|
||||
- wikideck_test_manifests.networkpolicy.spec.podSelector.matchLabels == deployment.spec.selector.matchLabels
|
||||
@@ -33,6 +34,7 @@
|
||||
- not container.securityContext.allowPrivilegeEscalation
|
||||
- container.securityContext.capabilities.drop == ['ALL']
|
||||
- container.image == wikideck_image
|
||||
- container.imagePullPolicy == wikideck_image_pull_policy
|
||||
- container.resources == wikideck_resources
|
||||
- container.ports[0].containerPort == 3001
|
||||
- container.startupProbe.httpGet.path == '/healthz'
|
||||
@@ -48,6 +50,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
|
||||
|
||||
+13
-6
@@ -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' });
|
||||
|
||||
Reference in New Issue
Block a user