- 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