Contributing
Local Plugin Development
sitectl has a core binary and optional plugin binaries named sitectl-<plugin>.
The current local development plugin chain in this workspace is:
sitectl: core binary; owns the operator-facing commandssitectl-isle: ISLE plugin; stack logic, component definitions, ISLE-specific jobssitectl-drupal: Drupal plugin; drush, sync, ULI; included by the ISLE plugin
__sitectl-rpc protocol dispatched based on the active context’s plugin field.
The SDK registers that private entrypoint automatically. Plugin code should register SDK handlers and runners, not define its own transport commands.
Core commands that fan out to plugins:
sitectl debug→debug.runsitectl validate→ core validators +validate.run(results merged)sitectl healthcheck→ core Compose runtime checks +healthcheck.run(results merged)sitectl verify→verify.run(results rendered by core)sitectl converge→converge.runsitectl set→set.runsitectl deploy→deploy.runsitectl job run→job.runsitectl component describe→component.describesitectl component set→component.set
isle, core sitectl routes dispatch commands to sitectl-isle. The ISLE plugin can further invoke included plugins such as sitectl-drupal.
See Plugin hierarchy for the full protocol.
Local Install Workflow
For local development, install the binaries into a directory on your$PATH so the core binary can discover and invoke plugin binaries. From the core repo:
- Build and install
sitectl - Change into
../sitectl-isleand runmake install - Change into
../sitectl-drupaland runmake install
install targets run make work before building so they use the local core sitectl checkout during development.
If you are only working on a single plugin:
Why make install matters
If you only rebuild sitectl locally but do not install the plugin binaries into a directory on PATH, core command dispatch will not see the current local plugin builds. Installing the full chain keeps the stack aligned while you work on:
- core command routing in
sitectl - stack logic in
sitectl-isle - Drupal-specific extensions in
sitectl-drupal
UI Architecture
sitectl supports two interaction modes:
- one-off command execution such as
sitectl compose ps - an embedded dashboard launched by running
sitectlwith no additional arguments
Rule
When a command needs interactive UI:- keep business logic separate from UI state and rendering
- make the UI self-contained inside the command or shared UI package
- ensure the same UI can run standalone or be embedded inside the dashboard
- service layer: pure command logic and side effects
- UI layer: Bubble Tea model and Bubbles-based components
- Cobra layer: chooses between non-interactive execution and launching the UI
Required Libraries
Interactivesitectl UIs should build on the shared stack already in use:
bubbleteafor state, events, and screen managementbubblesfor list, help, input, viewport, progress, and similar primitiveslipglossfor styling and layoutbubblezonefor click targets and mouse hit detection where neededharmonicafor motion and transitions where appropriatentchartsfor terminal charts where appropriate
What Not To Do
Do not implement custom terminal widgets when the library stack already provides them:- do not hand-roll a select menu when
bubbles/listfits - do not hand-roll a text input when
bubbles/textinputortextareafits - do not hand-roll help footers when
bubbles/helpfits - do not hand-roll scroll containers when
bubbles/viewportfits
lipgloss is for presentation and composition, not a replacement for Bubble Tea or Bubbles interaction primitives.
Shared Components
Reusable interaction primitives should live in shared UI packages so commands and the dashboard can both consume them:- shared prompt, select, and input components belong in
pkg/ui - command-specific interactive screens can live near the command, but should still be Bubble Tea models
- older bespoke prompt implementations should be migrated to shared Bubble Tea and Bubbles components over time
Design Goal
A command that has an interactive flow should be embeddable in the dashboard without rewriting its UI logic. That means a command UI should be structured so it can be:- launched directly from Cobra
- pushed or mounted inside the dashboard
Release Publishing
GoReleaser builds the release artifacts, including Linux packages vianfpms.
- GitHub release publishing: goreleaser.yaml
- The GoReleaser job uses only the caller’s source-repository-scoped
GITHUB_TOKENto create the GitHub release and upload its artifacts. - A separate, isolated tap-reconciliation job may use
HOMEBREW_REPOto update thelibops/homebrewrepository. That cross-repository token is not exposed to GoReleaser or substituted forGITHUB_TOKEN.
publish-package-repo: false. A derived plugin should enable it only after its repository has a trusted package publisher and the required cloud identity and variables. Leaving it disabled still publishes GitHub release artifacts and the native package files attached to the release. Homebrew publication is independent and runs only when the derived repository points its isolated tap-reconciliation job at a tap it controls and provides HOMEBREW_REPO to that job.
Linux package publishing
This repo publishes Debian and RPM repositories through the shared libops packaging infrastructure. GitHub Actions workflows:- core caller workflow: sitectl
goreleaser.yamlat the documentedv1.0.0commit - core reusable workflow:
reusable-goreleaser.yamlat481df51116aed2efd1c002ef1ef6a287699828a0 - package publisher pinned by that core workflow:
terraform-linux-packagesat72d2f0c3b01e5e396d6db074108b95e95eedf4d4 - ArchivesSpace, OJS, Omeka Classic, Omeka S, and WordPress v1.0.0 reusable workflow:
sitectl-plugin-goreleaser.yamlat39f81174b7b899f66aee31035308e1d9b1f3e165 - Drupal v1.0.0 and LibOps v1.3.0 reusable workflow:
sitectl-plugin-goreleaser.yamlate1e30b58c9c566f72b22f03e637cd5218d635727 - package publisher pinned by the five-application and Drupal/LibOps workflow revisions:
terraform-linux-packagesat285183831e6e5c14ec34d87e208629949f5b627c - ISLE v1.0.0 release-assets and Homebrew recovery workflow:
sitectl-plugin-goreleaser.yamlat8e27d95846671a9e319f1900e86a488a1d4f39b3 - ISLE v1.0.0 final Linux-package recovery workflow:
sitectl-plugin-goreleaser.yamlat77724fe807ede3e0808d4556f47e4ad0ae266bac - package publisher pinned by that final ISLE recovery workflow:
terraform-linux-packagesatb14fd2f95b4017e897c595b3321b9cc3f48b5ddd
8e27d95 revision to restore release assets and reconcile
Homebrew, then the 77724fe revision and its b14fd2f package publisher to
complete Debian and RPM repository publication. The successful final recovery
run records the
completed package publication. Advance a full SHA only with the caller and
publisher contract tests in the same change; never replace it with main.
Required GitHub secrets:
HOMEBREW_REPO: cross-repository token available only to the isolated Homebrew tap-reconciliation job
LIBOPS_PACKAGES_GCLOUD_OIDC_POOL: Workload Identity provider resource nameLIBOPS_PACKAGES_GCLOUD_PROJECT: Google Cloud project ID that holds the package infrastructureLIBOPS_PACKAGES_GSA: Google service account email used by GitHub ActionsLIBOPS_PACKAGES_GCS_BUCKET: bucket name that hosts the published package repositoryLIBOPS_PACKAGES_APTLY_GPG_KEY_ID: GPG key ID or fingerprint to use for signingLIBOPS_PACKAGES_APTLY_GPG_PRIVATE_KEY_SECRET: Secret Manager secret ID that stores the armored private keyLIBOPS_PACKAGES_APTLY_GPG_PASSPHRASE_SECRET: Secret Manager secret ID that stores the signing key passphrase
GCS_BUCKET_PREFIXdefault: emptyAPTLY_DISTRIBUTIONSdefault:bookwormAPTLY_COMPONENTdefault:mainAPTLY_ARCHITECTURESdefault:amd64,arm64APTLY_PUBLISH_PREFIXdefault:.APTLY_ORIGINdefault:libopsAPTLY_LABELdefault:sitectlAPTLY_PUBLIC_KEY_NAMEdefault:sitectl-archive-keyringRPM_REPOSITORY_PATHdefault:rpm
repodata/repomd.xml; the repository metadata authenticates package checksums, but the individual RPM payloads are not separately signed. Client configuration therefore uses repo_gpgcheck=1 and gpgcheck=0. Do not advertise per-RPM signatures or enable gpgcheck=1 until package signing is implemented.
The workflow rebuilds Debian and RPM repository metadata from the current release artifacts only. That is sufficient for fresh installs and upgrades, but does not preserve older package versions for pinning or rollback. A version in cloud-compose or another package manifest is durable only while the artifact remains available. Production operators that need rollback must retain a repository snapshot or archive the matching GitHub release package assets and checksums.txt in controlled storage.
