Release notes
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog.
Unreleased
[0.2.1] - 2026-11-11
Upgrading from v0.2.0: read the upgrade notes below; there is no migration and no new setting. Changes to security controls are in the section Security.
Upgrade notes
- From v0.2.0 only the version changes. There is no database migration (the schema and its compatibility level are those of v0.2.0, so replicas of v0.2.0 and v0.2.1 can run side by side during a rolling update, and
helm rollbackto v0.2.0 works), no new or changed setting and no new or changed value of the two charts; the installers, the systemd units andmyriarch-updaterwork as in v0.2.0. Update the server image or chart (helm upgrade myriarch oci://ghcr.io/myriarch/charts/myriarch --version 0.2.1 -n myriarch -f <your values file>) and, if you run it, the collector chart (--version 0.2.1): its pods restart once, because the chart version and the updater image are part of the pod template, while the collector and supervisor images keep the pins of v0.2.0. Hosts keep working with the updater of v0.2.0; run the installer of v0.2.1 on a host again to give it the updater that is built with Go 1.27.4 (see Security). Building from a checkout needs Go 1.27.4, the version ingo.mod. - Copied install commands name the version of the server. A server serves only the installer bundles of the release it runs. After the update it fetches those of v0.2.1 from
install.bundleSourceBaseUrl, so a mirror in an air gap needschecksums.txt,checksums.txt.sigstore.jsonand the updater archives of the platforms ofinstall.platformsunder<base>/v0.2.1/first, and a command that was copied before the update fails at the download with HTTP 404. Copy the command again from the dialog Add host once every replica runs v0.2.1. - From v0.1.0, follow Upgrade from v0.1.0 to v0.2.0: it applies to v0.2.1 unchanged, with
0.2.1where its commands name0.2.0.
Added
ci/check-stale-versions.py, run byci/check-versions.sh: a text that says “planned for” or “until” a release that exists, and a placeholder page planned for such a release, fail the check of the pull request that prepares the release.- Website: OpAMP explained. The concept page OpAMP, the Supervisor and the Collector explains what OpAMP is and the parts of an OpAMP setup (server, Supervisor, collector, OpAMP Bridge and
myriarch-updater). It shows how a configuration reaches a collector, how rollback works on the host and for a whole rollout, what the Supervisor cannot do and the two ways to run managed collectors on Kubernetes, with the dated sources of the upstream notes. The FAQ answers eight new questions: what OpAMP is, what the Supervisor can and cannot do, how the OpAMP extension differs from the Supervisor, which collector distribution myriarch manages, whether collectors can be updated without internet access, whether Ansible AWX or a pipeline can keep installing collectors, how to roll back a bad configuration, and how myriarch relates to the OpenTelemetry Operator and its OpAMP Bridge. The comparison has sections on the Operator and on existing automation. The page “What is Myriarch?” shows the key terms ofllms.txtand the hosting model.
Changed
- The message of the server for an OpAMP Bridge that is held back from a version rollout no longer names an executor that does not exist.
- Website: findability. The landing page says in its first lines what myriarch is (an open-source OpAMP server and fleet manager for OpenTelemetry Collectors) and which gap of the OpAMP Supervisor it closes. The same sentence opens
llms.txt, the FAQ, the project pages, the web app manifest, the README and the description of the home page. More pages have a search title of their own (website/data/seo_titles.yaml), and the main menu links the overview page; below a width of 992 px it leaves out the entry API reference, which the documentation links, so that the light/dark switch stays in view. The documentation index, the tutorials and the how-to guides link the overview, the comparison, the FAQ and the guide for many collectors. The Open Graph and Twitter titles of a page are its search title without the suffix, and the cards give the type, the size and a text alternative of the image. A page states a publication date only when it shows that date. The placeholder pages of planned guides carrynoindex, followand are left out of the sitemap;check-indexing.pyandcheck-seo.pycheck both.llms.txtlinks more operator pages and states that the collector numbers are design targets. The production aliasmyriarch.vercel.appredirects tohttps://myriarch.dev/, and the release notes link the documentation on the website. - Social images: a tagline that the documentation backs. The Open Graph image of the website, the image prepared for the GitHub social preview (
assets/brand/social-preview.png) and the description of the web UI now say “A GitOps-governed fleet manager for OpenTelemetry Collectors, built on OpAMP.” The old line, “Command ten thousand OpenTelemetry Collectors from one GitOps-governed control plane.”, read as a capacity claim, while the documentation gives around 1,000 collectors, with headroom up to 5,000, as design targets. ADR-0050 records the change and supersedes the tagline item and the explanation of the name in ADR-0015; the name keeps its etymology.
Fixed
- The charts on Artifact Hub. Artifact Hub shows the README of the chart package, and its links were relative to the repository (
../../../docs/operations/install-helm.md,values.schema.json), so they did not resolve there. The release now writes the links of the packaged README of both charts as links to the repository at the release tag (https://github.com/myriarch/myriarch/blob/v0.2.1/docs/operations/install-helm.md,treefor a directory, the anchor kept) and stops the packaging when a link names a path that the tagged commit does not have;verify-chartrefuses a chart whose README still has a relative link. The READMEs in the repository keep their relative links, which are right on GitHub and on the website. Both charts have more keywords for the search:opamp-server,opentelemetry-collector,gitopsandobservabilityfor the server chart, andopamp-supervisor,observabilityandkubernetesfor the collector chart.
Security
- Windows files of v0.2.1 are unsigned. The SignPath signing is not set up yet, so
v0.2.1shipsmyriarch-updater.exe,myrctl.exeand the two PowerShell scripts without Authenticode signatures, under a reviewed exception for exactly that tag (WINDOWS_UNSIGNED_FINAL_TAGindeploy/release/windows-signing.env, ADR-0034). Verify them with cosign before you run them (the security model). - Go 1.27.4. The Go toolchain of the main module and the Go build images (
myriarchd,myriarch-sim, the deprecated combined collector image) move from 1.27.3 to 1.27.4, which fixes GO-2026-6859 innet/httpreported bygovulncheck.
Known issues
- A nightly browser test of updates from the UI fails at one check. The job
ui-updatesof thee2eworkflow does not always see the short health-check phase of an update before the update ends. The other checks of the test pass. The job is not part ofci-okor of the tag guard.
[0.2.0] - 2026-11-06
Upgrading from v0.1.0: read the upgrade notes below first, and Upgrade from v0.1.0 to v0.2.0. Changes to security controls are in the section Security.
Upgrade notes
- Back up the database before you upgrade from v0.1.0; going back to v0.1.0 means restoring that backup. The upgrade applies new migrations (the first is
00006_schema_compat.sql, expand only). v0.1.0 does not check the schema and its migration library accepts a database with higher versions, so it would start on a migrated database without a warning. A rollback to v0.1.0 is not supported (ADR-0036). To prepare, take a backup of the PostgreSQL database (pg_dumpor a snapshot of the managed database) beforehelm upgradeor before you start the new image. To go back, restore that backup and start v0.1.0 against it. - The server can refuse to start because of the database schema. The database records a compatibility level, and
myriarchd servecompares it with its own migrations. A schema that is incompatible with the binary is refused with an error that names the schema version and the levels, whateverdatabase.migrateOnStartsays. Withdatabase.migrateOnStart: false, a schema that lacks migrations of the binary is refused at once and the error namesmyriarchd migrate, where v0.1.0 started and failed later. A newer schema that is still compatible only logs a warning. If you run withdatabase.migrateOnStart: false, runmyriarchd migratebefore you update the deployment; with the defaulttrue, nothing changes for you. myriarchd migratereads only the database settings and prints other lines. The public URL, the login path and the secret files of other features are no longer needed or checked, and other sections of the configuration file are ignored; a file withdatabase.dsnordatabase.dsnFilealone, orMYRIARCH_DATABASE_*variables alone, is enough. The outputschema version: Nis replaced byschema version before: N,schema version after: Nandcompatibility level: N. A script that reads the old line has to readschema version after:.- Helm chart: strict values schema.
helm install,upgrade,templateandlintnow validate the values againstvalues.schema.jsonand refuse what the chart used to ignore or to accept without a word: a key that the chart does not know (a misspelled setting, also insideconfig), a value of the wrong type or in the wrong form (a number where the chart needs a string, a string such as"false"where it needs a boolean, a duration written as an integer such asconfig.server.shutdownTimeout: 30or with a unit that Go does not know such as30d, an image digest withoutsha256:, a registry with a scheme) and a secret as a value. The schema has no key forconfig.database.dsn,config.auth.oidc.clientSecret,config.gitops.webhookSecret,config.notifications.channels[].urlor an enrollment token (enrollmentTokens[].token), and a Secret reference such asenrollmentTokens[].keymust not have the formmyre_...; use the*Filesettings and Secrets.config.auth.bootstrapAdmin,config.opamp.enrollmentTokens,config.devandconfig.auth.devLoginwere refused when the chart rendered and are now refused by the schema. Before you upgrade, runhelm lint(orhelm template) with your values file and the chart of this release and fix what it reports; the message names the path of the value. A release that was installed with--setor upgraded with--reuse-valueshas no values file:helm get values <release> -n <namespace> -o yaml > current-values.yamlwrites its values. See Install with Helm. - Helm chart: the registry of the image is a value of its own.
image.registry(defaultghcr.io) holds the registry host,image.repository(defaultmyriarch/myriarch) the repository without it, andglobal.imageRegistryreplaces the registry, for example with a mirror. A repository that starts with a registry host (registry.example.com/myriarch/myriarch, the form of the values of v0.1.0, orlocalhost:5001/myriarch) is used unchanged, so values that set the full repository and the defaults render the same image as before. A repository without a registry host now takesimage.registry: if you relied on the default registry of your container runtime for such a repository, setimage.registry: ""or put the host into the repository. The schema refuses a registry with a scheme or a path and a repository with a tag, a digest or upper case letters, which Helm rendered into an invalid image reference before. - A stalled
APPLYINGis offered again. When an agent reportsAPPLYINGfor the desired configuration on a connection and the status stays unchanged foropamp.staleApplyingAfter(new setting, default 90 seconds),myriarchdoffers the configuration once more on that connection, once per connection and never for a hash marked failed. The agent eventconfig_offeredcarries the reasonstale_applying. Before, a target in this state waited until theapplyTimeoutof its rollout policy, 10 minutes for configuration by default; a supervisor that restarts while it applies reports the storedAPPLYINGagain and nothing ends it. A supervisor answers the repeated offer of an unchanged configuration withAPPLIEDand does not restart the collector. What to do: nothing. To keep the old behaviour setopamp.staleApplyingAfter: 0; keep a value above theconfig_apply_timeoutof your supervisors (30 seconds in the shipped templates), so that a supervisor that is only slow is not offered the configuration again. A negative value is refused at startup. - The warning
bridge_selectedcan fail a strict GitOps check.myrctl validateand the GitOps sync warn when the selector of a fleet also matches an OpAMP Bridge agent of the OpenTelemetry Operator and the fleet does not set the new fieldspec.bridge.manage. An empty selector triggers it, and so does a selector made only ofNotInandDoesNotExistconditions; so does a sample agent of the repository that is a Bridge and matches such a fleet. A CI that runsmyrctl validate --strictfails on the warning; without--strictthe exit code stays 0. What to do: narrow the selector with a positive condition on an attribute that a Bridge does not report, for exampleos.typeorsite.type({key: os.type, operator: Exists}replaces an empty selector that is meant to match every collector), or setspec.bridge.manage: trueon a fleet that is meant to configure Bridges in standalone mode. Fleets with a positive condition (matchAttributes,In,ExistsorGlob) onos.type, asite.*attribute orlabels.*are not affected. A sample agent that is a Bridge is no longer rendered and does not have to match a fleet. - The installers stop at a package of the upstream project.
install-linux.shandinstall-windows.ps1stop withconflicting_install, before they change anything, when a package of the upstream project would run a collector next to the one that myriarch manages: on Windows a serviceopampsupervisor(supervisor MSI) orotelcol-contrib(contrib MSI), in any state; on Linux the unitotelcol-contrib.serviceoropampsupervisor.serviceof a deb or rpm package, active or enabled. The installers of v0.1.0 went on and left two collectors on the host, and a re-run on a host that was set up next to such a package stops now as well. Nothing is removed. What to do: remove the package as the message and the table “When the installer stops” in Connect a host describe, and run the installer again. A package whose units neither run nor are enabled (the rpm packages since 0.157.0 start nothing) does not stop the installer. - Helm chart: a test Pod, and a source for it in the NetworkPolicy. The chart renders the Pod of
helm test(templates/tests/test-connection.yaml, the Helm hooktest, valuetests.enabled, defaulttrue). Helm creates it only whilehelm testruns, so an installation or an upgrade creates no Pod. Tools that render hooks as ordinary resources create it as a normal Pod that runs once and ends:helm template | kubectl applydoes (add--skip-tests), andtests.enabled: falseremoves it. WithnetworkPolicy.ingress.allowSameNamespace: falseand withoutconfig.server.trustedProxiesandconfig.opamp.clientCertHeader, the NetworkPolicy gets one more source for port 8080, a pod selector with the labels of the test Pod (app.kubernetes.io/component: test), which every pod of the namespace that carries these labels can use. To keep the policy of v0.1.0, in which no pod of the namespace reaches port 8080 unlessnetworkPolicy.ingress.fromnames it, settests.enabled: false. WithtrustedProxiesorclientCertHeaderset the chart adds no rule, andhelm testcan connect only ifnetworkPolicy.ingress.fromnames the test Pod. The defaultallowSameNamespace: truealready admits every pod of the namespace.helm upgrade --reuse-valuesrenders the chart with the values of the installed release in place of its own defaults, and the values of v0.1.0 have neithertestsnorprometheusRule: the chart then renders as v0.1.0 did, without the test Pod, without the NetworkPolicy source for it and without the PrometheusRule, and--set tests.enabled=trueon top of them gives a Pod without requests and limits. To get the defaults of the new chart, upgrade with--reset-then-reuse-valuesor with a values file (helm get values <release> -n <namespace> -o yaml). See Install with Helm. install.grantTtlmust not exceedartifacts.downloadTokenTtl. The new settinginstall.grantTtl(default 1 hour) is the lifetime of the install grants, and production refuses a value aboveartifacts.downloadTokenTtl. A configuration of v0.1.0 that shortenedartifacts.downloadTokenTtlbelow one hour and does not know the new setting stays valid: wheninstall.grantTtlis left out (in the file and inMYRIARCH_INSTALL_GRANTTTL), its default is the shorter of one hour andartifacts.downloadTokenTtl. A value that you set yourself is checked as written. Setinstall.grantTtlto at mostartifacts.downloadTokenTtl, or turn the installation from the server off withinstall.enabled: false, and runmyriarchd config validatebefore you update.- A server that runs a release fetches the installer bundles of its own release. From v0.2.0, every replica of a server that runs a release (not a build from a Git checkout) fetches
checksums.txt, its Sigstore bundle and the updater archives of that release for the platforms ofinstall.platforms(three by default) frominstall.bundleSourceBaseUrl, shortly after the start (15 seconds) and again when somebody renders the install commands or asks for a bundle. The default source is the GitHub releases of the project (github.com/myriarch/myriarch), so this is a new outbound connection of the server, throughoutbound.allowedCidrs,artifacts.proxyUrlandartifacts.allowedRedirectHosts. A server that cannot reach the source logs a warning and shows the bundles asfailed; nothing else is affected, and the commands cannot be rendered until it can. In an air gap, pointinstall.bundleSourceBaseUrlto an internal mirror with the layout<base>/v<version>/<asset>, or setinstall.enabled: false, which keeps the surface of v0.1.0: the routes answer 404 and nothing is fetched. The bundles take about 25 MB ofartifacts.dirfor each release and replica, outsideartifacts.maxCacheBytes; the archives of an earlier version are removed after a day. See Server behaviour. - Enrollment tokens are install credentials now. With the default
install.enabled: true, an enrollment token also authorizes two routes that need no session,POST /api/v1/install/planandGET /api/v1/install/bundles/{filename}(also whereopamp.requireEnrollmentTokenis off), and, through the install grants of a plan, the download of collector and supervisor with their checksums and signatures. Its holder can ask for plans for the platforms ofinstall.platformsand so make the server prepare every release that the frame of its fleet allows. Handle the tokens as before and treat them as fleet credentials: one token for each channel or region, and revoke one that may have leaked, which stops its grants at once. Setinstall.enabled: falseif the hosts of your sites are installed on the manual path only. See Host installation from the server. - OpAMP Bridge agents are a kind of their own, and the server holds back what could change their collector resources. Migration
00007_agent_kind_bridge.sql(expand only) widens the kinds ofagents.kindbybridge. An agent that reports theservice.nameio.opentelemetry.operator-opamp-bridgeor the attributeopentelemetry.io/opamp.bridge.modeis stored as kindbridgewith its next description message. Such an agent receives remote configuration only from a fleet withspec.bridge.manage: true, and only in standalone mode; it is never sent a restart command (the REST action answers412) and never a target of a version rollout. If a Bridge of the OpenTelemetry Operator was connected before, it was treated as a collector so far: check that no fleet relies on configuring it, and scalemyriarchdto one replica for the update, because replicas of an earlier release classify a Bridge as a collector until all replicas run this version. - Fleet assignments are written as a compare-and-set; a rolling update from v0.1.0 needs nothing special. The assignment service writes an agent’s assignment only if the assignment in the database is still the one it read, and records the fleet change and resets the desired state (and with it
failed_config_hash) only when that write took effect. This needs no migration and no new column, so v0.1.0 and the new release can run side by side during a rolling update. Replicas of v0.1.0 still write unconditionally: while they run, two replicas can still record one fleet change twice, as before. The protection is complete when all replicas run this version. install-linux.shno longer requires--collector-version. Without--serverand without the option, the installer installs the version that its release was tested with (theOTELCOL_VERSIONofci/versions.env) and says so;--collector-version latestasks GitHub for the newest release, on request only, because that version is not the one that was tested. Before, the missing option stopped the run withoption_missing. With--serverthe version comes from the plan.install-windows.ps1still requires-CollectorVersionwithout-Server. A script that relied on the error now installs the tested version: pass the version of your fleet explicitly, or use--server.- The collector chart asks for more memory by default.
resources.limits.memoryis now1Gi(was512Mi),resources.requests.memoryis512Mi(was256Mi) andinitResources.limits.memoryis1Gi(was256Mi). With the old limit the main container was OOM-killed in a chart installation test; with1Giit ran without restarts. An installation that sets its ownresourcesorinitResourceskeeps them. Thememory_limiterof the blueprints takes its limit as a percentage of the container limit, so it follows the new value without a change in Git. The init containers run one after the other and before the main container, so the request of the pod does not grow; check that the nodes have room for the higher request of the main container before you upgrade a DaemonSet. - The collector chart always runs a
health_checkextension on0.0.0.0:13133. The chart mounts a small local configuration (healthCheck.enabled, on by default) that the supervisor merges before the configuration from the server. The server’s values win on conflicts, butservice.extensionsis concatenated, so a fleet cannot removehealth_check. A fleet configuration that movesextensions.health_check.endpointtolocalhostor another port makes the pod not Ready; keep it on the pod IP. SethealthCheck.enabled: falseto go back to the old behavior, where the pod is Ready only if the fleet configuration hashealth_checkon the pod IP (or while the startup fallback runs). - The upgrade from v0.1.0 to v0.2.0 is described in one place: Upgrade from v0.1.0 to v0.2.0. It covers the two expand-only migrations and the rolling update, the new and changed settings, the Helm chart, the installers, the GitOps warning
bridge_selectedand the deprecated Kubernetes collector files.
Added
- Bounded leader failover (
ha.leaderSessionTimeout). After a hard failure of the leader’s node (no TCP FIN), PostgreSQL held the advisory lock until it noticed the dead session, which can take hours with the operating system’s TCP defaults, and there was no leader and no rollout until then. The lock session and the event bus listener session now sendtcp_keepalives_idle,tcp_keepalives_interval,tcp_keepalives_countandtcp_user_timeoutderived from the new settingha.leaderSessionTimeout(default 30s, 10s to 10m;MYRIARCH_HA_LEADERSESSIONTIMEOUT), so a second replica becomes leader within that time in total: the setting is split into the time the server needs to drop the dead session and the retry of the followers (every 5 seconds, or a sixth of the setting if shorter). The leader gives up leadership itself after half of the timeout without an answer on its lock session, before another replica can take over, and asks the server to end that session. Both sessions now use a small pool of their own instead of a connection of the main pool, so a database with a tightmax_connectionssees up to two more connections per replica. See High availability for the failover time and for the sizing ofserver.shutdownTimeoutandterminationGracePeriodSeconds, whose worst case covers both drain phases and the flush (90 seconds with the defaults; the chart default of 60 covers a normal shutdown). - Database test matrix. The Go tests run in CI on an embedded PostgreSQL with TLS (
sslmode=verify-full) and with a collation other than C, next to the existing run against the PostgreSQL service. LocallyPGTEST_TLS=1andPGTEST_COLLATION=<locale>select the same environments (development guide). New tests apply every migration to an empty database and upgrade a database with the schema of v0.1.0 (whose migration files are pinned by hash). myriarch-updater install-self <dir>andmyriarch-updater copy <src> <dst>. Two commands for init containers: the upstream collector image isFROM scratchand has no shell orcp.install-selfcopies the running binary to<dir>/myriarch-updaterwith mode 0755;copycopies one regular file, keeps its execute bit and accepts a directory as destination. Both are atomic (temporary file, sync, rename), need absolute paths, refuse a symlink (also as destination directory), a directory or a device as destination, refuse a named pipe or device as source and make no network access (container mode).- Collector chart
deploy/helm/myriarch-collector. Runs OpenTelemetry Collectors that the server manages as a StatefulSet (a gateway, a PVC per replica for the identity) or a DaemonSet (a node agent,persistence.hostPathas an option), with the unchanged upstream images: an init container withmyriarch-updaterrunsinstall-self /shared, an init container withotel/opentelemetry-collector-contribruns/shared/myriarch-updater copy /otelcol-contrib /shared/, and the main container isotel/opentelemetry-collector-opampsupervisor. Every image has a tag and an optional digest; the tags and digests of the two upstream images are those ofci/versions.env. The values cover the enrollment token (an existing Secret, never a value), a CA bundle, the site attributes, awss://endpoint (ws://renders only withserver.allowInsecureTransport=true), resources, tolerations and a NetworkPolicy. The values schema is strict and is converted fromvalues.schema.source.yamlbymake docs-gen, like that of the server chart, and so is the values table of the README.ci/lint-helm.shlints the chart in both modes, in container mode and in the OpenShift profile and packages it, andci/helm-conform.sh(through the newci/helm-conform-collector.sh) renders it for every Kubernetes version of the gate, validates the manifests with kubeconform and asserts the contract, including the guards that make rendering fail. Install the collector with Helm. - Collector chart: container mode as an option.
containerMode.enabledmakesmyriarch-updaterPID 1 of the main container, adds the secondvolumeClaimTemplatebinaries, probes on:13135and aterminationGracePeriodSecondsof at least 360 seconds, and needs the Sigstore trusted root as a ConfigMap unless the verification is switched off on purpose. It is available for the StatefulSet only. - Collector chart: signed OCI artifact. From v0.2.0 every release also publishes
oci://ghcr.io/myriarch/charts/myriarch-collector, packaged with the digest of the signed updater image, signed keyless and attested like the server chart, and verified before publication. - Schema compatibility level. Migration
00006_schema_compat.sqladds the tableschema_compatwith one row. Its columnmin_binary_schemais the lowest schema level (the highest migration number a binary embeds) that a binary needs to run on the database. It starts at 3 and never exceeds the schema version. From 00006 on, the header of every migration states whether it is expand only (the level stays) or raises the level, a migration that raises it sets the value in Up and restores the previous one in Down, and tests check the statements against the effect (ADR-0036, development guide). myriarchd migrate --checkchanges nothing: it prints the schema version, the compatibility level, the schema level of the binary and a status (current,pending,newerorincompatible) and tells the state by its exit code: 0 ready, 3 migrations pending, 4 incompatible, 1 error. Without--check, an incompatible schema is refused with exit code 4 as well.- Helm chart: values schema.
deploy/helm/myriarch/values.schema.json(JSON Schema draft 2020-12) validates the values of the chart. Every object that the chart defines is closed, the Kubernetes objects that it passes on (such as volumes, security contexts, affinity terms and Gateway API references) are open, and the blockconfighas the keys, types, nesting and descriptions ofinternal/config. A parent chart that includes the chart as a dependency keeps working:globaland the keyenabledof the usualcondition: myriarch.enabledare accepted.make docs-gengenerates the file from the hand-writtenvalues.schema.source.yamland the configuration struct (a new generator ininternal/refdoc);ci/check-docs-gen.shfails on drift, and the unit tests fail on a value thatvalues.yamlor a template uses and the schema lacks and on a setting of the configuration that is named like a secret and can hold text. - Helm chart: image reference.
image.registryandglobal.imageRegistry(see the upgrade notes).podDisruptionBudget.maxUnavailable, which the template already read, is now a documented value. - Helm chart: signed OCI artifact. From v0.2.0 every release publishes the chart as
oci://ghcr.io/myriarch/charts/myriarchwith the version of the release as its version andappVersion. The release workflow (new jobschart,push-chartandverify-chart) writes the digest of the server image intoimage.digestof the packaged chart, so the chart references the image by digest only and its keyless signature and provenance attestation cover the image that is pulled; it checks withhelm template(default values and the OpenShift profile) that every image of the rendering is exactly that digest before it pushes. Like the images, the chart is pushed by digest without a tag, verified, and tagged with its version only byfinalizeafter the second approval, which also waits for the chart package to be public.ci/release/chart-package.shbuilds and checks the package without a network or a real registry (ci/tests/chart-package-test.sh);helmjoins the signing tool set but runs only in the jobschartandverify-chart. The packagecharts/myriarchhas to be made public by an organization owner once, like the image packages. Install and verify: Install with Helm, Verify a release. - Helm chart: generated table of the values. The README of the chart lists every value with its type, default and description, generated from
values.schema.jsonandvalues.yamlbymake docs-gen;ci/check-docs-gen.sh(gatedocs) fails when the table is out of date. - Helm chart: Artifact Hub metadata.
Chart.yamlhas the icon, the maintainers (the project address), the category, links to the documentation, the sources, the issues and the security policy,artifacthub.io/operatorand a placeholder for the images that the release job completes with the digest. - Release: Artifact Hub metadata. The workflow
.github/workflows/artifacthub.yml, which the maintainer starts by hand on the release tag, uploads the repository metadata fileartifacthub-repo.yml(one key,repositoryID) of the server chart as an OCI artifact under the tagartifacthub.ioofghcr.io/myriarch/charts/myriarch, so that Artifact Hub can show the chart as “Verified publisher”. The job needs the approval in the environmentrelease, refuses to run before the chart is published, writes only that tag and uses the pinnedcrane.verify-chartaccepts the tagartifacthub.ioonly with the Artifact Hub media types. See Releasing. - Stale
APPLYING. The settingopamp.staleApplyingAfter(default 90 seconds,0turns it off), the countermyriarch_opamp_config_reoffers_total(labelreason, valuestale_applying) and the reasonstale_applyingin the details of the agent eventconfig_offered. The rule is described in Server behaviour. spec.bridge.manage. The new field of a Fleet (defaultfalse) opts the fleet in to configure OpAMP Bridge agents. The opt-in applies to Bridges in standalone mode only. The new warningbridge_selectednames selectors that also match a Bridge. The REST API returns the block asbridgeof a fleet when it is set. See Fleets and the schema reference.- Dependency review. Pull requests and merge groups run a dependency review (
dependency-reviewinci.yml, a member ofci-ok):actions/dependency-review-actioncompares the dependency graph with the base and fails when a dependency that the change adds or updates, in the runtime scope, carries a license outside the CNCF allowlist (and Apache-2.0) or has a known advisory of high or critical severity. The review checks what the dependency graph of GitHub reports for the change with the scope runtime; which ecosystems (Go, npm, Python, GitHub Actions) that covers follows the dependency graph and is to be confirmed by the first runs. It holds the read-only token, writes no comment and asks no third party.ci/license-allowlist.txtis the source of both license lists: its listreleaseis the binding allowlist ofci/check-release-licenses.shfor the Go packages of the release builds, which is as strict as before (the npm packages of the embedded web UI are checked against a license list by the review only;tslibunder 0BSD ships in it today), and all its entries are theallow-licensesof.github/dependency-review-config.yml;ci/tests/license-allowlist-test.shkeeps the two in step and refuses an exception without a reason. The configuration records an exception, with its reason, for each of the 80 packages of the current dependency set that the review would otherwise report (tools, development trees, the brand generator, packages with combined permissive licenses).pkg:npm/elkjs(EPL-2.0, a dependency of Mermaid) has none until it is decided whether the website needs a notice for it, so a change that adds it or changes its resolved version fails the review. See ADR-0038 and “Dependency review” indocs/development.md. - Vulnerability reports in code scanning. The workflow
vulncheck.yml(pushes tomain, daily, by hand) reportsgovulncheckto the code scanning alerts as SARIF: the code ofmainbuilt like a release and with the build tagdevpg(ci/vulncheck.sh --sarif DIR) and the programs of the latest stable release (ci/vulncheck-binaries.sh, binary mode over the 16 archives). The scan of the binaries starts only after the keyless signature ofchecksums.txtand the SHA-256 of every archive were verified, and it reads out only the program of each archive. The release binaries are stripped (-s -w), and govulncheck cannot read the symbols of a stripped program (at its default level it would report every vulnerable symbol of an affected module as called), so the scan runs at module level: a result is a warning that says that a release was built with a module or Go version that an advisory affects, not that a binary contains or calls the vulnerable code. A finding that only the binary report has is closed by a release with the fixed version or dismissed in code scanning with a reason, not added to the allowlist of the gate. Findings thatci/vulncheck-allowlist.txtaccepts are left out until the review date of the entry has passed; from the day after, they are reported again, while the gate only warns. A manual run that names a release (release_tag) reports its binary scan to its own category,govulncheck/binaries-manual, so that it does not replace the analysis of the latest stable release. The workflow blocks nothing (the gatevulncheckincompliancestill does); its only write scope,security-events: writein the jobupload, is listed and reviewed inci/workflow-permissions.txt, and that job runs no code of the repository. - Mirror builder.
scripts/build-mirror-image-crane.shmirrors the OpAMP supervisor as well whenCOMPONENTS="contrib supervisor"is set (the default stayscontrib):cmd/opampsupervisor/v<version>/opampsupervisor_<version>_<os>_<arch>[.exe]with its.sha256and.sigstore.json, in the layout of the upstream releases, each bundle verified offline against the supervisor release workflow (base-binary-release.yaml) for exactly that tag. One directory (OUT_DIR) therefore servesartifacts.sourceBaseUrlofmyriarchdand--release-base-urlof the installers. A platform for which upstream publishes no supervisor is skipped with a warning.VERIFIER_BINnames amyriarch-updaterto verify with instead of building one.ci/tests/image-crane-test.shcovers the layout, the identities and the failure cases offline. - Host end-to-end test.
scripts/host-e2e/linux.sh manual, run by.github/workflows/host-e2e.ymlon fresh Ubuntu 24.04 runner VMs (amd64 and arm64) every night, for pull requests that touch the installer, the updater, the templates of a host or the harness, and on request, installs the collector, the supervisor and themyriarch-updaterwithinstall-linux.shas root (https, offline Sigstore verification, no--insecure) from a mirror of the real upstream release files, against amyriarchdbuilt from the commit. It checks the services under systemd, the registration (fleet,in_sync, health), a second run of the installer that changes nothing, the refusal of a tampered archive, an update with a killed collector (automatic rollback) and its retry, and--uninstall --purgewith the proof that nothing is left. The scripts refuse to run unlessCI=trueandHOST_E2E=1are set, on Linux, not as root and on a host without an earlier installation; a failed run uploads an allow-list of scrubbed log files, also a run that hangs (the step that runs the scenario has a timeout of its own, below that of the job, so that it counts as failed and the upload runs, and the harness saves the files right after the diagnostics). The harness (scripts/host-e2e/lib.sh) is separate from the scenario, so that a further scenario is a function and a step of the workflow.ci/tests/host-e2e-test.shtests the guard, the scrubber, the allow-list, the throwaway CA and the file server without a host. See “Host end-to-end test” in the development guide. - Kubernetes probe in CI.
.github/workflows/e2e-kind.ymlruns the probe ofhack/kind(make kind-probe) on a kind cluster on a runner every night, for pull requests that touch the chart, the updater, the images (their build scripts and pins), the tools or the probe, and on request (PROBE_SKIPskips optional parts). The matrix entry names the pin ofhack/kind/versions.envthat holds the node image (image-var), so a second Kubernetes version is a pin and an entry. The job summary shows the summary block of the probe, and a failed run uploads an allow-list from.cache/kind(log, results, durations, pod logs, diagnostics) and never the kubeconfig, the generated secrets or cookie jars; the step that runs the probe has a timeout of its own, below that of the job, so that a probe that hangs still uploads. Neither workflow is part ofci-ok.ci/tests/workflow-artifacts-test.shemulates the glob rules ofactions/upload-artifactand holds the patterns of both upload steps against names that must and must not be uploaded, the timeouts of the steps that run a test below that of the job, and the path filter of the kind workflow to the files that the probe depends on, with copies of the workflows that are broken on purpose. - Website: findability. Every page has a unique
<title>(a title fromwebsite/data/seo_titles.yaml,<tag> APIfor the REST API reference, or the page title) and, outside the generated references and the decision records, a description of 70 to 160 characters.sitemap.xmlgives every URL a<lastmod>, the date of the last commit that changed its source file (enableGitInfo; the pages of the content adapters get theirs fromwebsite/scripts/source-dates.py), and no longer writeschangefreqandpriority. The website build needs the full Git history and refuses a shallow clone; the website jobs of the workflows check out withfetch-depth: 0.scripts/check-seo.pyfails on a duplicate title, a description outside 70 to 160 characters and a URL without a validlastmod. New pages: What is Myriarch?, a comparison of the OpAMP Supervisor alone, a server on opamp-go and Myriarch with sources and dates, and the guides “Manage many OpenTelemetry Collectors with OpAMP and Git” and “Upgrade the OpenTelemetry Collector on many hosts”; the FAQ answers six more questions. All are linked from the project index, the documentation index, the FAQ andllms.txt. - Website: canonical links, structured data and
llms.txt. Every page now names its original: one absolute<link rel="canonical">below the production originhttps://myriarch.dev/, also in previews, which are built with another base URL (params.seo.canonicalBase, an origin without a path that is checked againstbaseURL); the 404 page has none. The home page carries structured data in JSON-LD, aSoftwareSourceCode(repository, license Apache-2.0, languages, and theversionof the latest release indata/release.yaml) and aWebSite, and every page below/docs/,/project/and/legal/carries aBreadcrumbListthat equals its visible breadcrumb (the three section pages themselves show no breadcrumb, because Docsy hides a trail of one item, and so have no list). The home page has a title that says what the project is (params.seo.homeTitle) instead of the bare site title./llms.txtis a curated summary for language models (what the project does and for whom, key terms, the most important pages), written fromdata/llms.yamlbylayouts/home.llms.txtthrough the output formatLLMSof Docsy and announced in every page by arel="describedby"link and a visually hidden line for AI agents.robots.txtstates the crawler policy in a comment: every crawler may read the site. The 404 page is nownoindex, because a static host may serve404.htmlunder its own address with status 200. The new checkwebsite/scripts/check-seo.pyenforces all of it in the website gate, for the production build and for previews: one canonical link per page that names the page, JSON-LD that parses, uses only properties its type defines and equals the configuration, the release file and the visible breadcrumb, anllms.txtof the right shape (every list item below a heading is a link, as llmstxt.org has it) that links only to the site and the repository, a 404 page that cannot be indexed, no blocked crawler in the productionrobots.txt, a sitemap that equals the canonical links, alt text on every image and the spelling rule of ADR-0015 in the home title andllms.txt.check-indexing.pyexempts the 404 page from the productionnoindexrule.website/README.mdhas the section “SEO and GEO” with the runbook for Search Console, Bing and the validators. - Preflight checks of the installers. Three new error codes, listed in the catalog at the top of
install-linux.shand indeploy/windows/README.md.language_mode_unsupported(Windows): the first action ofinstall-windows.ps1checks the PowerShell language mode, and in any mode belowFullLanguage(App Control for Business with script enforcement runs an unsigned script in Constrained Language Mode) the run stops, names the file and the rule that lets it run (a hash rule created from the verified copy) and changes nothing; the installer of v0.1.0 failed on its first method call with “Cannot invoke method. Method invocation is supported only on core types in this language mode”.conflicting_install(both installers): see the upgrade notes.supervisor_missing(both installers): when the installation step of the supervisor is over, the file has to be at the path of the service, an executable file on Linux (/opt/myriarch/supervisor/opampsupervisor), a regular file with the SHA-256 that was verified on Windows (C:\Program Files\Myriarch\supervisor\opampsupervisor.exe); otherwise the run stops with the path and the next step (the protection history of the antivirus, anoexecmount). On Windows theVERSIONfile of the supervisor folder, which lets a re-run take the installed file as it is, is removed before the file is replaced and written only after the check, so a run that stopped at the check fetches and verifies the file again. A directory or a link at the path is not replaced: the message tells to remove it, because a new run would stop at the same item (on Windows the run stops before it downloads anything). A security tool that quarantines the unsigned binary no longer shows up only as a service that does not start. - Remaining health wait on the rollout page. A target that has reported success waits in
verifyingfor thehealthWaitof its policy, 5 minutes in the example version policy, and the page did not say how long was left or why. It now shows the time that is left in the status column of the target list, in the step Health wait of the progress of a version target and on the card of a wave (the number of its targets in the health wait and the end of the one that finishes last). At 0 it says “finishing…” until the engine has evaluated the target. It keeps counting while the rollout is paused, because the engine keeps evaluating running targets, and it disappears when the target reaches a final status; it shows nothing for a rollout that the engine does not evaluate (failed,rolling_backand the final statuses). The step and the wave card also name the source of the wait:Policy <name> · healthWait <duration> · <path>@<commit>, with a link to the policy, where path and commit are those of the policy snapshot of the rollout. The API gets no new field: the browser computes the end asverifiedAtpluspolicy.healthWait(60 seconds when the snapshot has none), the rule of the engine, from whatGET /rollouts/{id}andGET /rollouts/{id}/targetsalready return, and counts down with its own clock. The rule is in Rollouts and in Server behaviour. myriarchd healthcheck. A command that checks a running server and tells the result by its exit code. It asksGET /readyz(200) and then for a WebSocket upgrade at the OpAMP path: without a token the server must refuse it with 401 (route and authentication are in place), with--token-fileit must accept it with 101; the command then closes the connection in the normal way. It sends no OpAMP message, so it registers no agent and stores nothing. Flags:--url(defaulthttp://127.0.0.1:8080),--opamp-path,--token-file,--ca-fileand--timeout(default10s). Exit codes: 0 healthy, 1 not healthy, 2 wrong usage, 3 no answer. It is the health check of the container image, which has neither a shell nor curl (["CMD", "/usr/local/bin/myriarchd", "healthcheck"]), and the program of the Pod ofhelm test. See Server behaviour and the command reference.- Helm chart:
helm test. The Pod of the test runsmyriarchd healthcheckof the server image against the Service of the release under the restricted Pod Security profile (no user ID in the OpenShift profile, no service account token, no Secret and no environment variable). It expects the 401 for the OpAMP upgrade without a token; withtests.token.existingSecretandtests.token.key(opt-in, a Secret with an enrollment token that exists for the test only) it expects the upgrade (101).tests.resourcessets the requests and limits, andtests.podAnnotationsadds annotations (for example the opt-out from the sidecar injection of a service mesh; the chart sets the annotations of the hook itself, so a key that starts withhelm.sh/hookis refused). The README of the chart describes the test, the NetworkPolicy rule and the opt-in. - Helm chart: PrometheusRule.
prometheusRule.enabled(defaultfalse) renders a PrometheusRule with the 13 alerts ofdeploy/examples/production/monitoring/prometheusrule.yaml(no leader, more than one leader, replicas not ready, metrics unreachable, dropped and rejected OpAMP connections, unauthorized connects, release tracker, GitOps sync, rollouts, notifications and housekeeping);prometheusRule.labelsadds labels, for example the one that selects the rule in the Prometheus resource. The rules select the metrics by the name of the Service (thejobof the ServiceMonitor of the chart) and the namespace of the release, and the replica alert compares withreplicaCount. The example for clusters without Helm has the same rules with the namespace matcher added, andci/helm-conform.shfails when the two differ. There is no dashboard. - Install plan.
POST /api/v1/install/plan(operationgetInstallPlan, new taginstall) gives a host that is not enrolled yet its plan: the fleet it would join, the version of collector and supervisor, the OpAMP endpoint, the URLs of the artifacts on the server with an install grant for each, and whether the updater is installed (ADR-0046). The host presents the enrollment token that it will use, always, also whereopamp.requireEnrollmentTokenis off. The server decides the fleet with the function that assigns a connected agent (the reported site attributes and the labels of the token); a fleet named in the request that disagrees is 409. The version is the effective target of the fleet; while a version rollout of the fleet is pending, running or waiting for approval it is the version before that rollout, so that the new host becomes a straggler of the last wave; a paused, failed or rolling back rollout is 409rollout_blocking, a fleet on hold or without a target 412no_target_version, and a version that the host asks for is accepted inside the frame in Git only (422). A target that the leader has not planned yet (a new target in Git, a new selection in the UI) is not handed out: the answer is 503preparingwithRetry-Afteruntil the rollout planner has created the rollout, so that a new host cannot skip the waves and approvals in the time between the change and the rollout. The release tracker must know both releases. Until the collector and the supervisor for the platform are downloaded the answer is 503preparingwithRetry-After(the first request for a platform downloads about 112 MB). The plan istext/plain(onekey=valueline each, values fromA-Z a-z 0-9 . _ : / @ + = -, at most 64 lines and 8 KiB, so that bash withoutjqand PowerShell 5.1 can read it) or JSON withAccept: application/json; a refusal is a problem document whose first issue carries the reason. The route is off withinstall.enabled: false. Nothing in the installers of v0.1.0 uses it. See Server behaviour and the API reference. - Install grants. A second kind of
myrd_download token for hosts that have no agent ID and no package offer yet. A grant is bound to one artifact and to the enrollment token of the request for the plan, and serves the artifact, its.sha256and its.sigstore.json(the files an installer checks it with) untilinstall.grantTtlhas passed, and only while that enrollment token is valid: revoking the token stops its grants at once on every replica. Turning the installation off (install.enabled: false) stops them too: a server that restarts with the setting off refuses the grants that it gave before. The token of a package offer and an install grant cannot be used for each other, and the token of an offer is unchanged. - Install settings and metric. The section
installof the configuration hasinstall.enabled(defaulttrue),install.grantTtl(default 1 hour, orartifacts.downloadTokenTtlwhen that is shorter and the setting is left out; production: positive and at mostartifacts.downloadTokenTtl),install.bundleSourceBaseUrl(the release source of the installer bundles of the server’s own release, default the GitHub releases of the project, never empty, production: https) andinstall.platforms(linux/amd64,linux/arm64,windows/amd64). The server warns at start wheninstall.enabledis on andreleases.enabledis off. The countermyriarch_install_plans_total{result}counts the plan requests. - Installer bundles. The server keeps the updater archive of the release that it runs for each platform of
install.platforms(myriarch-updater_<version>_<os>_<arch>.tar.gzor.zip: the installer scripts, their templates and themyriarch-updaterbinary) and delivers it to hosts withGET /api/v1/install/bundles/{filename}(downloadInstallBundle), after the authentication of the plan: an enrollment token, always, the same 401 for every problem of a token, 429 for too many failures. The list isGET /api/v1/install/bundles(listInstallBundles, permissionread). Before it fetches an archive frominstall.bundleSourceBaseUrl, the server verifies the keyless signature ofchecksums.txtof its own release against the release workflow of this project at the tag of its version (derived from the code, issuer GitHub Actions, the trusted root ofartifacts.verify) and requires exactly one line for the archive that the archive matches. The archive is stored under its SHA-256, hashed again before every delivery and sent from the same open file; an archive that changed on the disk is fetched again. The bytes of the release are never templated or repacked, which keeps App Control hash rules and later Authenticode signatures valid. Only a release has bundles (MAJOR.MINOR.PATCHor-rc.N, from 0.2.0); a build from a Git checkout has none. The source is fetched like every outbound connection (destination policy,artifacts.proxyUrl, redirect rules, size limits) and without credentials, by every replica for itself. The countermyriarch_install_bundle_downloads_total{result}counts the downloads. See Server behaviour and the security model. - Install commands.
POST /api/v1/install/commands(renderInstallCommands, permissionread, the CSRF header for a cookie session) renders, for each platform, the command that a person copies to a host: it downloads the installer bundle, checks the SHA-256 that it pins before it unpacks anything, and starts the installer from the bundle. The server never sees a token: the varianttoken_inlinehas a placeholder (@@MYRIARCH_ENROLLMENT_TOKEN@@) that the web UI replaces in the browser, the varianttoken_filereads the token from a file on the host. On Linux the token goes through the standard input of a root shell and is read and written by builtins, reachescurlas a header throughcurl -K -, lives in a privatemktemp -ddirectory of root and is deleted with it; on Windows the working directory directly below%ProgramFiles%(where standard users cannot write) is restricted to SYSTEM and the administrators before the first download, the installer starts with-ExecutionPolicy RemoteSignedfor that process only (neverBypass), and the script uses cmdlets and native programs only, so it runs in Constrained Language Mode. It runs in an elevated Windows PowerShell (integrity level High) and in a session of SYSTEM (level System), the context of scheduled tasks and management agents. The text of a command is a constant; every value is a quoted argument of its own, checked against an allowlist first (no quotation mark of any kind including U+2018 to U+201E, no backtick,$,;or control character, nothing that looks like a token), and anything else is 422. A command that carries a token needs an https server, and a server withopamp.requireClientCertrenders none (412server_settings) because the command cannot present a client certificate: such hosts use the manual path. A command starts the installer in the bundle with the options--serverand--fleet(-Server,-Fleet) of its server mode, which the installers of v0.1.0 do not have: it works with the installers of the same release, which take versions and artifacts from the plan. See Server behaviour. myriarchd dev --install-bundle-dir. A development server can serve the installer bundles from a directory of archives that are named like the ones of a release. The archives carry no signature, so onlydevhas the flag;serverefuses it with a message that says why, and the start refuses a directory outside development mode.- OpAMP Bridge protection in the server. The agent kind
bridge(RESTkind, filterkind=bridge, database migration00007_agent_kind_bridge.sql) and the agent eventbridge_locked. Withoutspec.bridge.manage: trueon its fleet, and without standalone mode, a Bridge is offered no remote configuration whatever starts the offer; the restart command is refused for every Bridge (412in the REST API); version rollouts skip it and say why on the agent; the server keeps no effective configuration of it. Each held-back action is an agent event and an audit entry (agent.bridge_held_back), written once per reason and not with every heartbeat. See Fleets and the OpAMP server notes. - Installation from the server on Linux.
install-linux.sh --server <url> --token-file <file>takes the versions, the OpAMP endpoint and the artifacts from the server instead of from the operator (ADR-0046). It asksPOST /api/v1/install/plan(the token only as a request header, read bycurlfrom standard input) and reads the plan strictly as data: known keys only, each value against a pattern, at most 64 lines and 8 KiB, URLs of the origin of--serveronly, https only, redirects only to the same origin. Collector and supervisor are downloaded with the install grants of the plan and verified with SHA-256 and their Sigstore bundle against an identity that the installer builds itself; the verifier is themyriarch-updaterof the installer bundle (verify-bundle), so the host needs nocosign, and nothing runs before its check. A plan that the server is still preparing is awaited (--plan-wait, default 15 minutes, afterRetry-After). New error codesplan_failed,plan_invalid,plan_rejected(with the code of the server) andplan_unavailable, each with the next step;https_required,conflicting_installandsupervisor_missingapply as before. The commands that the server renders for Linux run unchanged with the script (--server,--token-file,--ca-file,--fleet,--insecureand the site options). See Connect a host and Installing a host from the server. - Host end-to-end test: scenario
one-command.scripts/host-e2e/linux.sh one-commandcreates a token at run time, renders the command throughPOST /api/v1/install/commands, runs it unchanged as root on a fresh runner and checks a healthy agent in the expected fleet at its target version, a configuration rollout, a version update through the updater with a rollback and the retry, and the uninstallation with the proof that nothing is left. The workflowhost-e2e.ymlruns it aftermanual(the bundle is built from the commit and served from a directory by the development server), the path filter namesinternal/install/**and the parts of the bundle, and a manual run takes the inputrelease_tag(for examplev0.2.0) to install from the published, keyless verified installer bundle of that release instead. The token is masked in the log, and the upload takes an allow-list of files, never the token file or the rendered command. - Installation from the server on Windows.
install-windows.ps1 -Server <url> -TokenFile <file>does what the Linux installer does (ADR-0046): it asksPOST /api/v1/install/plan(the token only as a request header), reads the plan strictly as data (known keys only, each value against a pattern, at most 64 lines and 8 KiB, URLs of the origin of-Serveronly, https only, redirects only to https on the same origin, nothing evaluated as code), downloads collector and supervisor with the install grants of the plan and verifies them by SHA-256 and their Sigstore bundle against an identity, issuer and version that it builds itself, with themyriarch-updater.exeof the installer bundle as verifier, so the host needs nocosign.exe. New parameters are-Server,-Fleetand-PlanWait; the error codesplan_failed,plan_invalid,plan_rejectedandplan_unavailableapply as on Linux, next tohttps_required,conflicting_install,supervisor_missingandlanguage_mode_unsupported. The Windows commands that the server renders run unchanged with the script (-Server,-TokenFile,-Fleet,-CollectorVersion,-Insecureand the site options). The command sets the execution policy for its PowerShell process only (neverBypass, no change of the machine or user policy) and reports a policy from Group Policy instead of working around it. The working directory of the installer is made below%ProgramFiles%with a DACL for SYSTEM and the administrators before the first download. The certificate of the server is checked against the certificate store of the machine. The installer bundle has to be in a directory that only SYSTEM and the administrators can write (the command unpacks it below%ProgramFiles%), checked before the first request (directory_unsafe), because itsmyriarch-updater.exeverifies what is installed and runs as a SYSTEM service. The Windows files are not Authenticode-signed:AllSignedblocks them and App Control needs a hash rule for each release. See Connect a host and Installing a host from the server. - Host end-to-end test on Windows. The job
windowsofhost-e2e.ymlrunsscripts/host-e2e/windows.ps1 one-commandon a freshwindows-2025runner, in Windows PowerShell 5.1 and in PowerShell 7 at the same time. It buildsmyriarchd(embedded PostgreSQL),myriarch-updaterandmyrctlfrom the commit, serves the installer bundle (zip) from a directory by the development server, creates an enrollment token at run time, renders the command throughPOST /api/v1/install/commandsand runs it unchanged in an administrator session. It checks a healthy agent in the fleetwindows-hostsat the target version of that fleet, the two services (the supervisor underNT SERVICE\otelcol-supervisor, the updater as LocalSystem), the ACL of the registry key that holds the token and of the data directories, a configuration rollout, a version update through the updater with a killed collector (rollback by the updater) and the retry, andinstall-windows.ps1 -Uninstall -Purgewith the proof that no service, file, registry key, process, listener or working directory is left. The script refuses to run unlessCI=trueandHOST_E2E=1are set, on Windows, elevated, on a GitHub-hosted runner and on a host without an earlier installation; the throwaway CA is in the certificate store of the machine only while the scenario runs; the token is masked in the log and the upload takes an allow-list of scrubbed files, never the token file or the rendered command. The path filter names the Windows installer,deploy/updater/windows/**,deploy/supervisor/windows/**anddeploy/windows/**, and the inputrelease_tagalso runs the scenario against the published bundle of a release. The job runs in CI only; the support level of Windows stays “experimental” until it is green.ci/tests/workflow-artifacts-check.pychecks the shape of both jobs andci/tests/host-e2e-test.shthe helpers and the guard underpwsh. - Dialog “Add host” in the web UI. Agents and the enrollment tokens (Settings, Tokens) have a button Add host. The dialog renders one ready install command for each platform (Linux amd64, Linux arm64, Windows amd64) from
POST /api/v1/install/commands, with a copy button, an optional fleet hint and the site attributes, and the variants Token in the command (the default) and Token from a file. Everyone withreadcan open it; creating the token needsenrollment:manage, and a user without it pastes an existing token or uses the file. The token lives in the state of the dialog only: no browser storage, URL or log, never in the request to the server, masked on the page, and dropped when the dialog closes; the dialog warns that clipboard, shell history, PSReadLine, PowerShell transcription and script block logging, and terminal recordings keep it. It explains a server without bundle (a local build), a bundle that is being prepared or failed, a server that requires client certificates and refused input. The install example in the dialog that shows a new enrollment token is gone; the dialog offers Add host with this token instead. Connect a host has the section “The quick way: the dialog Add host”. - Installation test of both charts on kind with two Kubernetes versions.
ci/kind-chart-test.shinstalls the server chart (withhelm test), the collector chart as a StatefulSet under the Pod Security levelrestrictedand as a DaemonSet in aprivilegednamespace on a fresh kind cluster, checks that every agent registers healthy at the server through the REST API, uninstalls cleanly and runs ahelm upgradefrom the chart ofv0.1.0to the chart of the commit, with PASS / FAIL per step. The new jobchartsof.github/workflows/e2e-kind.ymlruns it on two Kubernetes versions (the new pinKIND_NODE_IMAGE_PREVIOUSinhack/kind/versions.envnext toKIND_NODE_IMAGE) and uploads the same allow-list of logs and diagnostics after a failure. Chart installation test. - Kubernetes Quickstart on kind.
hack/kind/quickstart.sh(make kind-quickstart,kind-quickstart-smoke,kind-quickstart-rollouts,kind-quickstart-all) creates a kind cluster, installs the server chart with a demo PostgreSQL, a demo Git server and a bootstrap admin, installs the collector chart as a gateway (a StatefulSet with two replicas in container mode, on collector 0.161.0) and as a node agent (a DaemonSet), optionally starts 20 simulated collectors, checks the installation without signing in, and runs a first configuration rollout and a first version rollout through the REST API with the waves approved. The page Kubernetes on kind (available from v0.2.0, with the published OCI charts) uses the same script;QUICKSTART_SOURCE=commitinstalls the charts and images of a checkout, which the new jobquickstartof.github/workflows/e2e-kind.ymldoes, measuring the duration of every step into the summary of the run.KIND_SYSTEM_TOOLS=1makeshack/kind/up.shusekind,kubectlandhelmfrom thePATH.make validate-examplesalso validates the Git repository of the Quickstart. - The page Support levels and tested platforms lists the support level of every component and the platforms of the automated tests: Ubuntu 24.04 (amd64 and arm64) and Windows Server 2025 (amd64, Windows PowerShell 5.1 and PowerShell 7) for hosts, and two Kubernetes versions on kind for the charts. The release notes carry the same list.
Changed
- Kubernetes collectors: the Kustomize base and the combined image are deprecated.
deploy/k8s/collector(with theDockerfileof the combined collector image),deploy/supervisor/k8s,deploy/updater/container/updater.yamlandscripts/build-collector-image-crane.shmoved tocontrib/legacy-kubernetes-collectorwith a README. They receive no support and will be removed once the kind probe and the production example use the chart. Use the collector chart instead (how to move); it needs no root init container, builds no user ID into an image (10001 is an overridable default of the standard profile; the OpenShift profile sets none) and needs no registry of your own. - Website. The search index loads when a visitor first uses a search box, not on every page load. Docsy’s script fetched the index (about 1.2 MB of JSON, 375 KB compressed) and built the lunr index at
DOMContentLoaded; that one long task, about 0.5 s on a phone, was the whole Total Blocking Time of the landing page (440 to 490 ms, performance 72 on mobile and 80 on desktop in PageSpeed Insights). A page now requests the index when a search box first receives the focus, a pointer press or an input, once for all boxes on the page, and renders a query that is submitted before the index has arrived as soon as it has; the results are the same. A load that fails (the network is down for a moment, or the tab was opened before a deployment and names an index file that the host no longer serves) is logged and tried again with the next query, and a query that waited for it gets a hint in its popover that asks for a reload. In a Lighthouse lab run with the throttling applied to the browser, the Total Blocking Time of the landing page on the mobile profile falls from about 400 ms to 0 ms and the performance score rises from 89 to 98. The script is an override ofassets/js/offline-search.jsof Docsy 0.18.0, pinned inwebsite/overrides.txt;check-overrides.pynow also covers the files ofwebsite/assetsthat replace a theme file, andcheck-requests.mjsfails when a page requests the index while it loads, when the search requests it other than once or when it does not recover from an index that fails to load. - Start and migrations.
myriarchd serve,myriarchd devandmyriarchd migrateshare one schema step. An incompatible schema is refused before anything is changed, a newer schema within the compatibility level starts with a warning, and the log linedatabase schema readynames the schema version, the compatibility level and the schema level. The generated references (configuration, environment variables, CLI) state thatmigrateloads the database settings only. - Tool installation.
ci/install-tools.shretries a download after a transient failure instead of failing the job: a release download (kubectl, shellcheck,pwsh, Hugo, Dart Sass, lychee, syft, zizmor, the source of osslsigncode) after a timeout, a failed connection or HTTP 408, 429, 500, 502, 503 or 504, with pauses of 1, 2, 4, … s and no attempt later than 120 s after the first (the policy of the installers), and a Go tool (kind, helm and the others) up to four times, 5, 10 and 20 s apart, whengo buildfails with a network error of the module download. A 404, a certificate error, a checksum that differs from the pin, a failed module verification and a compiler error still end the script at once, and the checks after a download are unchanged.smoke-kindof the release workflow benefits;docs/releasing.mdandci/release/smoke.shsay which steps still have a shorter policy of their own. - Windows installer: ARM64 stops before anything is changed. Without
-Arch amd64the installer stops withunsupported_platformas soon as it knows the architecture, before the work directory, the downloads, the verifier and the stop of the service. The installer of v0.1.0 stopped only at the supervisor step, after it had stopped the service and installed the ARM64 collector, which the next run kept. - Windows installer: a service that does not start is explained by its file. When
otelcol-supervisoris not running at the end of the run, the installer first checks the file of the binPath that the service has in the registry. A file that is gone is reported assupervisor_missing, with the path, in place ofservice_not_running; otherwiseservice_not_runningis as before. At the top of the script, the error line is printed withWrite-Hostin a restricted language mode, where a method call on the host is refused. - The rollout planner records the target it handled. After it planned a fleet without an error, the planner writes the effective target version of the fleet to the key-value store (
rollout/planned-target/<fleet>, when it changes and after it created the rollout; empty for a fleet without a target), next toplannedAtof a selection. The installation of hosts from the server reads it on every replica and waits until it equals the target (Planner). There is no setting and no migration; a leader writes the records again when it starts a term. - Demo values in the Quickstart policies.
examples/quickstart/policies/config-canary.yamlandcanary-then-all.yaml, the Git source of the Docker Compose Quickstart and the demo policies of the kind Quickstart, now havehealthWait: 30s(was 2 minutes for a configuration and 5 minutes for a version),maxConcurrent: 0(unlimited, was 5) and no maintenance windows incanary-then-all(it had two). A rollout in a Quickstart iscompletedabout a minute after the approval of the last wave and no longer waits for the evening. The production-like values stay ingitops-template/config/policies/with a comment on each value:healthWait2m for a configuration and 5m for a version,maxConcurrent: 5, and the commented maintenance window. A repository that you copied fromexamples/quickstartkeeps its own values; the image of the next release carries the new demo policies, so a Compose Quickstart that you start from it rolls out faster. The pages of the Quickstarts and Rollouts name the health wait. - Support levels of v0.2.0: the server Helm chart is beta (it was experimental), the collector Helm chart is experimental, Linux hosts and container mode stay beta, Windows hosts stay experimental.
- ROADMAP.md shows what v0.2.0 contains. The production Docker Compose setup with TLS and a Git source is no longer planned for v0.2.0; it is listed under later. The operations guides (upgrades in general, backup and restore, monitoring, air-gapped installation, artifact mirror) are planned for v0.3.0, and their placeholder pages on the website say so.
Fixed
- Artifacts with the same content on a Windows server. Two artifacts with the same content (for example the same file under two platforms) are stored at one content-addressed path, and their downloads end in a rename onto it. Windows can refuse to replace a file that is being replaced or read at that moment, so one download was recorded as
failedalthough the file was already in place, and the install plan kept answering 503preparing. A rename that fails is now accepted when the target is a regular file whose SHA-256 (hashed again) equals that of the download; every other case keeps the error. The same applies when a replica fetches an artifact again. - Install examples in the enrollment-token dialog. The Linux and Windows examples left out inputs that both installers require: without
--collector-version(-CollectorVersion) they stop withoption_missing, and without a Sigstore verifier withsigstore_verifier_missing. They also started the script next to nothing, although it reads its templates from the directory above its own and has to run from the unpacked updater archive, and the Windows example startedinstall-windows.ps1directly, which the default execution policyRestrictedrefuses. The examples now rundeploy/scripts/install-linux.shanddeploy\scripts\install-windows.ps1from that archive, name the collector version and the verifier (--verifier,-Verifier), and start the Windows installer withpowershell.exe -NoProfile -ExecutionPolicy RemoteSigned -File ..., which sets the execution policy for that one process and changes no setting of the machine. The Linux example runs in a root shell, in a directory that only root can write, withoutsudo, and takes the verifier as a full path (cosign, or themyriarch-updaterof the archive), because a directory or acosignthat the invoking user can write lets that user change what root runs. The token still reaches the installer only through a file that a shell builtin orSet-Contentwrites and the example deletes, and it appears in no other line. The Windows example gives that file its own DACL for SYSTEM and the administrators before the token is written, because a folder belowC:\Program Filesthat only administrators can write can still be read by every local user. Both examples say in a comment that the pasted command, token included, is recorded by a shell history, a PowerShell transcript or script block log and a terminal recording.internal/updater/deploy_snippets_test.goreads the required options from the installers and fails when the examples do not name them. - Configuration targets no longer wait for the
applyTimeoutafter a restart in the middle of applying. An agent that restarted while it applied a configuration reported the storedAPPLYINGfor the desired hash after it reconnected, and the server never offered a hash again that the agent reported asAPPLYING; the rollout target stayed in progress until itsapplyTimeout. The server now offers the configuration once more afteropamp.staleApplyingAfter(see the upgrade notes). The documentation of the supervisor behaviour is in Upstream notes. - Updates of Linux hosts stopped in the preflight. The systemd unit
myriarch-updater.servicesetNoNewPrivileges=true, and with it the updater could not start the new collector under the account of the supervisor for the preflight (otelcol-contrib validate): every update through the updater failed in the stepverifyingwith “operation not permitted”, and the old version kept running. The unit no longer sets it. Hosts that were installed with v0.1.0 get the new unit when the installer of this release runs again; until then a drop-in withNoNewPrivileges=falsehelps (Troubleshooting). The host end-to-end test found the defect. - Usage examples in the headers of the installation scripts. The example in
install-linux.shput the token on the command line and started the script withsudofrom the current directory, the examples ofinstall-windows.ps1andSet-ServiceEnvironment.ps1named a file belowC:\Temp, which unprivileged accounts can write, and started the script directly, which the default execution policy refuses. They now show what Connect a host documents: a root shell and the script from the verified archive, the token in a file that is deleted afterwards, a verifier, and on WindowsUnblock-Fileafter the checks andpowershell.exe -NoProfile -ExecutionPolicy RemoteSigned -File ...with a file in the setup folder that only SYSTEM and the administrators can read. Tests keep the examples in step with the scripts. - A fleet change is recorded once with several replicas. Two replicas that evaluated the same agent at the same time (
AssignAgenton one,ReassignAllon the other) could both write the assignment, record the fleet change twice and reset the desired state twice, which also clearedfailed_config_hash. The assignment is now a compare-and-set in the database.AssignAgentreads the snapshot after it has the agent’s lock, a listing older than five minutes is treated as stale, and pruning of the record of recent writes is throttled instead of a full scan under the lock on every write. - Windows installer with
-WithUpdater. The check of the data directory after the services have started no longer stops withdirectory_unsafewhen the supervisor has created files of its own: the supervisor’s service account is accepted as owner only below its two data directories (supervisor state and collector storage); everywhere else, any owner other than SYSTEM, the administrators or TrustedInstaller still stops the installation. - The collector chart is Ready with its default values, also while no fleet is assigned to the agent. Before, the readiness probe on port 13133 got “connection refused” once the supervisor was connected and the server sent no configuration, because the no-op pipeline that the supervisor runs then has no
health_check; the fallback configuration applies only while the server is unreachable at startup. - The main container of the collector chart is no longer OOM-killed with the default values. The default memory limit was too low for the supervisor and the contrib collector together.
- The seed init container of the collector chart is no longer OOM-killed while it copies the collector binary.
Security
- Windows files of v0.2.0 are unsigned. The SignPath signing is not set up yet, so
v0.2.0shipsmyriarch-updater.exe,myrctl.exeand the two PowerShell scripts without Authenticode signatures, under a reviewed exception for exactly that tag (WINDOWS_UNSIGNED_FINAL_TAGindeploy/release/windows-signing.env, ADR-0034). Verify them with cosign before you run them (the security model). - Go 1.27.3. The Go toolchain of the main module and the Go build images (
myriarchd,myriarch-sim, updater build of the collector image) move from 1.27.2 to 1.27.3, which fixes GO-2026-6809 incrypto/x509reported bygovulncheck. - Collector chart: Pod Security level
restricted, OpenShift without a user ID. The standard profile setsrunAsUser,runAsGroupandfsGroupto 10001 as overridable defaults (nullleaves a field out), because the supervisor image runs under a named user and the kubelet cannot checkrunAsNonRootwithout a numeric ID. The OpenShift profile renders none of the three. The pods add no capability, use a read-only root file system andseccompProfile: RuntimeDefault, and mount no API token. The chart creates no Role. The Kustomize base ran an init container as root to change the owner of the node directory; the chart has none. The one exception is a read-onlyhostPathof the node logs and the optionalhostPathfor the identity in the DaemonSet, which the Pod Security levelsbaselineandrestrictedrefuse; both are off by default and documented. - Helm chart: secrets and the development mode are not values. The values schema offers no key for a database DSN, an OIDC client secret, a webhook secret, the URL of a notification channel or an enrollment token, refuses a Secret reference that has the form of an enrollment token, and offers neither
config.devnorconfig.auth.devLogin; the templates keep their own refusals for a run with--skip-schema-validation. A test fails for a new setting of the configuration that can hold text (a string, a list of strings or a map of strings) and names a token, secret, password, key, credential, passphrase, bearer token or DSN, unless it holds a file, a directory or a prefix. The controls and their residual risk (free-text values such as annotations orextraEnv[].valueare not recognized as secrets) are in the section “Helm chart values” of the security model. - Helm chart: the switches of the TLS checks are booleans.
ingress.allowWithoutTlsandhttpRoute.tlsTerminatedAtGatewaymust be booleans, andopenshift.route.tls.insecureEdgeTerminationPolicyone ofRedirect,AllowandNone. A template counts the string"false"as true, so with the templates aloneingress.allowWithoutTls: "false"switched the TLS check of the Ingress off; the schema refuses it. The external entry points keep using TLS unless you opt out: the server refuses aserver.publicUrlthat is not https in production, an Ingress needsingress.tlsand an HTTPRoutehttpRoute.tlsTerminatedAtGatewayfor it, the install notes printwss://, and the OpenShift Route redirects plain HTTP unless the policy isAllow, the explicit opt-in for plain HTTP andws://at the router. Port 8080 inside the cluster is plain HTTP, limited by the NetworkPolicy; see the residual risk in the security model. - Unpinned package manager install removed.
ci/test-web.shhad no caller and was the only place that installed pnpm withnpm install --globalwithout a hash, the open Pinned-Dependencies finding of the OpenSSF Scorecard. The script and the comment that mentioned it are gone. Thewebgate and the CI jobs take pnpm from corepack, at the version of thepackageManagerfield ofweb/package.json. myriarch-updater.servicewithoutNoNewPrivileges. The preflight needs to switch to the account of the supervisor. The rest of the sandbox stays (capability bounding set,ProtectSystem=strict,RestrictSUIDSGID, restricted namespaces and kernel interfaces); the residual risk is in the security model.- Installers: preflight checks that fail closed. A host that is not fit for the installation is refused before anything is downloaded, stopped or written, and nothing is removed: a language mode below
FullLanguageon Windows (the check and its error line work in Constrained Language Mode), the services and units of the packages of the upstream project, and Windows on ARM64 without-Arch amd64. After the supervisor step the installers check that the supervisor is a regular file at the path of the service, and on Windows the marker that lets a re-run skip that step is written after the check, so a changed file is not accepted on the next run. The controls and their limits (the check knows two service names and two unit names; the check of the file sees one moment) are in “Preflight checks of the installers” in the security model. - Health check.
myriarchd healthcheckreads the enrollment token from a file only, never from an argument, the environment or standard input, and never prints it; the file must be a regular file of at most 4 KiB with one enrollment token, so a file with another secret (the database DSN, the token of the bootstrap admin) is refused before a request is made. The token goes to the host of--urlonly: no redirect is followed, no proxy is used and the certificate of anhttpsserver is always verified (--ca-filenarrows the trust to the CAs of that file; a wrong CA fails the check). The command sends no OpAMP message. A check without a token counts as an unauthorized OpAMP connect inmyriarch_opamp_connect_rejected_totalandmyriarch_auth_agent_requests_total. See Server behaviour. - Helm chart: the test Pod. The Pod of
helm testfollows the restricted Pod Security profile, mounts no Secret and has no environment variable without the opt-in, and its container settings are fixed; the chart refuses the Secrets of the server itself as the test token, and the command refuses a file that holds anything but an enrollment token. Its NetworkPolicy source (withallowSameNamespace: false) can be used by every pod that carries the labels of the test Pod, so it never exists together withtrustedProxiesorclientCertHeader. The controls and their residual risk are in the section “Health check and Helm test” of the security model. - The routes of the installation always need an enrollment token, and their failures are limited. The plan (and with it the grants) is authenticated by the enrollment token also where
opamp.requireEnrollmentTokenis off, with the lookups and the client certificate rule of the OpAMP endpoint. A missing, malformed, unknown, revoked or expired token gets the same 401, so a request cannot tell whether a token exists. The routes can be reached by anyone and share the bound on token lookups with the OpAMP endpoint, so failed authentications are also limited per client address (30 free, then one per second, IPv6 per /64): an address without budget is answered with 429 before any lookup, and one address cannot use up the lookups that enrolling agents need. The token is read from one header and never written to a log, a metric label, an audit entry, a URL or an answer. See Host installation from the server. - The command that installs a host keeps the token out of arguments, URLs and logs. See the bullet “Install commands” above: the token is read and written by builtins, sent as a header only, kept in a private directory and deleted with it, and the command checks the pinned SHA-256 before it unpacks or runs anything. What typed text leaves behind (shell histories, PSReadLine, PowerShell transcription and script block logging, session recordings) is outside these controls; hosts that record their input use the variant with the token from a file. The first installation trusts the server that displays the command (trust on first use); the security model lists the residual risks, and the manual path stays for sites that cannot accept them.
- A new host cannot skip the waves of a version change. The plan waits (503
preparing) until the rollout planner has created the rollout for a new target, so a host that is installed in the time between a change in Git or in the UI and the rollout joins the last wave of that rollout, instead of taking the new version past the waves and approvals. Install grants also stop working wheninstall.enabledis turned off, and the commands are not rendered for a server that requires a client certificate. See Host installation from the server. - Windows installer: data directory reset. Before it changes any permission or owner in
C:\ProgramData\Myriarch, the installer stops withdirectory_unsafeif a file there has more than one hard link, so that the change only reaches files of the data directory. The supervisor’s service account is accepted as owner only below its two data directories, not for the directories themselves.
Documentation
- Collectors on Kubernetes. The new page Install the collector with Helm (installation, modes, Pod Security, OpenShift, container mode, moving from the Kustomize base) and Container mode describe the chart and its unchanged upstream images. The release list in Releasing covers both charts.
- Helm chart. Install with Helm has the sections “Registry and repository of the image” and “The values schema” with the findings to expect when you upgrade from v0.1.0. The README of the chart describes the image values, the schema and the two layers of misconfiguration messages (the schema and the templates). The development guide lists the values schema among the generated files and the rules for changing it,
AGENTS.mdandCONTRIBUTING.mdname it in the list of generated files, and the security model describes the controls. - Upgrade and rollback. The server behaviour (“Start, migrations and schema compatibility”), the security model, the troubleshooting page, the architecture page, the Helm installation page and the chart README describe the schema check, the error messages and the rollback rules. The development guide explains how to write a migration, how to choose the compatibility level and how the changelog sections (“Upgrade notes”, “Added”, “Changed”, “Fixed”, “Security”, “Documentation”) are used.
- Host installation from the server (decision record). ADR-0046 records how hosts are installed from the server.
myriarchdprovides the plan (fleet, versions, OpAMP endpoint), the artifacts and the installer bundle of its own release as data, and the host verifies every file with SHA-256 and Sigstore against an identity that the installer builds itself: identity, issuer and trust root never come from the server. The first installation trusts the hash pinned in the copied command (trust on first use), and the manual verified path stays for sites that cannot accept that. The record also fixes the rules for the token in a copied command, the version that a new host installs, the Windows start with an execution policy for one process only (neverBypass, never a machine-wide change) andconflicting_installfor packages of the upstream project. It also states what the design does not prevent: a compromised server can steer a new host to an older genuine upstream version, and a pasted command that carries the token is recorded by PowerShell transcription and script block logging, the history of a shell and session recordings, so hosts that record input use the variant with the token from a file. It refines ADR-0007, ADR-0012 and ADR-0013. The installation with one command ships in this release, and the manual path ofdocs/tasks/connect-a-host.mdstays supported. - Connect a host.
docs/tasks/connect-a-host.mdlists every input of the installers of v0.1.0 (the OpAMP endpoint, the token, the site type and ID, the required collector version, the installer release and a Sigstore verifier) and every step. It also says which collector version a new host installs when its fleet has no target version, is on hold or runs a version rollout. Download the updater archive of a release, verify it as Verify releases describes, unpack it and run the script from the unpacked archive, because it needs its templates. The Linux steps run in a root shell in a directory below/root, and the Windows steps in a folder with a DACL of its own for SYSTEM and the administrators, which also keeps the token file from other local users; the checks are chained, so that the first failure unpacks nothing. Choose a verifier:cosign3.0.4 or newer, on Windows from a folder that only administrators can write, themyriarch-updaterof the verified archive, or the updater verified by its SHA-256. The host needs https access to the Sigstore TUF repository (tuf-repo-cdn.sigstore.dev) or a localtrusted_root.json. On Windows the guide shows the start withpowershell.exe -NoProfile -ExecutionPolicy RemoteSigned -File ...after the checks andUnblock-File, names what to avoid (Bypass, a change of the policy of the machine) and what wins over it (Group Policy). The new table “When the installer stops” coversoption_missing,usage_error,unsupported_platform(an unsupported CPU, and Windows on ARM64 without-Arch amd64),file_unreadable,sigstore_verifier_missing,sigstore_verification_failed(including a host that cannot reach the Sigstore TUF repository),template_missing, a script that PowerShell refuses, and a supervisor or collector installed from an MSI of the upstream project, which is another service under another name and path that the installer of v0.1.0 neither detects nor removes.deploy/windows/README.md,docs/security/verify-releases.md,deploy/README.md, the troubleshooting page and the known limitations ofdocs/security/model.mddescribe the same start. AGENTS.mdlists the tests that use fixed ports, a shared cluster or the machine itself, and says thatscripts/host-e2eruns in CI only. The development guide has the sections “Host end-to-end test” and, for the kind probe, “In CI”; the security model describes the controls of both workflows.docs/releasing.mdno longer says that no CI job runs kind.- Preflight checks of the installers. Connect a host marks the behaviour of v0.2.0 and has the rows for
conflicting_install,supervisor_missingandlanguage_mode_unsupportedin “When the installer stops” (cause and next step on both systems), and the rows forunsupported_platformandservice_not_runningsay what changed. The README of the Windows installer has the new section “Language mode and App Control” and the codes in its table, Verify releases says what the installers check before they change anything and how to allow the script under App Control, and the security model describes the controls and their residual risks. The deploy README and the troubleshooting page point to the codes. - Health wait. Rollouts describes the rule (a target in
verifyingsucceeds when the agent is online and healthy andnow - verifiedAt >= healthWait), the default of 60 seconds,spec.healthWait, what fails a target during the wait (an agent that goes offline or reports an error), that the wait goes on while a rollout is paused, and where the page shows the remaining time. The comments ofexamples/quickstart/policies/canary-then-all.yamlandconfig-canary.yamlsay why the example policies keep 5 minutes and 2 minutes. - Health check. The server behaviour specifies the command (flags, requests, output, exit codes and the metrics of a check), the development guide names its exit codes, and the architecture page mentions it. The command reference is generated.
- Helm test and alerting rules. Install with Helm and the README of the chart describe
helm test, the opt-in token, the NetworkPolicy rule and the PrometheusRule, the troubleshooting page lists the failures of the test, and the security model describes the controls. - Installation from the server. Server behaviour describes the plan, its version rule, its line format and its errors, and the artifact cache section the install grants and the preparation without waiting. Version selection names the plan as a consumer of the effective target. The security model has a new section with the controls and the residual risks, and the sections on token formats, agent authentication, artifact verification and the known limitations mention the grants and the routes. The production checklist lists the
installsettings. The configuration, environment variable, metrics and API references are generated. - Installer bundles and install commands. Server behaviour describes where a bundle comes from, what is verified first, where it is kept, when it is prepared and how it is delivered, and the commands with their inputs, the steps of the Linux and the Windows command and what they cannot hide. The security model has the new controls, the residual risks (trust on first use, the records of typed text, Group Policy and App Control) and a row of the outbound connections for the release source; the production checklist names
install.bundleSourceBaseUrl, and the development guide the fixtures and the tests that run the commands.
Known issues
- A nightly browser test of updates from the UI fails at one check. The job
ui-updatesof thee2eworkflow does not always see the short health-check phase of an update before the update ends. The other checks of the test pass. The job is not part ofci-okor of the tag guard.
[0.1.0] - 2026-10-13
First final release. The release candidates v0.1.0-rc.3 and v0.1.0-rc.4 were published before it as GitHub pre-releases. The sections below describe what the project ships; the history before publication is in the Git log.
Added
Defaults. Without configuration, myriarchd behaves neutrally:
| Area | Default |
|---|---|
| Maintenance window time zone | rollouts.defaultTimezone is UTC |
| Artifact prefetch | artifacts.prefetchPlatforms is empty |
| UI language | English, with detection from the browser language (ui.defaultLanguage, ui.languages) |
Login path in serve mode | myriarchd serve requires auth.oidc.enabled or auth.bootstrapAdmin.enabled |
| Webhook and AWX targets | executors.allowedWebhookHosts is empty and refuses every target (fail closed, in every mode) |
| Helm chart ingress | ingress.enabled is false |
| Helm chart GitOps | config.gitops.enabled is false |
| Helm chart network policy | No fixed ingress-controller or monitoring namespace; port 8080 is open to the pods of the release namespace (networkPolicy.ingress.from and networkPolicy.metrics.from widen it) |
| Helm chart storage | emptyDir.sizeLimit 4Gi, persistence.size 10Gi, config.artifacts.maxCacheBytes 3 GiB |
| Helm chart download tokens | config.auth.downloadTokenSecretFile is empty (key stored in the database, shared by all replicas) |
- Time zone. Rollout policies whose maintenance windows follow the agent’s time zone (
timezoneempty oragent) and that set nospec.defaultTimezonefall back torollouts.defaultTimezonefor agents without a valid time zone attribute.myriarchdlogs one warning at startup that lists such policies and names the effective default, and the rollout engine logs the same warning once per policy the first time it plans a rollout with it. - Prefetch. The prefetch of new target versions adds the platforms of the members of updater fleets. An explicit prefetch request (
POST /artifacts/actions/prefetch) that names no platforms usesartifacts.prefetchPlatforms; if that is empty too, it uses the distinct platforms of the connected collectors of the fleets that use the requested distribution. The request fails with 422 when that set is empty as well. - UI language. The UI picks the language in this order: the choice stored in the browser, the first supported browser language (
de-ATmatchesde), the server defaultui.defaultLanguage, English. The first day of the week in the date pickers follows the language. - Fail-closed allowlist. An empty
executors.allowedWebhookHostsrefuses every webhook and AWX target without sending a request. - Login path.
myriarchd serve,myriarchd config validateandmyriarchd migratefail with a clear error when neither single sign-on nor the bootstrap admin is configured. The development login exists only inmyriarchd dev(ADR-0025). The Helm chart enables the bootstrap admin by default (bootstrapAdmin.enabled: true) and generates a one-time token. - Feature-gated secret files.
myriarchdreads a*Filesetting only while its feature is active: the OIDC secret and certificate files whileauth.oidc.enabled, the bootstrap files while the bootstrap admin is active, the GitOps credentials andgitops.webhookSecretFilewhilegitops.enabled, andreleases.tokenFilewhilereleases.enabled. The chart renders these file paths only for active features. - Interfaces. The binaries are
myriarchd(server),myrctl(CLI),myriarch-updater(host updater) andmyriarch-sim(simulator); the short CLI aliasmyris documented indocs/reference/cli/myrctl.mdand not shipped. The GitOpsapiVersionismyriarch.dev/v1alpha1. The images areghcr.io/myriarch/myriarchandghcr.io/myriarch/myriarch-updater, and the Helm chart isdeploy/helm/myriarch(ADR-0015, ADR-0029). - Web UI. The UI shows the project logo (the tetractys mark) in the favicon, the header, the login page and the loading screen, and serves
favicon.svg,favicon.icoandapple-touch-icon.png. The logo files and the brand guide are inassets/brand/. - Rollout page. The rollout targets list shows targets with running work first (in progress and verifying, then waiting for a maintenance window, failed and rolled back, pending, succeeded, skipped, cancelled) and can switch to the wave order. A wave card shows “Approval required” only before the approval, and “Approved” after it. The wave percentage is labelled as the cumulative share of all targets. Progress bars draw pending targets as empty track and use yellow only for targets waiting for a maintenance window.
- API.
GET /api/v1/rollouts/{id}/targetsacceptssort=wave(default, unchanged order: wave, hostname, agent ID) orsort=activity, andagent=<agent ID>to return the target of one agent. The open detail view of a target on the rollout page follows its target when the row moves to another page. - Examples and tooling.
myriarchd devreadsexamples/quickstartby default (--gitops-dir);make dev,make validate-examplesandci/verify-gitops.shuse it.make sim,myriarch-simand the compose and image examples useexamples/sim/mixed-fleet.yaml. The e2e load run isE2E_AGENTS=1000 scripts/e2e.sh. The crane image scripts requireIMAGE_REPO, read the OCI labels fromci/image-labels.envand derive the source label fromSOURCE_URLor the git remote. The mirror image uses the public busybox base, pinned by digest. - Installers. Every error and warning line of the installers carries a stable code:
[myriarch-install] ERROR [<code>] ...; the catalog is indeploy/scripts/install-linux.shanddeploy/windows/README.md. Helmfailmessages start with the value path, for exampleconfig.server.trustedProxies: .... - API and web UI version. The version of the REST API (
info.version) and of the web UI package follows the release version instead of1.0.0-alpha.1(Releasing). - Download retries. The installers (
install-linux.sh,install-windows.ps1) and the release smoke tests (ci/release/smoke.sh,ci/release/smoke-windows.ps1) retry a download after a transient failure: a timeout, a failed connection, or HTTP 408, 429, 500, 502, 503 or 504. The pauses double from 1 second (1, 2, 4, … s), and no attempt starts later than 2 minutes after the first one, so a short outage of github.com or of a mirror no longer stops an installation or a smoke test. Behind anHTTPS_PROXYthat refuses the connection, the status of the proxy decides (503 is retried, 407 is not); the PowerShell scripts need PowerShell 7.5 or later for this, and PowerShell 7.4 retries such a refusal like a failed connection. Any other failure, for example a 404 or a certificate error, still fails at once, and[download_failed]names the number of attempts. Before,install-linux.shgave up after 4 attempts within about 6 seconds and did not retry a refused connection, andinstall-windows.ps1tried every download three times, a 404 included. The https, SHA-256 and Sigstore checks are unchanged. - Installer prerequisites.
install-linux.shchecks forgzipnext totarand stops with[tool_missing]that names the tool, instead of reporting a valid collector archive as[archive_invalid]: GNUtarrunsgzipto unpack the.tar.gzarchives. - Keyless updater verification. The installers verify the updater through the keyless Sigstore bundle of the release instead of a key:
--updater-checksums-url(-UpdaterChecksumsUrl) downloadschecksums.txtandchecksums.txt.sigstore.json(--updater-bundle-url/-UpdaterBundleUrlfor another location), verifies the bundle against the identityrelease.yml@refs/tags/v<version>of this repository with the issuerhttps://token.actions.githubusercontent.com, and takes the hash of the updater from the verified file. The version comes from--updater-version(-UpdaterVersion) or from the file namemyriarch-updater_<version>_<os>_<arch>of--updater-url; if both are given they must agree (option_conflict). New error codesoption_removedandsigstore_verifier_outdated.--updater-sha256stays for hosts without a verifier. - Language guard.
make check-localruns the language guard strictly forcmd,internal,scripts,ci,contrib,deploy,examples,web/src,docs,.githuband the root Markdown files.LANGUAGE_GUARD=warnmakes it report-only andLANGUAGE_GUARD=offskips it. - New top-level configuration section
uiwith the optionsui.defaultLanguage(defaulten) andui.languages(default empty, meaning every language the UI ships). Both are validated at startup: language tags must match^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$, must not repeat, andui.defaultLanguagemust be one ofui.languageswhen that list is set. - The audit entry
artifact.prefetchrecords the resolved platforms (platforms) and where they came from (platformsSource:request,configorconnected). - The public endpoint
GET /api/v1/auth/configreturns the new fieldui(defaultLanguage, optionallanguages). It contains no secrets. - The UI derives its languages from a locale registry (
web/src/i18n/locales.ts); the language menu and the profile settings offer the shipped languages, restricted byui.languages. Entries ofui.languagesmay carry region subtags (de-DE); like the browser language andui.defaultLanguage, they match a shipped language by the full tag or the primary subtag. - Bootstrap admin.
auth.bootstrapAdmin(enabled,username,passwordHashFile,tokenFile,keepWithOidc) configures a break-glass administrator for the initial setup and emergencies. Credentials come from files only: a bcrypt hash for password sign-in, a random token for one-time token sign-in (single use, cluster-wide marker, rotated by rewriting the file). The account is active unless OIDC is enabled withoutkeepWithOidc; the configuration is the only source of truth, andPATCH /users/{id}on the account answers412. Sign-in is rate limited per client address (IPv6 per /64) and per account (per replica, one verification at a time), deactivating the account revokes the API tokens it created, every attempt is audited, and the UI shows a banner on every page while such a session is open. The revocation converges across restarts: the steps of an account run in one transaction, every start removes what an already disabled account still holds (auth.bootstrap.deactivatewithalreadyDisabled: true), and an account that becomes active again loses the sessions and tokens of its earlier life before it is enabled (counted inauth.bootstrap.activate). The account backoff is shared by every client, so network access toPOST /api/v1/auth/bootstrap-loginshould be restricted; restarting the pod clears the counter. Seedocs/operations/authentication.md. - IPv6 clients. The failure limiters of the invalid bearer tokens and of the dev login count IPv6 clients per /64 prefix, like the bootstrap sign-in.
- AWX API prefix.
spec.awx.apiPrefixselects the API path of an AWX or Ansible Automation Platform executor (default/api/v2/; Ansible Automation Platform 2.5 and later:/api/controller/v2/).myrctl validateand the syncer check the value (executor_invalid). - OpenShift.
openshift.enabled(true,falseorauto) switches the chart to an OpenShift profile: no fixed UID, GID or fsGroup, a Route (openshift.route.*), the router namespace in the network policy and the OpenShift DNS pods.autodetectssecurity.openshift.io/v1;helm templatewithout--api-versions security.openshift.io/v1renders the standard profile. - Helm chart.
examples/quickstart-dex.yamlwithexamples/dex/dex.yaml(Dex as demo identity provider, OIDC enabled, bootstrap admin off);resourcessetephemeral-storagerequests and limits; the PodDisruptionBudget renders only withreplicaCount > 1;secretChecksum.enabled(default) rolls the pods when a mounted Secret changes (checksum/secret, derived from the uid and resourceVersion of the Secrets, not from their content);helm templatefails when neither OIDC nor the bootstrap admin is enabled; the egress rule includes the Git SSH port (22, or the port of anssh://URL) when GitOps uses SSH,networkPolicy.egress.extraPortsandextraRulesextend it, andnetworkPolicy.egress.dnssets the DNS rule;networkPolicy.ingress.allowSameNamespaceandingress.allowWithoutTls/allowWithoutTrustedProxiesmake the safety checks explicit. myriarchd auth hash-password [--cost N]reads a password from standard input (12 to 72 bytes, cost 10 to 14, default 12) and prints a bcrypt hash forauth.bootstrapAdmin.passwordHashFile.- Declarative enrollment tokens.
opamp.enrollmentTokens[](name,tokenFile,labels,expiresAt) provisions enrollment tokens from files at start. Only the SHA-256 hash and the display prefix are stored; the start is idempotent, a revoked token is never re-activated, and a token that is removed from the configuration is not revoked automatically (a warning names it). A change of labels or expiry reaches all replicas at once. Such tokens showcreatedBy: configinGET /enrollment-tokensand a badge in the UI. - Secret file permissions. Outside Windows the server logs a warning at start for every secret file that other users of the host can read or write (mode bits 0007): the bootstrap admin and enrollment token files, the database DSN file, the OIDC client secret and key files, the TLS key file, the download token secret, the GitOps token, SSH key and webhook secret files, the release tracker token, the artifact mirror credentials, the notification URL and secret files and the registered executor secrets (
executors.secrets; other files inexecutors.secretDirare not checked). Only the file mode is examined, the content is never read. The message names the setting and the path, never the content. Files of features that have a switch (OIDC, GitOps, release tracker) are checked only while the feature is active; notification channels, the artifact mirror credentials and executor secrets have no switch and are checked when they are configured. - Vendor-neutral OIDC.
auth.oidc.nameClaim(defaultname) andauth.oidc.emailClaim(defaultemail);auth.oidc.roleMapping.usersmaps single users bysub:<subject>oremail:<address>(the e-mail form requiresemail_verified: trueandemailClaim: email, because the flag covers only that claim);groupsClaimalso accepts an object claim and uses its keys (Zitadel project roles). OIDC callbacks reject the reserved subject prefixeslocal:andbootstrap:. - Provider hints.
internal/configload/providers.goholds a table with issuer checks and warnings for Microsoft Entra ID, Keycloak, Okta, Dex and Google.docs/operations/authentication.mdhas recipes for Keycloak, Dex, Microsoft Entra ID, Okta, authentik, Zitadel and Google. - API.
POST /api/v1/auth/bootstrap-login(operationbootstrapLogin, public).GET /auth/configreturnsbootstrapPasswordEnabledandbootstrapTokenEnabled,GET /system/inforeturnsfeatures.bootstrapAdmin, andauthMethodand the userprovidercan bebootstrap.PATCH /users/{id}can answer412. - Audit events.
auth.bootstrap.login(success and failure withreason, rate limiting withlimitedBy),auth.bootstrap.activateandauth.bootstrap.deactivate(actor system); declarative tokens writeenrollment_token.createandenrollment_token.updatewithsource: "config". The metricmyriarch_auth_logins_totalhas the methodbootstrap. - Database. Migration
00005_bootstrap_admin.sqlallows the user providerbootstrap(expand only). The latest schema version is 5. - Helm chart. The top-level values
bootstrapAdmin.*(generated one-time token Secret<fullname>-bootstrapor an existing Secret with a token or a bcrypt hash) andenrollmentTokens[](generated or existing Secret per token, mounted below/etc/myriarchd/enrollment) render the configuration above.NOTES.txtexplains how to read the generated token without printing it.oidcFromEnvironment: truedeclares that single sign-on is switched on by environment variables (MYRIARCH_AUTH_OIDC_ENABLEDinextraEnv), which the chart cannot read: the login path check passes, the bootstrap Secret is mounted only withkeepWithOidc, and theconfig.auth.oidc.*Filefields stay rendered. - Web UI. The login page offers the bootstrap admin with password or token (collapsed behind “Use the bootstrap admin” next to other methods, expanded when it is the only one); the users list shows the account as managed by configuration.
examples/quickstart/is a small, generic GitOps repository (fleetslinux-hosts,windows-hosts,k8s-gateway; site typesedge,datacenter,cloud) with a version policycanary-then-allwhose maintenance windows use the site-local time zone and UTC, and a config policyconfig-canary. A README describes it.examples/sim/mixed-fleet.yamlis the matching simulator profile (1000 agents: 400 / 200 / 300 / 100 and 900 myriarch-updaters).deploy/compose/quickstart.yaml: PostgreSQL,myriarchd devand the simulator from the release image, for an evaluation on one machine (loopback only, well-known logins; the data directory/var/lib/myriarchdis on a named volume).- Licensing. The repository has the Apache-2.0
LICENSE, an SPDX header in every source file and a REUSE 3.3 configuration (REUSE.toml,LICENSES/); the gatelicensesofmake check-localchecks the headers, runsreuse lintand detects drift ofTHIRD_PARTY_NOTICES.THIRD_PARTY_NOTICESlists the licenses and notices of the Go modules and web UI packages in the binaries and is generated byscripts/gen-third-party-notices.sh; every distinct license text is printed once and referenced by the components that ship it, and the generator fails when the file exceeds 1,000,000 bytes. It is part of every release archive and container image,myriarchdserves it withLICENSEat/licenseswithout login, and Settings > System links both. The OpenAPI document,web/package.jsonand the Helm chart declare Apache-2.0.myriarch-updater licenses [--third-party]prints both texts from the binary, so the bare updater binary carries them; the Helm chart containsLICENSE; the container images setorg.opencontainers.image.licenses. The licenses of dependencies with a package list inci/license-overrides.txtfail closed when a new package of the module is linked. - Website. The sources of the project website (myriarch.dev) live in
website/: Hugo extended with the Docsy theme, built fromdocs/and the root Markdown files without copying them, with offline search, light and dark mode, no third-party requests and no cookies (ADR-0031).make website,make website-serve,make website-previewandmake website-checkbuild, serve and check it, and the new gatewebsiteofmake check-localruns the build, the link check, markdownlint and the checks ofwebsite/scripts. Hugo, Dart Sass and lychee are pinned inci/versions.envwith SHA-256 values. The site has a landing page with screenshots of the web UI, the REST API reference, the Helm chart, GitOps template and brand pages, a project section with a FAQ, and placeholder pages for documentation that is still being written. The documentation is grouped as getting started, how-to guides, concepts, reference, operations and security. The license texts of the shipped third-party components are published under/licenses/, and the gate checks that every link into the repository names an existing path. - Website. The REST API reference is generated from
api/openapi.yaml(make docs-gen, checked for drift): one page per tag and a data types page with the site’s theme, dark mode and search, and no JavaScript. It replaces the Redoc viewer. - Website. The mark on the landing page plays a short rollout (control plane, canary, waves, full fleet), holds the unchanged mark for about 7 s, retracts the dots briefly and plays again. A click or tap on the mark pauses the loop and shows the static mark, another click resumes it (WCAG 2.2.2; there is no visible button, the mark is the control, also with Enter and Space and a focus ring for the keyboard); the loop also stands still while the hero is out of view or the tab is hidden. With reduced motion the mark is static and not a control; without JavaScript the rollout plays once.
- Website.
.github/workflows/website.ymlbuilds the site with the pinned tools, runs the website gate and deployswebsite/publicas prebuilt static output to Vercel: production on pushes tomain, protected previews (noindex) for pull requests from branches of the repository, never for forks.WEBSITE_ENV=previewbuilds with the production asset pipeline (minified, fingerprinted, no source maps) and only addsnoindexandDisallow: /; previews useWEBSITE_BASEURL=/. A new check,check-indexing.py, verifies both modes. The setup (Vercel project, the environmentswebsite-previewandwebsite-production, domain records) is inwebsite/README.md.
Security
Fixes and hardening from the pre-publication security audit. The controls are described in docs/security/model.md; the limitations that remain are in its section “Known limitations”.
- GitOps source. The clone always follows
gitops.repoUrl: a changed URL with the samegitops.cloneDirno longer keeps applying the previous repository, and an existing clone whose origin differs is replaced. - HTTP listeners. Every request has 60 seconds to deliver its body and an idle write timeout of 60 seconds (accepted event streams and handed-over WebSocket connections are exempt; a rejected request keeps both budgets), request headers are limited to 64 KiB on both listeners,
/readyzcaches the database check for 2 seconds and coalesces concurrent checks, and public state-changing routes (sign-in) requireContent-Type: application/json. - Agent authentication. The OpAMP connect limit uses one bucket per IPv6 /64 (IPv4-mapped addresses count as IPv4). Only well-formed enrollment tokens are looked up; unknown tokens are cached in a separate small cache, concurrent lookups are coalesced and lookups of uncached tokens are limited per replica (
429withRetry-After). Token usage is written with one statement per token and flush. - Sign-in. Failed OIDC callbacks are limited per client address (both OIDC endpoints refuse an address with an empty bucket), failures before a verified login state are audited at most once per minute and address, and responses of the identity provider are limited to 1 MiB. Group names are matched exactly (no case or Unicode folding) and e-mail entries ignoring the case of ASCII letters only: a mapping that relied on a different case of a group name has to use the exact name. The session cookie is named
__Host-myriarch_sessionwhere cookies are Secure (production); existing sessions end with the upgrade. API tokens of local development accounts are refused where the development login is off. - API tokens. New setting
auth.apiTokenMaxTtl(default 90 days;0means unlimited and logs a warning): a token created withoutexpiresAtgets the maximum, a laterexpiresAtis refused with422, and older tokens stop working. Existing tokens without an expiry stop working 90 days after their creation. The token list and the web UI show the effective expiry. Offboarding a user at the identity provider does not end API tokens: disable the user in myriarch. - Configuration checks. Production refuses
server.trustedProxiesranges that cover every address and a zeroartifacts.downloadTokenTtl, and refuses unanchoredartifacts.verify.certificateIdentityRegexppatterns. It warns about/metricson the main listener, a remote database connection without certificate verification, emptyartifacts.dirandgitops.cloneDir, secrets set as literal values in the configuration file, credentials inartifacts.proxyUrland very broad trusted proxy ranges.myriarchd config printredacts credentials in URL-valued settings. The development mode guard also coversserver.metricsListenAddr. The removed constantconfig.DefaultDataDirwas unused. - Outbound policy. Only the exact proxy address is exempt from the address check and only in the transport that uses it (
ALL_PROXYexempts nothing), more special-purpose ranges are blocked (IPv4-compatible IPv6, Teredo, the discard prefix,192.88.99.0/24and the IPv4 documentation ranges;outbound.allowedCidrscan open them), and address literals andlocalhostin a request URL are refused locally when a forward proxy applies. - Secret files. The start-up check also covers files below
executors.secretDirthat are bound inexecutors.secretFileHostsand the directory itself, and operator-chosen executor secret files are read as regular files of at most 1 MiB.myriarch-updaterwarns about loose modes of its token file. - Error responses. Database errors that become 4xx answers no longer carry the operation name, constraint names or driver text; they are logged with the request ID.
- Installers and units. The certificate identities passed to
cosignandverify-bundleare bound to the requested version. The Linux installer requirescurlunless--insecureis given, extracts archives without owner and mode, and copies--ca-fileto/etc/myriarch/ca.pem(the configuration files now reference that path; the option takes an absolute path). The Windows installer prefers the verified updater overcosign.exefromPATH, creates its work directory with a protected DACL, hashes verified files again after copying, and applies and verifies a fresh ACL on the service key before it writes the token (Set-ServiceEnvironment.ps1does the same).otelcol-supervisor.servicedrops all capabilities and sets further hardening directives. Both updater andverify-bundlerefuse unanchored identity patterns. - Installers verify the keyless release bundle (breaking).
--signing-key,--updater-checksums-sig-url,-SigningKeyand-UpdaterChecksumsSigUrland the key-signedchecksums.txt.sigwere removed; using one fails withoption_removed, and the message names the replacement (--updater-checksums-urlwith its bundle,--updater-bundle-url). The error codessignature_invalid,signature_malformed,signature_verifier_missingandsigning_key_invalidno longer exist. The verifier ofchecksums.txtiscosign3.0.4 or newer or an already installedmyriarch-updater, and never the updater that is being installed; an oldercosignstops the installation withsigstore_verifier_outdated, also for the collector and supervisor bundles.--updater-checksums-urlcannot be combined with--verify-signatures skip. A-Verifierof the Windows installer must be in a directory that only administrators can write (directory_unsafeotherwise), a pre-release ofcosign3.0.4 counts as older, and on Linux every--verifierwhose file name starts withcosigngets the version check.-UpdaterSignerThumbprintstays for private PKIs (a pinned thumbprint breaks at every certificate renewal). Known limitation: under App Control script enforcement the unsignedinstall-windows.ps1runs in Constrained Language Mode and fails; allow it by its hash. - Kubernetes. The collector manifests set
automountServiceAccountToken: false, the node agent changes only the top storage directory, and the Helm chart refuses to render inline secrets (config.database.dsn,config.auth.oidc.clientSecret,config.gitops.webhookSecret,config.notifications.channels[].url). The examples usesslmode=verify-full. - Dependencies and gates. Go 1.27.2 and
golang.org/x/net0.60.0 fix the reachable vulnerabilities reported bygovulncheck. New gatesvulncheck(with an allowlist that records reason and review date) andsecrets(gitleaks,.gitleaks.toml) inmake check-local; thegogate also vets for Windows and compiles the updater tests for it. The security tests of the updater now compile for Windows. - Build and release. The collector image defaults to the reviewed upstream digests and verifies the upstream binaries by default when it pushes, the
devimage variant is refused forghcr.io/myriarch/*, and the Docker build contexts exclude every git-ignored path. - Verification before publication.
ci/release/verify.shchecks the files from which the draft release is created before anyone can publish it: the 42 files, the keyless signature ofchecksums.txtagainst the exact identity (tag, commit, workflow, trigger), the ADR-0029 pattern and the version-bound pattern of the installers, the build provenance of all 39 listed files, and for both images the index, the platform manifests and configs (user 65532, one layer, labels), the signatures (a cosign signature of the digest, not only an attestation of the same identity), the provenance and SBOM attestations and the tag list.ci/release/finalize.shbinds the draft to that result through GitHub’s SHA-256 asset digests, refuses unless both packages are readable without credentials and the tag still points at the tagged commit (tag_moved), plans all image tags before it sets one (an immutable tag is never moved,latestnever exists), reads the draft by ID again and repeats the checks directly before it publishes, compares the published, immutable release with the verified files once more (release_assets_changed), and checks that the release carries GitHub’s release attestation.
Build
- Release images.
ghcr.io/myriarch/myriarch(myriarchd,myrctl,myriarch-simand the example repositories) andghcr.io/myriarch/myriarch-updaterare image indexes forlinux/amd64andlinux/arm64, builtFROM scratchwith one layer from the linux archives of the release build instead of from a second compilation. They run as user and group 65532, hold the license files under/usr/share/licenses/myriarch/and carry the OCI labels in the platform configs and the index annotations.IMAGE_SOURCE=dist IMAGE_KIND=server|updater scripts/build-image-crane.shwrites a deterministic OCI layout (the same archives give the same uncompressed layers, and the same digests with the same zlib) and one SPDX SBOM per platform;ci/release/push-images.shpushes a layout by digest, never sets a tag and verifies the digest it pushed. Seedeploy/README.md. - Server image content and data directory. The published server image carries
myriarchd(entrypoint),myrctlandmyriarch-simin/usr/local/binand the example repositories/opt/myriarch/examples/quickstartand/opt/myriarch/examples/sim, so a Compose quickstart or a first try needs no repository checkout. All of it is read from the release archives and covered bychecksums.txt: themyriarchdarchive now also holdsexamples/quickstart, the builder takesmyrctlandmyriarch-simfrom their own archives, and it refuses archives whose license files differ. The image creates the empty data directory/var/lib/myriarchd(0750, owned by 65532:65532), so a named volume, anemptyDiror a PVC mounted there works for the non-root user and the artifact cache survives restarts.deploy/docker/myriarchd.Dockerfileand the image built from source follow the same contract, and the development Compose file mounts a named volume at that path. The updater image needs no state directory (container mode keeps it on the volume of the collector image). - Image user. The image that
scripts/build-image-crane.shbuilds from the work tree runs as user and group 65532 (it was 1001), like the Helm chart. It refusesghcr.io/myriarch/*: those repositories are written only by the release workflow. - Updater trust store.
myriarch-updaterembeds the Mozilla root CAs as a fallback, like the other binaries, so that the scratch-based updater image can verify TLS servers. The host trust store still wins. - Removed images.
deploy/docker/myriarchd-release.Dockerfileanddeploy/docker/myrctl.Dockerfileare gone, together withMYRCTL_BASE_IMAGE. The project publishes nomyrctlimage; the binary is in the release archives, and the GitLab starter ingitops-template/says to build an image from it. - Collector image.
deploy/k8s/collector/Dockerfilecan copymyriarch-updaterfrom the published updater image (--build-arg UPDATER_SOURCE=image --build-arg UPDATER_IMAGE=ghcr.io/myriarch/myriarch-updater@sha256:<digest>). Container mode is beta and needs a self-built collector image until the collector chart of v0.2.0 (docs/design/container-mode.md,ROADMAP.md). - No signing key in the release build. goreleaser signs nothing (no
signs:block),ci/build-release.shhas only thesnapshotmode, anddeploy/release/authenticode.shis gone. Releases are signed keyless by the release workflow (ADR-0023). The format contract of key-based signatures and the key handling moved tocontrib/enterprise-mirror/signing.mdfor organizations that mirror and re-sign releases. - SBOMs. The release build writes an SPDX JSON SBOM per archive (
<archive>.sbom.json) and one for the web UI (myriarch-webui_<version>.sbom.json, production dependencies only), made with the pinned syft without network access (syft’s temporary extraction directory is not listed).checksums.txtlists them, and alsoLICENSEandTHIRD_PARTY_NOTICES(39 entries for a release). - Windows version resources.
myriarch-updater.exeandmyrctl.execarry version information (product “Myriarch”, file and product version from the tag, original file name), generated with the pinned go-winres. - Windows signing hook. The release build prepares Authenticode for the Windows files in stages (
MYRIARCH_WINDOWS_STAGE): the unsigned files are collected,ci/release/windows-sign-hook.shis the contract with a signing provider, and the packaging step accepts a signed file only if its signature, attached to the job’s own reproducible build, verifies against the committed chain, subject and RFC 3161 timestamp. A release build has to know the configured signing mode (MYRIARCH_WINDOWS_SIGNING) and refuses a hook result of another provider.make release-rehearsalwalks the whole path in a snapshot with a throwaway test CA, and CI runs it. osslsigncode is built from a pinned source archive (ci/install-tools.sh osslsigncode). The Authenticode slot is described in ADR-0034. - The Linux updater archives no longer contain the Windows scripts (
install-windows.ps1,Set-ServiceEnvironment.ps1); they are in the Windows archives only. - Release builds omit the
devpgbuild tag, so the embedded PostgreSQL (and its dependencies) is not linked.myriarchd devin such a build needs--dsn;make devandmake buildkeep the tag. Seedocs/development.md. ci/check-release-licenses.sh(part ofmake check-local) runsgo-licensesover the release builds against a license allowlist; exceptions are documented indocs/development.md.- The UI uses system font stacks instead of bundled web fonts, so the embedded UI ships no font files.
IMAGE_VARIANT=release scripts/build-image-crane.shbuilds the server image withoutdevpgand without the OpenTelemetry release fixtures (/opt/myriarch/release-fixtures); the defaultdevvariant keeps both.- The in-process LRU caches use the internal
internal/lrupackage, sogithub.com/hashicorp/golang-lruis not a dependency. - Continuous integration. GitHub Actions runs the gates of
ci/check-local.shas jobs (.github/workflows/ci.yml), with a platform matrix for the Go tests (Linux amd64 and arm64 with a PostgreSQL service and-race, macOS with-race, Windows without-race), the release rehearsal, a secret scan of the commits of a pull request and the script tests. The aggregate jobci-okis the only required check..github/workflows/e2e.ymlruns the end-to-end smoke test on pull requests and the load run and the UI version update test nightly. Only GitHub-owned actions pinned to a commit SHA are used, the token is read-only and the CI jobs have no secret; a deployment job can read an environment secret only whenci/workflow-secrets.txtlists it, the job declares anenvironment:and the secret is passed asKEY: ${{ secrets.NAME }}in anenv:(ADR-0032). - Workflows gate.
make check-localhas a gateworkflows:actionlint(pinned inci/tools/actionlint) and the policy checkci/check-workflows.py, which also verifies that every gate has a CI job and reads workflow files and local actions with the extension.ymlas well as.yaml. The gatesgo(vet for macOS) andhelm(schemas of Kubernetes 1.30.0 and 1.34.12) check more;GO_TEST_RACE,GO_TEST_DEVPG,GO_TEST_PERF,HELM_KUBERNETES_VERSIONSandCHECK_LOCAL_STREAMselect variants. The render budget of the server (5000 agents in under 5 s) is asserted only by a run of its own without-race(PERFTEST=1,GO_TEST_PERF), not inside the parallelgo test ./..., and applies to the median of three renders. - Workflow policy for Scorecard and releases. The policy check allows one third-party action,
ossf/scorecard-action, pinned to a commit SHA and only in.github/workflows/scorecard.yml, jobanalysis(rule R1). Rule R12 holds that workflow to the shape the OpenSSF Scorecard API requires for published results (push tomainand a schedule, one job, onlyuses:steps from a fixed list, exactly the permissionscontents: read,actions: read,security-events: writeandid-token: write, no secrets or caches). Rule R13 prepares.github/workflows/release.yml: a trigger for pushedv*tags only, no caches or local actions, the literal environmentrelease, aguardjob that every job needs, no Node.js, Go, pnpm, npm, goreleaser, make or similar build tool in a job with a write scope,id-token,attestationsor an environment, noif:that tests job status and nocontinue-on-errorthat would defeatneeds:, no script on standard input or foreignshell:in a privileged job, and an approval before every privileged job (ADR-0033, which amends ADR-0032). - CodeQL, Scorecard and zizmor.
.github/workflows/codeql.ymlruns CodeQL (security-extended) on pushes tomain, on every pull request tomain(forks and Dependabot included) and weekly. It analyses Go (a traced build for Linux, Windows and macOS, also with theembeduianddevpgtags), JavaScript and TypeScript, Python and the workflows themselves..github/workflows/scorecard.ymlruns the OpenSSF Scorecard weekly and on pushes tomain, publishes the result and uploads the findings to code scanning, in the shape that rule R12 requires. The write scopes of both jobs are reviewed inci/workflow-permissions.txt. Theworkflowsgate runs zizmor with the pedantic persona andzizmor.yml: offline in the local gate, with the online audits against github.com only in CI, and a file that zizmor cannot parse fails the gate. The new rule R14 of the policy check allows only ignore entries with a worded reason inzizmor.ymland in inline comments, and it rejects files that the check and a YAML parser would read differently (U+0085, U+2028, U+2029, control characters, a byte order mark, text that is not UTF-8). Dependabot now waits 7 days after a release before it proposes a version update; security updates are not delayed (the security model,docs/development.md). - Toolchain files.
.node-versionpins the Node.js LTS release (24.21.0) for CI and local development; theenginesofweb/require Node.js 22.22.2, 24.15 or newer (^22.22.2 || ^24.15.0 || >=26).pnpm format:check(prettier) is part of thewebgate andmake lint, andplaywright-coreis a development dependency ofweb/for the UI smoke tests. - Dependency updates. Dependabot (
.github/dependabot.yml) now proposes weekly grouped updates for the GitHub Actions, the root Go module, the packages ofweb/andwebsite/and the brand generator (assets/brand/source), and security updates for the CI tool modules (ci/tools/*);docs/development.md, “Dependency updates”, says what each kind of pull request needs besides the update itself (the required DCO check passes Dependabot’s commits as they are). The pins behind the open alerts are raised: Pillow 12.3.0 for the brand generator, which now needs Python 3.10 or newer (BRAND_PYTHONselects the interpreter), and the vulnerable packages of the CI tool modules and the website tooling. The stamp of a Go tool in.cache/binnow holds a digest of itsgo.modandgo.sum, so a raised requirement rebuilds the tool, also from a tool cache that CI restored from an older run (before, the old binary stayed until the tool version changed). - End-to-end test with the real supervisor.
E2E_REAL=1 scripts/e2e.shdownloads the upstream supervisor andotelcol-contribofOTELCOL_VERSIONfor darwin and linux on amd64 and arm64 (previously darwin/arm64 only) and verifies both against the published SHA-256 on every run. WithE2E_REAL=1orE2E_UI=1a missing prerequisite fails the run instead of skipping the step.E2E_REAL_BASE_URLreplaces the download location. - Secret scan of commit ranges.
SECRETS_SCAN_RANGE=<base>..<head> ci/secrets-scan.shscans the commits of a range with gitleaks. - Version source. The file
VERSIONholds the next release tag without thev(for example0.1.0-rc.3), andscripts/set-version.sh <version>writes it together with every copy: the chartversionandappVersion,web/package.json,api/openapi.yaml, the image tags of the production example and of the GitOps template, and the default image tag of the Compose quickstart; then it runsmake docs-genandpnpm gen:api.ci/check-versions.shfails on drift (part of thegogate andci/check.sh). - Release tag guard.
ci/release/guard.shrefuses a release tag that is malformed, lightweight, not onmain, different fromVERSIONor one of its copies, without aCHANGELOG.mdheading and entries (final tags) or without entries under “Unreleased” (rc tags), without a successfulci-okcheck run, or against the Windows signing rule ofdeploy/release/windows-signing.env(a final tag with signingoffneeds the reviewed exception for exactly that tag). - Authenticode for Windows. ADR-0034 decides how the Windows release files get Authenticode signatures: the SignPath Foundation is the target, an IV certificate with an HSM key in Google Cloud is the fallback (trigger, budget and privacy consequence are recorded), and the release workflow keeps no long-lived signing secret. Authenticode adds to the Sigstore signature of ADR-0023 and never replaces it. The Windows files of the v0.1.0 release candidates are unsigned (candidates may ship with signing off), and those of v0.1.0 under a reviewed exception for exactly that tag; verify them with cosign before you run them (the security model).
- Release scripts.
ci/releaseholds the scripts of the release workflow (ADR-0035):meta.sh(the outputs of the guard job; it refuses a run whoseGITHUB_WORKFLOW_REFis notrelease.ymlof the project at the tag, which keeps the signing identity frozen),artifact.sh(one deterministic tar per artifact, unpacked only after its SHA-256 matches),tools.sh(builds the signing and the build tool set, and checks a set after the unpack),stage-assets.shandverify-dist.sh,images.sh,registry-login.sh,verify-blob.sh(the one place that composescosign verify-blob),sign-checksums.sh,sign-image.sh,notes.sh,draft.sh,verify.shandfinalize.sh.ci/release/identity.envholds the signing identity as data, andci/tests/release-scripts-test.shpins it tointernal/brand, ADR-0029, the installers andpush-images.sh. - Release asset list.
deploy/release/assets.jsonis the complete set of release files: 42 names, 40 built (ci/release/stage-assets.shandverify-dist.shcheck them against the goreleaser output;make release-rehearsaldoes this on every pull request) and two added by the signing job (checksums.txt.sigstore.jsonandmyriarch_<version>_provenance.intoto.jsonl).checksums.txtlists exactly 39 of them. - Release notes.
ci/release/notes.shrendersdeploy/release/notes.md.tmpland the matchingCHANGELOG.mdsection into the notes of a release: release candidate, final, unsigned-Windows and signed variants, the verification commands with the exact identity and the image digests. - Release workflow.
.github/workflows/release.ymlbuilds, signs, verifies and publishes a release for every pushedv*tag: build code runs only in jobs without write scopes, the tools come as two sets (cosign, crane and gh are built first on a runner of their own and are the only tools of the privileged jobs), jobs exchange artifacts by ID with a SHA-256 check, two approvals of the environmentreleasegate the signing slot and the publication, and smoke tests of the published release run in the same workflow (ADR-0035).ci/tests/release-workflow-test.shpins its shape. - Smoke tests after publication.
ci/release/smoke.sh(subcommandsassets,linux,image-runandkind) andci/release/smoke-windows.ps1(modesAssetsandInstall) check a release the way a user does: anonymous downloads of the 42 release files and anonymous registry reads, the keyless signature ofchecksums.txt(exact and ADR-0029 identity, with a tampered copy as negative control), GitHub’s release attestation, the build provenance and the image attestations, the installers from the verified archives in staging mode with the real collector,docker runof both images (user 65532, no shell, read-only), the Helm chart of the tag on a kind cluster (non-root pods,/healthz,/readyz, bootstrap sign-in) and the Windows files (version resources, Authenticode state, zip contents). The release workflow runs them after finalize (ADR-0035);ci/tests/release-smoke-test.shtests them with stubs. - Enterprise mirror toolkit, keyless mode.
contrib/enterprise-mirror/verify-release.sh --keyless --version X.Y.Z --dir|--base-url ...verifies the keyless bundlechecksums.txt.sigstore.jsonof the project’s release with cosign 3.0.4 or newer against the exact identity ofrelease.ymlat the tag (and optionally every file againstchecksums.txt;--trusted-rootfor hosts without access to Sigstore), andpublish-mirror.sh --keyless --dir DIR --version X.Y.Zverifies a downloaded release and uploads the host files unchanged,checksums.txtand the bundle last. The key-based mode is unchanged;--keylessand--public-keyexclude each other. - Release fixture. The keyless bundle of
checksums.txtofv0.1.0-rc.3is a test fixture (internal/updater/testdata/sigstore/myriarch_0.1.0-rc.3_*):myriarch-updater verify-bundle,install-linux.shandinstall-windows.ps1are tested offline, with the pinned trusted root, against the genuine release signature (accepted for 0.1.0-rc.3; refused for 0.1.0-rc.2, 0.1.0 and a tampered copy), and the real-cosign cases of the installer tests are required in CI.
Documentation
The documentation is English (ADR-0020) and has a new structure under docs/, with Hugo-compatible front matter on every page and a map in docs/_index.md.
- Concepts (
docs/concepts/): GitOps, agents, blueprints, fleets, inventory, rollouts and executors. - Tasks (
docs/tasks/): step-by-step guides, for example connecting a host, validating a GitOps repository, rolling out a configuration change or a collector version, and simulating a fleet. - Getting started and operations:
docs/getting-started/local-dev.md, the production checklist and troubleshooting next to the existing authentication guide. - Reference (
docs/reference/): the GitOps schema reference with every kind, field, template function and issue code, and a command-line reference for all four binaries thatmake docs-gengenerates from the command trees.make docs-checkverifies that the generated files are up to date, that links resolve and that the front matter is valid. - Generated references for the server configuration (
docs/reference/configuration.md), the environment variables and the metrics, produced bymake docs-genfrom the configuration struct, the loader and the metric definitions;make docs-checkfails on drift. - Security model (
docs/security/model.md): trust boundaries, agent and user authentication, artifact verification, outbound connections and the Kubernetes network policy. - Verify releases (
docs/security/verify-releases.md, parts 1 and 2): verifychecksums.txtwith its Sigstore bundle and the exact identity (cosign verify-blob), then the files withsha256sum; verify a downloaded archive before you run its installer, andUnblock-Fileonly after that.docs/tasks/connect-a-host.md,deploy/windows/README.mdandcontrib/enterprise-mirror/README.mddescribe the new options, hosts without a verifier, upgrades, and mirrors that carry the keyless bundles unchanged. Part 2 covers the build provenance (gh attestation verifywith--signer-workflow,--source-refand--deny-self-hosted-runners, online and with the provenance asset), the immutable release (gh release verify,gh release verify-asset), the container images (by digest:cosign verify,gh attestation verify oci://... --bundle-from-oci, the SBOM attestation, thesha256-<hex>tags, pinning the digest in Helm and Compose) and the unsigned macOS binaries. - Architecture and decisions:
docs/architecture.mdand the decision records ADR-0001 to ADR-0035 indocs/adr/.ROADMAP.mdholds the roadmap. - Developer guide (
docs/development.md) covers the server, the web UI and the documentation tooling. - Community files:
README.md(rewritten),CONTRIBUTING.md(DCO sign-off, AI-assisted contributions),CODE_OF_CONDUCT.md,SECURITY.md(private vulnerability reporting),MAINTAINERS.md,GOVERNANCE.md,CONTRIBUTOR_LADDER.md,ADOPTERS.md,ROADMAP.md,AGENTS.md(guidance for coding agents) andCLAUDE.md(a pointer toAGENTS.md), plus issue forms, a pull request template,CODEOWNERSand release note categories in.github/. - Tutorial
docs/getting-started/first-fleet-and-rollout.md: write a small GitOps directory with a fleet and a rollout policy, enroll ten simulated collectors with an enrollment token and roll out a configuration change in two waves with an approval, all from source and without containers. - Tutorial
docs/getting-started/docker-compose.md: start the server, PostgreSQL and 200 simulated collectors from the release images withdeploy/compose/quickstart.yaml, use your own GitOps directory, and reset; it states prominently that the setup is dev mode and never a production setup. - Guide
docs/operations/install-helm.md: install the experimental Helm chart from a release tag, with the minimal values, the first sign-in, a GitOps repository, anHTTPRouteand a declared enrollment token, and how to build and use your own image without a container runtime. The values file of the guide isdeploy/helm/myriarch/examples/install-guide.yaml;ci/helm-conform.shrenders and checks it. - The language guard of
make check-localalso coversdocs/,.github/and the root Markdown files. - Releasing runbook (
docs/releasing.md): the job graph of the release workflow, the frozen signing identity, the settings a maintainer makes once (release immutability, the environmentrelease, tag ruleset, packages), releasing a candidate and a final version with the two approvals, the review checklist for the draft, making the packages public after the first push, rc numbering and re-runs, what to do when a job fails, and what never to do. ADR-0035 records the decision. - Release build reference (
deploy/release/README.md): the 42 assets andassets.json, how the release workflow uses the build, and the release notes template with its placeholders;docs/development.mdhas a subsection “Release workflow” (how to test it, how to add a job under R13, never renamerelease.yml).