Files
ansible-role-wikideck/playbooks/wikideck.yaml
T
2026-11-01 17:11:07 -05:00

269 lines
12 KiB
YAML

- 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