diff --git a/.github/workflows/goreleaser.yaml b/.github/workflows/goreleaser.yaml index 83e68c6..e786262 100644 --- a/.github/workflows/goreleaser.yaml +++ b/.github/workflows/goreleaser.yaml @@ -8,43 +8,26 @@ on: permissions: contents: write + id-token: write jobs: goreleaser: - runs-on: ubuntu-24.04 - env: - CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} - CLOUDSMITH_NAMESPACE: ${{ vars.CLOUDSMITH_NAMESPACE || 'libops' }} - CLOUDSMITH_REPOSITORY: ${{ vars.CLOUDSMITH_REPOSITORY || 'sitectl' }} - CLOUDSMITH_DEB_TARGETS: ${{ vars.CLOUDSMITH_DEB_TARGETS || 'debian/bookworm ubuntu/noble' }} - CLOUDSMITH_RPM_TARGETS: ${{ vars.CLOUDSMITH_RPM_TARGETS || 'fedora/41' }} - CLOUDSMITH_ALPINE_TARGETS: ${{ vars.CLOUDSMITH_ALPINE_TARGETS || 'alpine/any-version' }} - steps: - - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6 - with: - go-version: '>=1.25.8' - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6 - with: - distribution: goreleaser - version: latest - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.HOMEBREW_REPO }} - - - name: Install Cloudsmith CLI - if: ${{ env.CLOUDSMITH_API_KEY != '' }} - uses: cloudsmith-io/cloudsmith-cli-action@v1 - with: - api-key: ${{ env.CLOUDSMITH_API_KEY }} - - - name: Publish Linux packages to Cloudsmith - if: ${{ env.CLOUDSMITH_API_KEY != '' }} - run: make publish-cloudsmith + uses: libops/terraform-linux-packages/.github/workflows/reusable-goreleaser.yaml@main + permissions: + contents: write + id-token: write + secrets: inherit + with: + go-version: ">=1.25.8" + publish-package-repo: true + package-name: sitectl + package-repo-prefix: sitectl + package-repo-label: sitectl + package-public-key-name: sitectl-archive-keyring + gcp-project: ${{ vars.LIBOPS_PACKAGES_GCLOUD_PROJECT }} + workload-identity-provider: ${{ vars.LIBOPS_PACKAGES_GCLOUD_OIDC_POOL }} + service-account: ${{ vars.LIBOPS_PACKAGES_GSA }} + gcs-bucket: ${{ vars.LIBOPS_PACKAGES_GCS_BUCKET }} + aptly-gpg-key-id: ${{ vars.LIBOPS_PACKAGES_APTLY_GPG_KEY_ID }} + aptly-gpg-private-key-secret: ${{ vars.LIBOPS_PACKAGES_APTLY_GPG_PRIVATE_KEY_SECRET }} + aptly-gpg-passphrase-secret: ${{ vars.LIBOPS_PACKAGES_APTLY_GPG_PASSPHRASE_SECRET }} diff --git a/.github/workflows/publish-apt-repo.yaml b/.github/workflows/publish-apt-repo.yaml new file mode 100644 index 0000000..5f6412a --- /dev/null +++ b/.github/workflows/publish-apt-repo.yaml @@ -0,0 +1,37 @@ +name: publish-apt-repo + +on: + workflow_dispatch: + push: + tags: + - "*" + +permissions: + contents: read + id-token: write + +jobs: + publish-apt-repo: + if: ${{ vars.LIBOPS_PACKAGES_GCLOUD_PROJECT != '' && vars.LIBOPS_PACKAGES_GCLOUD_OIDC_POOL != '' && vars.LIBOPS_PACKAGES_GSA != '' && vars.LIBOPS_PACKAGES_GCS_BUCKET != '' && vars.LIBOPS_PACKAGES_APTLY_GPG_KEY_ID != '' }} + uses: libops/terraform-linux-packages/.github/workflows/reusable-publish-apt-repo.yaml@main + permissions: + contents: read + id-token: write + secrets: inherit + with: + go-version: ">=1.25.8" + gcp-project: ${{ vars.LIBOPS_PACKAGES_GCLOUD_PROJECT }} + workload-identity-provider: ${{ vars.LIBOPS_PACKAGES_GCLOUD_OIDC_POOL }} + service-account: ${{ vars.LIBOPS_PACKAGES_GSA }} + gcs-bucket: ${{ vars.LIBOPS_PACKAGES_GCS_BUCKET }} + gcs-bucket-prefix: ${{ vars.GCS_BUCKET_PREFIX }} + aptly-gpg-key-id: ${{ vars.LIBOPS_PACKAGES_APTLY_GPG_KEY_ID }} + aptly-gpg-private-key-secret: ${{ vars.LIBOPS_PACKAGES_APTLY_GPG_PRIVATE_KEY_SECRET || 'aptly-gpg-private-key' }} + aptly-gpg-passphrase-secret: ${{ vars.LIBOPS_PACKAGES_APTLY_GPG_PASSPHRASE_SECRET || 'aptly-gpg-passphrase' }} + aptly-distributions: ${{ vars.APTLY_DISTRIBUTIONS || 'bookworm' }} + aptly-component: ${{ vars.APTLY_COMPONENT || 'main' }} + aptly-architectures: ${{ vars.APTLY_ARCHITECTURES || 'amd64,arm64' }} + aptly-publish-prefix: ${{ vars.APTLY_PUBLISH_PREFIX || '.' }} + aptly-origin: ${{ vars.APTLY_ORIGIN || 'libops' }} + aptly-label: ${{ vars.APTLY_LABEL || 'sitectl' }} + aptly-public-key-name: ${{ vars.APTLY_PUBLIC_KEY_NAME || 'sitectl-archive-keyring' }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 885d9eb..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,103 +0,0 @@ -# Contributing - -## UI Architecture - -`sitectl` supports two interaction modes: - -- one-off command execution such as `sitectl compose ps` -- an embedded TUI dashboard launched by running `sitectl` with no additional arguments - -Because both modes need to share behavior, interactive command UIs must be designed as composable Bubble Tea models instead of bespoke terminal flows. - -### 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 - -In practice, command implementations should follow this split: - -- 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 - -Interactive `sitectl` UIs should build on the shared TUI stack already in use: - -- `bubbletea` for state, events, and screen management -- `bubbles` for list, help, input, viewport, progress, and similar primitives -- `lipgloss` for styling and layout -- `bubblezone` for click targets and mouse hit detection where needed -- `harmonica` for motion and transitions where appropriate -- `ntcharts` for terminal charts where appropriate - -### What Not To Do - -Do not implement custom terminal widgets when the library stack already provides them. - -Examples: - -- do not hand-roll a select menu when `bubbles/list` fits -- do not hand-roll a text input when `bubbles/textinput` or `textarea` fits -- do not hand-roll help footers when `bubbles/help` fits -- do not hand-roll scroll containers when `bubbles/viewport` fits - -`lipgloss` should be used for presentation and composition, not as a replacement for Bubble Tea/Bubbles interaction primitives. - -### Shared Components - -Reusable interaction primitives should live in shared UI packages so commands and the dashboard can both consume them. - -Current direction: - -- shared prompt/select/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/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 TUI - -If a proposed command UI cannot be reused that way, it should be redesigned before being added. - -## Release Publishing - -GoReleaser builds the release artifacts, including Linux packages via `nfpms`. - -- GitHub release publishing runs from [`.github/workflows/goreleaser.yaml`](/workspace/sitectl/.github/workflows/goreleaser.yaml) -- Cloudsmith publishing is handled by [`scripts/publish-cloudsmith.sh`](/workspace/sitectl/scripts/publish-cloudsmith.sh) -- CI and local publishing should both use `make publish-cloudsmith` - -### Cloudsmith - -To enable Cloudsmith uploads in GitHub Actions, set: - -- secret `CLOUDSMITH_API_KEY` -- variable `CLOUDSMITH_NAMESPACE` such as `libops` -- variable `CLOUDSMITH_REPOSITORY` such as `sitectl` - -Optional target overrides: - -- `CLOUDSMITH_DEB_TARGETS` default: `debian/bookworm ubuntu/noble` -- `CLOUDSMITH_RPM_TARGETS` default: `fedora/41` -- `CLOUDSMITH_ALPINE_TARGETS` default: `alpine/any-version` - -To publish an already-built `dist/` directory locally: - -```bash -CLOUDSMITH_API_KEY=... \ -CLOUDSMITH_NAMESPACE=libops \ -CLOUDSMITH_REPOSITORY=sitectl \ -CLOUDSMITH_DEB_TARGETS="debian/bookworm ubuntu/noble" \ -CLOUDSMITH_RPM_TARGETS="fedora/41" \ -CLOUDSMITH_ALPINE_TARGETS="alpine/any-version" \ -make publish-cloudsmith -``` diff --git a/Makefile b/Makefile index 1c8b727..130041a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ -.PHONY: build deps lint test docker integration-test docs plugins install-plugins publish-cloudsmith +.PHONY: build deps lint test docker integration-test docs docs-host plugins install-plugins publish-aptly-repo BINARY_NAME=sitectl +DOCS_PORT ?= 3000 deps: go get . @@ -9,6 +10,17 @@ deps: build: deps go build -o $(BINARY_NAME) . +docs: + docker run --rm -it \ + -p $(DOCS_PORT):$(DOCS_PORT) \ + -v "$(CURDIR):/work" \ + -w /work \ + node:22-bookworm \ + sh -lc "npx mint dev --port $(DOCS_PORT) --host 0.0.0.0" + +docs-host: + npx mint dev + lint: go fmt ./... golangci-lint run @@ -23,5 +35,5 @@ lint: test: build go test -v -race ./... -publish-cloudsmith: - bash ./scripts/publish-cloudsmith.sh +publish-aptly-repo: + bash ./scripts/publish-aptly-repo.sh diff --git a/README.md b/README.md index 4f001da..7ec9a7d 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,6 @@ # sitectl CLI -Command line utility to interact with your local and remote docker compose sites. - -## Install - -### Homebrew - -You can install `sitectl` using homebrew - -```bash -brew tap libops/homebrew https://github.com/libops/homebrew -brew install libops/homebrew/sitectl -``` - -### Download Binary - -Instead of homebrew, you can download a binary for your system from [the latest release of sitectl](https://github.com/libops/sitectl/releases/latest) - -Then put the binary in a directory that is in your `$PATH` - -### Linux Packages - -Releases also publish native Linux packages for: - -- Alpine (`.apk`) -- Debian and Ubuntu (`.deb`) -- Fedora and other RPM-based distributions (`.rpm`) - -[![OSS hosting by Cloudsmith](https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith&style=flat-square)](https://cloudsmith.com) - -Linux package repository hosting is graciously provided by [Cloudsmith](https://cloudsmith.com). - -## Usage - -```bash -$ sitectl --help -Interact with your docker compose site - -Usage: - sitectl [command] - -Available Commands: - completion Generate the autocompletion script for the specified shell - compose Run docker compose commands on sitectl contexts - config Manage sitectl command configuration - help Help about any command - make Run custom make commands - port-forward Forward one or more local ports to a service - sequelace Connect to your MySQL/Mariadb database using Sequel Ace (Mac OS only) - -Flags: - --context string The sitectl context to use. See sitectl config --help for more info (default "local") - -h, --help help for sitectl - --log-level string The logging level for the command (default "DEBUG") - -Use "sitectl [command] --help" for more information about a command. -``` - -## Why sitectl vs Docker Context? - -While [Docker's native context feature](https://docs.docker.com/engine/manage-resources/contexts/) handles basic daemon connections, `sitectl` is purpose-built for Docker Compose projects and adds: - -- **Enhanced remote operations**: SFTP file operations (read env files, upload/download), sudo support, and helpful SSH error messages -- **Container utilities**: Resolve service names to containers, extract secrets/env vars to better support `exec` operations inside containers, get container IPs within Docker networks -- **Service management**: Enable/disable services in docker-compose.yml with automatic cleanup of orphaned resources and Drupal configuration -- **Compose-first design**: Set the equivalent of `DOCKER_HOST`, `COMPOSE_PROJECT_NAME`, `COMPOSE_FILE`, `COMPOSE_ENV_FILES` automatically based on sitectl context settings - - See [Docker's documentation](https://docs.docker.com/compose/how-tos/environment-variables/envvars/#configuration-details) for what these environment variables do - -## Plugins - -`sitectl` can be extended for project-specific needs - -- [islandora](https://github.com/libops/sitectl-isle) -- [drupal](https://github.com/libops/sitectl-drupal) - -## Contributing - -Contributor guidance, including the TUI and command UI architecture rules, lives in [CONTRIBUTING.md](./CONTRIBUTING.md). +The docs are available at https://sitectl.libops.io ## Attribution diff --git a/docs.json b/docs.json new file mode 100644 index 0000000..ef3608e --- /dev/null +++ b/docs.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "mint", + "name": "sitectl", + "colors": { + "primary": "#16A34A", + "light": "#07C983", + "dark": "#15803D" + }, + "favicon": "/favicon.ico", + "navigation": { + "tabs": [ + { + "tab": "Home", + "groups": [ + { + "group": "Getting started", + "pages": [ + "index", + "docs/install", + "docs/quickstart" + ] + }, + { + "group": "Concepts", + "pages": [ + "docs/tui", + "docs/context", + "docs/plugins", + "docs/components" + ] + }, + { + "group": "Usage", + "pages": [ + "docs/commands/compose" + ] + }, + { + "group": "Plugins", + "pages": [ + "docs/plugins/drupal", + "docs/plugins/isle" + ] + }, + { + "group": "Contributors", + "pages": [ + "docs/contributors", + "docs/contributors/docs" + ] + } + ] + }, + { + "tab": "Drupal plugin", + "groups": [ + { + "group": "Commands", + "pages": [ + "docs/plugins/drupal/drush" + ] + } + ] + }, + { + "tab": "ISLE plugin", + "groups": [ + { + "group": "Commands", + "pages": [ + "docs/plugins/isle/index" + ] + }, + { + "group": "Components", + "pages": [ + "docs/plugins/isle/fcrepo", + "docs/plugins/isle/blazegraph" + ] + } + ] + } + ] + }, + "logo": { + "light": "/docs/logo/libops-light.png", + "dark": "/docs/logo/libops-dark.png" + }, + "contextual": { + "options": [ + "copy" + ] + }, + "footer": { + "socials": { + "github": "https://github.com/libops/sitectl" + } + } +} diff --git a/docs/commands/compose.mdx b/docs/commands/compose.mdx new file mode 100644 index 0000000..7a54633 --- /dev/null +++ b/docs/commands/compose.mdx @@ -0,0 +1,18 @@ +--- +title: Compose command +description: Run Docker Compose commands through the active sitectl context. +--- + +# Compose command + +The `compose` command runs Docker Compose commands against the active `sitectl` context. + +Use it when you want the same Compose operation to respect the site and environment wiring already stored in the context. + +Examples: + +```bash +sitectl compose ps +sitectl compose logs +sitectl compose exec app bash +``` diff --git a/docs/components.mdx b/docs/components.mdx new file mode 100644 index 0000000..0618b76 --- /dev/null +++ b/docs/components.mdx @@ -0,0 +1,19 @@ +--- +title: Components +description: Understand how sitectl uses components to express reviewed stack defaults and operator tradeoffs. +--- + +# Components + +Components describe optional parts of a stack in a structured way. + +They are how `sitectl` marries infrastructure settings and app-specific settings into one reviewed configuration. + +Instead of treating a stack as one large blob, `sitectl` lets each component carry its own defaults, follow-up questions, and operator guidance. + +Common component states: + +- `enabled` +- `disabled` +- `superseded` +- `distributed` diff --git a/docs/context.mdx b/docs/context.mdx new file mode 100644 index 0000000..822fe8a --- /dev/null +++ b/docs/context.mdx @@ -0,0 +1,18 @@ +--- +title: Context +description: How sitectl models sites, environments, and the saved context that connects to each one. +--- + + +`sitectl` organizes around a **site** and its **environments** into what it calls a context. + +- A **site** is the project itself. +- An **environment** is where that site runs: `local`, `staging`, `prod`, and so on. +- A **context** is the saved connection information for the given site environment. + +Examples: + +- `museum-local` +- `museum-prod` + +Contexts tell `sitectl` where Docker Compose lives and how to reach it. diff --git a/docs/contributors/docs.mdx b/docs/contributors/docs.mdx new file mode 100644 index 0000000..c42d09b --- /dev/null +++ b/docs/contributors/docs.mdx @@ -0,0 +1,61 @@ +--- +title: Documentation +description: How to edit, preview, and organize the Mintlify documentation in the sitectl repository. +--- + +import { TUI } from "/docs/snippets/tui-tooltip.mdx"; + +The docs site is driven by Mintlify from this repository. + +Mintlify's docs are available at https://www.mintlify.com/docs + +Mintlify reads: + +- [docs.json](https://github.com/libops/sitectl/blob/main/docs.json) for navigation and site settings +- [index.mdx](https://github.com/libops/sitectl/blob/main/index.mdx) for the homepage +- files under [`docs/`](https://github.com/libops/sitectl/tree/main/docs) for the rest of the documentation + +## Local development + +Mintlify’s local dev startup command is `mint dev`, but Mintlify does not support Node 25+. This repo avoids that issue by running Mintlify in a Node 22 Docker container: + +```bash +make docs +``` + +The docs server binds to port `3000` by default. You can override it with: + +```bash +make docs DOCS_PORT=3333 +``` + +If you already have a supported LTS Node version installed locally, you can also run the host-native command: + +```bash +make docs-host +``` + +## Making docs changes + +When you update the docs: + +- keep pages in `docs/` unless the page is the root homepage, which lives at `index.mdx` +- preserve the existing nav structure in `docs.json` and only add to it when needed +- use MDX for new pages +- keep contributor and operator guidance in the docs site instead of reviving `CONTRIBUTING.md` + +## content + +The tour reads: + +- `internal/tuitour/content/index.md` +- `internal/tuitour/content/tui.md` +- `internal/tuitour/content/context.md` +- `internal/tuitour/content/plugins.md` +- `internal/tuitour/content/components.md` + +That content is intentionally separate from the Mintlify docs so the in-app tour can stay plain markdown and keep its own presentation. + +## Deployment + +Docs deploy through the Mintlify GitHub App connected to this repository. There is no GitHub Pages workflow to maintain here. diff --git a/docs/contributors/index.mdx b/docs/contributors/index.mdx new file mode 100644 index 0000000..06266d3 --- /dev/null +++ b/docs/contributors/index.mdx @@ -0,0 +1,119 @@ +--- +title: Contributing +description: Contributor guidance for sitectl, including UI architecture rules and release publishing expectations. +--- + +import { TUI } from "/docs/snippets/tui-tooltip.mdx"; + +# Contributing + +## UI Architecture + +`sitectl` supports two interaction modes: + +- one-off command execution such as `sitectl compose ps` +- an embedded dashboard launched by running `sitectl` with no additional arguments + +Because both modes need to share behavior, interactive command UIs must be designed as composable Bubble Tea models instead of bespoke terminal flows. + +## 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 + +In practice, command implementations should follow this split: + +- 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 + +Interactive `sitectl` UIs should build on the shared stack already in use: + +- `bubbletea` for state, events, and screen management +- `bubbles` for list, help, input, viewport, progress, and similar primitives +- `lipgloss` for styling and layout +- `bubblezone` for click targets and mouse hit detection where needed +- `harmonica` for motion and transitions where appropriate +- `ntcharts` for terminal charts where appropriate + +## What Not To Do + +Do not implement custom terminal widgets when the library stack already provides them. + +Examples: + +- do not hand-roll a select menu when `bubbles/list` fits +- do not hand-roll a text input when `bubbles/textinput` or `textarea` fits +- do not hand-roll help footers when `bubbles/help` fits +- do not hand-roll scroll containers when `bubbles/viewport` fits + +`lipgloss` should be used for presentation and composition, not as 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. + +Current direction: + +- 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 + +If a proposed command UI cannot be reused that way, it should be redesigned before being added. + +## Release Publishing + +GoReleaser builds the release artifacts, including Linux packages via `nfpms`. + +- GitHub release publishing runs from [goreleaser.yaml](https://github.com/libops/sitectl/blob/main/.github/workflows/goreleaser.yaml) + +### Linux package publishing + +This repo also publishes Debian and RPM repositories through the shared libops packaging infrastructure. + +GitHub Actions workflow: + +- [publish-apt-repo.yaml](https://github.com/libops/sitectl/blob/main/.github/workflows/publish-apt-repo.yaml) +- shared publishing script: [publish-package-repo.sh](https://github.com/libops/terraform-linux-packages/blob/main/scripts/publish-package-repo.sh) + +Required GitHub secrets: + +- `HOMEBREW_REPO`: token used by GoReleaser for release publishing + +Required GitHub variables: + +- `LIBOPS_PACKAGES_GCLOUD_OIDC_POOL`: Workload Identity provider resource name +- `LIBOPS_PACKAGES_GCLOUD_PROJECT`: Google Cloud project ID that holds the package infrastructure +- `LIBOPS_PACKAGES_GSA`: Google service account email used by GitHub Actions +- `LIBOPS_PACKAGES_GCS_BUCKET`: bucket name that hosts the published package repository +- `LIBOPS_PACKAGES_APTLY_GPG_KEY_ID`: GPG key ID or fingerprint to use for signing +- `LIBOPS_PACKAGES_APTLY_GPG_PRIVATE_KEY_SECRET`: Secret Manager secret ID that stores the armored private key +- `LIBOPS_PACKAGES_APTLY_GPG_PASSPHRASE_SECRET`: Secret Manager secret ID that stores the signing key passphrase + +Optional GitHub variables: + +- `GCS_BUCKET_PREFIX` default: empty +- `APTLY_DISTRIBUTIONS` default: `bookworm` +- `APTLY_COMPONENT` default: `main` +- `APTLY_ARCHITECTURES` default: `amd64,arm64` +- `APTLY_PUBLISH_PREFIX` default: `.` +- `APTLY_ORIGIN` default: `libops` +- `APTLY_LABEL` default: `sitectl` +- `APTLY_PUBLIC_KEY_NAME` default: `sitectl-archive-keyring` +- `RPM_REPOSITORY_PATH` default: `rpm` + +The workflow rebuilds Debian and RPM repository metadata from the current release artifacts only. That is enough for fresh installs and upgrades, but it does not preserve older package versions for pinning or rollback. diff --git a/docs/embed.go b/docs/embed.go deleted file mode 100644 index 844958e..0000000 --- a/docs/embed.go +++ /dev/null @@ -1,73 +0,0 @@ -package docs - -import ( - "embed" - "fmt" - "io/fs" - "path/filepath" - "sort" - "strings" -) - -//go:embed index.md tour/*.md -var content embed.FS - -type TourPane struct { - Slug string - Title string - Markdown string -} - -func LoadTour() ([]TourPane, error) { - panes := make([]TourPane, 0, 4) - - indexData, err := content.ReadFile("index.md") - if err != nil { - return nil, fmt.Errorf("read embedded tour index: %w", err) - } - panes = append(panes, TourPane{ - Slug: "index", - Title: firstHeading(string(indexData), "Tour"), - Markdown: string(indexData), - }) - - entries, err := fs.ReadDir(content, "tour") - if err != nil { - return nil, fmt.Errorf("read embedded tour docs: %w", err) - } - - names := make([]string, 0, len(entries)) - for _, entry := range entries { - if entry.IsDir() || filepath.Ext(entry.Name()) != ".md" { - continue - } - names = append(names, entry.Name()) - } - sort.Strings(names) - - for _, name := range names { - path := filepath.Join("tour", name) - data, err := content.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("read embedded tour doc %q: %w", path, err) - } - markdown := string(data) - panes = append(panes, TourPane{ - Slug: strings.TrimSuffix(name, filepath.Ext(name)), - Title: firstHeading(markdown, strings.TrimSuffix(name, filepath.Ext(name))), - Markdown: markdown, - }) - } - - return panes, nil -} - -func firstHeading(markdown, fallback string) string { - for line := range strings.SplitSeq(markdown, "\n") { - line = strings.TrimSpace(line) - if after, ok := strings.CutPrefix(line, "# "); ok { - return strings.TrimSpace(after) - } - } - return strings.TrimSpace(fallback) -} diff --git a/docs/install.mdx b/docs/install.mdx new file mode 100644 index 0000000..0ad9130 --- /dev/null +++ b/docs/install.mdx @@ -0,0 +1,69 @@ +--- +title: Install +description: Install sitectl with Homebrew, native Linux packages, or direct binaries. +--- + +## Homebrew + +You can install `sitectl` using Homebrew: + +```bash +brew tap libops/homebrew https://github.com/libops/homebrew +brew install libops/homebrew/sitectl +``` + +## Linux Packages + +Releases publish native Linux packages through the libops package repository. + + + +```bash Debian / Ubuntu +curl -fsSL https://packages.libops.io/sitectl/sitectl-archive-keyring.asc | sudo gpg --dearmor -o /usr/share/keyrings/sitectl-archive-keyring.gpg +echo "deb [signed-by=/usr/share/keyrings/sitectl-archive-keyring.gpg] https://packages.libops.io/sitectl ./" | sudo tee /etc/apt/sources.list.d/sitectl.list >/dev/null +sudo apt update +sudo apt install sitectl +``` + +```bash Fedora / Rocky / RHEL +sudo tee /etc/yum.repos.d/sitectl.repo >/dev/null <<'EOF' +[sitectl] +name=sitectl +baseurl=https://packages.libops.io/sitectl/rpm +enabled=1 +gpgcheck=0 +repo_gpgcheck=1 +gpgkey=https://packages.libops.io/sitectl/sitectl-archive-keyring.asc +EOF + +sudo dnf makecache +sudo dnf install sitectl +``` + + + +## Binary Install + +You can install sitectl by either downloading or building the `sitectl` binary + + + + +You can download a binary for your system from [the latest release of sitectl](https://github.com/libops/sitectl/releases/latest). + + + + +Requires `go` and `make` + +```bash +git clone https://github.com/libops/sitectl +cd sitectl +make build +./sitectl --help +``` + + + + +Once `sitectl` is on your system the you can put the binary in a directory that is in your `$PATH`. diff --git a/docs/logo/dark.svg b/docs/logo/dark.svg new file mode 100644 index 0000000..8b343cd --- /dev/null +++ b/docs/logo/dark.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/logo/libops-dark.png b/docs/logo/libops-dark.png new file mode 100644 index 0000000..e3fbb46 Binary files /dev/null and b/docs/logo/libops-dark.png differ diff --git a/docs/logo/libops-light.png b/docs/logo/libops-light.png new file mode 100644 index 0000000..1a2b14f Binary files /dev/null and b/docs/logo/libops-light.png differ diff --git a/docs/logo/libops-vertical.png b/docs/logo/libops-vertical.png new file mode 100644 index 0000000..2dbed42 Binary files /dev/null and b/docs/logo/libops-vertical.png differ diff --git a/docs/logo/light.svg b/docs/logo/light.svg new file mode 100644 index 0000000..03e62bf --- /dev/null +++ b/docs/logo/light.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/plugins.mdx b/docs/plugins.mdx new file mode 100644 index 0000000..7cbaa6f --- /dev/null +++ b/docs/plugins.mdx @@ -0,0 +1,13 @@ +--- +title: Plugins +description: How sitectl plugins extend the core CLI with stack-specific workflows. +--- + +Plugins extend `sitectl` with stack-specific commands and create flows. + +Examples: + +- `drupal` adds Drupal-oriented utilities +- `isle` adds Islandora and ISLE workflows + +The core binary discovers installed plugins and exposes them as `sitectl ...`. diff --git a/docs/plugins/drupal.mdx b/docs/plugins/drupal.mdx new file mode 100644 index 0000000..a8bfdc4 --- /dev/null +++ b/docs/plugins/drupal.mdx @@ -0,0 +1,14 @@ +--- +title: Drupal plugin +description: Drupal-oriented sitectl workflows and command surface. +--- + +# Drupal plugin + +The Drupal plugin extends `sitectl` with Drupal-oriented workflows and commands. + +Use it when you want Drupal-specific operations layered on top of the core context and compose model. + +See also: + +- [Drush command](/docs/plugins/drupal/drush) diff --git a/docs/plugins/drupal/drush.mdx b/docs/plugins/drupal/drush.mdx new file mode 100644 index 0000000..8cb4ae4 --- /dev/null +++ b/docs/plugins/drupal/drush.mdx @@ -0,0 +1,10 @@ +--- +title: Drush +description: Drupal plugin command reference entry point for Drush-oriented workflows. +--- + +# Drush + +The Drupal plugin exposes Drush-oriented workflows through `sitectl`. + +This page is the landing page for Drupal command documentation and can be expanded as the plugin surface grows. diff --git a/docs/plugins/isle.mdx b/docs/plugins/isle.mdx new file mode 100644 index 0000000..49c2082 --- /dev/null +++ b/docs/plugins/isle.mdx @@ -0,0 +1,14 @@ +--- +title: ISLE plugin +description: Islandora and ISLE-oriented sitectl workflows and component guides. +--- + +# ISLE plugin + +The ISLE plugin extends `sitectl` with Islandora and ISLE-specific workflows. + +See also: + +- [ISLE overview](/docs/plugins/isle/index) +- [Fcrepo component](/docs/plugins/isle/fcrepo) +- [Blazegraph component](/docs/plugins/isle/blazegraph) diff --git a/docs/plugins/isle/blazegraph.mdx b/docs/plugins/isle/blazegraph.mdx new file mode 100644 index 0000000..2dc08fd --- /dev/null +++ b/docs/plugins/isle/blazegraph.mdx @@ -0,0 +1,10 @@ +--- +title: Blazegraph +description: Reference anchor for the Blazegraph component in ISLE-oriented sitectl stacks. +--- + +# Blazegraph + +Blazegraph is another core ISLE service concept. + +This page exists as the anchor for ISLE component documentation and can be expanded with deployment and operator guidance. diff --git a/docs/plugins/isle/fcrepo.mdx b/docs/plugins/isle/fcrepo.mdx new file mode 100644 index 0000000..c62faf6 --- /dev/null +++ b/docs/plugins/isle/fcrepo.mdx @@ -0,0 +1,10 @@ +--- +title: Fcrepo +description: Reference anchor for the Fcrepo component in ISLE-oriented sitectl stacks. +--- + +# Fcrepo + +Fcrepo is one of the core service concepts that appears in ISLE-oriented stacks. + +This page exists as the anchor for ISLE component documentation and can be expanded with stack-specific operator guidance. diff --git a/docs/plugins/isle/index.mdx b/docs/plugins/isle/index.mdx new file mode 100644 index 0000000..fc9c6b1 --- /dev/null +++ b/docs/plugins/isle/index.mdx @@ -0,0 +1,10 @@ +--- +title: ISLE plugin overview +description: Overview of the sitectl ISLE plugin and where it fits in Islandora-oriented stacks. +--- + +# ISLE plugin overview + +The ISLE plugin adds Islandora-specific behavior on top of the core `sitectl` workflow. + +It is intended for sites and stacks that need Islandora-aware create flows and component guidance. diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx new file mode 100644 index 0000000..32ce7f4 --- /dev/null +++ b/docs/quickstart.mdx @@ -0,0 +1,68 @@ +--- +title: Quickstart +description: Learn the sitectl command shape and the core areas to explore after installation. +--- + +import { TUI } from "/docs/snippets/tui-tooltip.mdx"; + +## Setup + +After [installing `sitectl`](/docs/install), start by running: + +```bash +sitectl +``` + +Running `sitectl` with no arguments opens the , where you can: + + + + Connect sitectl to a Compose project you already run locally or remotely. + + + Use the guided flow to start a new site with the available stack-specific tooling. + + + +## Monitoring + +After you setup or install a site, you can use sitectl to monitor the health of the docker compose project. + +Information like general container health, host stats, and logs will all be available in the sitectl dashboard. + +## Operations + +After you've configured your site(s) and environment(s) as sitectl contexts, you can begin using sitectl to help with site operations. + +You can also run individual commands directly for one-off tasks when you do not need the full flow. + +The main areas to learn from there are: + + + + Describe a site and its environments so sitectl knows how to connect and operate. + + + Run Docker Compose commands through sitectl against local and remote contexts. + + + Add stack-specific behavior for the technologies your sites use. + + + Work with reviewed operator defaults for optional parts of a stack. + + + +## Basic command shape + +```bash +sitectl [subcommand] [flags] +``` + +Common top-level commands: + +- `compose` +- `config` +- `make` +- `port-forward` +- `sequelace` diff --git a/docs/snippets/tui-tooltip.mdx b/docs/snippets/tui-tooltip.mdx new file mode 100644 index 0000000..1ff85b8 --- /dev/null +++ b/docs/snippets/tui-tooltip.mdx @@ -0,0 +1,8 @@ +export const TUI = () => ( + + TUI + +); diff --git a/docs/tui.mdx b/docs/tui.mdx new file mode 100644 index 0000000..f394ad8 --- /dev/null +++ b/docs/tui.mdx @@ -0,0 +1,15 @@ +--- +title: Terminal UI +description: The interactive terminal interface for moving between sites, environments, and operator actions. +--- + +import { TUI } from "/docs/snippets/tui-tooltip.mdx"; + +The `sitectl` is the main interface for managing a site and its environments. + +It gives you one place to: + +- inspect environments +- run commands against a specific environment +- install new sites +- move between local and remote environments for the same site diff --git a/index.mdx b/index.mdx new file mode 100644 index 0000000..6bc030f --- /dev/null +++ b/index.mdx @@ -0,0 +1,53 @@ +--- +title: sitectl +description: Command line utility to interact with your local and remote Docker Compose sites. +--- + +import { TUI } from "/docs/snippets/tui-tooltip.mdx"; + +## Overview + +Many open source applications can run well on Docker Compose. + +Compose also offers a strong developer experience because the same workload can often run in development and production with only modest environment-specific differences. + +## The gap + +The friction starts when a team is running several Compose projects across local and remote environments. + +There is usually enough toil around contexts, repeated operational commands, and stack-specific behavior to justify better tooling, but not enough complexity to justify Kubernetes. + +## Why sitectl exists + +`sitectl` is a tool for technoligists running a handful of sites using docker compose. Using sitectl is a value add to Docker Compose; the Compose-first workflow remains intact while making it easier to operate that stack day to day. + + + + Use the for routine site setup, monitoring, and operator workflows. + + + Track local and remote environments so sitectl can understand where a site lives and how to reach it. + + + Add stack-specific behavior for common technologies without abandoning the core Compose workflow. + + + Model reviewed stack defaults and operator choices in a more structured way than ad hoc Compose notes. + + + +## Why sitectl vs Docker Context? + +While [Docker's native context feature](https://docs.docker.com/engine/manage-resources/contexts/) handles basic docker daemon connections, `sitectl` is purpose-built for Docker Compose projects and adds: + + + + SFTP file operations, sudo support, and clearer SSH error handling. + + + General helpers to do things like resolve service names to containers, extract secrets and env vars for `exec` commands, and inspect container network details. + + + Automatically set the equivalent of `DOCKER_HOST`, `COMPOSE_PROJECT_NAME`, `COMPOSE_FILE`, and `COMPOSE_ENV_FILES` from the active sitectl context. + + diff --git a/docs/tour/04-components.md b/internal/tuitour/content/components.md similarity index 60% rename from docs/tour/04-components.md rename to internal/tuitour/content/components.md index ac91eaf..bdd89a4 100644 --- a/docs/tour/04-components.md +++ b/internal/tuitour/content/components.md @@ -12,12 +12,3 @@ Common component states are: - `disabled`: the component is not used - `superseded`: another component replaces its role - `distributed`: responsibility is moved out to external or split services - -When you change a default component, you should expect operator implications such as: - -- different infrastructure requirements -- different app-level wiring or environment values -- data movement or migration work -- different maintenance and failure modes - -That is why component review matters: changing one default can affect both platform behavior and application behavior at the same time. diff --git a/docs/tour/02-contexts.md b/internal/tuitour/content/context.md similarity index 61% rename from docs/tour/02-contexts.md rename to internal/tuitour/content/context.md index 1dde6b8..f5bdb3f 100644 --- a/docs/tour/02-contexts.md +++ b/internal/tuitour/content/context.md @@ -1,14 +1,14 @@ -# Contexts +# Context -`sitectl` organizes around a **site** and its **environments** into what's known as a **context** +`sitectl` organizes around a **site** and its **environments** into what it calls a context. -- A **site** is the project itself. e.g. your Drupal site +- A **site** is the project itself. - An **environment** is where that site runs: `local`, `staging`, `prod`, and so on. - A **context** is the saved connection information for the given site environment. Examples: -- `museum-local`: the museum site on your laptop -- `museum-prod`: the same site on a remote server +- `museum-local` +- `museum-prod` Contexts tell `sitectl` where Docker Compose lives and how to reach it. diff --git a/docs/index.md b/internal/tuitour/content/index.md similarity index 100% rename from docs/index.md rename to internal/tuitour/content/index.md diff --git a/docs/tour/03-plugins.md b/internal/tuitour/content/plugins.md similarity index 100% rename from docs/tour/03-plugins.md rename to internal/tuitour/content/plugins.md diff --git a/docs/tour/01-tui.md b/internal/tuitour/content/tui.md similarity index 100% rename from docs/tour/01-tui.md rename to internal/tuitour/content/tui.md diff --git a/internal/tuitour/load.go b/internal/tuitour/load.go new file mode 100644 index 0000000..91838c7 --- /dev/null +++ b/internal/tuitour/load.go @@ -0,0 +1,54 @@ +package tuitour + +import ( + "embed" + "fmt" + "strings" +) + +//go:embed content/*.md +var content embed.FS + +type Pane struct { + Slug string + Title string + Markdown string +} + +func Load() ([]Pane, error) { + paths := []string{ + "content/index.md", + "content/tui.md", + "content/context.md", + "content/plugins.md", + "content/components.md", + } + + panes := make([]Pane, 0, len(paths)) + for _, path := range paths { + data, err := content.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read embedded tour doc %q: %w", path, err) + } + + markdown := string(data) + slug := strings.TrimSuffix(strings.TrimPrefix(path, "content/"), ".md") + panes = append(panes, Pane{ + Slug: slug, + Title: firstHeading(markdown, slug), + Markdown: markdown, + }) + } + + return panes, nil +} + +func firstHeading(markdown, fallback string) string { + for line := range strings.SplitSeq(markdown, "\n") { + line = strings.TrimSpace(line) + if after, ok := strings.CutPrefix(line, "# "); ok { + return strings.TrimSpace(after) + } + } + return strings.TrimSpace(fallback) +} diff --git a/pkg/tui/dashboard.go b/pkg/tui/dashboard.go index e83d9ca..9128e61 100644 --- a/pkg/tui/dashboard.go +++ b/pkg/tui/dashboard.go @@ -21,7 +21,7 @@ import ( "charm.land/lipgloss/v2" "github.com/NimbleMarkets/ntcharts/v2/sparkline" "github.com/kballard/go-shellquote" - "github.com/libops/sitectl/docs" + "github.com/libops/sitectl/internal/tuitour" "github.com/libops/sitectl/pkg/config" "github.com/libops/sitectl/pkg/docker" "github.com/libops/sitectl/pkg/plugin" @@ -145,7 +145,7 @@ type dashboardModel struct { cfg *config.Config sites []siteGroup plugins []plugin.InstalledPlugin - tourPanes []docs.TourPane + tourPanes []tuitour.Pane currentContext string siteIndex int @@ -1161,8 +1161,8 @@ func pluginMenuItems(plugins []plugin.InstalledPlugin) []menuItem { return items } -func loadTourPanes() []docs.TourPane { - panes, err := docs.LoadTour() +func loadTourPanes() []tuitour.Pane { + panes, err := tuitour.Load() if err != nil { return nil } diff --git a/scripts/publish-cloudsmith.sh b/scripts/publish-cloudsmith.sh deleted file mode 100644 index 7684ecf..0000000 --- a/scripts/publish-cloudsmith.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail -shopt -s nullglob - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -DIST_DIR="${DIST_DIR:-$ROOT_DIR/dist}" - -: "${CLOUDSMITH_NAMESPACE:?CLOUDSMITH_NAMESPACE is required}" -: "${CLOUDSMITH_REPOSITORY:?CLOUDSMITH_REPOSITORY is required}" - -publish_packages() { - local format="$1" - local pattern="$2" - local targets="$3" - local -a packages=() - - while IFS= read -r package; do - packages+=("$package") - done < <(find "$DIST_DIR" -maxdepth 1 -type f -name "$pattern" -print | sort) - - if [ ${#packages[@]} -eq 0 ]; then - echo "No ${format} packages found in ${DIST_DIR}/" - return 0 - fi - if [ -z "${targets// }" ]; then - echo "No Cloudsmith targets configured for ${format}; skipping" - return 0 - fi - - for package in "${packages[@]}"; do - for target in $targets; do - cloudsmith push "$format" "$CLOUDSMITH_NAMESPACE/$CLOUDSMITH_REPOSITORY/$target" "$package" --no-wait-for-sync - done - done -} - -publish_packages "deb" "*.deb" "${CLOUDSMITH_DEB_TARGETS:-}" -publish_packages "rpm" "*.rpm" "${CLOUDSMITH_RPM_TARGETS:-}" -publish_packages "alpine" "*.apk" "${CLOUDSMITH_ALPINE_TARGETS:-}"