Repository navigation
Releases
v0.4.0
jcloud can now watch a server, not only check a tree. A new command gathers the observations on the host, jcloud.yml declares what the machine itself must be, and survey tells a new failure from one already known. Templates under contrib/ run all of it on a schedule. jcloud still reaches no network, keeps no state and alerts nobody: the transport, the last run and the alert stay the platform's.
observe: the collector, shipped with jcloud
jcloud observe [--root <dir>]runs on the host, in the deployed tree, and prints the linessurveyreads: each container docker has up and how it stands, the repo digest it runs, each service's pin frompin_file, and each compose profile a manifest declares (onwhenCOMPOSE_PROFILESnames it or one of its services is up). Names come fromjcloud.yml, so a platform no longer needs a collector of its own. A docker that cannot be asked is an error, never an empty answer.
host: in jcloud.yml
Absent, nothing about the host is asserted. A platform opts in by declaring:
host:
timers:
- japode-healthwatch # must be active, last run succeeded
- {name: japode-backup, max_age: 7h} # …and finished within 7h
paths:
- {path: ., mode: "750", owner: runner:runner}
firewall: ufw # installed and active
disks: [/, /var/lib/docker]
disk_free: 15%
certs: [caddy/certificates/*/*.crt] # a glob is allowed
cert_days: 14- Timers are read with
systemctl: active, how the unit they fire last ended, and when. An active timer whose job fails every night is reported, as is a backup older than itsmax_age. - Disks are held to a free-space floor (
df), and certificates to a number of days left, read from the file withcrypto/x509, never by connecting to the host name. - A fact that could not be read (
ufw statuswithout root, a failedstat) is reported as unread, never as a pass.
survey
--since <previous observations>marks each findingneworstandingand lists the onesgone. Findings are matched by a stable code and subject, so a reworded sentence, or a disk going from 9% to 8% free, is still the same finding. The exit code is still this run's verdict.--jsonprints{"findings": [...]}, each withcode(container.missing,digest.moved,timer.failed,backup.stale,disk.low,cert.expiring, …),subject,saysand, with--since,state. Nothing else goes to stdout in this mode.
contrib/: a watch that runs itself
Templates a platform copies; jcloud never runs them.
contrib/systemd/: a script, service and timer (every 15 minutes) that observe, survey against the last run and leavereport.jsonandreport.txt. The unit fails when the server diverges, so it shows insystemctl --failed.contrib/github/jcloud-watch.yml: a workflow for a self-hosted runner on the server that keeps one GitHub issue per finding: opened when new, updated only when its wording changes, closed when gone.
agree
- A compose environment value that is a number or boolean (
PORT: 2700) no longer stopsagree --composeat the decode. It is rendered as compose renders it and compared. - A file it could not read is named once, with slashes, on every OS. The message no longer repeats the absolute path from the OS error, and the Windows build no longer mixes
services\paste\…andservices/paste/…in one report.
Not yet
- A manifest's backup implying its own timer waits on Viglet Cloud's manifests (JCT23). Until then, list each timer under
host.timers.
Install
Download a binary below, or go install github.com/Japode/jcloud@v0.4.0.
v0.3.0
jcloud can now check a platform laid out differently from Japode. Everything it used to assume about where files sit and how services are reached is now something the platform declares. The motivating case is Viglet Cloud: an nginx edge, one shared compose file split by profiles, shared postgres/redis/Keycloak, and per-client copies of each product.
A platform describes itself
jcloud.ymlat the platform root names the manifest path, route file and kind, container prefix, image variable, pin file, deploy command, addon path, backup kind, whether images are built on the host, and where.env.examplelives. Without the file, every value is Japode's, so an existing Japode tree is read exactly as before.
agree
- nginx edges.
--routestakesnginx -Toutput whenroute.kind: nginx. Hosts come fromserver_name, the upstream fromproxy_pass(withset $varandupstreamblocks resolved), plusclient_max_body_sizeandproxy_set_header(an empty value counts as a strip). Caddy'scaddy adaptJSON still works, and--adaptedis kept as an alias. - The resolved compose model.
--composetakesdocker compose --profile '*' config --no-interpolate --format jsonand finds each service by name, so one shared compose file split by profiles is checked correctly. A manifest may declareprofile:. - Shared needs.
needs: [{name, type, shared: postgres}]points at a platform service several products use. jcloud checks that service against the addon and checks that the client waits on it and reaches it. - Backups.
backup: noneskips the backup.conf comparison and says so in a non-failing note. Each sqlite file or database the platform claims nothing for is named. - Variables.
env_prefix:declares a service's key prefix (e.g.VIGLET_TURING_).shared: trueon a variable makes it a bare key, such asOPENAI_API_KEY, that every manifest declaring it must agree on. - Per-client copies.
instances:names copies by template (turing-{client}). Each copy found in the compose model and the edge is held to the product's port, image variable, healthcheck and route. - Auth gates. A route may state
gate: /oauth2/auth, compared with nginxauth_requestor Caddyforward_authin both directions. A declared gate the route doesn't run is reported as open to anyone. - TCP probes.
health.probe: tcpneeds no path. A service with noexpose:and noports:may show its port through its healthcheck; one that declares anexpose:must still list the port.
up and survey
- Host-built images.
build: <Dockerfile>replacesimage:wherejcloud.ymlsaysbuilds: true. Such a service is ready without a pin, andsurveycompares no digest for it. - Moving tags. A pin that is a tag rather than a digest is named, along with the digest to pin from
--published. - Opt-in services.
surveydoesn't report a service missing when its compose profile wasn't reported on.
Output changes
::error/::warningannotations are printed only whenGITHUB_ACTIONS=true. A terminal now shows each problem once. If you run jcloud in a container from an Actions step, pass the variable through (docker run -e GITHUB_ACTIONS …), or the annotations disappear.comparereports only the lines that differ, each named by its field (port:,env[2]:), instead of every line between two distant differences.
Contract tests
JCLOUD_PLATFORMtakes a:-separated list of platform trees. Each is checked through its ownjcloud.yml.
Install
Download a binary below, or go install github.com/Japode/jcloud@v0.3.0.
v0.2.0
The first release with prebuilt binaries. jcloud behaves the same as in v0.1.0; what changed is how you get it.
- Binaries on every tag. GoReleaser builds jcloud for Linux, macOS and Windows on amd64 and arm64, and publishes the archives with a
checksums.txt. The release workflow runs the sameci/check.shgate as every push first, so a tag can't publish what CI would refuse. - The version comes from the tag.
jcloud versionprints the release, the schema version and the Go compiler that built it. There is no version constant to bump.
Install
Download a binary below and check it against checksums.txt, or go install github.com/Japode/jcloud@v0.2.0.
v0.1.0
jcloud as a module of its own, extracted from tools/jcloud in the Japode platform repository (Japode/cloud). It checks a service's japode.yml manifest against the files that have to agree with it. It renders nothing and deploys nothing.
checkrefuses a manifest that doesn't state what a service must declare. With--repo, it also compares the manifest against the service's own Dockerfile, Caddyfile and publishing workflows.agreecompares the manifest against the platform files deployed beside it: the compose file, the Caddy route (read fromcaddy adaptoutput, so a commented-out block can't pass),backup.conf,.env.example, and the addons aneeds:entry brings.comparereports where the platform's copy of a manifest and the service's own copy disagree.surveycompares a server, as observed by the platform's collector script, against what the manifests declare.upnames what stands between each service and a deploy: missing variables, a missing pin, and the digest its CI published.envmanages.env.age, a configuration store whose keys are readable and whose values are age-encrypted.
This tag has no prebuilt binaries; those start with v0.2.0. Install it with go install github.com/Japode/jcloud@v0.1.0.