Publish railiance01 resource evidence and close RAIL-HO-WP-0008
Non-secret identity, a reproducible capacity observation, and host-ops labor/exit inputs for resource:hosteurope:railiance01. Booked price and contract dates stay unknown for their owners.
This commit is contained in:
parent
39070ed292
commit
0a97ce12be
9 changed files with 669 additions and 8 deletions
10
Makefile
10
Makefile
|
|
@ -270,6 +270,16 @@ verify: ## Run Goss test suite against all hosts, commit TAP reports — exits n
|
|||
git diff --cached --quiet && echo "No new reports to commit." || \
|
||||
git commit -m "chore: Goss verification reports $$(date -u +%Y-%m-%dT%H%M%SZ)"
|
||||
|
||||
observe-railiance01: ## Timestamped host capacity observation for resource-control
|
||||
@mkdir -p docs/evidence/resource-hosteurope-railiance01/observations
|
||||
@out=$$(mktemp); \
|
||||
python3 scripts/observe-host-capacity.py Railiance01 > $$out; \
|
||||
stamp=$$(python3 -c "import json; print(json.load(open('$$out'))['observed_at'].replace(':',''))"); \
|
||||
dest=docs/evidence/resource-hosteurope-railiance01/observations/$$stamp.json; \
|
||||
mv $$out $$dest; \
|
||||
ln -sfn $$stamp.json docs/evidence/resource-hosteurope-railiance01/observations/latest.json; \
|
||||
echo "wrote $$dest"
|
||||
|
||||
verify-host: ## Run Goss against one host: make verify-host HOST=Railiance01
|
||||
@test -n "$(HOST)" || (echo "Usage: make verify-host HOST=Railiance01"; exit 1)
|
||||
@echo "Running Goss baseline assertions on $(HOST)..."
|
||||
|
|
|
|||
3
SCOPE.md
3
SCOPE.md
|
|
@ -89,6 +89,9 @@ higher layer (Kubernetes, platform, etc.) can run.
|
|||
- **Firewall posture**: `RAIL-HO-WP-0009` finished. k3s API is tunnel-only
|
||||
(ADR-005); live `Railiance01` UFW matches the declaration. CoulombCore
|
||||
UFW stays unmanaged (`ufw_manage: false`)
|
||||
- **Resource evidence**: `RAIL-HO-WP-0008` finished. Non-secret identity,
|
||||
capacity observations, and host-ops labor live under
|
||||
`docs/evidence/resource-hosteurope-railiance01/`
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
63
docs/evidence/resource-hosteurope-railiance01/README.md
Normal file
63
docs/evidence/resource-hosteurope-railiance01/README.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Evidence interface — `resource:hosteurope:railiance01`
|
||||
|
||||
Stable, non-secret facts that `resource-control` should read for the Host
|
||||
Europe virtual server `Railiance01`. This directory is the interface.
|
||||
`resource-control` remains the portfolio record; `fin-hub` remains booked
|
||||
cost. Do not copy invoices or credentials into either side.
|
||||
|
||||
## Files
|
||||
|
||||
| Path | Task | What it fills |
|
||||
| --- | --- | --- |
|
||||
| `identity-lifecycle.yaml` | T01 | provider, product, IDs, location, lifecycle, classified unknowns |
|
||||
| `observations/*.json` | T02 | timestamped OS / provider / k3s capacity and utilization |
|
||||
| `observations/latest.json` | T02 | symlink to the newest observation |
|
||||
| `labor-and-exit.yaml` | T03 | recurring host-ops activities, estimated hours, exit path |
|
||||
|
||||
Producer: `scripts/observe-host-capacity.py Railiance01`
|
||||
|
||||
```bash
|
||||
make observe-railiance01
|
||||
```
|
||||
|
||||
Recommended cadence: monthly, and after any provider resize. Traffic
|
||||
allowance is still unknown (operator / panel).
|
||||
|
||||
## How to fold into resource-control
|
||||
|
||||
Map, do not duplicate operating strings into a shadow inventory:
|
||||
|
||||
| Inventory field | Source |
|
||||
| --- | --- |
|
||||
| `provider.name` | Host Europe GmbH |
|
||||
| `provider.product_ref` | `oh.hosting.c2.r4.d100` |
|
||||
| `provider.provider_resource_id` | `c0e697d1-4c71-40f5-9746-0b4c4b9e5c17` |
|
||||
| `provider.account_ref` | `openstack-project:f6491e4f796146d1a7ce30d569d5f122` |
|
||||
| `location.country` | `FR` |
|
||||
| `location.region` | `sxb1` |
|
||||
| `location.residency` | `EU` |
|
||||
| `lifecycle.commissioned_on` | `2026-03-08` |
|
||||
| `lifecycle.ordered_on` / `renews_on` / `cancel_by` | remain `null` (unknown, operator) |
|
||||
| `cost.*` booked price | remain unknown; owner `fin-hub` / `FIN-WP-0004` |
|
||||
| `capacity[]` | latest observation, kinds `usable` / `provisioned` / `observed` |
|
||||
| labor hours in forecasts | `labor-and-exit.yaml#monthly_host_ops_hours` (basis `estimated`) |
|
||||
|
||||
Cluster utilization and PVC allocation stay `RAIL-BS-WP-0014`.
|
||||
|
||||
## Layers in an observation
|
||||
|
||||
1. **provider_declared** — instance type and UUIDs from named cloud-init keys.
|
||||
2. **operating_system** — what the kernel sees (CPU, memory, root filesystem).
|
||||
3. **kubernetes** — k3s capacity, allocatable, and `kubectl top` when present.
|
||||
|
||||
Do not treat k3s allocatable as the provider grant. On 2026-08-15 they
|
||||
happened to match for CPU and memory; ephemeral-storage allocatable is
|
||||
smaller than the root filesystem.
|
||||
|
||||
## What is still unknown
|
||||
|
||||
- Host Europe order date, renewal date, cancellation deadline, contract term
|
||||
- Billing customer number (restricted)
|
||||
- Traffic allowance
|
||||
- Booked recurring price and tax treatment (`fin-hub`)
|
||||
- Measured operator hours (no time records; estimates only)
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
# Non-secret identity and lifecycle for resource:hosteurope:railiance01
|
||||
# Consumer: resource-control inventory fields on that resource.
|
||||
# Booked price, invoices, customer numbers, and payment instruments are
|
||||
# out of scope (fin-hub / operator). Unknowns stay unknown.
|
||||
|
||||
schema_version: "1.0"
|
||||
resource_id: resource:hosteurope:railiance01
|
||||
inventory_name: Railiance01
|
||||
public_ipv4: 92.205.62.239
|
||||
status: active
|
||||
management_model: provider_managed
|
||||
resource_class: compute_instance
|
||||
published_at: "2026-08-15"
|
||||
authority: railiance-infra
|
||||
workplan: RAIL-HO-WP-0008-T01
|
||||
|
||||
provider:
|
||||
name: Host Europe GmbH
|
||||
as_number: AS21499
|
||||
parent: GoDaddy
|
||||
product_class: virtual server
|
||||
product_family: vps4
|
||||
product_ref:
|
||||
value: oh.hosting.c2.r4.d100
|
||||
basis: measured
|
||||
source: cloud-init ds.ec2_metadata.instance-type
|
||||
note: Provider instance-type string. Not a booked catalogue confirmation.
|
||||
provider_resource_id:
|
||||
value: c0e697d1-4c71-40f5-9746-0b4c4b9e5c17
|
||||
basis: measured
|
||||
source: cloud-init ds.meta_data.uuid (OpenStack Nova)
|
||||
provider_instance_id_ec2:
|
||||
value: i-000197c7
|
||||
basis: measured
|
||||
source: cloud-init ds.ec2_metadata.instance-id
|
||||
instance_name:
|
||||
value: 1fxw7h4zug
|
||||
basis: measured
|
||||
source: cloud-init ds.meta_data.name
|
||||
account_ref:
|
||||
value: openstack-project:f6491e4f796146d1a7ce30d569d5f122
|
||||
basis: measured
|
||||
source: cloud-init ds.meta_data.project_id
|
||||
classification: non-secret-join-key
|
||||
note: Cloud tenant UUID for joins. Not the billing customer number.
|
||||
billing_customer_number:
|
||||
value: null
|
||||
classification: restricted
|
||||
owner: operator
|
||||
source: Host Europe control panel
|
||||
provisioning:
|
||||
value: manual
|
||||
basis: quoted
|
||||
source: resource-control/docs/evidence/RESOURCE-WP-0002-host-europe-resolution-2026-08-15.md
|
||||
note: Host Europe has no API to provision this class of resource.
|
||||
|
||||
location:
|
||||
country:
|
||||
value: FR
|
||||
basis: derived
|
||||
sources:
|
||||
- cloud-init local-hostname contains sxb1 (Strasbourg site code)
|
||||
- ipinfo.io 2026-08-15 city=Strasbourg country=FR org=AS21499 Host Europe GmbH
|
||||
- Host Europe Group operates a Strasbourg data centre
|
||||
region:
|
||||
value: sxb1
|
||||
basis: measured
|
||||
source: cloud-init ds.ec2_metadata.local-hostname = 1fxw7h4zug.cloud.prd.sxb1.gdg
|
||||
locality:
|
||||
value: Strasbourg
|
||||
basis: derived
|
||||
source: sxb1 + ipinfo.io
|
||||
residency:
|
||||
value: EU
|
||||
basis: derived
|
||||
source: country FR
|
||||
failure_domains:
|
||||
- provider:host-europe
|
||||
- site:sxb1
|
||||
- host:railiance01
|
||||
ptr: 239.62.205.92.host.secureserver.net
|
||||
|
||||
lifecycle:
|
||||
proposed_on:
|
||||
value: null
|
||||
classification: unknown
|
||||
owner: operator
|
||||
ordered_on:
|
||||
value: null
|
||||
classification: unknown
|
||||
owner: operator
|
||||
source: Host Europe control panel or invoice
|
||||
commissioned_on:
|
||||
value: "2026-03-08"
|
||||
basis: measured
|
||||
source: workplans/archived/260702-RAIL-HO-WP-0001-hosteurope-bootstrap.md and state-hub-inbox/2026-03-08-railiance01-bootstrap.yaml
|
||||
note: Date this repo took the host into S1 management. Not the provider order date.
|
||||
renews_on:
|
||||
value: null
|
||||
classification: unknown
|
||||
owner: operator
|
||||
source: Host Europe control panel or invoice
|
||||
cancel_by:
|
||||
value: null
|
||||
classification: unknown
|
||||
owner: operator
|
||||
source: Host Europe contract / cancellation notice period
|
||||
contract_term:
|
||||
value: null
|
||||
classification: unknown
|
||||
owner: operator
|
||||
retired_on:
|
||||
value: null
|
||||
classification: not_applicable
|
||||
note: Resource is active.
|
||||
|
||||
cost_boundary:
|
||||
currency: EUR
|
||||
booked_price:
|
||||
value: null
|
||||
classification: unknown
|
||||
owner: fin-hub
|
||||
workplan: FIN-WP-0004
|
||||
tax_treatment:
|
||||
value: null
|
||||
classification: unknown
|
||||
owner: fin-hub
|
||||
this_repo_publishes: identity, location, lifecycle dates we can prove, host observations, host-ops labor
|
||||
this_repo_does_not_publish:
|
||||
- invoices
|
||||
- customer numbers
|
||||
- payment instruments
|
||||
- booked recurring price
|
||||
|
||||
residual_credential:
|
||||
finding: cloud-init metadata on the host still exposes an admin_pass field
|
||||
action: rotate or disable that password via the Host Europe panel if it is still valid
|
||||
recorded: false
|
||||
note: The value was not copied into this repository.
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
# Host-level operations labor and exit inputs for resource:hosteurope:railiance01
|
||||
# Consumer: resource-control forecasts that need to separate host infrastructure,
|
||||
# recurring host-ops labor, and replacement/exit effort.
|
||||
# Does not estimate workload, cluster, or platform labor.
|
||||
|
||||
schema_version: "1.0"
|
||||
resource_id: resource:hosteurope:railiance01
|
||||
published_at: "2026-08-15"
|
||||
authority: railiance-infra
|
||||
workplan: RAIL-HO-WP-0008-T03
|
||||
|
||||
scope:
|
||||
includes:
|
||||
- OS baseline convergence
|
||||
- host firewall declared state
|
||||
- host verification (Goss)
|
||||
- SSH access and host identity
|
||||
- host incident response
|
||||
- replacement and cancellation of this virtual server
|
||||
excludes:
|
||||
- k3s / cluster operations (railiance-cluster)
|
||||
- platform services (railiance-platform)
|
||||
- application and tenant labor (those repos)
|
||||
- booked Host Europe spend (fin-hub)
|
||||
|
||||
recurring_activities:
|
||||
- id: os-baseline-converge
|
||||
description: Apply ansible/playbooks/bootstrap.yaml or a tagged subset
|
||||
cadence: event-driven
|
||||
typical_hours: 1.0
|
||||
basis: estimated
|
||||
note: One tagged run. Full untagged converge is deliberately not routine.
|
||||
- id: firewall-declared-state
|
||||
description: Review and apply UFW from inventory (make converge-firewall)
|
||||
cadence: event-driven
|
||||
typical_hours: 0.5
|
||||
basis: estimated
|
||||
note: Hourly Goss timer is unattended; this is operator time when it fails.
|
||||
- id: host-verify
|
||||
description: make verify-host HOST=Railiance01 and read TAP
|
||||
cadence: monthly
|
||||
typical_hours: 0.25
|
||||
basis: estimated
|
||||
- id: ssh-and-identity
|
||||
description: SSH CA, authorized_keys, ops-bridge key, inventory IP
|
||||
cadence: event-driven
|
||||
typical_hours: 0.5
|
||||
basis: estimated
|
||||
- id: host-incident
|
||||
description: Host-level outage, disk, SSH lockout, provider ticket
|
||||
cadence: unscheduled
|
||||
typical_hours: 2.0
|
||||
basis: estimated
|
||||
note: Not every month. Use as a rare-event allowance, not a measured mean.
|
||||
|
||||
monthly_host_ops_hours:
|
||||
value: 2.0
|
||||
unit: hours
|
||||
basis: estimated
|
||||
method: Sum of typical_hours for monthly verify plus a share of event-driven work
|
||||
uncertainty: No time records exist. This is a forecast placeholder, not a measurement.
|
||||
owner: railiance-infra
|
||||
|
||||
one_off_not_recurring:
|
||||
- id: RAIL-HO-WP-0009
|
||||
description: Firewall declared-state integrity and k3s API tunnel-only cutover
|
||||
completed: "2026-08-15"
|
||||
hours: null
|
||||
basis: unknown
|
||||
note: Do not amortise into the monthly host-ops figure until a time record exists.
|
||||
|
||||
exit:
|
||||
path:
|
||||
- Provision replacement compute (manual Host Europe panel or another provider)
|
||||
- Restore or redeploy k3s and stateful services from their owning repos
|
||||
- Switch DNS and ingress
|
||||
- Verify workloads
|
||||
- Cancel the Host Europe virtual server in the panel
|
||||
- Confirm disk wipe / instance destroy
|
||||
replacement_cutover_hours:
|
||||
value: 8.0
|
||||
unit: hours
|
||||
basis: estimated
|
||||
uncertainty: Host-ops only. Cluster and workload cutover hours belong to those repos.
|
||||
cancellation:
|
||||
notice_period:
|
||||
value: null
|
||||
classification: unknown
|
||||
owner: operator
|
||||
source: Host Europe contract
|
||||
panel: Host Europe / GoDaddy VPS control panel
|
||||
provider_api: none
|
||||
data_at_rest:
|
||||
items:
|
||||
- Ubuntu root filesystem (193 GiB provisioned class)
|
||||
- local-path PVs for cluster workloads (failure domain host:railiance01)
|
||||
note: PV contents are not S1-owned; list them only as correlated-exit risk.
|
||||
|
||||
cadence_for_consumers:
|
||||
republish_labor: when the activity catalog changes, or after a quarter of time records
|
||||
republish_exit: when replacement procedure or cancel terms become known
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"schema_version": "1.0",
|
||||
"record_type": "host_capacity_observation",
|
||||
"resource_id": "resource:hosteurope:railiance01",
|
||||
"inventory_name": "Railiance01",
|
||||
"observed_at": "2026-08-15T17:02:50Z",
|
||||
"observer": "railiance-infra/scripts/observe-host-capacity.py",
|
||||
"source_commands": [
|
||||
"nproc / lscpu /proc/meminfo /proc/loadavg statvfs(/)",
|
||||
"cloud-init query <named keys only>",
|
||||
"sudo k3s kubectl get node -o json",
|
||||
"sudo k3s kubectl top node"
|
||||
],
|
||||
"layers": {
|
||||
"provider_declared": {
|
||||
"instance_type": "oh.hosting.c2.r4.d100",
|
||||
"instance_uuid": "c0e697d1-4c71-40f5-9746-0b4c4b9e5c17",
|
||||
"instance_id_ec2": "i-000197c7",
|
||||
"instance_name": "1fxw7h4zug",
|
||||
"product_family": "vps4",
|
||||
"product_role": "vps4_default",
|
||||
"local_hostname": "1fxw7h4zug.cloud.prd.sxb1.gdg",
|
||||
"project_id": "f6491e4f796146d1a7ce30d569d5f122",
|
||||
"note": "Declared by the provider metadata service. Not a booked SKU confirmation."
|
||||
},
|
||||
"operating_system": {
|
||||
"hostname": "239.62.205.92.host.secureserver.net",
|
||||
"os": "Ubuntu 24.04.4 LTS",
|
||||
"cpu": {
|
||||
"value": 4,
|
||||
"unit": "vCPU",
|
||||
"kind": "usable",
|
||||
"model": "AMD EPYC Processor (with IBPB)",
|
||||
"hypervisor": "KVM"
|
||||
},
|
||||
"memory": {
|
||||
"total_kib": 16377056,
|
||||
"available_kib": 10577368,
|
||||
"unit": "KiB",
|
||||
"kind": "usable"
|
||||
},
|
||||
"swap": {
|
||||
"total_kib": 4194300,
|
||||
"unit": "KiB",
|
||||
"kind": "observed",
|
||||
"note": "OS swapfile, not a provider disk grant"
|
||||
},
|
||||
"root_filesystem": {
|
||||
"total_bytes": 206900281344,
|
||||
"used_bytes": 77003415552,
|
||||
"avail_bytes": 129880088576,
|
||||
"kind": "provisioned"
|
||||
},
|
||||
"block_devices": "sda 200G disk QEMU HARDDISK\nsr0 838K rom QEMU DVD-ROM",
|
||||
"loadavg": [
|
||||
"1.85",
|
||||
"1.38",
|
||||
"1.31"
|
||||
],
|
||||
"uptime_seconds": 1738009.47
|
||||
},
|
||||
"kubernetes": {
|
||||
"node_name": "239.62.205.92.host.secureserver.net",
|
||||
"capacity": {
|
||||
"cpu": "4",
|
||||
"memory": "16377056Ki",
|
||||
"ephemeral_storage": "202051056Ki",
|
||||
"pods": "110"
|
||||
},
|
||||
"allocatable": {
|
||||
"cpu": "4",
|
||||
"memory": "16377056Ki",
|
||||
"ephemeral_storage": "196555267123",
|
||||
"pods": "110"
|
||||
},
|
||||
"observed": {
|
||||
"cpu": "1715m",
|
||||
"cpu_pct": "42%",
|
||||
"memory": "6308Mi",
|
||||
"memory_pct": "39%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"unknown": [
|
||||
{
|
||||
"field": "traffic_allowance",
|
||||
"classification": "unknown",
|
||||
"owner": "operator",
|
||||
"source": "Host Europe control panel or contract"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
2026-08-15T170250Z.json
|
||||
216
scripts/observe-host-capacity.py
Executable file
216
scripts/observe-host-capacity.py
Executable file
|
|
@ -0,0 +1,216 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Collect a non-secret host capacity observation over SSH.
|
||||
|
||||
Does not dump cloud-init metadata wholesale (that blob can contain
|
||||
admin_pass). Only named keys are read.
|
||||
|
||||
Usage:
|
||||
scripts/observe-host-capacity.py Railiance01
|
||||
scripts/observe-host-capacity.py Railiance01 > docs/evidence/resource-hosteurope-railiance01/observations/....json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
REMOTE = r"""
|
||||
set -euo pipefail
|
||||
python3 - <<'PY'
|
||||
import json, os, subprocess, shutil
|
||||
|
||||
def sh(cmd):
|
||||
return subprocess.check_output(cmd, shell=True, text=True).strip()
|
||||
|
||||
def cloud(key):
|
||||
if not shutil.which("cloud-init"):
|
||||
return None
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
["cloud-init", "query", key], text=True, stderr=subprocess.DEVNULL
|
||||
).strip()
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
if not out or out.lower() in {"none", "null"}:
|
||||
return None
|
||||
return out
|
||||
|
||||
mem = open("/proc/meminfo").read().split()
|
||||
kv = {}
|
||||
for i, tok in enumerate(mem):
|
||||
if tok.endswith(":") and i + 1 < len(mem):
|
||||
try:
|
||||
kv[tok[:-1]] = int(mem[i + 1])
|
||||
except ValueError:
|
||||
pass
|
||||
st = os.statvfs("/")
|
||||
k3s = {}
|
||||
if shutil.which("k3s"):
|
||||
raw = subprocess.check_output(
|
||||
["sudo", "k3s", "kubectl", "get", "node", "-o", "json"], text=True
|
||||
)
|
||||
node = json.loads(raw)["items"][0]
|
||||
cap = node["status"]["capacity"]
|
||||
alloc = node["status"]["allocatable"]
|
||||
k3s = {
|
||||
"node_name": node["metadata"]["name"],
|
||||
"capacity": {
|
||||
"cpu": cap.get("cpu"),
|
||||
"memory": cap.get("memory"),
|
||||
"ephemeral_storage": cap.get("ephemeral-storage"),
|
||||
"pods": cap.get("pods"),
|
||||
},
|
||||
"allocatable": {
|
||||
"cpu": alloc.get("cpu"),
|
||||
"memory": alloc.get("memory"),
|
||||
"ephemeral_storage": alloc.get("ephemeral-storage"),
|
||||
"pods": alloc.get("pods"),
|
||||
},
|
||||
}
|
||||
try:
|
||||
top = subprocess.check_output(
|
||||
["sudo", "k3s", "kubectl", "top", "node", "--no-headers"],
|
||||
text=True,
|
||||
stderr=subprocess.DEVNULL,
|
||||
).split()
|
||||
# NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
|
||||
if len(top) >= 5:
|
||||
k3s["observed"] = {
|
||||
"cpu": top[1],
|
||||
"cpu_pct": top[2],
|
||||
"memory": top[3],
|
||||
"memory_pct": top[4],
|
||||
}
|
||||
except subprocess.CalledProcessError:
|
||||
k3s["observed"] = None
|
||||
|
||||
print(json.dumps({
|
||||
"hostname": sh("hostname -f"),
|
||||
"os_pretty": sh("grep ^PRETTY_NAME= /etc/os-release").split("=", 1)[1].strip('"'),
|
||||
"nproc": int(sh("nproc")),
|
||||
"cpu_model": sh("lscpu | awk -F: '/Model name/ {print $2; exit}'").strip(),
|
||||
"hypervisor": sh("lscpu | awk -F: '/Hypervisor vendor/ {print $2; exit}'").strip() or None,
|
||||
"mem_total_kib": kv.get("MemTotal"),
|
||||
"mem_available_kib": kv.get("MemAvailable"),
|
||||
"swap_total_kib": kv.get("SwapTotal"),
|
||||
"root_total_bytes": st.f_frsize * st.f_blocks,
|
||||
"root_used_bytes": st.f_frsize * (st.f_blocks - st.f_bfree),
|
||||
"root_avail_bytes": st.f_frsize * st.f_bavail,
|
||||
"block_devices": sh("lsblk -dn -o NAME,SIZE,TYPE,MODEL"),
|
||||
"loadavg": open("/proc/loadavg").read().split()[:3],
|
||||
"uptime_seconds": float(open("/proc/uptime").read().split()[0]),
|
||||
"instance_type": cloud("ds.ec2_metadata.instance-type"),
|
||||
"instance_id_ec2": cloud("ds.ec2_metadata.instance-id"),
|
||||
"instance_uuid": cloud("ds.meta_data.uuid"),
|
||||
"instance_name": cloud("ds.meta_data.name"),
|
||||
"product_family": cloud("ds.meta_data.meta.app"),
|
||||
"product_role": cloud("ds.meta_data.meta.role"),
|
||||
"local_hostname": cloud("ds.ec2_metadata.local-hostname"),
|
||||
"project_id": cloud("ds.meta_data.project_id"),
|
||||
"k3s": k3s or None,
|
||||
}))
|
||||
PY
|
||||
"""
|
||||
|
||||
|
||||
def inventory_ssh(name: str) -> tuple[str, str]:
|
||||
import yaml
|
||||
|
||||
path = os.path.join(os.path.dirname(__file__), "..", "inventory", "servers.yaml")
|
||||
with open(path) as f:
|
||||
servers = yaml.safe_load(f).get("servers") or []
|
||||
for s in servers:
|
||||
if s.get("name") == name:
|
||||
return s["ip"], s.get("ssh_user", "tegwick")
|
||||
raise SystemExit(f"unknown inventory host: {name}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: observe-host-capacity.py <InventoryName>", file=sys.stderr)
|
||||
return 2
|
||||
name = sys.argv[1]
|
||||
ip, user = inventory_ssh(name)
|
||||
observed_at = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
raw = subprocess.check_output(
|
||||
["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=15", f"{user}@{ip}", REMOTE],
|
||||
text=True,
|
||||
)
|
||||
host = json.loads(raw)
|
||||
rec = {
|
||||
"schema_version": "1.0",
|
||||
"record_type": "host_capacity_observation",
|
||||
"resource_id": "resource:hosteurope:railiance01" if name == "Railiance01" else f"resource:host:{name.lower()}",
|
||||
"inventory_name": name,
|
||||
"observed_at": observed_at,
|
||||
"observer": "railiance-infra/scripts/observe-host-capacity.py",
|
||||
"source_commands": [
|
||||
"nproc / lscpu /proc/meminfo /proc/loadavg statvfs(/)",
|
||||
"cloud-init query <named keys only>",
|
||||
"sudo k3s kubectl get node -o json",
|
||||
"sudo k3s kubectl top node",
|
||||
],
|
||||
"layers": {
|
||||
"provider_declared": {
|
||||
"instance_type": host.get("instance_type"),
|
||||
"instance_uuid": host.get("instance_uuid"),
|
||||
"instance_id_ec2": host.get("instance_id_ec2"),
|
||||
"instance_name": host.get("instance_name"),
|
||||
"product_family": host.get("product_family"),
|
||||
"product_role": host.get("product_role"),
|
||||
"local_hostname": host.get("local_hostname"),
|
||||
"project_id": host.get("project_id"),
|
||||
"note": "Declared by the provider metadata service. Not a booked SKU confirmation.",
|
||||
},
|
||||
"operating_system": {
|
||||
"hostname": host["hostname"],
|
||||
"os": host["os_pretty"],
|
||||
"cpu": {
|
||||
"value": host["nproc"],
|
||||
"unit": "vCPU",
|
||||
"kind": "usable",
|
||||
"model": host.get("cpu_model"),
|
||||
"hypervisor": host.get("hypervisor"),
|
||||
},
|
||||
"memory": {
|
||||
"total_kib": host["mem_total_kib"],
|
||||
"available_kib": host["mem_available_kib"],
|
||||
"unit": "KiB",
|
||||
"kind": "usable",
|
||||
},
|
||||
"swap": {
|
||||
"total_kib": host["swap_total_kib"],
|
||||
"unit": "KiB",
|
||||
"kind": "observed",
|
||||
"note": "OS swapfile, not a provider disk grant",
|
||||
},
|
||||
"root_filesystem": {
|
||||
"total_bytes": host["root_total_bytes"],
|
||||
"used_bytes": host["root_used_bytes"],
|
||||
"avail_bytes": host["root_avail_bytes"],
|
||||
"kind": "provisioned",
|
||||
},
|
||||
"block_devices": host["block_devices"],
|
||||
"loadavg": host["loadavg"],
|
||||
"uptime_seconds": host["uptime_seconds"],
|
||||
},
|
||||
"kubernetes": host.get("k3s"),
|
||||
},
|
||||
"unknown": [
|
||||
{
|
||||
"field": "traffic_allowance",
|
||||
"classification": "unknown",
|
||||
"owner": "operator",
|
||||
"source": "Host Europe control panel or contract",
|
||||
}
|
||||
],
|
||||
}
|
||||
json.dump(rec, sys.stdout, indent=2)
|
||||
sys.stdout.write("\n")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -4,11 +4,11 @@ type: workplan
|
|||
title: "Publish railiance01 resource and commercial evidence"
|
||||
domain: financials
|
||||
repo: railiance-infra
|
||||
status: ready
|
||||
status: finished
|
||||
owner: codex
|
||||
topic_slug: railiance
|
||||
created: "2026-08-11"
|
||||
updated: "2026-08-11"
|
||||
updated: "2026-08-15"
|
||||
related:
|
||||
- RESOURCE-WP-0003
|
||||
state_hub_workstream_id: "7122657f-87c8-46b5-a725-a1af1ba0af12"
|
||||
|
|
@ -27,7 +27,7 @@ Origin: `RESOURCE-WP-0003-T04` delegated evidence gap.
|
|||
|
||||
```task
|
||||
id: RAIL-HO-WP-0008-T01
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "cde55584-229d-4e27-8fc8-0f54d78c71ed"
|
||||
```
|
||||
|
|
@ -41,11 +41,23 @@ customer numbers, or payment instruments.
|
|||
Done when resource-control can update the provider and lifecycle fields with
|
||||
provenance, while unknown or restricted fields remain explicitly classified.
|
||||
|
||||
**Done 2026-08-15.**
|
||||
`docs/evidence/resource-hosteurope-railiance01/identity-lifecycle.yaml`
|
||||
|
||||
- Product class: virtual server, family `vps4`, type `oh.hosting.c2.r4.d100`
|
||||
- Provider resource ID: OpenStack UUID `c0e697d1-4c71-40f5-9746-0b4c4b9e5c17`
|
||||
- Join key: `openstack-project:f6491e4f796146d1a7ce30d569d5f122` (not the
|
||||
billing customer number)
|
||||
- Location: `sxb1` / Strasbourg / `FR` / EU
|
||||
- Commissioned into S1 management: `2026-03-08`
|
||||
- Order, renewal, cancel-by, contract term: unknown, owner operator
|
||||
- Booked price: unknown, owner fin-hub (`FIN-WP-0004`)
|
||||
|
||||
## T02 — Publish host capacity and utilization evidence
|
||||
|
||||
```task
|
||||
id: RAIL-HO-WP-0008-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "8b0e5547-23ae-4516-b6d7-9c196882d088"
|
||||
```
|
||||
|
|
@ -58,11 +70,20 @@ Kubernetes observations.
|
|||
Done when at least one reproducible observation can be consumed by
|
||||
resource-control and its source and cadence are documented.
|
||||
|
||||
**Done 2026-08-15.** Producer
|
||||
`scripts/observe-host-capacity.py`; first observation
|
||||
`docs/evidence/resource-hosteurope-railiance01/observations/2026-08-15T170250Z.json`
|
||||
(also `observations/latest.json`). Cadence: monthly, and after a resize.
|
||||
Traffic allowance remains unknown.
|
||||
|
||||
Sample at `2026-08-15T17:02:50Z`: 4 vCPU, 16377056 KiB RAM, 193 GiB root
|
||||
(72 GiB used); k3s `kubectl top` 1715m CPU / 6308 MiB memory.
|
||||
|
||||
## T03 — Record infrastructure operations labor and exit inputs
|
||||
|
||||
```task
|
||||
id: RAIL-HO-WP-0008-T03
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "8bdaa96f-400b-455f-87db-d5c11e287142"
|
||||
```
|
||||
|
|
@ -74,8 +95,23 @@ Do not estimate another repository's workload labor.
|
|||
Done when resource-control can separate host infrastructure, recurring labor,
|
||||
and migration effort in forecasts and optimization cases.
|
||||
|
||||
**Done 2026-08-15.**
|
||||
`docs/evidence/resource-hosteurope-railiance01/labor-and-exit.yaml`
|
||||
|
||||
Recurring host-ops catalog plus `monthly_host_ops_hours: 2.0` (basis
|
||||
`estimated`, no time records). Replacement cutover 8 host-ops hours
|
||||
(estimated). Cancellation notice period unknown. Cluster and workload labor
|
||||
excluded.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] Provider identity and lifecycle evidence is non-secret and attributable.
|
||||
- [ ] Capacity and utilization observations have timestamps and provenance.
|
||||
- [ ] Host labor and exit inputs can be consumed without duplicating booked cost.
|
||||
- [x] Provider identity and lifecycle evidence is non-secret and attributable.
|
||||
- [x] Capacity and utilization observations have timestamps and provenance.
|
||||
- [x] Host labor and exit inputs can be consumed without duplicating booked cost.
|
||||
|
||||
## Outcome
|
||||
|
||||
Finished 2026-08-15. Interface:
|
||||
`docs/evidence/resource-hosteurope-railiance01/`. resource-control can fold
|
||||
the known fields and must leave booked price and contract dates unknown until
|
||||
their owners publish them.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue