2026-11-01 17:11:07 -05:00
2026-11-01 17:11:07 -05:00
2026-10-29 11:44:17 -04:00
2026-10-29 11:44:17 -04:00
2026-11-01 17:11:07 -05:00
2026-11-01 17:11:07 -05:00
2026-11-01 17:11:07 -05:00
2026-11-01 17:11:07 -05:00
v2
2026-10-19 12:51:18 -04:00
2026-11-01 17:11:07 -05:00
v2
2026-10-19 12:51:18 -04:00
v2
2026-10-19 12:51:18 -04:00

Ansible role: Wikideck

Deploy the Wikideck production container to an existing Kubernetes cluster using kubernetes.core.k8s. The role creates a single-replica Deployment, a persistent volume claim, a dedicated ServiceAccount, a ClusterIP Service, and a NetworkPolicy. An HTTPS Ingress is optional and disabled by default.

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. See the upstream mainnet launch gates and operations/recovery guidance.

Requirements

  • A Linux/macOS/WSL controller with Python 3.10+ and Ansible Core 2.16–2.19 (the selected Ansible release may require a newer Python).
  • Install controller packages from requirements.txt and the collection from requirements.yml.
  • A supported Kubernetes cluster with apps/v1 and networking.k8s.io/v1 APIs, a storage provisioner or an existing PVC, and a NetworkPolicy-capable CNI.
  • 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, immutable image built from Wikideck's production Dockerfile. There is no assumed official registry image. Set wikideck_image to your actual tagged or 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. 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.

Install dependencies on the controller with pip install -r requirements.txt and ansible-galaxy collection install -r requirements.yml. Use an isolated Python environment. Windows controllers must run Ansible through WSL or Linux containers.

Usage

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 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 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. 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 for browser wallet cryptography. Configure HTTP-to-HTTPS redirect at your ingress controller; the example includes the ingress-nginx annotation. Ingress creation does not wait for DNS, certificate issuance, or an externally reachable endpoint.

Connect validators

Set wikideck_peers to a list of up to 16 compatible peer HTTPS origins, such as https://peer-a.example.org. The role joins this list into WIKIDECK_PEERS. 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 those routes. Keep peer JSON responses uncompressed and do not redirect HTTPS peer requests: the client rejects redirects, non-JSON and compressed responses. An interactive login gateway on /p2p/* will break the current unauthenticated peer protocol; use deliberate source restrictions/abuse controls instead.

Public peers must use HTTPS and public DNS/IP addresses. Wikideck validates every connection's DNS answers and pins the connection to that validated result. Never disable TLS verification to work around a certificate or split-horizon DNS error. The default policy allows public TCP 443 only when peers are configured. For HTTPS peer origins on another port, add that port to wikideck_network_policy_https_ports, or supply narrowly scoped explicit egress rules. Browser Wikimedia image fetches are client-side; validators no longer fetch random Wikipedia articles.

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:

wikideck_peers:
  - http://other-node.games.svc.cluster.local:80
wikideck_allow_private_peers: true
wikideck_network_policy_https_to: []
wikideck_network_policy_peer_egress:
  - to:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: games
        podSelector:
          matchLabels:
            app.kubernetes.io/instance: other-node
    ports:
      - protocol: TCP
        port: 3001

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. 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. 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 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 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.

Variable Default Description
wikideck_image empty, required Published image with explicit tag or SHA-256 digest; prefer a verified digest.
wikideck_name wikideck Resource name and instance label; at most 50 characters.
wikideck_namespace wikideck Namespace for all namespaced resources.
wikideck_create_namespace true Create the namespace; disable for a pre-provisioned namespace.
wikideck_kubeconfig / wikideck_kube_context null Override controller authentication/context. API TLS verification remains enabled.
wikideck_wait / wikideck_wait_timeout true / 900 Wait for Deployment readiness, timeout in seconds.
wikideck_startup_probe_failure_threshold 180 Failed probes at 5-second intervals before startup restart; default budget 900 seconds.
wikideck_image_pull_policy IfNotPresent Always, IfNotPresent, or Never.
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.
wikideck_ingress_class_name empty Omit to use the cluster default IngressClass.
wikideck_ingress_annotations {} Controller/cert-manager-specific configuration.
wikideck_tls_secret_name {{ wikideck_name }}-tls Existing or externally provisioned TLS Secret.
wikideck_existing_claim empty Use an existing PVC instead of creating one.
wikideck_storage_size 1Gi Requested PVC capacity; resizing requires StorageClass support.
wikideck_storage_class null null: cluster default; empty string: no class; otherwise a class name.
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.
wikideck_network_policy_https_to public IPv4/IPv6 ranges, private/link-local excluded Public peer destinations; omitted when there are no configured peers.
wikideck_network_policy_https_ports [443] Public peer TCP ports; extend for custom HTTPS origins.
wikideck_network_policy_peer_egress [] Additional explicit to + numeric TCP ports rules, emitted only with configured peers.

wikideck_offline / WIKIDECK_OFFLINE have been removed. The current catalog is fixed and genesis-bound, not a live Wikipedia feed. Remove the old role variable; it no longer controls networking. The image's packaged RANDOMX_BINARY and default entrypoint are deliberately preserved; no production fake-PoW fallback is added.

