[{"data":1,"prerenderedAt":2703},["ShallowReactive",2],{"navigation_docs":3,"-platform-development-server":237,"-platform-development-server-surround":2698},[4,142],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":36},"Kinotic Apps","i-lucide-rocket","\u002Fapps","01.apps",[10,14,18,37,58,91,102,122,127],{"title":11,"path":12,"stem":13},"Introduction","\u002Fapps\u002Fintroduction","01.apps\u002F01.introduction",{"title":15,"path":16,"stem":17},"Quick Start","\u002Fapps\u002Fquick-start","01.apps\u002F02.quick-start",{"title":19,"icon":20,"path":21,"stem":22,"children":23,"page":36},"Application Structure","i-lucide-folder-tree","\u002Fapps\u002Fapplication-structure","01.apps\u002F03.application-structure",[24,28,32],{"title":25,"path":26,"stem":27},"Overview","\u002Fapps\u002Fapplication-structure\u002Foverview","01.apps\u002F03.application-structure\u002F01.overview",{"title":29,"path":30,"stem":31},"Applications and Projects","\u002Fapps\u002Fapplication-structure\u002Fapplications-and-projects","01.apps\u002F03.application-structure\u002F02.applications-and-projects",{"title":33,"path":34,"stem":35},"Artifact Types","\u002Fapps\u002Fapplication-structure\u002Fartifact-types","01.apps\u002F03.application-structure\u002F03.artifact-types",false,{"title":38,"icon":39,"path":40,"stem":41,"children":42,"page":36},"Services","i-lucide-network","\u002Fapps\u002Fservices","01.apps\u002F04.services",[43,46,50,54],{"title":25,"path":44,"stem":45},"\u002Fapps\u002Fservices\u002Foverview","01.apps\u002F04.services\u002F01.overview",{"title":47,"path":48,"stem":49},"Publishing Services","\u002Fapps\u002Fservices\u002Fpublishing-services","01.apps\u002F04.services\u002F02.publishing-services",{"title":51,"path":52,"stem":53},"Service Proxies","\u002Fapps\u002Fservices\u002Fservice-proxies","01.apps\u002F04.services\u002F03.service-proxies",{"title":55,"path":56,"stem":57},"Streaming","\u002Fapps\u002Fservices\u002Fstreaming","01.apps\u002F04.services\u002F04.streaming",{"title":59,"icon":60,"path":61,"stem":62,"children":63,"page":36},"Persistence","i-lucide-database","\u002Fapps\u002Fpersistence","01.apps\u002F05.persistence",[64,67,71,75,79,83,87],{"title":25,"path":65,"stem":66},"\u002Fapps\u002Fpersistence\u002Foverview","01.apps\u002F05.persistence\u002F01.overview",{"title":68,"path":69,"stem":70},"Defining Entities","\u002Fapps\u002Fpersistence\u002Fdefining-entities","01.apps\u002F05.persistence\u002F02.defining-entities",{"title":72,"path":73,"stem":74},"Entity Decorators","\u002Fapps\u002Fpersistence\u002Fentity-decorators","01.apps\u002F05.persistence\u002F03.entity-decorators",{"title":76,"path":77,"stem":78},"CRUD Operations","\u002Fapps\u002Fpersistence\u002Fcrud-operations","01.apps\u002F05.persistence\u002F04.crud-operations",{"title":80,"path":81,"stem":82},"Named Queries","\u002Fapps\u002Fpersistence\u002Fnamed-queries","01.apps\u002F05.persistence\u002F05.named-queries",{"title":84,"path":85,"stem":86},"Multi-Tenancy","\u002Fapps\u002Fpersistence\u002Fmulti-tenancy","01.apps\u002F05.persistence\u002F06.multi-tenancy",{"title":88,"path":89,"stem":90},"Migrations","\u002Fapps\u002Fpersistence\u002Fmigrations","01.apps\u002F05.persistence\u002F07.migrations",{"title":92,"icon":93,"path":94,"stem":95,"children":96,"page":36},"Security","i-lucide-shield-check","\u002Fapps\u002Fsecurity","01.apps\u002F06.security",[97],{"title":98,"path":99,"stem":100,"icon":101},"Authentication","\u002Fapps\u002Fsecurity\u002Fauthentication","01.apps\u002F06.security\u002F01.authentication","i-lucide-key-round",{"title":103,"icon":104,"path":105,"stem":106,"children":107,"page":36},"Deployment","i-lucide-cloud-upload","\u002Fapps\u002Fdeployment","01.apps\u002F07.deployment",[108,113,118],{"title":109,"path":110,"stem":111,"icon":112},"Deployment Workflow","\u002Fapps\u002Fdeployment\u002Fworkflow","01.apps\u002F07.deployment\u002F01.workflow","i-lucide-git-branch",{"title":114,"path":115,"stem":116,"icon":117},"Environments","\u002Fapps\u002Fdeployment\u002Fenvironments","01.apps\u002F07.deployment\u002F02.environments","i-lucide-server",{"title":119,"path":120,"stem":121,"icon":6},"Push to Deploy","\u002Fapps\u002Fdeployment\u002Fpush-to-deploy","01.apps\u002F07.deployment\u002F03.push-to-deploy",{"title":123,"path":124,"stem":125,"icon":126},"CLI Reference","\u002Fapps\u002Fcli-reference","01.apps\u002F08.cli-reference","i-lucide-terminal",{"title":128,"icon":129,"path":130,"stem":131,"children":132,"page":36},"Reference","i-lucide-book-open","\u002Fapps\u002Freference","01.apps\u002F09.reference",[133,138],{"title":134,"path":135,"stem":136,"icon":137},"Decorators Reference","\u002Fapps\u002Freference\u002Fdecorators","01.apps\u002F09.reference\u002F01.decorators","i-lucide-at-sign",{"title":139,"path":140,"stem":141,"icon":60},"Migration SQL Grammar","\u002Fapps\u002Freference\u002Fmigration-sql-grammar","01.apps\u002F09.reference\u002F02.migration-sql-grammar",{"title":143,"icon":117,"path":144,"stem":145,"children":146,"page":36},"Kinotic OS","\u002Fplatform","02.platform",[147,152,156,161,166,171,175,180,185,190,195,200,222,227,232],{"title":148,"path":149,"stem":150,"icon":151},"System Architecture","\u002Fplatform\u002Farchitecture","02.platform\u002F01.architecture","i-lucide-boxes",{"title":153,"path":154,"stem":155,"icon":6},"Deployment Guide","\u002Fplatform\u002Fdeployment-guide","02.platform\u002F02.deployment-guide",{"title":157,"path":158,"stem":159,"icon":160},"Configuration","\u002Fplatform\u002Fconfiguration","02.platform\u002F03.configuration","i-lucide-settings",{"title":162,"path":163,"stem":164,"icon":165},"Organization Management","\u002Fplatform\u002Forganization-management","02.platform\u002F04.organization-management","i-lucide-building",{"title":167,"path":168,"stem":169,"icon":170},"System Security","\u002Fplatform\u002Fsystem-security","02.platform\u002F05.system-security","i-lucide-shield",{"title":172,"path":173,"stem":174,"icon":93},"Defense in Depth","\u002Fplatform\u002Fdefense-in-depth","02.platform\u002F06.defense-in-depth",{"title":176,"path":177,"stem":178,"icon":179},"MCP Tools","\u002Fplatform\u002Fmcp-tools","02.platform\u002F07.mcp-tools","i-lucide-bot",{"title":181,"path":182,"stem":183,"icon":184},"Observability","\u002Fplatform\u002Fobservability","02.platform\u002F08.observability","i-lucide-activity",{"title":186,"path":187,"stem":188,"icon":189},"Contributing","\u002Fplatform\u002Fcontributing","02.platform\u002F09.contributing","i-lucide-git-pull-request",{"title":191,"path":192,"stem":193,"icon":194},"System Migrations","\u002Fplatform\u002Fsystem-migrations","02.platform\u002F10.system-migrations","i-lucide-database-zap",{"title":196,"path":197,"stem":198,"icon":199},"Service Call Liveness","\u002Fplatform\u002Fservice-call-liveness","02.platform\u002F11.service-call-liveness","i-lucide-heart-pulse",{"title":128,"icon":129,"path":201,"stem":202,"children":203,"page":36},"\u002Fplatform\u002Freference","02.platform\u002F12.reference",[204,209,214,218],{"title":205,"path":206,"stem":207,"icon":208},"CRI Format","\u002Fplatform\u002Freference\u002Fcri-format","02.platform\u002F12.reference\u002F01.cri-format","i-lucide-link",{"title":210,"path":211,"stem":212,"icon":213},"Grind Jobs","\u002Fplatform\u002Freference\u002Fgrind-jobs","02.platform\u002F12.reference\u002F02.grind-jobs","i-lucide-workflow",{"title":215,"path":216,"stem":217,"icon":151},"Project Publishing Design","\u002Fplatform\u002Freference\u002Fproject-publishing-design","02.platform\u002F12.reference\u002F03.project-publishing-design",{"title":219,"path":220,"stem":221,"icon":101},"Sign-In Design","\u002Fplatform\u002Freference\u002Fsign-in-design","02.platform\u002F12.reference\u002F04.sign-in-design",{"title":223,"path":224,"stem":225,"icon":226},"Development Server","\u002Fplatform\u002Fdevelopment-server","02.platform\u002F13.development-server","i-lucide-server-cog",{"title":228,"path":229,"stem":230,"icon":231},"Supply Chain Security","\u002Fplatform\u002Fsupply-chain-security","02.platform\u002F14.supply-chain-security","i-lucide-package-check",{"title":233,"path":234,"stem":235,"icon":236},"Enterprise License","\u002Fplatform\u002Fenterprise-license","02.platform\u002F15.enterprise-license","i-lucide-badge-check",{"id":238,"title":223,"body":239,"description":2691,"extension":2692,"links":2693,"meta":2694,"navigation":2695,"path":224,"seo":2696,"stem":225,"__hash__":2697},"docs\u002F02.platform\u002F13.development-server.md",{"type":240,"value":241,"toc":2660},"minimark",[242,246,250,253,262,265,269,274,302,313,316,319,323,334,338,356,359,370,374,381,385,388,391,395,415,419,449,468,472,494,498,512,516,527,531,538,617,626,772,802,820,823,833,855,859,862,982,989,1053,1059,1063,1066,1382,1399,1402,1671,1675,1685,1689,1706,1710,1731,1817,1835,1846,1865,1999,2006,2024,2166,2180,2184,2206,2215,2219,2301,2305,2308,2441,2444,2516,2520,2540,2544,2625,2629,2656],[243,244,25],"h2",{"id":245},"overview",[247,248,249],"p",{},"This page is the design record and setup plan for the development server: a development\nenvironment on one physical host — a Ryzen 9 with 96 GB of RAM and four SSDs — plus two Intel\nNUCs that run the peers' workloads, where a few peers build Kinotic applications against the\nreal platform: organization sign-up,\npush-to-deploy into micro VMs, published UIs served through the Cloudflare sites Worker, email, logs and traces\nin the portal. Every service runs on the host. Azure keeps what a single host cannot provide\n(the sites storage account, Communication Services email) and what the migration path needs (a\nKey Vault, a snapshot container); Cloudflare keeps both zones, the customers' with the sites\ndomain, where the sites Worker serves every published UI, and the platform's, where the same\nWorker serves the portal and the system console from the same account.",[247,251,252],{},"When Kinotic Cloud opens, the peers' organizations, applications, and data move there and keep\nworking. The migration is a designed step of this environment rather than an afterthought, and\ntwo of the decisions below exist only because of it.",[254,255,257,261],"callout",{"type":256},"info",[258,259,260],"strong",{},"Status."," The host, the Azure root, the three servers behind the edge, and two nodes (a NUC\nand a VM on the host) are built, and the first system user is bootstrapped; the end-to-end\ndeployment is still to come. This page records the decisions, the target topology, and the\nsteps that build it, in the order they have to happen.",[263,264],"development-server-diagram",{},[243,266,268],{"id":267},"decisions","Decisions",[270,271,273],"h3",{"id":272},"proxmox-ve-a-container-per-service-the-nodes-are-their-own-machines","Proxmox VE: a container per service; the nodes are their own machines",[247,275,276,277,281,282,285,286,289,290,293,294,297,298,301],{},"The platform and a vm-manager node cannot share one operating system. The node's provisioning\n(",[278,279,280],"code",{},"deployment\u002Fvm-node\u002Fsetup-node.sh",") owns ",[278,283,284],{},"\u002Fetc\u002Fdocker\u002Fdaemon.json",",\n",[278,287,288],{},"br_netfilter",", and the ",[278,291,292],{},"DOCKER-USER"," chain, and its firewall floor (",[278,295,296],{},"kinotic-node-firewall",")\ndrops every guest packet addressed to the node itself — so a gateway on the node's own address\nis unreachable from every workload, and the kit's README says outright not to colocate the\napi-gateway with workloads. So the nodes are separate machines: two Intel NUCs (32 GB, 250 GB\neach) running the kit on their own KVM, and the host runs no workloads at all. Two nodes also\nexercise placement, heartbeat expiry, and draining, which one node never does. If capacity\noutgrows the NUCs, a node VM on the host is the fallback: nested KVM is the configuration every\nmeasurement in the Cloud Hypervisor evaluation was taken in (Azure ",[278,299,300],{},"Standard_D4s_v3",").",[247,303,304,305,308,309,312],{},"Proxmox over plain Ubuntu with libvirt because it adds ZFS, snapshots, containers created from\nOCI images, and the ",[278,306,307],{},"bpg\u002Fproxmox"," Terraform provider, so the host can be a terraform\nroot beside ",[278,310,311],{},"azure\u002F",". Incus is the credible alternative for an all-code, no-UI\nhost; XCP-ng is out because Xen's nested virtualization is off the path the vm-manager was\ntested on; Harvester wants three nodes.",[247,314,315],{},"Every other service is a Proxmox container of its own, created from the image the compose\nstack pulls — Proxmox VE 9.1 creates LXC containers from OCI images — rather than a VM\nrunning docker-compose. Each service is then a first-class guest: its own address, its own\nCPU and memory limits, its own mounts and startup order, visible and restartable in the\nProxmox UI, sharing the host's kernel with no VM in between. There is no compose, no\nKubernetes, and no second layer of orchestration inside a guest. What the Proxmox API does\nnot take for such a container — an environment with Elasticsearch's dotted setting names,\nwhich it validates as words, a resolv.conf, which it writes into no OCI image, the console\nlog, and the ownership of the mounted directories, which must be the container's user's\nbefore the image is unpacked over them — a script terraform uploads applies on the host from\na manifest per container, and the same script is where the operator's secrets are merged in,\nso no secret passes through terraform. Proxmox also does not restart a container whose\nentrypoint exits, so a host timer does.",[247,317,318],{},"A node is never a container on the host: it needs its own kernel modules, sysctls, and iptables.",[270,320,322],{"id":321},"no-kubernetes","No Kubernetes",[247,324,325,326,329,330,333],{},"Nothing the servers need comes from Kubernetes. Ignite discovers peers by shared filesystem or\nstatic addresses as readily as by the Kubernetes API (",[278,327,328],{},"KinoticIgniteConfig","), platform secrets\nare two JSON files at a path, TLS is two PEM files at a path, and the docker-compose stack in\n",[278,331,332],{},"deployment\u002Fdocker-compose\u002F"," is the complete platform — with Tempo and Mimir, which the Helm\ncharts do not deploy yet. The development server runs that stack's images and config files,\none container each. The Helm charts stay the production artifact. If the host ever needs to rehearse them, a k3s\nVM slots in without touching anything else.",[270,335,337],{"id":336},"three-elasticsearch-nodes-one-physical-disk-each-one-shard-and-one-replica","Three Elasticsearch nodes, one physical disk each, one shard and one replica",[247,339,340,341,344,345,348,349,348,352,355],{},"On one host, a node is a failure domain only if its disk is. Redundancy is therefore the\nconjunction of replicas across nodes ",[258,342,343],{},"and"," one physical disk per node: two nodes whose data\ndirectories share a disk, or two volumes carved from one pool, replicate a shard onto the same\nfailure domain and lose it together. Each node's data directory is a dataset on a ZFS pool\nthat spans exactly one disk (",[278,346,347],{},"\u002Fes1\u002Fdata",", ",[278,350,351],{},"\u002Fes2\u002Fdata",[278,353,354],{},"\u002Fes3\u002Fdata","), bind-mounted into that\nnode's container, never a volume from a shared pool.",[247,357,358],{},"Three nodes rather than two because of quorum, not storage: with two master-eligible nodes the\ncluster stops accepting writes the moment one disk dies, even though the surviving replica holds\nevery shard. With three, one disk can fail and the cluster stays writable.",[247,360,361,362,365,366,369],{},"One shard and one replica per index. The production defaults are three and two\n(",[278,363,364],{},"PersistenceProperties","), and every published EntityDefinition costs ",[278,367,368],{},"shards × (1 + replicas)","\nagainst the 1000-shard-per-node ceiling, so the production numbers on three small nodes would\ncap the peers' definitions early for no gain.",[270,371,373],{"id":372},"elasticsearch-on-a-private-network-with-security-off","Elasticsearch on a private network, with security off",[247,375,376,377,380],{},"The three nodes attach only to a private network: a Proxmox SDN simple zone, a bridge with no\nphysical port, whose subnet the host gateways and source-NATs. The migration, the three\nservers and the edge's second interface are on it; nothing on the LAN or the internet has a\nroute to it. With that isolation the cluster runs the way the local compose stack does,\n",[278,378,379],{},"xpack.security.enabled: false",": no transport TLS to issue and rotate, no passwords to place.\nThe NAT is for one direction only — the nodes reach the snapshot container in Azure, and the\nservers reach Azure and the stores on the LAN.",[270,382,384],{"id":383},"three-servers-on-the-private-network-behind-an-sni-edge","Three servers on the private network, behind an SNI edge",[247,386,387],{},"The platform is three servers — org, system and app — each at a hostname of its own, and the\nISP's router forwards a port only to the same port on one device. An edge container takes that\none forward: HAProxy on 443, in TCP mode, reads the SNI of each connection's ClientHello and\npasses the connection through unopened to the server the hostname belongs to, and closes a\nconnection that names none of them. Every server still terminates TLS itself, with a\ncertificate for its own names, as in the cluster, so the edge holds no key and reads no\ntraffic. A forwarded port per server would work as well, at the cost of a port in every\npublished application's API URL.",[247,389,390],{},"The servers live on the private network alone. The edge is the only way in, from the LAN and\nthe internet alike — for the peers, GitHub's webhook, and the nodes' vm-manager and workloads —\nso no server port and none of Ignite's is reachable from the LAN, and the three form their one\nIgnite cluster over their private addresses. The servers see the edge's address as every\nclient's; the edge's log keeps the client's.",[270,392,394],{"id":393},"the-node-runs-the-cloud-hypervisor-provider","The node runs the Cloud Hypervisor provider",[247,396,397,400,401,404,405,410,411,414],{},[278,398,399],{},"BOXLITE"," is the provider that runs anywhere a developer works; ",[278,402,403],{},"CLOUD_HYPERVISOR"," is the one a\nnode is provisioned for, with a health contract the node re-checks on every heartbeat, egress\ndenied by default, host-side stdout capture, and no mount limit (see\n",[406,407,409],"a",{"href":408},"\u002Fplatform\u002Fconfiguration#vm-provider","VM provider","). The development server is a production\nshape, so the node is provisioned with the kit under ",[278,412,413],{},"deployment\u002Fvm-node"," exactly as any other\nnode is.",[270,416,418],{"id":417},"azure-keeps-the-sites-account-email-dns-and-a-server-key-vault-cloudflare-the-sites","Azure keeps the sites account, email, DNS, and a server Key Vault; Cloudflare the sites",[247,420,421,422,425,426,429,430,285,433,436,437,440,441,444,445,448],{},"The developer root ",[278,423,424],{},"deployment\u002Fterraform\u002Fazure\u002Fdev"," already builds everything a server off Azure\nneeds to publish UIs and send email: a resource group, the sites storage account, the sites\nWorker and domain ",[278,427,428],{},"\u003Cenvironment>.kinotic.io",", and a service principal whose ",[278,431,432],{},"AZURE_CLIENT_ID",[278,434,435],{},"AZURE_CLIENT_SECRET",", and ",[278,438,439],{},"AZURE_TENANT_ID"," ",[278,442,443],{},"DefaultAzureCredential"," takes before anything\nelse. A ",[278,446,447],{},"dev-server"," root is that root plus four things: a Key Vault for the servers, the\nportal and the system console on the sites Worker at hostnames of the platform zone, the\nservers' hostnames, and a container for Elasticsearch snapshots.",[247,450,451,452,455,456,459,460,463,464,467],{},"The servers' Key Vault is kept deliberately. Without it the secret storage backend is in-memory\n(",[278,453,454],{},"SecretStorageConfiguration","): nothing stored survives a restart and nothing can be carried to\nthe cloud. Nothing calls ",[278,457,458],{},"SecretStorageService"," today, so the cost is nil this week; the first\nfeature that stores a peer's secret would turn it into silent data loss. With the ",[278,461,462],{},"AZURE","\nbackend, which the servers load from the ",[278,465,466],{},"kinotic-ee"," module, the peers' secrets are already in\nAzure on migration day.",[270,469,471],{"id":470},"migrations-become-append-only-from-the-first-peer-sign-up","Migrations become append-only from the first peer sign-up",[247,473,474,475,478,479,482,483,486,487,285,490,493],{},"Index mappings are strict, ",[278,476,477],{},"V1__init.sql"," is edited in place while the version is ",[278,480,481],{},"-SNAPSHOT",",\nand a restored ",[278,484,485],{},"migration_history"," says every migration is applied — so a field added to a table\nbetween the development server's launch and cutover fails the first save of that entity on the\ncloud side. The repository rule starts append-only discipline \"when the first release exists\";\nfor every table holding a peer's data, that launch is the release. From then on a change to one\nof those tables is a new versioned file. Tables no peer writes (",[278,488,489],{},"kinotic_vm_node",[278,491,492],{},"kinotic_workload",", the fixtures) can keep being edited in place.",[270,495,497],{"id":496},"the-same-github-app-as-kinotic-cloud","The same GitHub App as Kinotic Cloud",[247,499,500,503,504,507,508,511],{},[278,501,502],{},"kinotic_github_app_installation.githubInstallationId"," belongs to one App (the ",[278,505,506],{},"appId"," in the\norg and system servers' ",[278,509,510],{},"application.yml","), and one App has one webhook URL. Using\nthe cloud's App for the development server means the installation rows migrate untouched and\ncutover is a change of webhook URL; a separate App would mean every peer re-installs on their\nrepos after migration. The cost is that while the development server owns the webhook URL,\nnothing else receives pushes from that App.",[270,513,515],{"id":514},"backups-are-elasticsearch-snapshots-to-azure-blob-and-they-are-the-migration-vehicle","Backups are Elasticsearch snapshots to Azure Blob, and they are the migration vehicle",[247,517,518,519,522,523,526],{},"Replicas across disks protect against a disk. They do nothing for a dead host, a power supply,\nor a bad migration. A snapshot repository of type ",[278,520,521],{},"azure"," (the module ships with ES 9) with a\ndaily SLM policy is the backup, and restoring the latest snapshot into the cloud cluster is how\nthe peers' data gets there. ",[278,524,525],{},"deployment\u002Fterraform\u002Fazure\u002FPRODUCTION.md"," lists ES snapshots to\nAzure Blob as an open item for the cloud; the development server builds it first.",[243,528,530],{"id":529},"host","Host",[247,532,533,534,537],{},"Four 512 GB NVMe drives, one per M.2 slot: Proxmox on its own, and one per Elasticsearch\nnode. Each ES drive is a ZFS pool of its own, so a disk failure is contained to the node that\nowns it, and ",[278,535,536],{},"smartd"," on the host watches each drive.",[539,540,541,554],"table",{},[542,543,544],"thead",{},[545,546,547,551],"tr",{},[548,549,550],"th",{},"Drive",[548,552,553],{},"Holds",[555,556,557,569,587,602],"tbody",{},[545,558,559,563],{},[560,561,562],"td",{},"1",[560,564,565,566],{},"Proxmox, every container's root filesystem, the Loki\u002FTempo\u002FMimir\u002FGrafana data under ",[278,567,568],{},"\u002Fvar\u002Flib\u002Fkinotic\u002Fdata",[545,570,571,574],{},[560,572,573],{},"2",[560,575,576,577,580,581,583,584],{},"ZFS pool ",[278,578,579],{},"es1",", dataset ",[278,582,347],{},", mounted into ",[278,585,586],{},"es-1",[545,588,589,592],{},[560,590,591],{},"3",[560,593,576,594,580,597,583,599],{},[278,595,596],{},"es2",[278,598,351],{},[278,600,601],{},"es-2",[545,603,604,607],{},[560,605,606],{},"4",[560,608,576,609,580,612,583,614],{},[278,610,611],{},"es3",[278,613,354],{},[278,615,616],{},"es-3",[247,618,619,620,622,623,625],{},"Four is the smallest count that gives every ES node its own disk and Proxmox its own. Three\nworks, with ",[278,621,616],{}," on a partition of the Proxmox drive, since that is still a different device\nfrom the other two nodes; the cost is rebuilding ",[278,624,616],{}," from its replicas whenever the boot\ndrive is replaced. 512 GB is enough everywhere: an ES node holds about 430 GB at the\nallocation watermark, and the Proxmox drive's largest tenant is the stores' retention.",[539,627,628,647],{},[542,629,630],{},[545,631,632,635,638,641,644],{},[548,633,634],{},"Guest",[548,636,637],{},"Kind",[548,639,640],{},"vCPU",[548,642,643],{},"RAM",[548,645,646],{},"Network",[555,648,649,671,695,712,729,756],{},[545,650,651,659,662,665,668],{},[560,652,653,348,655,348,657],{},[278,654,586],{},[278,656,601],{},[278,658,616],{},[560,660,661],{},"container",[560,663,664],{},"2 each",[560,666,667],{},"4 GB each (2 GB heap)",[560,669,670],{},"private",[545,672,673,684,687,690,693],{},[560,674,675,348,678,348,681],{},[278,676,677],{},"kinotic-server-management",[278,679,680],{},"kinotic-server-system",[278,682,683],{},"kinotic-server-app",[560,685,686],{},"containers",[560,688,689],{},"4 each",[560,691,692],{},"4 GB each",[560,694,670],{},[545,696,697,702,704,706,709],{},[560,698,699],{},[278,700,701],{},"edge",[560,703,661],{},[560,705,562],{},[560,707,708],{},"256 MB",[560,710,711],{},"LAN + private",[545,713,714,719,722,724,727],{},[560,715,716],{},[278,717,718],{},"kinotic-migration",[560,720,721],{},"container, runs once",[560,723,573],{},[560,725,726],{},"2 GB",[560,728,670],{},[545,730,731,745,747,750,753],{},[560,732,733,348,736,348,739,348,742],{},[278,734,735],{},"loki",[278,737,738],{},"tempo",[278,740,741],{},"mimir",[278,743,744],{},"grafana",[560,746,686],{},[560,748,749],{},"2, 2, 2, 1",[560,751,752],{},"1, 1, 2, 0.5 GB",[560,754,755],{},"LAN",[545,757,758,761,764,766,769],{},[560,759,760],{},"Proxmox",[560,762,763],{},"—",[560,765,763],{},[560,767,768],{},"the rest, ~65 GB",[560,770,771],{},"ZFS ARC, and headroom to grow the ES heaps",[247,773,774,775,778,779,782,783,786,787,790,791,348,794,797,798,801],{},"The stores have static addresses on ",[278,776,777],{},"vmbr0","; the edge's LAN interface takes its address by\nDHCP under a fixed MAC, because the ISP's router forwards by device and reserves the\ndevice's address when the forward is saved. The private network is ",[278,780,781],{},"10.10.0.0\u002F24"," with the\nhost at ",[278,784,785],{},".1",", the edge at ",[278,788,789],{},".10",", and the org, system and app servers at ",[278,792,793],{},".20",[278,795,796],{},".22"," and ",[278,799,800],{},".23",".\nOnly the edge is reachable from the internet, on one forwarded port, 443.",[247,803,804,805,808,809,812,813,816,817,819],{},"Proxmox itself is installed by hand, and ",[278,806,807],{},"host\u002Fprepare-host.sh"," runs once on it: the three ZFS\npools, the directories, ",[278,810,811],{},"vm.max_map_count",", the keepalive and address-updater timers, and the\nhost's exposure, SSH by key alone and a firewall that admits SSH and the web UI from the LAN\nonly. From there the host is a\nterraform root, ",[278,814,815],{},"deployment\u002Fterraform\u002Fproxmox",", using ",[278,818,307],{},": the private network, the\nten images, the containers with their mounts, and the manifests and config files it uploads\nto the host. Everything it uploads comes from this\nrepository; nothing secret passes through it; its README is the runbook.",[243,821,822],{"id":686},"Containers",[824,825,830],"pre",{"className":826,"code":828,"language":829},[827],"language-text","deployment\u002Fterraform\u002Fproxmox\u002F\n  main.tf                            # the network, images, containers, and the applier run\n  haproxy.cfg.tftpl                  # the edge: one route per server hostname, by SNI\n  host\u002Fprepare-host.sh               # once on the host: ZFS pools, directories, sysctl, keepalive timer\n  host\u002Fkinotic-apply-container.py    # on the host after every apply: environment, config files, ownership\n  generate-secrets.sh                # a JWT key set per server, the master key, Grafana's password\n  sync-secrets.sh                    # copies them to the host and re-applies the containers that read them\ndeployment\u002Fdocker-compose\u002F\n  tempo.yml, mimir.yml, grafana-*.yaml, dashboards\u002F   # the stores' config files, addresses substituted\nkinotic-{org,system,app}-server\u002Fsrc\u002Fmain\u002Fresources\u002F\n  application-dev-server.yml         # each server's dev-server profile: what is fixed for this shape\n\u002Fetc\u002Fkinotic\u002Fsecrets\u002F                # on the host, placed by hand\n  kinotic-servers.env                # AZURE_CLIENT_SECRET, merged into every server's environment\n  kinotic-{org,system,app}-server\u002F   # mounted at \u002Fetc\u002Fkinotic in that server: secrets.yml, platform-secrets\u002F, certs\u002F\n  grafana.env                        # the admin password\n\u002Fvar\u002Flib\u002Fkinotic\u002F{config,data}\u002F\u003Cservice>, \u002Fes{1,2,3}\u002Fdata   # each container's files and state\n","text",[278,831,828],{"__ignoreMap":832},"",[247,834,835,836,839,840,843,844,847,848,850,851,854],{},"Each server runs with ",[278,837,838],{},"SPRING_PROFILES_ACTIVE=production,dev-server",". ",[278,841,842],{},"production"," is what\ntakes it off the development conveniences: no auto-seeded platform secrets\n(",[278,845,846],{},"kinotic.platformSecrets.generateMissing"," is set by the development profile alone), the halting Ignite failure handler, the\nproduction shard defaults — which ",[278,849,447],{}," then overrides. Its environment is composed by\nterraform from what the three share — the compose services', the Azure root's outputs, and the\naddresses only the proxmox root knows — and what is its own; its secrets arrive through\n",[278,852,853],{},"secrets.yml",", which the profile imports, and the env file the applier merges.",[270,856,858],{"id":857},"elasticsearch","Elasticsearch",[247,860,861],{},"Three containers from the same image, identical except for name, address and mount:",[539,863,864,877],{},[542,865,866],{},[545,867,868,871,874],{},[548,869,870],{},"Setting",[548,872,873],{},"Value",[548,875,876],{},"Why",[555,878,879,894,910,925,940,957,967],{},[545,880,881,886,891],{},[560,882,883],{},[278,884,885],{},"node.roles",[560,887,888],{},[278,889,890],{},"master,data,ingest,transform",[560,892,893],{},"Three master-eligible nodes give quorum through one disk loss; a dedicated master is wasted on one host",[545,895,896,904,907],{},[560,897,898,348,901],{},[278,899,900],{},"discovery.seed_hosts",[278,902,903],{},"cluster.initial_master_nodes",[560,905,906],{},"the three private addresses, the three names",[560,908,909],{},"Cluster bootstrap",[545,911,912,917,922],{},[560,913,914],{},[278,915,916],{},"xpack.security.enabled",[560,918,919],{},[278,920,921],{},"false",[560,923,924],{},"The private network is the isolation; see the decision above",[545,926,927,932,937],{},[560,928,929],{},[278,930,931],{},"ES_JAVA_OPTS",[560,933,934],{},[278,935,936],{},"-Xms2048m -Xmx2048m",[560,938,939],{},"Half of each container's 4 GB",[545,941,942,945,954],{},[560,943,944],{},"data mount",[560,946,947,950,951],{},[278,948,949],{},"\u002FesN\u002Fdata"," → ",[278,952,953],{},"\u002Fusr\u002Fshare\u002Felasticsearch\u002Fdata",[560,955,956],{},"One ZFS pool per node, one disk per pool",[545,958,959,961,964],{},[560,960,755],{},[560,962,963],{},"none",[560,965,966],{},"The servers, the migration, and the host reach the cluster on the private network; 9200 is never on the LAN",[545,968,969,973,979],{},[560,970,971],{},[278,972,811],{},[560,974,975,978],{},[278,976,977],{},"262144"," on the host",[560,980,981],{},"Containers share the host kernel; ES refuses to start without it",[247,983,984,985,988],{},"Server side, in the servers' ",[278,986,987],{},"application-dev-server.yml"," and their environment:",[539,990,991,1000],{},[542,992,993],{},[545,994,995,998],{},[548,996,997],{},"Property",[548,999,873],{},[555,1001,1002,1024,1041],{},[545,1003,1004,1009],{},[560,1005,1006],{},[278,1007,1008],{},"kinotic.domain.elasticConnections[0..2]",[560,1010,1011,348,1014,348,1017,1020,1021],{},[278,1012,1013],{},"10.10.0.11",[278,1015,1016],{},".12",[278,1018,1019],{},".13"," on 9200, ",[278,1022,1023],{},"http",[545,1025,1026,1035],{},[560,1027,1028,1031,1032],{},[278,1029,1030],{},"kinotic.managementApi.entityDefinition.numberOfShards"," \u002F ",[278,1033,1034],{},"numberOfReplicas",[560,1036,1037,1031,1039],{},[278,1038,562],{},[278,1040,562],{},[545,1042,1043,1048],{},[560,1044,1045],{},[278,1046,1047],{},"KINOTIC_MIGRATION_ELASTIC_HOST",[560,1049,1050,1052],{},[278,1051,1013],{},", for the one-shot migration container",[247,1054,1055,1056,1058],{},"The snapshot repository is registered once ES is up: an ",[278,1057,521],{}," repository named for the\nenvironment, its account key in each node's keystore, and an SLM policy taking a daily\nsnapshot of every index with 30 days' retention.",[270,1060,1062],{"id":1061},"servers","Servers",[247,1064,1065],{},"What differs between the three:",[539,1067,1068,1086],{},[542,1069,1070],{},[545,1071,1072,1074,1078,1082],{},[548,1073],{},[548,1075,1076],{},[278,1077,677],{},[548,1079,1080],{},[278,1081,680],{},[548,1083,1084],{},[278,1085,683],{},[555,1087,1088,1111,1129,1158,1179,1199,1218,1241,1260,1277,1302,1328,1363],{},[545,1089,1090,1093,1098,1103],{},[560,1091,1092],{},"Hostnames, which its certificate carries",[560,1094,1095],{},[278,1096,1097],{},"dev-api.kinotic.ai",[560,1099,1100],{},[278,1101,1102],{},"dev-system-api.kinotic.ai",[560,1104,1105,348,1108],{},[278,1106,1107],{},"apps-api.dev.kinotic.io",[278,1109,1110],{},"*.apps-api.dev.kinotic.io",[545,1112,1113,1116,1119,1122],{},[560,1114,1115],{},"Serves",[560,1117,1118],{},"the portal, the CLI, MCP hosts, the GitHub webhook",[560,1120,1121],{},"the system console, the nodes' vm-manager",[560,1123,1124,1125,1128],{},"every application at ",[278,1126,1127],{},"\u003CorganizationId>--\u003CapplicationId>.apps-api.dev.kinotic.io",", for its UIs and its runtime workloads",[545,1130,1131,1134,1142,1150],{},[560,1132,1133],{},"Gateway port behind the edge, private address",[560,1135,1136,348,1139],{},[278,1137,1138],{},"58503",[278,1140,1141],{},"10.10.0.20",[560,1143,1144,348,1147],{},[278,1145,1146],{},"58504",[278,1148,1149],{},"10.10.0.22",[560,1151,1152,348,1155],{},[278,1153,1154],{},"58505",[278,1156,1157],{},"10.10.0.23",[545,1159,1160,1165,1175,1177],{},[560,1161,1162],{},[278,1163,1164],{},"kinotic.managementApi.apiBaseUrl",[560,1166,1167,1170,1171,1174],{},[278,1168,1169],{},"https:\u002F\u002Fdev-api.kinotic.ai",", the OIDC redirect URIs' base and, with no ",[278,1172,1173],{},"issuerBaseUrl"," set, the OAuth issuer",[560,1176,763],{},[560,1178,763],{},[545,1180,1181,1187,1193,1196],{},[560,1182,1183,1186],{},[278,1184,1185],{},"kinotic.domain.appApiBaseUrl",", which every server shares",[560,1188,1189,1192],{},[278,1190,1191],{},"https:\u002F\u002Fapps-api.dev.kinotic.io",", the host of an application's OIDC callback",[560,1194,1195],{},"the same; the URL a UI build is handed",[560,1197,1198],{},"the same; what it serves, each application's host its own issuer",[545,1200,1201,1206,1212,1215],{},[560,1202,1203,1186],{},[278,1204,1205],{},"kinotic.domain.portalBaseUrl",[560,1207,1208,1211],{},[278,1209,1210],{},"https:\u002F\u002Fdev-portal.kinotic.ai",", where browser flows return and the emailed links open",[560,1213,1214],{},"the same; the invites its member service sends open there",[560,1216,1217],{},"the same",[545,1219,1220,1228,1230,1239],{},[560,1221,1222,348,1225],{},[278,1223,1224],{},"kinotic.systemServer.apiBaseUrl",[278,1226,1227],{},"consoleBaseUrl",[560,1229,763],{},[560,1231,1232,1235,1236],{},[278,1233,1234],{},"https:\u002F\u002Fdev-system-api.kinotic.ai","; ",[278,1237,1238],{},"https:\u002F\u002Fdev-console.kinotic.ai",[560,1240,763],{},[545,1242,1243,1248,1251,1254],{},[560,1244,1245],{},[278,1246,1247],{},"kinotic.domain.cors.allowedOriginPattern",[560,1249,1250],{},"the portal",[560,1252,1253],{},"the console",[560,1255,1256,1259],{},[278,1257,1258],{},"https:\u002F\u002F[a-z0-9-]+\\."," and the sites domain: the published UIs",[545,1261,1262,1267,1273,1275],{},[560,1263,1264],{},[278,1265,1266],{},"kinotic.managementServer.turnstile.*",[560,1268,1269,1270,1272],{},"Cloudflare's always-passing Turnstile test site, from the ",[278,1271,447],{}," profile: the find-my-organizations form renders the widget and every token verifies",[560,1274,763],{},[560,1276,763],{},[545,1278,1279,1287,1296,1300],{},[560,1280,1281,1031,1284],{},[278,1282,1283],{},"kinotic.managementApi.github.appPrivateKey",[278,1285,1286],{},"webhookSecret",[560,1288,1289,1290,1292,1293],{},"from ",[278,1291,853],{},"; the cloud's App, whose webhook URL is ",[278,1294,1295],{},"https:\u002F\u002Fdev-api.kinotic.ai\u002Fapi\u002Fgithub\u002Fwebhook",[560,1297,1289,1298],{},[278,1299,853],{},[560,1301,763],{},[545,1303,1304,1315,1317,1326],{},[560,1305,1306,348,1309,348,1312],{},[278,1307,1308],{},"kinotic.systemApi.disableAzureStorage",[278,1310,1311],{},"uiDeployment.sitesStorageEndpoint",[278,1313,1314],{},"kinotic.domain.publishedSites.*",[560,1316,763],{},[560,1318,1319,1321,1322,1325],{},[278,1320,921],{},", so the sites Worker serves the published UIs; the development server's sites account; ",[278,1323,1324],{},"dev.kinotic.io"," and the Cloudflare zone each organization's record is created in",[560,1327,763],{},[545,1329,1330,1347,1349,1361],{},[560,1331,1332,797,1335,1338,1339,1031,1341,1031,1344],{},[278,1333,1334],{},"kinotic.systemApi.deployment.managementServer",[278,1336,1337],{},"appServer",": ",[278,1340,529],{},[278,1342,1343],{},"port",[278,1345,1346],{},"useSsl",[560,1348,763],{},[560,1350,1351,797,1353,348,1355,348,1358],{},[278,1352,1097],{},[278,1354,1107],{},[278,1356,1357],{},"443",[278,1359,1360],{},"true",[560,1362,763],{},[545,1364,1365,1370,1372,1380],{},[560,1366,1367],{},[278,1368,1369],{},"kinotic.systemApi.deployment.syncAllowedHosts",[560,1371,763],{},[560,1373,1374,348,1377],{},[278,1375,1376],{},"github.com",[278,1378,1379],{},"registry.npmjs.org",[560,1381,763],{},[247,1383,1384,1385,1388,1389,1392,1393,1395,1396,1398],{},"A workload dials its server by the name the server's certificate carries: each node pins every\nserver's name to the edge's LAN address in its ",[278,1386,1387],{},"\u002Fetc\u002Fhosts",", which its resolver answers guests\nfrom and the vm-manager admits in the name's egress set, so the traffic stays on the LAN, and\nthe guest trusts the Let's Encrypt chain. The node's resolver admits what it answers for each\nof ",[278,1390,1391],{},"syncAllowedHosts"," too, so the same names serve ",[278,1394,399],{}," development and the\n",[278,1397,403],{}," nodes here.",[247,1400,1401],{},"What the three share:",[539,1403,1404,1414],{},[542,1405,1406],{},[545,1407,1408,1410,1412],{},[548,1409,997],{},[548,1411,873],{},[548,1413,876],{},[555,1415,1416,1437,1451,1480,1506,1529,1550,1568,1592,1613,1647],{},[545,1417,1418,1429,1434],{},[560,1419,1420,1031,1423,1031,1426],{},[278,1421,1422],{},"kinotic.apiGateway.ssl.enabled",[278,1424,1425],{},"certPath",[278,1427,1428],{},"keyPath",[560,1430,1431,1433],{},[278,1432,1360],{},", the two certbot PEMs of the server's own certificate",[560,1435,1436],{},"Vert.x terminates TLS, matching Azure; the edge passes each connection through unopened",[545,1438,1439,1444,1448],{},[560,1440,1441],{},[278,1442,1443],{},"kinotic.apiGateway.webServer.enabled",[560,1445,1446],{},[278,1447,921],{},[560,1449,1450],{},"Nothing is served from a jar's webroot; the gateway port is the only one a server listens on, as in the cluster",[545,1452,1453,1458,1462],{},[560,1454,1455],{},[278,1456,1457],{},"kinotic.domain.cors.allowCredentials",[560,1459,1460],{},[278,1461,1360],{},[560,1463,1464,1465,1468,1469,348,1472,1475,1476,1479],{},"The ",[278,1466,1467],{},"kubernetes"," profiles hardcode the production origins, ",[278,1470,1471],{},"portal.kinotic.ai",[278,1473,1474],{},"console.kinotic.ai"," and the sites under ",[278,1477,1478],{},"kinotic.io",", and are not active here",[545,1481,1482,1493,1503],{},[560,1483,1484,1031,1487,1031,1490],{},[278,1485,1486],{},"kinotic.ignite.discoveryType",[278,1488,1489],{},"localAddresses",[278,1491,1492],{},"localAddress",[560,1494,1495,1498,1499,1502],{},[278,1496,1497],{},"LOCAL",", the three private addresses on ",[278,1500,1501],{},"47500",", the server's own",[560,1504,1505],{},"The one Ignite cluster forms over the private network, and discovery binds the private address alone",[545,1507,1508,1513,1519],{},[560,1509,1510],{},[278,1511,1512],{},"kinotic.platformSecrets.jwtSigningKeysPath",[560,1514,1515,1518],{},[278,1516,1517],{},"\u002Fetc\u002Fkinotic\u002Fplatform-secrets\u002Fjwt-signing-keys",", a key set per server",[560,1520,1521,1524,1525,1528],{},[278,1522,1523],{},"activeKeyId"," plus a ",[278,1526,1527],{},"keys"," list, so a key can be added to the set. A token one server issued verifies at that server alone",[545,1530,1531,1536,1541],{},[560,1532,1533],{},[278,1534,1535],{},"kinotic.domain.secretStorage.masterKey",[560,1537,1289,1538,1540],{},[278,1539,853],{},", one key for the three",[560,1542,1543,1546,1547],{},[278,1544,1545],{},"SecretNameDeriver"," derives every stored secret's name from it, and the servers store and read the same secrets, so it is generated once and carried to the cloud, as the platform vault's ",[278,1548,1549],{},"kinotic-secret-storage-master-key",[545,1551,1552,1560,1565],{},[560,1553,1554,1031,1557],{},[278,1555,1556],{},"kinotic.domain.secretStorage.backend",[278,1558,1559],{},"azure.vaultUrl",[560,1561,1562,1564],{},[278,1563,462],{},", the development server's vault",[560,1566,1567],{},"The Key Vault decision above",[545,1569,1570,1581,1589],{},[560,1571,1572,1031,1575,1031,1578],{},[278,1573,1574],{},"kinotic.domain.email.enabled",[278,1576,1577],{},"endpoint",[278,1579,1580],{},"senderAddress",[560,1582,1583,1585,1586],{},[278,1584,1360],{},", the global ACS endpoint, ",[278,1587,1588],{},"DoNotReply@kinotic.ai",[560,1590,1591],{},"Sign-up and invite mail; the principal holds Contributor on the email service",[545,1593,1594,1602,1608],{},[560,1595,1596,1031,1598,348,1600],{},[278,1597,432],{},[278,1599,439],{},[278,1601,435],{},[560,1603,1604,1605],{},"the Azure root's outputs; ",[278,1606,1607],{},"kinotic-servers.env",[560,1609,1464,1610,1612],{},[278,1611,447],{}," root's service principal, which the three run as",[545,1614,1615,1626,1644],{},[560,1616,1617,348,1620,348,1623],{},[278,1618,1619],{},"OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_ENDPOINT",[278,1621,1622],{},"OTEL_EXPORTER_OTLP_HEADERS",[278,1624,1625],{},"OTEL_SERVICE_NAME",[560,1627,1628,1629,1632,1633,1636,1637,1235,1640,1643],{},"Tempo's ",[278,1630,1631],{},":4318",", Mimir's ",[278,1634,1635],{},":9009\u002Fotlp",", Loki's ",[278,1638,1639],{},":3100\u002Fotlp",[278,1641,1642],{},"X-Scope-OrgID=kinotic-system","; the server's name",[560,1645,1646],{},"No collector: the agent exports each signal to its store under the platform tenant, which is what the compose collector stamps on",[545,1648,1649,1654,1668],{},[560,1650,1651],{},[278,1652,1653],{},"OTEL_RESOURCE_ATTRIBUTES",[560,1655,1656,1659,1660,1663,1664,1667],{},[278,1657,1658],{},"service.name",", the server's name, and ",[278,1661,1662],{},"service.instance.id",", the name and the container's vmid (",[278,1665,1666],{},"kinotic-server-app-123",")",[560,1669,1670],{},"The instance id labels the node's logs, which the system console's Cluster page selects one node's by",[270,1672,1674],{"id":1673},"loki-tempo-mimir-grafana","Loki, Tempo, Mimir, Grafana",[247,1676,1677,1678,1681,1682,1684],{},"Four containers on the LAN, running the config files from ",[278,1679,1680],{},"deployment\u002Fdocker-compose"," with\nthe compose service names replaced by the containers' addresses — one definition of each\nstore for local development and the development server. Loki's 3100, Tempo's 4318, and\nMimir's 9009 are what the node's Alloy ships to; Grafana on 3000 asks for a login, since the\nLAN reaches it. Their data lives under ",[278,1683,568],{},", so a container replaced for a\nnewer image keeps it.",[270,1686,1688],{"id":1687},"tls","TLS",[247,1690,1691,1692,1695,1696,285,1698,436,1700,1702,1703,1705],{},"certbot on the host with the ",[278,1693,1694],{},"dns-cloudflare"," plugin and the DNS token the servers hold, which\nholds DNS edit on both zones. A certificate per server, for its hostnames: ",[278,1697,1097],{},[278,1699,1102],{},[278,1701,1107],{}," with ",[278,1704,1110],{},".\nEach certificate's deploy hook\ninstalls the PEMs into the secrets directory that server's container mounts and reboots the\ncontainer, which is the whole of what Reloader does in the cluster. The edge holds none of\nthem. The portal's and the console's certificates are the platform zone's universal certificate\non Cloudflare.",[243,1707,1709],{"id":1708},"nodes","Nodes",[247,1711,1712,1713,1338,1716,1719,1720,1723,1724,1727,1728,1730],{},"Two Intel NUCs, 32 GB and 250 GB each, Ubuntu 26.04 LTS (what the kit is verified on, kernel\n7 included) installed with two XFS partitions mounted with ",[278,1714,1715],{},"prjquota",[278,1717,1718],{},"\u002Fvar\u002Flib\u002Fdocker",", which ",[278,1721,1722],{},"setup-node.sh"," keeps\ninstead of creating its 40 GB loop image, and ",[278,1725,1726],{},"\u002Fvar\u002Flib\u002Fkinotic\u002Fworkloads",". Then the kit from\n",[278,1729,413],{},":",[824,1732,1736],{"className":1733,"code":1734,"language":1735,"meta":832,"style":832},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","sudo .\u002Fsetup-node.sh            # docker, kata 4.1.0 on cloud-hypervisor, daemon.json, firewall floor\nsudo mkdir -p \u002Fetc\u002Fkinotic && sudo touch \u002Fetc\u002Fkinotic\u002Fegress-default-deny && sudo systemctl restart kinotic-node-firewall\nsudo .\u002Finstall-vm-manager.sh    # bun, @kinotic-ai\u002Fvm-manager, kinotic-vm-manager.service\nsudo .\u002Fverify-node.sh           # every invariant, again after every reboot\n","bash",[278,1737,1738,1755,1795,1806],{"__ignoreMap":832},[1739,1740,1743,1747,1751],"span",{"class":1741,"line":1742},"line",1,[1739,1744,1746],{"class":1745},"sBMFI","sudo",[1739,1748,1750],{"class":1749},"sfazB"," .\u002Fsetup-node.sh",[1739,1752,1754],{"class":1753},"sHwdD","            # docker, kata 4.1.0 on cloud-hypervisor, daemon.json, firewall floor\n",[1739,1756,1758,1760,1763,1766,1769,1773,1776,1779,1782,1784,1786,1789,1792],{"class":1741,"line":1757},2,[1739,1759,1746],{"class":1745},[1739,1761,1762],{"class":1749}," mkdir",[1739,1764,1765],{"class":1749}," -p",[1739,1767,1768],{"class":1749}," \u002Fetc\u002Fkinotic",[1739,1770,1772],{"class":1771},"sMK4o"," &&",[1739,1774,1775],{"class":1745}," sudo",[1739,1777,1778],{"class":1749}," touch",[1739,1780,1781],{"class":1749}," \u002Fetc\u002Fkinotic\u002Fegress-default-deny",[1739,1783,1772],{"class":1771},[1739,1785,1775],{"class":1745},[1739,1787,1788],{"class":1749}," systemctl",[1739,1790,1791],{"class":1749}," restart",[1739,1793,1794],{"class":1749}," kinotic-node-firewall\n",[1739,1796,1798,1800,1803],{"class":1741,"line":1797},3,[1739,1799,1746],{"class":1745},[1739,1801,1802],{"class":1749}," .\u002Finstall-vm-manager.sh",[1739,1804,1805],{"class":1753},"    # bun, @kinotic-ai\u002Fvm-manager, kinotic-vm-manager.service\n",[1739,1807,1809,1811,1814],{"class":1741,"line":1808},4,[1739,1810,1746],{"class":1745},[1739,1812,1813],{"class":1749}," .\u002Fverify-node.sh",[1739,1815,1816],{"class":1753},"           # every invariant, again after every reboot\n",[247,1818,1819,1820,1823,1824,797,1826,1828,1829,839,1831,1834],{},"A node VM on the host, from the Proxmox root's ",[278,1821,1822],{},"workers",", is provisioned the same way: Ubuntu\n26.04 from the cloud image on the host's CPU type, so the guest has nested KVM, with\n",[278,1825,1718],{},[278,1827,1726],{}," on XFS disks of its own that cloud-init mounts\nwith ",[278,1830,1715],{},[278,1832,1833],{},"dev-node-2"," is one, with 12 GB and 6 cores, beside the NUCs.",[247,1836,1837,1838,1841,1842,1845],{},"The Azure IMDS and WireServer drops install and verify unchanged; they protect nothing here and\nare left in so every node is provisioned by one path. A NUC's 32 GB is roughly a dozen runtime\nVMs at 1 GB beside a couple of 2 GB sync VMs (",[278,1839,1840],{},"DeploymentProperties","); a first deployment lands\non the first ",[278,1843,1844],{},"ONLINE"," node with room for it.",[247,1847,1848,1849,1852,1853,1856,1857,1860,1861,1864],{},"The vm-manager runs as root under ",[278,1850,1851],{},"kinotic-vm-manager.service"," with Bun (root for iptables, the\nDocker socket, and project quotas). Its environment is ",[278,1854,1855],{},"\u002Fetc\u002Fkinotic\u002Fvm-manager.env",": the\nproxmox root's ",[278,1858,1859],{},"vm_manager_env"," output, which carries the system server's name and the stores'\naddresses, plus the node's own id. The machine credentials go in ",[278,1862,1863],{},"\u002Fetc\u002Fkinotic\u002Fvm-manager.secrets.env",",\nwhich the service waits for:",[539,1866,1867,1876],{},[542,1868,1869],{},[545,1870,1871,1874],{},[548,1872,1873],{},"Variable",[548,1875,873],{},[555,1877,1878,1889,1903,1929,1950,1961,1975],{},[545,1879,1880,1885],{},[560,1881,1882],{},[278,1883,1884],{},"KINOTIC_VM_PROVIDER",[560,1886,1887],{},[278,1888,403],{},[545,1890,1891,1896],{},[560,1892,1893],{},[278,1894,1895],{},"KINOTIC_NODE_ID",[560,1897,1898,348,1901],{},[278,1899,1900],{},"dev-node-1",[278,1902,1833],{},[545,1904,1905,1916],{},[560,1906,1907,1031,1910,1031,1913],{},[278,1908,1909],{},"KINOTIC_SERVER_HOST",[278,1911,1912],{},"KINOTIC_SERVER_PORT",[278,1914,1915],{},"KINOTIC_SERVER_USE_SSL",[560,1917,1918,1920,1921,1923,1924,348,1926,1928],{},[278,1919,1102],{},", which the node's ",[278,1922,1387],{}," pins to the edge's LAN address, ",[278,1925,1357],{},[278,1927,1360],{},": the vm-manager connects to the system server",[545,1930,1931,1939],{},[560,1932,1933,1031,1936],{},[278,1934,1935],{},"KINOTIC_CLIENT_ID",[278,1937,1938],{},"KINOTIC_CLIENT_SECRET",[560,1940,1941,1942,1945,1946,1949],{},"a SYSTEM-scope machine, created by the Proxmox root's ",[278,1943,1944],{},"bootstrap-identities.sh"," or in the system console at ",[258,1947,1948],{},"Members → Machines","; client credentials are the non-Azure machine path",[545,1951,1952,1957],{},[560,1953,1954],{},[278,1955,1956],{},"KINOTIC_WORKLOAD_DATA_DIR",[560,1958,1959],{},[278,1960,1726],{},[545,1962,1963,1968],{},[560,1964,1965],{},[278,1966,1967],{},"KINOTIC_WORKLOAD_DNS",[560,1969,1970,1971,1974],{},"the node's own dnsmasq on the docker bridge address, ",[278,1972,1973],{},"172.17.0.1",", permitted on port 53 for every workload; it is what lets a hostname in a workload's egress policy be enforced",[545,1976,1977,1988],{},[560,1978,1979,1031,1982,1031,1985],{},[278,1980,1981],{},"KINOTIC_LOKI_URL",[278,1983,1984],{},"KINOTIC_TEMPO_URL",[278,1986,1987],{},"KINOTIC_MIMIR_URL",[560,1989,1990,348,1993,348,1996],{},[278,1991,1992],{},"http:\u002F\u002F\u003Cloki>:3100",[278,1994,1995],{},"http:\u002F\u002F\u003Ctempo>:4318",[278,1997,1998],{},"http:\u002F\u002F\u003Cmimir>:9009\u002Fotlp",[243,2000,2002,2003,2005],{"id":2001},"azure-the-dev-server-terraform-root","Azure: the ",[278,2004,447],{}," terraform root",[247,2007,2008,2011,2012,2015,2016,2019,2020,2023],{},[278,2009,2010],{},"deployment\u002Fterraform\u002Fazure\u002Fdev-server"," shares ",[278,2013,2014],{},"modules\u002Fdev-environment"," with the ",[278,2017,2018],{},"dev\u002F"," root\na developer uses for their own machine — the resource group, the sites module, the sites key\nvault, and the service principal — and adds what a server peers depend on needs. Local state,\n",[278,2021,2022],{},"environment = \"dev\"",", creating:",[539,2025,2026,2036],{},[542,2027,2028],{},[545,2029,2030,2033],{},[548,2031,2032],{},"Resource",[548,2034,2035],{},"Purpose",[555,2037,2038,2049,2059,2077,2092,2103,2137,2155],{},[545,2039,2040,2046],{},[560,2041,2042,2043],{},"Resource group ",[278,2044,2045],{},"rg-kinotic-dev",[560,2047,2048],{},"Everything below but the principal",[545,2050,2051,2056],{},[560,2052,2053],{},[278,2054,2055],{},"modules\u002Fsites",[560,2057,2058],{},"The sites storage account, private, and the read-only SAS the Worker reads it with",[545,2060,2061,2069],{},[560,2062,2063,2066,2067],{},[278,2064,2065],{},"modules\u002Fcloudflare-sites"," under ",[278,2068,1324],{},[560,2070,2071,2072,797,2074,2076],{},"The sites Worker, the zone's settings and Total TLS, and the app server's proxied hosts ",[278,2073,1107],{},[278,2075,1110],{}," on the router's address",[545,2078,2079,2085],{},[560,2080,2081,2082],{},"Key Vault ",[278,2083,2084],{},"kv-kinotic-dev",[560,2086,2087,2088,2091],{},"The servers' secret storage; the principal holds Key Vault Secrets Officer, as ",[278,2089,2090],{},"cluster\u002Fkeyvault.tf"," grants the workload identity",[545,2093,2094,2100],{},[560,2095,2096,2097],{},"Storage account ",[278,2098,2099],{},"stkinoticdevsnapshots",[560,2101,2102],{},"The ES snapshot repository's container",[545,2104,2105,2115],{},[560,2106,2107,2108,348,2111,2114],{},"Records and Worker routes ",[278,2109,2110],{},"dev-portal.\u003Czone>",[278,2112,2113],{},"dev-console.\u003Czone>"," in the platform zone",[560,2116,2117,2118,2121,2122,797,2125,2128,2129,2132,2133,2136],{},"The portal and the system console, served from ",[278,2119,2120],{},"sites\u002F\u003Chostname>\u002F"," in the sites account by the Worker exactly as it serves every published site, on the platform zone and never the customers', as the cluster's ",[278,2123,2124],{},"portal",[278,2126,2127],{},"console"," are; ",[278,2130,2131],{},"deploy-ui.sh dev-server dev-server"," builds both with ",[278,2134,2135],{},"--mode dev-server"," and uploads them",[545,2138,2139,2148],{},[560,2140,2141,2142,797,2145,2114],{},"Records ",[278,2143,2144],{},"dev-api",[278,2146,2147],{},"dev-system-api",[560,2149,2150,2151,2154],{},"The host's public address, unproxied, for the management server and the system server; ",[278,2152,2153],{},"kinotic-dyndns.timer"," on the host rewrites them when the ISP changes the address, with the DNS token",[545,2156,2157,2163],{},[560,2158,2159,2160],{},"Service principal ",[278,2161,2162],{},"kinotic-dev-server",[560,2164,2165],{},"Storage Blob Data Contributor on the sites account, Contributor on the email service, Key Vault Secrets Officer on the servers' vault",[247,2167,2168,2169,2172,2173,2176,2177,2179],{},"Its ",[278,2170,2171],{},"dev_server_env"," output is the non-secret half of the servers' environment, which the\nproxmox root merges into each server container's; its ",[278,2174,2175],{},"secrets_env"," output is the principal's\nsecret and the DNS token, which the operator places on the host in ",[278,2178,1607],{}," by\nhand.",[243,2181,2183],{"id":2182},"network-and-access","Network and access",[247,2185,2186,2187,2189,2190,2192,2193,2195,2196,2199,2200,2202,2203,2205],{},"The router forwards one port to the edge container, 443, on which HAProxy listens and hands\neach TLS connection to the server its SNI names: ",[278,2188,1169],{}," is the org\nserver (REST, STOMP, MCP, the GitHub webhook), ",[278,2191,1234],{}," the system\nserver, and ",[278,2194,1191],{}," with every application's\n",[278,2197,2198],{},"https:\u002F\u002F\u003CorganizationId>--\u003CapplicationId>.apps-api.dev.kinotic.io"," the app server, none with a\nport in the URL. The ISP's router forwards a port to the same port only and names its targets\nby device, so the edge's MAC carries a name in the router's device list. The portal at\n",[278,2201,1210],{}," and the system console at ",[278,2204,1238],{}," are\nstatic files in the sites account that the sites Worker serves the way it serves every\npublished site, at hostnames of the platform zone apart from the customers' sites, so the host\nruns nothing for them and the router forwards nothing to them, and no published site is\nsame-site with them or with the servers. This is the cloud's layout, hosted SPAs\nagainst API hosts, each server terminating its own TLS; the edge stands in for the cloud's\nper-server load balancers. The router's address is dynamic: a timer on the host rewrites the\nservers' A records whenever it changes, as the servers' principal.",[247,2207,2208,2209,2211,2212,2214],{},"Peers use ",[278,2210,1210],{},": organization sign-up with email verification\nthrough ACS, social login through the platform OIDC providers registered with\n",[278,2213,1169],{}," redirect URIs, the CLI's device grant, and MCP hosts through the\nauthorization-code grant. Their applications' users sign in on the applications' own hosts.\nNothing on the LAN besides the forwarded port is reachable from outside.",[243,2216,2218],{"id":2217},"backups","Backups",[539,2220,2221,2234],{},[542,2222,2223],{},[545,2224,2225,2228,2231],{},[548,2226,2227],{},"What",[548,2229,2230],{},"How",[548,2232,2233],{},"Restore drill",[555,2235,2236,2246,2256,2272,2281,2291],{},[545,2237,2238,2240,2243],{},[560,2239,858],{},[560,2241,2242],{},"SLM daily snapshot to the Azure Blob repository, 30 days kept",[560,2244,2245],{},"Restore the latest snapshot into a scratch three-node cluster on the host, once, before the first peer signs up, and again before cutover",[545,2247,2248,2251,2253],{},[560,2249,2250],{},"Container root filesystems",[560,2252,963],{},[560,2254,2255],{},"Recreated from the images; every container's state is in a host directory",[545,2257,2258,2264,2267],{},[560,2259,2260,2261,1667],{},"Secrets (",[278,2262,2263],{},"\u002Fetc\u002Fkinotic\u002Fsecrets",[560,2265,2266],{},"The generated directory, kept off the host",[560,2268,2269],{},[278,2270,2271],{},"sync-secrets.sh",[545,2273,2274,2276,2278],{},[560,2275,1709],{},[560,2277,963],{},[560,2279,2280],{},"Ubuntu and the kit again; the checkouts are redeployable",[545,2282,2283,2286,2288],{},[560,2284,2285],{},"Workload checkouts",[560,2287,963],{},[560,2289,2290],{},"Redeploy from GitHub",[545,2292,2293,2296,2298],{},[560,2294,2295],{},"Logs, traces, metrics",[560,2297,963],{},[560,2299,2300],{},"Accepted loss",[243,2302,2304],{"id":2303},"migration-to-kinotic-cloud","Migration to Kinotic Cloud",[247,2306,2307],{},"Everything a peer has lives in one of these places, and moves as follows:",[539,2309,2310,2323],{},[542,2311,2312],{},[545,2313,2314,2317,2320],{},[548,2315,2316],{},"State",[548,2318,2319],{},"Lives in",[548,2321,2322],{},"Moves by",[555,2324,2325,2338,2349,2371,2385,2396,2413,2430],{},[545,2326,2327,2330,2335],{},[560,2328,2329],{},"Organizations, identities, credentials, OIDC configurations, applications, projects, project SBOMs, entity definitions, named queries, refresh tokens, GitHub installations, job runs",[560,2331,2332,2333,1667],{},"os-data indices (",[278,2334,477],{},[560,2336,2337],{},"The last snapshot, restored into the cloud cluster",[545,2339,2340,2343,2346],{},[560,2341,2342],{},"Customer entity data",[560,2344,2345],{},"entity indices",[560,2347,2348],{},"The same snapshot",[545,2350,2351,2354,2360],{},[560,2352,2353],{},"Access tokens in flight",[560,2355,2356,2357],{},"Signed with each server's own ",[278,2358,2359],{},"jwt-signing-keys",[560,2361,2362,2363,2366,2367,2370],{},"Each server's key is added to the same server's cloud key set as a non-active entry; ",[278,2364,2365],{},"KinoticJwtIssuer"," verifies by ",[278,2368,2369],{},"kid"," against every key in the set, so tokens it issued verify until they expire",[545,2372,2373,2376,2382],{},[560,2374,2375],{},"Refresh tokens",[560,2377,2378,2381],{},[278,2379,2380],{},"kinotic_refresh_token"," rows, opaque and hashed",[560,2383,2384],{},"Restored with the snapshot; they do not depend on the signing key",[545,2386,2387,2390,2393],{},[560,2388,2389],{},"Stored secrets",[560,2391,2392],{},"The development server's Key Vault",[560,2394,2395],{},"Copied to the cluster vault, or the cluster is pointed at this vault until it is empty",[545,2397,2398,2401,2407],{},[560,2399,2400],{},"Published UIs",[560,2402,2403,2404],{},"The development server's sites account under ",[278,2405,2406],{},"sites\u002F\u003Capp>--\u003Cui>.\u003Corg>.dev.kinotic.io\u002F",[560,2408,2409,2410],{},"Each project is redeployed after cutover; the URL changes to ",[278,2411,2412],{},"\u003Capp>--\u003Cui>.\u003Corg>.kinotic.io",[545,2414,2415,2418,2427],{},[560,2416,2417],{},"Deployments and checkouts",[560,2419,2420,797,2423,2426],{},[278,2421,2422],{},"kinotic_project_deployment.nodeId",[278,2424,2425],{},"hostDir",", the node's disk",[560,2428,2429],{},"Scrubbed, then redeployed",[545,2431,2432,2435,2438],{},[560,2433,2434],{},"Sessions, logs, traces, metrics",[560,2436,2437],{},"Ignite, Loki, Tempo, Mimir",[560,2439,2440],{},"Not migrated",[247,2442,2443],{},"The cutover, in order:",[2445,2446,2447,2451,2454,2485,2491,2494,2500,2513],"ol",{},[2448,2449,2450],"li",{},"Announce a freeze to the peers; take a final snapshot; stop the development server.",[2448,2452,2453],{},"Restore the snapshot into the cloud os-data and entity clusters. Both clusters must be at the\ndevelopment server's migration version or carry only appended migrations beyond it.",[2448,2455,2456,2457,797,2459,2461,2462,285,2465,348,2467,348,2470,436,2473,2476,2477,2480,2481,2484],{},"Delete the restored ",[278,2458,489],{},[278,2460,492],{}," rows and clear ",[278,2463,2464],{},"nodeId",[278,2466,2425],{},[278,2468,2469],{},"syncWorkloadId",[278,2471,2472],{},"uiPublishWorkloadId",[278,2474,2475],{},"sbomWorkloadId"," on every\n",[278,2478,2479],{},"kinotic_project_deployment"," row. ",[278,2482,2483],{},"resolveTarget"," reuses an existing deployment's node\nwithout checking it exists, so a row still pointing at the development node would fail its\nnext deployment. A re-home operation is the durable version of this step.",[2448,2486,2487,2488,2490],{},"Add each server's signing key to the same server's cloud ",[278,2489,2359],{}," set, inactive;\ndrop them a day later.",[2448,2492,2493],{},"Point the GitHub App's webhook URL at the cloud gateway.",[2448,2495,2496,2497,2499],{},"Re-run the deployment of every project, which places it on a cloud node and republishes its\nUIs under ",[278,2498,1478],{},".",[2448,2501,2502,2503,2505,2506,2508,2509,2512],{},"Change ",[278,2504,1097],{}," to a CNAME of the cloud management server so the peers' CLI logins and\nproject configuration keep resolving, and stop the address updater; they sign in again on\n",[278,2507,1471],{},", since sessions are ",[278,2510,2511],{},"__Host-"," cookies and the social-login redirect\nURIs are registered per host.",[2448,2514,2515],{},"Disable the development node's machine identity; keep the development server running\nread-only for a week, then tear it down.",[243,2517,2519],{"id":2518},"developers-own-environments","Developers' own environments",[247,2521,2522,2523,2015,2525,2527,2528,2532,2533,2535,2536,2539],{},"The same pieces serve a developer's machine, without the Proxmox host: the compose stack\nfrom ",[278,2524,1680],{},[278,2526,399],{}," vm-manager beside it, as the\n",[406,2529,2531],{"href":2530},"\u002Fplatform\u002Fcontributing#running-workloads-on-your-machine","contributing guide"," describes, and\nthe ",[278,2534,2018],{}," root for the sites account, the Cloudflare side, and email. Every\ndevelopment environment shares one GitHub App. An App has one webhook URL, and it points at\nthe development server, so a developer's own server sees no push events: deployments there\nstart from the re-run path, and a developer who needs the webhook registers an App of their\nown, as the ",[406,2537,2531],{"href":2538},"\u002Fplatform\u002Fcontributing#your-tunnel-and-github-app"," describes. Sign-in through Azure and Google is outside this page until that support is defined.",[243,2541,2543],{"id":2542},"build-order","Build order",[2445,2545,2546,2554,2563,2575,2589,2609,2619],{},[2448,2547,2548,440,2551,2553],{},[258,2549,2550],{},"Azure root.",[278,2552,2010],{},"; apply.",[2448,2555,2556,2559,2560,2562],{},[258,2557,2558],{},"Host."," Proxmox installed by hand on disk 0; ",[278,2561,807],{}," with the three\nElasticsearch disks.",[2448,2564,2565,440,2568,2571,2572,2574],{},[258,2566,2567],{},"Secrets and the certificate.",[278,2569,2570],{},"generate-secrets.sh",", the Azure secret and the GitHub\nApp's key filled in, ",[278,2573,2271],{},"; certbot on the host.",[2448,2576,2577,440,2580,2582,2583,2586,2587,2499],{},[258,2578,2579],{},"The fleet.",[278,2581,815],{},"; apply. The applier brings up the cluster,\nthe stores, runs the migration to completion and verifies it, and starts the servers and\nthe edge.\nRegister the snapshot repository and SLM policy; ",[278,2584,2585],{},"deploy-ui.sh"," for the two UIs; confirm\nsign-up mail arrives and the portal loads on ",[278,2588,1210],{},[2448,2590,2591,2594,2595,2598,2599,2601,2602,2605,2606,2608],{},[258,2592,2593],{},"Nodes."," Ubuntu and the kit on each NUC or worker VM, ",[278,2596,2597],{},"vm-manager.env"," from the terraform\noutput plus the node's id; then the Proxmox root's ",[278,2600,1944],{}," creates the\nfirst system user and each node's SYSTEM machine, installing the machine's credentials in\nthe node's ",[278,2603,2604],{},"vm-manager.secrets.env","; confirm each node is ",[278,2607,1844],{}," with no health message.",[2448,2610,2611,2614,2615,2618],{},[258,2612,2613],{},"End to end."," Deploy the template project from a peer's organization: the sync VM fetches\nthrough the allowlist, the runtime VM registers its microservice, the UI appears at\n",[278,2616,2617],{},"\u003Capp>--\u003Cui>.\u003Corg>.dev.kinotic.io",", and the run's log and the microservice's traces show in the\nportal.",[2448,2620,2621,2624],{},[258,2622,2623],{},"Restore drill."," Restore the previous night's snapshot into a scratch cluster and open the\nportal against it. Repeat before cutover.",[243,2626,2628],{"id":2627},"open-items","Open items",[2630,2631,2632,2635,2638,2641,2644,2650,2653],"ul",{},[2448,2633,2634],{},"Containers from OCI images are a technology preview in Proxmox VE 9.1. The environment,\nthe resolvers and the console log are applied on the host until the API takes them, and a\ncontainer whose entrypoint exits is restarted by a host timer; both fold into the terraform\nroot as Proxmox catches up.",[2448,2636,2637],{},"Loki, Tempo, and Mimir are on the LAN without authentication, because the nodes' Alloy ships\nto them from the LAN. A VLAN holding the host and the nodes is the change to make if the LAN\nis not trusted.",[2448,2639,2640],{},"The compose stack runs Elasticsearch 9.5.1 and the ECK values pin 9.0.2. The cloud cluster\nmust be at or above the development server's version on restore day.",[2448,2642,2643],{},"Tempo and Mimir are in compose and not in Helm. When the charts gain them nothing changes on\nthe development server; until then the cloud has no trace or metric history to migrate into,\nwhich is accepted.",[2448,2645,2646,2647,2649],{},"Whether ",[278,2648,1097],{}," stays as a CNAME indefinitely or is retired after the peers have\nmoved their configuration.",[2448,2651,2652],{},"The one-time scrub in cutover step 3 versus building the re-home operation before cutover.",[2448,2654,2655],{},"Whether the node's IPv4-only egress allowlist for GitHub and npm is maintained by hand from\ntheir published ranges or by a resolver-backed ipset, which the kit's README names as the path\nfor hostname allowlists.",[2657,2658,2659],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":832,"searchDepth":1757,"depth":1757,"links":2661},[2662,2663,2675,2676,2682,2683,2685,2686,2687,2688,2689,2690],{"id":245,"depth":1757,"text":25},{"id":267,"depth":1757,"text":268,"children":2664},[2665,2666,2667,2668,2669,2670,2671,2672,2673,2674],{"id":272,"depth":1797,"text":273},{"id":321,"depth":1797,"text":322},{"id":336,"depth":1797,"text":337},{"id":372,"depth":1797,"text":373},{"id":383,"depth":1797,"text":384},{"id":393,"depth":1797,"text":394},{"id":417,"depth":1797,"text":418},{"id":470,"depth":1797,"text":471},{"id":496,"depth":1797,"text":497},{"id":514,"depth":1797,"text":515},{"id":529,"depth":1757,"text":530},{"id":686,"depth":1757,"text":822,"children":2677},[2678,2679,2680,2681],{"id":857,"depth":1797,"text":858},{"id":1061,"depth":1797,"text":1062},{"id":1673,"depth":1797,"text":1674},{"id":1687,"depth":1797,"text":1688},{"id":1708,"depth":1757,"text":1709},{"id":2001,"depth":1757,"text":2684},"Azure: the dev-server terraform root",{"id":2182,"depth":1757,"text":2183},{"id":2217,"depth":1757,"text":2218},{"id":2303,"depth":1757,"text":2304},{"id":2518,"depth":1757,"text":2519},{"id":2542,"depth":1757,"text":2543},{"id":2627,"depth":1757,"text":2628},"A development environment for a few peers on one physical host, what it keeps in Azure, and how their applications move to Kinotic Cloud.","md",null,{},{"icon":226},{"title":223,"description":2691},"BMA5BM6Wv0gjcCSioDvShvUiAmEw70DodXFfAB8fcus",[2699,2701],{"title":219,"path":220,"stem":221,"description":2700,"icon":101,"children":-1},"How OIDC configurations, scoped logins and the one-account-many-memberships model fit together, in seven figures.",{"title":228,"path":229,"stem":230,"description":2702,"icon":231,"children":-1},"How Kinotic inventories, scans and gates the container images it publishes, and where the evidence for each control is kept.",1792555581088]