Ansible source
Read the playbooks and supporting code used to reconcile this demo.
Run the combined deployment
ansible-playbook -i localhost, ansible/expanded.yml
ansible-playbook -i localhost, ansible/verify.yml
The combined playbook covers the database, email, registry and source-control checks, load balancer and web application. Existing compute, S3, DNS and TLS remain prerequisites. AI generation and API Gateway are excluded.
This workflow was tested against the existing demo. A fresh empty-account build has not been tested. It requires operator credentials, private state and administrative SSH access. The recovery playbook handles a confirmed managed-worker library failure for a named demo resource.
These are the actual source files, including role defaults and helper scripts. Credential values, private state, SSH keys and inventories are excluded.
Browse the files
Select a filename to read its source.
ansible/expanded.yml
---
- name: Reconcile expanded Piranha Superdemo without replacing existing infrastructure
hosts: localhost
connection: local
gather_facts: false
vars:
superdemo_platform: piranha
roles:
- role: expanded_services
tags: [services]
- role: expanded_webapp
tags: [webapp]
ansible/roles/expanded_services/files/run_action.py
#!/usr/bin/env python3
"""Fixed demo operations. Never accepts executable code or resource arguments."""
import base64,fcntl,hashlib,json,os,subprocess,sys,tempfile,time
from pathlib import Path
ACTIONS={'compute','launch','platform','dns','storage','email','registry','source','balancer'}
action=sys.argv[1] if len(sys.argv)==2 else ''
if action not in ACTIONS:raise SystemExit('Unknown demo action')
credentials=json.loads(Path('/etc/superdemo/api.json').read_text())
key=credentials['key'];secret=credentials['secret'];env=os.environ.copy()
env.update(AWS_ACCESS_KEY_ID=key,AWS_SECRET_ACCESS_KEY=secret,AWS_DEFAULT_REGION='eu-west-2',AWS_EC2_METADATA_DISABLED='true',AWS_PAGER='',GIT_TERMINAL_PROMPT='0')
def execute(args,stdin=None):
p=subprocess.run(args,input=stdin,capture_output=True,text=True,env=env,timeout=45)
if p.returncode:raise RuntimeError((p.stderr or 'Operation failed').replace(key,'[redacted]').replace(secret,'[redacted]')[:2000])
return p.stdout[:64000]
try:
aws=['/opt/superdemo-tools/bin/aws'];endpoint=['--endpoint-url','https://aws.piranha.sh','--region','eu-west-2','--output','json']
if action in {'compute','platform','launch'}:
args=aws+['ec2','describe-instances','--instance-ids','10117367']
if action=='launch':args+=['--query','Reservations[].Instances[].LaunchTime']
output=execute(args+endpoint)
elif action=='dns':output=execute(aws+['route53','list-resource-record-sets','--hosted-zone-id','0d3d16f1c5db2ef8']+endpoint)
elif action=='storage':
with tempfile.TemporaryDirectory(prefix='superdemo-object-') as directory:
target=Path(directory)/'kitten.jpg';metadata=execute(aws+['s3api','get-object','--bucket','demo-assets-1749723872','--key','kitten.jpg',str(target)]+endpoint)
data=target.read_bytes();output=metadata+'\nDownloaded kitten.jpg: '+str(len(data))+' bytes\nSHA-256: '+hashlib.sha256(data).hexdigest()
elif action=='email':
with open('/var/lib/superdemo/run/email.lock','a+') as lock:
if not fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB)==None:raise RuntimeError('Email test already running')
lock.seek(0);last=float(lock.read() or 0)
if time.time()-last<60:raise RuntimeError('Wait one minute between demo emails.')
lock.seek(0);lock.truncate();lock.write(str(time.time()));lock.flush()
execute(['python3','/usr/local/lib/superdemo/email_proof.py','--state','/var/lib/superdemo/email','--credentials','/etc/superdemo/mailbox.json'])
mail=json.loads(Path('/var/lib/superdemo/email/mail-proof.json').read_text());output='Delivered to: '+mail['recipient']+'\nSubject: '+mail['subject']+'\n\n'+mail['body']
elif action=='registry':
config='user = '+json.dumps(key+':'+secret)+'\n'
output=execute(['curl','--silent','--show-error','--fail','--max-time','30','--config','-','--header','Accept: application/vnd.docker.distribution.manifest.v2+json','https://pcr.piranha.sh/v2/d1be53099a9625807233466c/superdemo-web/manifests/1'],config)
output=json.dumps(json.loads(output),indent=2)
elif action=='source':
auth=base64.b64encode((key+':'+secret).encode()).decode();env.update(GIT_CONFIG_COUNT='1',GIT_CONFIG_KEY_0='http.extraHeader',GIT_CONFIG_VALUE_0='Authorization: Basic '+auth)
output=execute(['git','ls-remote','https://scm.piranha.sh/git/d1be53099a9625807233466c/superdemo-example','refs/heads/master'])
elif action=='balancer':output=execute(['curl','--silent','--show-error','--fail','--include','--max-time','15','http://45.148.50.46/lb-proof'])
print(json.dumps({'ok':True,'output':output}))
except Exception as error:
print(json.dumps({'ok':False,'error':str(error).replace(key,'[redacted]').replace(secret,'[redacted]')}));sys.exit(1)
ansible/demo-artifacts.yml
---
- name: Publish and read back the dedicated demo Git repository and container
hosts: localhost
connection: local
gather_facts: false
tasks:
- name: Push source, clone it, build/push/pull image and test its HTTP response
ansible.builtin.command:
argv: [python3, "{{ playbook_dir }}/../scripts/publish_demo_artifacts.py"]
no_log: true
changed_when: true
scripts/publish_demo_artifacts.py
#!/usr/bin/env python3
"""Publish only the small demo-owned example; credentials remain in private state."""
import base64,configparser,json,os,subprocess,tempfile,urllib.request,urllib.parse,time
from pathlib import Path
from datetime import datetime,timezone
root=Path(__file__).resolve().parents[1]
state=Path.home()/'.local/state/superdemo-resources';state.mkdir(mode=0o700,parents=True,exist_ok=True)
c=configparser.ConfigParser();c.read(Path.home()/'.aws/credentials');p=c['piranhademo'];key=p['aws_access_key_id'];secret=p['aws_secret_access_key']
auth=base64.b64encode((key+':'+secret).encode()).decode()
def run(cmd,**kw):
r=subprocess.run(cmd,text=True,capture_output=True,timeout=240,**kw)
if r.returncode:
log=state/'artifact-command-error.log';log.write_text((r.stdout+r.stderr).replace(secret,'[redacted]').replace(key,'[redacted]').replace(auth,'[redacted]'));log.chmod(0o600)
raise RuntimeError('Command failed: '+cmd[0]+' '+cmd[1]+' (details retained privately)')
return r.stdout
# Create is idempotent: inspect this exact repository before requesting creation.
def api(path,args):
args=dict(args,key_id=key,secret_key=secret)
with urllib.request.urlopen('https://api.piranha.sh'+path,data=urllib.parse.urlencode(args).encode(),timeout=45) as r:return json.load(r)
name='superdemo-example';repo_url='https://scm.piranha.sh/git/d1be53099a9625807233466c/'+name
try: exists=api('/api/scm/repository/one',{'repository_id':name}).get('status')=='OK'
except Exception:exists=False
if not exists:
result=api('/api/scm/repository/create',{'repository_name':name,'repository_description':'Source for the Piranha Superdemo service demonstration'})
if result.get('status')!='OK':raise RuntimeError('Demo repository creation failed')
env=os.environ.copy();env.update(GIT_CONFIG_COUNT='1',GIT_CONFIG_KEY_0='http.extraHeader',GIT_CONFIG_VALUE_0='Authorization: Basic '+auth,GIT_TERMINAL_PROMPT='0')
with tempfile.TemporaryDirectory(prefix='superdemo-git-') as d:
repo=Path(d)/'repo';run(['git','clone',repo_url,str(repo)],env=env)
for f in ['Dockerfile','index.html','serve.sh']:(repo/f).write_bytes((root/'examples/container-web'/f).read_bytes())
run(['git','add','Dockerfile','index.html','serve.sh'],cwd=repo,env=env)
if run(['git','diff','--cached','--name-only'],cwd=repo,env=env).strip():
run(['git','-c','user.name=Superdemo','-c','user.email=superdemo-proof@piranhademo.website','commit','-m','Add the Hello from Piranha container example'],cwd=repo,env=env)
run(['git','push','origin','HEAD:master'],cwd=repo,env=env)
clone=Path(d)/'readback';run(['git','clone','--branch','master',repo_url,str(clone)],env=env)
commit=run(['git','rev-parse','HEAD'],cwd=clone).strip()
for f in ['Dockerfile','index.html','serve.sh']:assert (clone/f).read_bytes()==(root/'examples/container-web'/f).read_bytes()
conf=state/'docker-demo';conf.mkdir(mode=0o700,exist_ok=True);cp=conf/'config.json';cp.write_text(json.dumps({'auths':{'pcr.piranha.sh':{'auth':auth}}}));cp.chmod(0o600)
image='pcr.piranha.sh/d1be53099a9625807233466c/superdemo-web:1';docker=['docker','--config',str(conf)]
run(docker+['build','-t',image,str(root/'examples/container-web')]);run(docker+['push',image]);run(docker+['pull',image])
container=run(docker+['run','--detach','--network','none',image]).strip()
try:
for attempt in range(10):
try: body=run(docker+['exec',container,'wget','-qO-','http://127.0.0.1:8080']);break
except RuntimeError:time.sleep(1)
else:raise RuntimeError('Container HTTP response unavailable')
assert body==(root/'examples/container-web/index.html').read_text()
finally:run(docker+['rm','--force',container])
digest=json.loads(run(docker+['image','inspect',image]))[0]['RepoDigests'][0]
proof={'checked_at':datetime.now(timezone.utc).isoformat(timespec='seconds'),'repository':name,'clone_url':repo_url,'commit':commit,'files':{f:(root/'examples/container-web'/f).read_text() for f in ['Dockerfile','index.html','serve.sh']},'image':image,'image_digest':digest,'git_clone_readback':True,'registry_push_pull':True,'container_http_body_match':True,'container_response':body}
(root/'website/demo-artifacts.json').write_text(json.dumps(proof,indent=2)+'\n')
print(json.dumps({k:v for k,v in proof.items() if k not in ['files','container_response']}))
ansible/verify.yml
---
- name: Verify the expanded live Piranha demo with real operations
hosts: localhost
connection: local
gather_facts: false
vars_files:
- roles/expanded_services/defaults/main.yml
tasks:
- name: Refresh authenticated registry and SCM evidence
ansible.builtin.command:
argv: ["{{ ansible_playbook_python }}", "{{ playbook_dir }}/roles/expanded_services/files/resources.py", --mode, evidence, --state, "{{ superdemo_state_dir }}", --config, "{{ superdemo_state_dir }}/desired.json", --profile, "{{ superdemo_profile }}"]
changed_when: false
- name: Exercise SQL persistence, rejected CSRF, LB forwarding, S3 and received email
ansible.builtin.command:
argv: ["{{ ansible_playbook_python }}", "{{ playbook_dir }}/roles/expanded_services/files/verify.py", --state, "{{ superdemo_state_dir }}", --profile, "{{ superdemo_profile }}"]
register: verification
changed_when: false
- name: Report verified outcomes
ansible.builtin.debug:
var: verification.stdout
ansible/email-proof.yml
---
- name: Send one verification email only to the dedicated demo-controlled mailbox
hosts: localhost
connection: local
gather_facts: false
vars_files:
- roles/expanded_services/defaults/main.yml
tasks:
- name: Send via authenticated SMTP TLS and match the received IMAP body
ansible.builtin.command:
argv: ["{{ ansible_playbook_python }}", "{{ playbook_dir }}/roles/expanded_services/files/email_proof.py", --state, "{{ superdemo_state_dir }}"]
changed_when: true
- name: Save the public receipt without mailbox credentials
ansible.builtin.copy:
src: "{{ superdemo_state_dir }}/mail-proof.json"
dest: "{{ playbook_dir }}/../website/email-proof.json"
mode: '0644'
ansible/recover.yml
---
- name: Recover one named Superdemo managed job after confirmed library collision
hosts: localhost
connection: local
gather_facts: false
vars_files:
- roles/expanded_services/defaults/main.yml
vars:
superdemo_control_host: piranha@prod-control-2
tasks:
- name: Load private resource identities
ansible.builtin.set_fact:
managed: "{{ lookup('file', superdemo_state_dir + '/managed-resources.json') | from_json }}"
- name: Validate bounded recovery selection
ansible.builtin.assert:
that:
- superdemo_recovery_kind in ['db', 'lb']
- name: Select the named demo job
ansible.builtin.set_fact:
superdemo_recovery_id: "{{ managed.database.id if superdemo_recovery_kind == 'db' else managed.load_balancer.id }}"
superdemo_recovery_name: "{{ managed.database.name if superdemo_recovery_kind == 'db' else managed.load_balancer.name }}"
worker_name: "{{ 'dbcreate' if superdemo_recovery_kind == 'db' else 'loadbalancercreate' }}"
- name: Validate resource identity before rendering PHP
ansible.builtin.assert:
that:
- superdemo_recovery_id is match('^[0-9]+$')
- superdemo_recovery_name is match('^superdemo-[a-z0-9-]+$')
- superdemo_project is match('^cl-[a-f0-9]+$')
- name: Render isolated worker library-loader recovery
ansible.builtin.template:
src: roles/expanded_services/templates/recover.php.j2
dest: "{{ superdemo_state_dir }}/recover-{{ superdemo_recovery_kind }}.php"
mode: '0600'
- name: Find the existing production worker without changing local kube context
ansible.builtin.command:
argv:
- ssh
- -o
- BatchMode=yes
- "{{ superdemo_control_host }}"
- "sudo kubectl -n piranhacloud get pods -o name"
register: workers
changed_when: false
- name: Select exactly one worker
ansible.builtin.set_fact:
worker_pods: "{{ workers.stdout_lines | select('search', 'cronplus-' + worker_name + '-depl-') | list }}"
- ansible.builtin.assert:
that: worker_pods | length == 1
- name: Copy isolated script to control host
ansible.builtin.command:
argv: [scp, -q, "{{ superdemo_state_dir }}/recover-{{ superdemo_recovery_kind }}.php", "{{ superdemo_control_host }}:/tmp/superdemo-recover-{{ superdemo_recovery_kind }}.php"]
changed_when: false
- name: Execute scoped recovery and retain private worker logs
ansible.builtin.command:
argv:
- ssh
- -o
- BatchMode=yes
- "{{ superdemo_control_host }}"
- "sudo kubectl -n piranhacloud cp /tmp/superdemo-recover-{{ superdemo_recovery_kind }}.php {{ worker_pods[0] | regex_replace('^pod/', '') }}:/tmp/superdemo-recover.php && sudo kubectl -n piranhacloud exec {{ worker_pods[0] | regex_replace('^pod/', '') }} -- sh -c 'umask 077; php /tmp/superdemo-recover.php > /tmp/superdemo-recover-{{ superdemo_recovery_id }}.log 2>&1'"
async: 1200
poll: 10
no_log: true
ansible/roles/expanded_services/defaults/main.yml
---
superdemo_state_dir: "{{ lookup('env', 'HOME') }}/.local/state/superdemo-resources"
superdemo_profile: piranhademo
superdemo_region: eu-west-2
superdemo_config:
api_url: https://api.piranha.sh
aws_endpoint: https://aws.piranha.sh
web_instance_id: '10117367'
db_name: superdemo-dsit-db
db_class: db.t3.micro
email_domain: piranhademo.website
mailbox_user: superdemo-proof
lb_name: superdemo-dsit-lb
group_name: superdemo-dsit-web
lb_url: http://45.148.50.46/lb-proof
registry_url: https://pcr.piranha.sh
registry_image: d1be53099a9625807233466c/superdemo-web
registry_tag: '1'
scm_repository: superdemo-example
superdemo_compute_host: piranha@prod-compute-cluster-2
superdemo_project: cl-d1be53099a9625807233466c
superdemo_web_ip: 45.148.50.73
ansible/roles/expanded_services/tasks/main.yml
---
- name: Require explicit Piranha scope for expanded service operations
ansible.builtin.assert:
that: superdemo_platform == 'piranha'
fail_msg: Expanded service operations are Piranha-native. The existing deploy.yml retains its AWS workflow.
- name: Create private operator state directory
ansible.builtin.file:
path: "{{ superdemo_state_dir }}"
state: directory
mode: '0700'
- name: Save non-secret desired demo configuration
ansible.builtin.copy:
content: "{{ superdemo_config | to_nice_json }}"
dest: "{{ superdemo_state_dir }}/desired.json"
mode: '0600'
- name: Reconcile named demo database, mailbox, balanced group and load balancer
ansible.builtin.command:
argv:
- "{{ ansible_playbook_python }}"
- "{{ role_path }}/files/resources.py"
- --mode
- ensure
- --state
- "{{ superdemo_state_dir }}"
- --config
- "{{ superdemo_state_dir }}/desired.json"
- --profile
- "{{ superdemo_profile }}"
- --region
- "{{ superdemo_region }}"
register: superdemo_resources_result
changed_when: (superdemo_resources_result.stdout | from_json).changed
no_log: true
- name: Expose only non-secret resource identity and status
ansible.builtin.set_fact:
superdemo_resources: "{{ (superdemo_resources_result.stdout | from_json).resources }}"
- name: Verify actual registry manifest and SCM commit read
ansible.builtin.command:
argv:
- "{{ ansible_playbook_python }}"
- "{{ role_path }}/files/resources.py"
- --mode
- evidence
- --state
- "{{ superdemo_state_dir }}"
- --config
- "{{ superdemo_state_dir }}/desired.json"
- --profile
- "{{ superdemo_profile }}"
register: superdemo_artifact_result
changed_when: false
- name: Report named resource status
ansible.builtin.debug:
var: superdemo_resources
- name: Configure only the dedicated managed database guest and restricted SQL user
ansible.builtin.command:
argv:
- "{{ ansible_playbook_python }}"
- "{{ role_path }}/files/configure_database.py"
- --state
- "{{ superdemo_state_dir }}"
- --compute-host
- "{{ superdemo_compute_host }}"
- --project
- "{{ superdemo_project }}"
- --web-ip
- "{{ superdemo_web_ip }}"
register: superdemo_db_setup
changed_when: (superdemo_db_setup.stdout | from_json).changed
no_log: true
ansible/roles/expanded_webapp/defaults/main.yml
---
superdemo_web_ip: 45.148.50.73
superdemo_ssh_key: "{{ playbook_dir }}/../aws-cli/demo-key-piranha.pem"
superdemo_state_dir: "{{ lookup('env', 'HOME') }}/.local/state/superdemo-resources"
ansible/roles/expanded_webapp/tasks/main.yml
---
- name: Register existing demo web instance
ansible.builtin.add_host:
name: superdemo_web
ansible_host: "{{ superdemo_web_ip }}"
ansible_connection: ssh
ansible_user: ubuntu
ansible_ssh_private_key_file: "{{ superdemo_ssh_key }}"
ansible_python_interpreter: /usr/bin/python3
changed_when: false
- name: Use HTTPS package mirrors on the demo VM
ansible.builtin.replace:
path: /etc/apt/sources.list
regexp: 'http://(archive|security)\.ubuntu\.com'
replace: 'https://\1.ubuntu.com'
delegate_to: superdemo_web
become: true
- name: Ensure PHP database extension on the demo VM
ansible.builtin.apt:
name: php8.1-mysql
state: present
update_cache: false
delegate_to: superdemo_web
become: true
register: superdemo_php_package
- name: Create private web application configuration directory
ansible.builtin.file:
path: /etc/superdemo
state: directory
owner: root
group: www-data
mode: '0750'
delegate_to: superdemo_web
become: true
- name: Install database credentials outside document root
ansible.builtin.copy:
src: "{{ superdemo_state_dir }}/app-db.json"
dest: /etc/superdemo/database.json
owner: root
group: www-data
mode: '0640'
delegate_to: superdemo_web
become: true
no_log: true
- name: Install database CA certificate
ansible.builtin.copy:
src: "{{ superdemo_state_dir }}/db-ca.pem"
dest: /etc/superdemo/db-ca.pem
owner: root
group: www-data
mode: '0640'
delegate_to: superdemo_web
become: true
- name: Build public Ansible source page from reviewed allowlist
ansible.builtin.command:
argv: ["{{ ansible_playbook_python }}", "{{ playbook_dir }}/../scripts/build_ansible_source.py"]
register: superdemo_source_page
changed_when: superdemo_source_page.stdout == 'changed'
- name: Create private email runtime directories
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
owner: "{{ item.owner }}"
group: "{{ item.owner }}"
mode: '0755'
loop:
- {path: /usr/local/lib/superdemo, owner: root}
- {path: /var/lib/superdemo/email, owner: www-data}
delegate_to: superdemo_web
become: true
- name: Deploy bounded email demonstration worker
ansible.builtin.copy:
src: "{{ playbook_dir }}/roles/expanded_services/files/email_proof.py"
dest: /usr/local/lib/superdemo/email_proof.py
owner: root
group: www-data
mode: '0640'
delegate_to: superdemo_web
become: true
- name: Deploy credentials for the demo-only mailbox outside document root
ansible.builtin.copy:
src: "{{ superdemo_state_dir }}/mailbox.json"
dest: /etc/superdemo/mailbox.json
owner: root
group: www-data
mode: '0640'
delegate_to: superdemo_web
become: true
no_log: true
- name: Prepare private demo login and execution credentials
ansible.builtin.command:
argv: ["{{ ansible_playbook_python }}", "{{ playbook_dir }}/../scripts/prepare_demo_login.py"]
register: demo_login_preparation
changed_when: (demo_login_preparation.stdout | from_json).changed
no_log: true
- name: Ensure only the demo web IP has authenticated preview API access
ansible.builtin.command:
argv: ["{{ ansible_playbook_python }}", "{{ playbook_dir }}/../scripts/ensure_demo_api_access.py", --state, "{{ superdemo_state_dir }}", --ip, "{{ superdemo_web_ip }}"]
register: demo_api_access
changed_when: (demo_api_access.stdout | from_json).changed
no_log: true
- name: Install reviewed demo command tools
ansible.builtin.apt:
name: [python3-venv, git, curl]
install_recommends: false
state: present
update_cache: true
cache_valid_time: 3600
delegate_to: superdemo_web
become: true
- name: Install isolated AWS CLI matching the verified API client protocol
ansible.builtin.pip:
name: awscli==1.38.31
virtualenv: /opt/superdemo-tools
virtualenv_command: /usr/bin/python3 -m venv
delegate_to: superdemo_web
become: true
- name: Create private authentication and execution state directories
ansible.builtin.file:
path: "/var/lib/superdemo/{{ item }}"
state: directory
owner: www-data
group: www-data
mode: '0700'
loop: [auth, run]
delegate_to: superdemo_web
become: true
- name: Install private login hash and API credentials
ansible.builtin.copy:
src: "{{ superdemo_state_dir }}/{{ item }}.json"
dest: "/etc/superdemo/{{ item }}.json"
owner: root
group: www-data
mode: '0640'
loop: [login, api]
delegate_to: superdemo_web
become: true
no_log: true
- name: Install fixed-action command runner
ansible.builtin.copy:
src: "{{ playbook_dir }}/roles/expanded_services/files/run_action.py"
dest: /usr/local/lib/superdemo/run_action.py
owner: root
group: www-data
mode: '0640'
delegate_to: superdemo_web
become: true
- name: Deploy reviewed demo application files
ansible.builtin.copy:
src: "{{ playbook_dir }}/../website/{{ item }}"
dest: "/var/www/html/{{ item }}"
owner: root
group: root
mode: '0644'
validate: "{{ 'php -l %s' if item.endswith('.php') else omit }}"
loop:
- auth.php
- login.php
- logout.php
- console.php
- console.css
- console.js
- sql-console.php
- run.php
- card-info.php
- card-info.css
- card-info.js
- display-evidence.json
- ansible-source.html
- database.php
- email.php
- load-balancer.php
- artifacts.php
- demo-artifacts.json
- service-inventory.php
- service-inventory.json
- email-proof.json
- artifact-proof.json
- index.php
delegate_to: superdemo_web
become: true
- name: Install demo-only nginx configuration with TLS and LB proof route
ansible.builtin.copy:
src: "{{ playbook_dir }}/../readiness/nginx-demo.conf"
dest: /etc/nginx/sites-available/default
owner: root
group: root
mode: '0644'
delegate_to: superdemo_web
become: true
register: superdemo_nginx_config
- name: Check nginx configuration
ansible.builtin.command: nginx -t
delegate_to: superdemo_web
become: true
changed_when: false
- name: Reload nginx when configuration changed
ansible.builtin.service:
name: nginx
state: reloaded
delegate_to: superdemo_web
become: true
when: superdemo_nginx_config.changed
- name: Reload PHP when database extension was installed
ansible.builtin.service:
name: php8.1-fpm
state: reloaded
delegate_to: superdemo_web
become: true
when: superdemo_php_package.changed
- name: Verify LB forwarding and re-push only this LB configuration if needed
ansible.builtin.command:
argv: ["{{ ansible_playbook_python }}", "{{ playbook_dir }}/roles/expanded_services/files/resources.py", --mode, lb, --state, "{{ superdemo_state_dir }}", --config, "{{ superdemo_state_dir }}/desired.json", --profile, "{{ superdemo_profile | default('piranhademo') }}"]
register: superdemo_lb_verification
changed_when: (superdemo_lb_verification.stdout | from_json).changed
ansible/roles/expanded_services/files/resources.py
#!/usr/bin/env python3
"""Reconcile only named demo resources; never deletes or edits other resources."""
import argparse, base64, configparser, json, os, secrets, subprocess, tempfile
import urllib.request
from pathlib import Path
class Demo:
def __init__(self, args):
self.args=args; self.state=Path(args.state).expanduser();self.state.mkdir(parents=True,exist_ok=True);self.state.chmod(0o700)
self.config=json.loads(Path(args.config).read_text());self.changes=[]
c=configparser.ConfigParser();c.read(Path.home()/'.aws/credentials')
p=c[args.profile] if c.has_section(args.profile) else {}
self.key=os.environ.get('AWS_ACCESS_KEY_ID') or p.get('aws_access_key_id')
self.secret=os.environ.get('AWS_SECRET_ACCESS_KEY') or p.get('aws_secret_access_key')
if not self.key or not self.secret:raise RuntimeError('Demo AWS credentials are required outside the repository')
def save(self,name,value):
p=self.state/name;p.write_text(json.dumps(value,indent=2)+'\n');p.chmod(0o600)
def api(self,path,params=None):
from urllib.parse import urlencode
data={'key_id':self.key,'secret_key':self.secret};data.update(params or {})
req=urllib.request.Request(self.config['api_url']+path,data=urlencode(data).encode())
with urllib.request.urlopen(req,timeout=120) as r:d=json.load(r)
if d.get('status')!='OK':raise RuntimeError(path+': '+str(d.get('message',d.get('error_message','API failed'))))
return d
def all(self,path,key):
first=self.api(path,{'page':1});rows=first.get(key,[])
for page in range(2,min(int(first.get('page_count') or 1),100)+1):rows+=self.api(path,{'page':page}).get(key,[])
return rows
def named(self,rows,name,fields):
found=[r for r in rows if any(r.get(f)==name for f in fields)]
if len(found)>1:raise RuntimeError('Duplicate demo identity: '+name)
return found[0] if found else None
def ensure(self):
cfg=self.config
db=self.named(self.all('/api/database/instance/all','instances'),cfg['db_name'],['name','vm_name'])
if db is None:
request={'DBInstanceIdentifier':cfg['db_name'],'DBInstanceClass':cfg['db_class'],'Engine':'mysql','EngineVersion':'8.0','MasterUsername':'superdemo_admin','MasterUserPassword':secrets.token_urlsafe(30),'AllocatedStorage':20,'PubliclyAccessible':False}
self.save('create-db.json',request)
command=['aws','rds','create-db-instance','--cli-input-json','file://'+str(self.state/'create-db.json'),'--profile',self.args.profile,'--region',self.args.region,'--endpoint-url',cfg['aws_endpoint'],'--output','json']
result=subprocess.run(command,capture_output=True,text=True)
if result.returncode:raise RuntimeError('AWS CreateDBInstance failed: '+result.stderr[:400])
self.save('create-db-result.json',json.loads(result.stdout));self.changes.append('database')
db=self.named(self.all('/api/database/instance/all','instances'),cfg['db_name'],['name','vm_name'])
domains=self.all('/api/email/domain/all','domains')
domain=self.named(domains,cfg['email_domain'],['domain_name','domain'])
if not domain:raise RuntimeError('Expected existing demo-owned email domain is missing; refusing to alter another domain')
addresses=self.all('/api/email/address/all','addresses')
mailbox=self.named([r for r in addresses if r.get('domain')==cfg['email_domain']],cfg['mailbox_user'],['username'])
if mailbox is None:
params={'username':cfg['mailbox_user'],'domain_id':domain['id'],'domain_name':cfg['email_domain'],'password':secrets.token_urlsafe(32),'quota':'100','forward_to':'','autoresponder_enabled':'0'}
self.save('mailbox.json',params);mailbox=self.api('/api/email/address/create',params)['address'];self.changes.append('mailbox')
elif not (self.state/'mailbox.json').exists():raise RuntimeError('Existing demo mailbox found but its private credentials are unavailable; no password reset performed')
bg=self.named(self.all('/api/sc1/balancedgroup/all','balancedgroups'),cfg['group_name'],['balancedgroup_name'])
if bg is None:
bg=self.api('/api/sc1/balancedgroup/create',{'balancedgroup_name':cfg['group_name'],'description':'Dedicated Superdemo web backend','balance_type':'roundrobin','proxy_protocol_enabled':'0','ports':json.dumps([{'port':80,'protocol':'http','health_path':'/lb-proof'}]),'targets':json.dumps([{'target_type':'vm','target_id':cfg['web_instance_id']}])})['balancedgroup'];self.changes.append('balanced_group')
lb=self.named(self.all('/api/sc1/loadbalancer/all','instances'),cfg['lb_name'],['name'])
if lb is None:
r=self.api('/api/sc1/loadbalancer/create',{'name':cfg['lb_name'],'description':'Dedicated Superdemo HTTP load balancer','size_id':'nano','listener_network':'internet','target_network':''});self.save('lb-create-result.json',r);lb=r['vm'];self.changes.append('load_balancer')
# Association is deferred until the managed worker has finished configuring the guest.
if lb.get('status')=='running' and lb.get('balanced_group_id')!=bg['id']:
self.api('/api/sc1/loadbalancer/associate_group',{'loadbalancer_id':lb['id'],'balanced_group_id':bg['id']});self.changes.append('lb_association')
state={'database':{'id':str(db['vm_id']),'name':cfg['db_name'],'status':db['status']},'mailbox':{'id':mailbox['id'],'address':cfg['mailbox_user']+'@'+cfg['email_domain']},'load_balancer':{'id':str(lb['id']),'name':cfg['lb_name'],'status':lb['status']},'balanced_group':{'id':bg['id'],'name':cfg['group_name']}}
self.save('managed-resources.json',state)
return {'changed':bool(self.changes),'changes':self.changes,'resources':state}
def evidence(self):
cfg=self.config;auth=base64.b64encode((self.key+':'+self.secret).encode()).decode()
url=cfg['registry_url']+'/v2/'+cfg['registry_image']+'/manifests/'+cfg['registry_tag']
req=urllib.request.Request(url,headers={'Authorization':'Basic '+auth,'Accept':'application/vnd.docker.distribution.manifest.v2+json'})
with urllib.request.urlopen(req,timeout=30) as r:
raw=r.read();manifest=json.loads(raw);digest=r.headers.get('Docker-Content-Digest')
import hashlib,datetime
if digest and digest!='sha256:'+hashlib.sha256(raw).hexdigest():raise RuntimeError('Registry manifest digest mismatch')
if manifest.get('schemaVersion')!=2 or not manifest.get('layers'):raise RuntimeError('Registry manifest has no real layers')
scm=self.api('/api/scm/repository/commit/all',{'repository_id':cfg['scm_repository'],'branch':'master'})
if not scm.get('commits'):raise RuntimeError('SCM repository has no readable commits')
commit=scm['commits'][0]
evidence={'checked_at':datetime.datetime.now(datetime.timezone.utc).isoformat(timespec='seconds'),'registry':{'image':cfg['registry_image']+':'+cfg['registry_tag'],'digest':digest,'layers':manifest['layers'],'schema_version':2},'scm':{'repository':cfg['scm_repository'],'commit':commit['commit'],'message':commit['message'],'date':commit['date']}}
self.save('artifact-proof.json',evidence);return {'changed':False,'evidence':evidence}
def load_balancer(self):
def probe():
try:
with urllib.request.urlopen(self.config['lb_url'],timeout=15) as r:
return r.status==200 and r.headers.get('X-Superdemo-Backend')==self.config['web_instance_id'] and r.read()==('Piranha Superdemo backend '+self.config['web_instance_id']+'\n').encode()
except Exception:return False
if probe():return {'changed':False,'forwarding_verified':True}
managed=json.loads((self.state/'managed-resources.json').read_text())
result=self.api('/api/sc1/loadbalancer/repush_config',{'loadbalancer_id':managed['load_balancer']['id']})
if not result.get('push_result',{}).get('success'):raise RuntimeError('Platform LB configuration push failed')
import time
for attempt in range(5):
if probe():return {'changed':True,'forwarding_verified':True}
time.sleep(2)
raise RuntimeError('LB did not forward the backend marker; no readiness claim')
def main():
p=argparse.ArgumentParser();p.add_argument('--state',required=True);p.add_argument('--config',required=True);p.add_argument('--profile',default='piranhademo');p.add_argument('--region',default='eu-west-2');p.add_argument('--mode',choices=['ensure','evidence','lb'],required=True);a=p.parse_args();d=Demo(a);print(json.dumps(d.ensure() if a.mode=='ensure' else d.load_balancer() if a.mode=='lb' else d.evidence()))
if __name__=='__main__':main()
ansible/roles/expanded_services/files/configure_database.py
#!/usr/bin/env python3
"""Demo-only guest setup; no changes to shared worker code or other instances."""
import argparse,ipaddress,json,secrets,shlex,subprocess
from pathlib import Path
p=argparse.ArgumentParser();p.add_argument('--state',required=True);p.add_argument('--compute-host',default='piranha@prod-compute-cluster-2');p.add_argument('--project',required=True);p.add_argument('--web-ip',required=True);a=p.parse_args()
state=Path(a.state);managed=json.loads((state/'managed-resources.json').read_text());vm='db-'+managed['database']['id'];changes=[]
assert vm[3:].isdigit() and a.project.startswith('cl-');ipaddress.ip_address(a.web_ip)
def remote(host,args,stdin=None):
r=subprocess.run(['ssh','-o','BatchMode=yes','-o','ConnectTimeout=10',host,shlex.join(args)],input=stdin,text=True,capture_output=True,timeout=180)
if r.returncode:raise RuntimeError('Demo guest operation failed: '+r.stderr[-300:])
return r.stdout
row=json.loads(remote(a.compute_host,['sudo','incus','query','/1.0/instances/'+vm+'?project='+a.project]))
if row.get('name')!=vm:raise RuntimeError('Demo guest identity mismatch')
node=row['location'];host=a.compute_host.split('@')[0]+'@'+node
if row['status']!='Running':raise RuntimeError('Managed database guest must be running before configuring SQL')
def guest(args,stdin=None):return remote(host,['sudo','incus','exec',vm,'--project',a.project,'--']+args,stdin)
if not any(d.get('network')=='internet1' for d in row.get('expanded_devices',{}).values()):
remote(host,['sudo','incus','network','attach','internet1',vm,'enp6s0','--project',a.project]);changes.append('demo_db_network')
addresses=json.loads(guest(['ip','-4','-j','address']))
ips=[x['local'] for n in addresses if n['ifname']!='lo' for x in n.get('addr_info',[]) if x.get('scope')=='global']
if not ips:raise RuntimeError('Demo database has no reachable address yet')
ip=str(ipaddress.ip_address(ips[0]));private=state/'app-db.json'
c=json.loads(private.read_text()) if private.exists() else {'database':'superdemo','username':'superdemo_app','password':secrets.token_urlsafe(32)}
if c.get('host')!=ip:changes.append('app_db_address')
c.update(host=ip,instance_id=managed['database']['id']);private.write_text(json.dumps(c));private.chmod(0o600)
# Fixed schema and host-restricted user; no arbitrary SQL comes from the browser.
exists=guest(['mysql','-N','-e',"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='superdemo' AND table_name='proof'; SELECT COUNT(*) FROM mysql.user WHERE user='superdemo_app' AND host='"+a.web_ip+"';"]).split()
if exists!=['1','1']:
sql="CREATE DATABASE IF NOT EXISTS superdemo; CREATE TABLE IF NOT EXISTS superdemo.proof (id INT PRIMARY KEY,token VARCHAR(64) NOT NULL,writes INT NOT NULL DEFAULT 0,checked_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP); CREATE USER IF NOT EXISTS 'superdemo_app'@'"+a.web_ip+"' IDENTIFIED BY '"+c['password']+"' REQUIRE SSL; GRANT SELECT,INSERT,UPDATE ON superdemo.* TO 'superdemo_app'@'"+a.web_ip+"';"
guest(['mysql'],sql);changes.append('demo_schema_user')
script=r'''set -eu
changed=0
if [ ! -f /var/lib/mysql/superdemo-server-cert.pem ] || ! openssl x509 -in /var/lib/mysql/superdemo-server-cert.pem -noout -ext subjectAltName | grep -Fq 'IP Address:__IP__'; then
openssl req -newkey rsa:2048 -nodes -keyout /var/lib/mysql/superdemo-server-key.pem -out /tmp/superdemo-server.csr -subj '/CN=__IP__' >/dev/null 2>&1
printf 'subjectAltName=IP:__IP__\nextendedKeyUsage=serverAuth\n' >/tmp/superdemo-server.ext
openssl x509 -req -in /tmp/superdemo-server.csr -CA /var/lib/mysql/ca.pem -CAkey /var/lib/mysql/ca-key.pem -CAcreateserial -out /var/lib/mysql/superdemo-server-cert.pem -days 365 -extfile /tmp/superdemo-server.ext >/dev/null 2>&1
chown mysql:mysql /var/lib/mysql/superdemo-server-*.pem
chmod 600 /var/lib/mysql/superdemo-server-key.pem
changed=1
fi
cat >/tmp/superdemo.cnf <<'CONF'
[mysqld]
bind-address=0.0.0.0
ssl-ca=/var/lib/mysql/ca.pem
ssl-cert=/var/lib/mysql/superdemo-server-cert.pem
ssl-key=/var/lib/mysql/superdemo-server-key.pem
require_secure_transport=ON
CONF
if ! cmp -s /tmp/superdemo.cnf /etc/mysql/mysql.conf.d/superdemo.cnf; then
install -m 644 /tmp/superdemo.cnf /etc/mysql/mysql.conf.d/superdemo.cnf
changed=1
fi
if [ "$changed" = 1 ]; then systemctl restart mysql; fi
printf '%s' "$changed"
'''.replace('__IP__',ip)
if guest(['bash'],script).strip()=='1':changes.append('mysql_tls')
(state/'db-ca.pem').write_text(guest(['cat','/var/lib/mysql/ca.pem']))
print(json.dumps({'changed':bool(changes),'changes':changes,'host':ip,'instance':vm,'compute_node':node}))
ansible/roles/expanded_services/files/email_proof.py
#!/usr/bin/env python3
import argparse,json,smtplib,imaplib,ssl,uuid,time
from pathlib import Path
from email.message import EmailMessage
from email import policy
from email.parser import BytesParser
from datetime import datetime,timezone
p=argparse.ArgumentParser();p.add_argument('--state',required=True);p.add_argument('--credentials');a=p.parse_args();state=Path(a.state);d=json.loads((Path(a.credentials) if a.credentials else state/'mailbox.json').read_text());address=d['username']+'@'+d['domain_name']
if address!='superdemo-proof@piranhademo.website':raise RuntimeError('This test is restricted to the dedicated demo-controlled mailbox')
nonce=uuid.uuid4().hex;msg=EmailMessage();msg['From']=address;msg['To']=address;msg['Subject']='Hello from Piranha';msg['Message-ID']='<'+nonce+'@piranhademo.website>';msg.set_content('Hello from Piranha!\n\nThis message was sent through Piranha email and read back from the demo inbox.\n\nVerification reference: '+nonce)
with smtplib.SMTP('mail.piranha.sh',587,timeout=15) as smtp:
smtp.ehlo();smtp.starttls(context=ssl.create_default_context());smtp.ehlo();smtp.login(address,d['password']);assert smtp.send_message(msg)=={}
received=None
for attempt in range(5):
imap=imaplib.IMAP4_SSL('mail.piranha.sh',993,ssl_context=ssl.create_default_context(),timeout=15)
try:
imap.login(address,d['password']);imap.select('INBOX',readonly=True)
for seq in range(1,31):
_,parts=imap.fetch(str(seq),'(BODY.PEEK[])')
for part in parts:
if isinstance(part,tuple):
m=BytesParser(policy=policy.default).parsebytes(part[1])
if str(m['Message-ID'])==str(msg['Message-ID']) and nonce in m.get_content():received=m
if received:break
finally:
try:imap.logout()
except imaplib.IMAP4.abort:pass
if received:break
time.sleep(2)
if received is None:raise RuntimeError('SMTP accepted the test but its body could not be retrieved; no delivery success claimed')
r={'checked_at':datetime.now(timezone.utc).isoformat(timespec='seconds'),'sender':address,'recipient':address,'subject':str(received['Subject']),'message_id':str(received['Message-ID']),'token':nonce,'smtp_starttls_authenticated':True,'imap_tls_body_match':True,'body':received.get_content()};(state/'mail-proof.json').write_text(json.dumps(r,indent=2)+'\n');print(json.dumps({'delivered':True,'message_id':r['message_id']}))
ansible/roles/expanded_services/files/verify.py
#!/usr/bin/env python3
"""Live integration checks: SQL persistence, negative CSRF, LB, S3 and mailbox receipt."""
import argparse,re,hashlib,http.cookiejar,imaplib,json,ssl,subprocess,tempfile,urllib.error,urllib.parse,urllib.request
from email import policy
from email.parser import BytesParser
from pathlib import Path
from datetime import datetime,timezone
p=argparse.ArgumentParser();p.add_argument('--state',required=True);p.add_argument('--base-url',default='https://demo.piranhademo.website');p.add_argument('--lb-url',default='http://45.148.50.46/lb-proof');p.add_argument('--profile',default='piranhademo');a=p.parse_args();state=Path(a.state);checks={}
jar=http.cookiejar.CookieJar();client=urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
def request(url,data=None):
with client.open(urllib.request.Request(url,data=None if data is None else urllib.parse.urlencode(data).encode()),timeout=20) as r:return r.status,r.read(),dict(r.headers)
login=request(a.base_url+'/login.php')[1].decode();token=re.search('name="csrf" value="([^\"]+)"',login)[1]
credentials=json.loads((state/'login-access.json').read_text());console=request(a.base_url+'/login.php',dict(credentials,csrf=token))[1].decode();csrf=re.search('name="demo-csrf" content="([^\"]+)"',console)[1]
status,body,_=request(a.base_url+'/');assert status==200 and b'superdemo-proof@piranhademo.website' in body and b'superdemo-example' in body
assert b'adminer' not in body and b'3 groups' not in body
for path in ['/email.php','/artifacts.php?service=source','/artifacts.php?service=registry','/load-balancer.php']:
assert request(a.base_url+path)[0]==200
assert b'Generation not demonstrated' not in body and b'Password:' not in body
checks['https_page']=True
for asset in ['/assets/css/demo.css','/assets/js/demo.js']:
assert request(a.base_url+asset)[0]==200
checks['assets']=True
for path in ['/demo.env','/setup.sql']:
try:request(a.base_url+path);raise AssertionError('Sensitive demo file was accessible')
except urllib.error.HTTPError as e:assert e.code==403
checks['private_files_blocked']=True
url=a.base_url+'/database.php?format=json';before=json.loads(request(url)[1]);assert before['ok'] and before['tls_cipher']
try:request(url,{'csrf':'invalid','command':'SELECT * FROM proof;'});raise AssertionError('Invalid CSRF accepted')
except urllib.error.HTTPError as e:assert e.code==403
after_bad=json.loads(request(url)[1]);assert after_bad['rows']==before['rows'];checks['csrf_rejected_without_write']=True
import secrets
token=secrets.token_hex(12)
written=json.loads(request(url,{'csrf':csrf,'command':"UPDATE proof SET token = '"+token+"' WHERE id = 1;"})[1]);read=json.loads(request(url)[1]);assert written['ok'] and read['rows'][0]['token']==token
checks['sql_write_read_persisted']=True;checks['sql_tls_cipher']=written['tls_cipher'];checks['sql_token']=token
status,body,headers=request(a.lb_url);assert status==200 and body==b'Piranha Superdemo backend 10117367\n';assert headers.get('X-Superdemo-Backend')=='10117367';checks['load_balancer_forwarding']=True
public=request('https://demo-assets-1749723872.storage.piranha.sh/kitten.jpg')[1]
with tempfile.TemporaryDirectory() as directory:
target=Path(directory)/'kitten.jpg';cmd=['aws','s3api','get-object','--bucket','demo-assets-1749723872','--key','kitten.jpg',str(target),'--endpoint-url','https://aws.piranha.sh','--region','eu-west-2','--profile',a.profile];subprocess.run(cmd,check=True,stdout=subprocess.DEVNULL);assert target.read_bytes()==public
checks['s3_cli_public_sha256']=hashlib.sha256(public).hexdigest()
mail=json.loads((state/'mailbox.json').read_text());receipt=json.loads((state/'mail-proof.json').read_text());address=mail['username']+'@'+mail['domain_name'];assert receipt['recipient']==address and address=='superdemo-proof@piranhademo.website'
imap=imaplib.IMAP4_SSL('mail.piranha.sh',993,ssl_context=ssl.create_default_context(),timeout=15)
try:
imap.login(address,mail['password']);imap.select('INBOX',readonly=True);found=False
for seq in range(1,21):
_,parts=imap.fetch(str(seq),'(BODY.PEEK[])')
for part in parts:
if isinstance(part,tuple):
message=BytesParser(policy=policy.default).parsebytes(part[1])
if str(message['Message-ID'])==receipt['message_id']:
assert receipt['token'] in message.get_content();found=True
if found:break
assert found,'Previously sent demo verification message could not be read'
finally:
try:imap.logout()
except imaplib.IMAP4.abort:pass
checks['email_imap_received_body_reverified']=True
# A public registry request must not bypass authentication.
try:urllib.request.urlopen('https://pcr.piranha.sh/v2/d1be53099a9625807233466c/superdemo-web/manifests/1',timeout=15);raise AssertionError('Unauthenticated registry request unexpectedly succeeded')
except urllib.error.HTTPError as e:assert e.code==401
checks['registry_unauthenticated_rejected']=True
proof=json.loads((state/'artifact-proof.json').read_text());checks['registry_authenticated_digest']=proof['registry']['digest'];checks['scm_commit']=proof['scm']['commit']
result={'checked_at':datetime.now(timezone.utc).isoformat(timespec='seconds'),'checks':checks,'all_passed':True};(state/'verification-last.json').write_text(json.dumps(result,indent=2)+'\n');print(json.dumps(result))
ansible/roles/expanded_services/templates/recover.php.j2
<?php
// Recovery is restricted to the named demo job; shared libraries stay untouched.
$root = '/var/www/monorepo';
require_once $root . '/server/vendor/autoload.php';
require_once $root . '/server/app/Http/Controllers/Libraries/vendor/autoload.php';
spl_autoload_register(static function ($class) use ($root) {
$prefix = 'GuzzleHttp\\';
if (strpos($class, $prefix) !== 0) return;
$file = $root . '/server/vendor/guzzlehttp/guzzle/src/' . str_replace('\\', '/', substr($class, strlen($prefix))) . '.php';
if (is_file($file)) require_once $file;
}, true, true);
$id = '{{ superdemo_recovery_id }}';
$org = '{{ superdemo_project | regex_replace("^cl-", "") }}';
$name = '{{ superdemo_recovery_name }}';
$kind = '{{ superdemo_recovery_kind }}';
$queue = $kind === 'db' ? 'queue_create_database_virtualmachine' : 'queue_create_compute_loadbalancer';
$table = $kind === 'db' ? 'database_virtualmachine_instances' : 'compute_loadbalancer_instances';
$lock = fopen('/tmp/superdemo-recovery-' . $id . '.lock', 'c');
if (!flock($lock, LOCK_EX | LOCK_NB)) throw new RuntimeException('Demo recovery already running');
\App\Http\Controllers\LoginController::$authenticated_user['organisation_id'] = $org;
$rows = \App\Http\Controllers\APIDatastoreHelper::getCollectionEntries($queue, ['vm_id'=>$id,'organisation_id'=>$org])['data'];
if (count($rows) !== 1 || $rows[0]['vm_name'] !== $name) throw new RuntimeException('Demo queue identity mismatch');
$entry=$rows[0];
$backend=new \App\Http\Controllers\BackendProviders\Compute\LXD\LXDInstance();
try {
$info=$backend->lxd->instances->info($kind.'-'.$id);
} catch (Throwable $error) {
if (strpos($error->getMessage(), '404') === false && strpos(strtolower($error->getMessage()), 'not found') === false) throw $error;
$info=null;
}
if (is_array($info)) {
$status=strtolower($info['status'] ?? '');
if ($status !== 'running') throw new RuntimeException('Guest already exists but is not running; inspect the demo guest before recovery');
$stored=\App\Http\Controllers\APIDatastoreHelper::getCollectionEntries($table,['vm_id'=>$id,'organisation_id'=>$org])['data'];
if (count($stored)!==1) throw new RuntimeException('Demo resource missing');
// Reconcile only observed live state, never mark a missing/failed guest ready.
$wanted=$kind==='db'?'available':'running';
if ($stored[0]['status']!==$wanted) {
\App\Http\Controllers\APIDatastoreHelper::updateCollectionEntries($table,['status'=>$wanted,'state'=>'running','status_code'=>'running','status_code_internal'=>'running'],['vm_id'=>$id,'organisation_id'=>$org]);
}
exit(0);
}
$original='/tmp/'.$id.'_'.$entry['id'].'_creation.log';
if (!is_file($original) || strpos(file_get_contents($original),'Proxy::wrapTlsFallback')===false) throw new RuntimeException('No confirmed library-collision failure; refusing blind job retry');
$script=$kind==='db'?'/server/scripts/dbmodify/db_create.php':'/server/scripts/loadbalancer/loadbalancer_create.php';
$argv=[$script,$id,$entry['id'],$org,json_encode($entry)];
require $root.$script;
Source snapshot regenerated by the Ansible web application role when deployed.