Storage and safe updates

  • Exactly one replica and Recreate updates are deliberate and cannot be changed through role variables. Updates briefly interrupt service. Do not add an HPA, manually scale, or attach the same chain PVC to another writer.
  • ReadWriteOnce alone does not enforce a single pod on the same node. Prefer 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.
  • 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. No privileged/root ownership-fixing init container is used.
  • The PVC is created before the Deployment without waiting, supporting WaitForFirstConsumer storage classes. Existing claims are not modified.
  • The role never deletes PVCs or Namespaces, and does not assign ownerReferences that would garbage-collect storage with the Deployment. Renaming resources or switching claims does not migrate data or remove previous resources.
  • Back up the chain before upgrades. Stop the Deployment for a consistent file 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.
  • 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

The application removes its lock on graceful shutdown but deliberately does not automatically recover a stale lock after an OOM kill, forced termination, node loss, or interruption during startup replay. Kubernetes restarts can remain in CrashLoopBackOff until an operator intervenes. This role never deletes the lock in an init container or as an automatic "repair".

To recover, first stop the Deployment and verify every former owner is gone; fence an unreachable old node before mounting the claim elsewhere. Take a backup, then remove only the confirmed stale lock using an approved maintenance pod or storage procedure. Preserve the ledger, restore the Deployment to one replica, and inspect replay and peer status. Do not automatically remove locks based on their PID: PIDs are reused and isolated between containers.

Each validator uses native light-mode RandomX, requiring a 256 MiB cache plus scratchpad, Node heap, ledger states, and process overhead. The new 1 GiB limit matches the updated upstream Compose starting budget, but must be measured against your ledger, peer load and replay. Watch OOM events, CPU throttling, replay time, and block propagation. Fast miners need over 2.3 GiB per process and separate capacity/security planning; this role never launches one or mounts its wallet.

Security and networking

The workload uses a read-only root filesystem, drops all Linux capabilities, disallows privilege escalation, uses runtime-default seccomp, and has no mounted service account token or RBAC grants. Only persistent data and a 32 MiB memory temporary directory are writable. All three HTTP probes target /healthz on port 3001; Kubernetes does not use the image's Docker HEALTHCHECK. A 30-second grace period accommodates the application's 25-second shutdown deadline.

NetworkPolicy defaults allow same-namespace clients, cluster DNS in kube-system, and public HTTPS only when peer origins are configured. With ingress, allow the ingress controller's namespace/pods as shown in the example. Peer entries are additive OR alternatives: put a namespaceSelector and podSelector in the same entry to restrict pods within that namespace. Empty peer lists deny that traffic; empty peer objects are rejected to avoid accidentally allowing every source/destination.

Standard NetworkPolicy cannot allow peer origins by hostname; public TCP 443 egress is broader than the app's fixed peer allowlist. Narrow CIDRs or use CNI FQDN policies or an egress gateway if required. Policies are additive with any other policies in the namespace and have no effect without an enforcing CNI. NodeLocal DNS, host-network ingress controllers, service meshes, and private proxies may need peer changes.

TLS terminates at ingress; backend HTTP is private but not encrypted by this role. Use a properly configured service mesh if in-cluster encryption is required. TLS keys and registry credentials must be provisioned separately, not committed to playbooks. Use trusted, rotated certificates and retain Kubernetes API certificate verification. The role does not embed secrets or implement its own cryptography; image signature verification and encrypted storage are cluster operator responsibilities. Scan and rebuild the application image regularly.

Native RandomX uses ordinary memory allocations and secure W^X JIT; the current app does not request huge pages, host devices, executable files in /tmp, or privileged capabilities. Keep the existing hardening. Verify actual hashing on 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_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.

Validation

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, 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 is a real-cluster smoke test using the local checkout. Syntax-check it with ansible-playbook -i tests/inventory tests/test.yml --syntax-check. To deploy to a disposable test namespace, run it with ansible-playbook -i tests/inventory tests/test.yml -e wikideck_image=YOUR_IMAGE -e wikideck_namespace=wikideck-test. Run it a second time to verify idempotence. Check Pod readiness, /healthz, PVC retention across updates, DNS/peer egress, TLS, and wallet/transfer flows on your chosen cluster. /healthz is only process health, not sync readiness. Inspect /api/peers for connected peers and /p2p/status for matching network, chainId and tip across independently deployed nodes. /api/peers exposes configuration; apply your access policy deliberately. Offline tests do not prove CSI/CNI/controller behavior or consensus safety. Mining a real block is a separate explicit-consent test; it must not be part of routine deployment or health probes.

tests/runtime-smoke.mjs is a disposable image-level check: it runs the native known-vector self-test (hashing, not mining), checks node API health, and verifies graceful lock cleanup and restart durability. Mount it read-only and invoke Node inside the current image with disposable writable data and no network. It does not use or modify a real chain or wallet.

Ansible check mode (--check --diff) is supported by the Kubernetes module, but still needs API access; creating a new namespace in check mode may require a separate namespace provisioning pass. No uninstall or automatic data deletion is provided.

License

MIT-0. See LICENSE. The application and Wikipedia/Wikimedia content retain their own upstream licenses and attribution requirements.

S
Description
Ansible role to deplooy WikiDeck to Kubernetes.
Readme MIT-0
122 KiB
Languages
Jinja 73.5%
JavaScript 26.5%