Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.git
.github
.vscode
.worktrees
bin
dist
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- run: docker buildx build --target test .
34 changes: 34 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Release

on:
release:
types: [published]

permissions:
contents: write

jobs:
binaries:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build
run: >-
docker buildx build
--platform darwin/arm64,darwin/amd64,linux/arm64,linux/amd64
--build-arg VERSION="${TAG#v}"
--output dist .
env:
TAG: ${{ github.event.release.tag_name }}
# Raw binaries named ceryx_<os>_<arch>: github.com/marcosnils/bin picks the asset
# by OS/arch in the name and strips them to install it as "ceryx". It skips .txt.
- name: Upload
run: |
mkdir assets
for f in dist/*/ceryx; do cp "$f" "assets/ceryx_$(basename "$(dirname "$f")")"; done
(cd assets && sha256sum ceryx_* > checksums.txt)
gh release upload "$TAG" assets/* --clobber
env:
TAG: ${{ github.event.release.tag_name }}
GH_TOKEN: ${{ github.token }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.worktrees/
/bin/
/dist/
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["golang.go"]
}
21 changes: 21 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"window.title": "ceryx-cli",
// Editor support only; build and test via the Docker tasks in tasks.json.
"[go]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
// ./bin/ceryx from the "build" task is on PATH in VS Code terminals.
"terminal.integrated.env.osx": {
"PATH": "${workspaceFolder}/bin:${env:PATH}"
},
"terminal.integrated.env.linux": {
"PATH": "${workspaceFolder}/bin:${env:PATH}"
},
"search.exclude": {
"bin/**": true,
"dist/**": true
}
}
30 changes: 30 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
// Everything runs through docker buildx, so only Docker is needed on the host.
"version": "2.0.0",
"tasks": [
{
"label": "build",
"detail": "Build ./bin/ceryx for this machine (on PATH in VS Code terminals)",
"type": "shell",
"command": "docker buildx build --platform \"$(uname -s | tr A-Z a-z)/$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')\" --output bin .",
"group": { "kind": "build", "isDefault": true },
"problemMatcher": []
},
{
"label": "build: release binaries",
"detail": "Build dist/<os>_<arch>/ceryx for macOS and Linux",
"type": "shell",
"command": "docker buildx build --platform darwin/arm64,darwin/amd64,linux/arm64,linux/amd64 --output dist .",
"group": "build",
"problemMatcher": []
},
{
"label": "test",
"detail": "go vet + go test in Docker",
"type": "shell",
"command": "docker buildx build --target test --no-cache-filter test .",
"group": { "kind": "test", "isDefault": true },
"problemMatcher": []
}
]
}
23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# syntax=docker/dockerfile:1

# Builds and tests run here so the host needs only Docker. The build stage runs on the
# host platform and cross-compiles for --platform (e.g. darwin/arm64).
FROM --platform=$BUILDPLATFORM golang:1.27-alpine AS base
WORKDIR /src
ENV CGO_ENABLED=0
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .

FROM base AS test
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \
go vet ./... && go test ./...

FROM base AS build
ARG TARGETOS TARGETARCH VERSION=dev
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \
GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-s -w -X main.version=$VERSION" -o /out/ceryx ./cmd/ceryx

# Export with: docker buildx build --platform darwin/arm64 --output bin .
FROM scratch AS binary
COPY --from=build /out/ceryx /
233 changes: 231 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,231 @@
# ceryx-cli
Command line client for the Ceryx API.
# Ceryx CLI

Command line client for the [Ceryx](https://github.com/withlogicco/ceryx) API.

## Install

### Install script

Installs the latest release to `/usr/local/bin` (uses `sudo` if needed):

```sh
curl -fsSL https://raw.githubusercontent.com/withlogicco/ceryx-cli/main/install.sh | sh
```

Set `CERYX_VERSION=v0.1.0` for a specific release or `INSTALL_DIR=~/.local/bin` for another directory, e.g. `curl -fsSL … | INSTALL_DIR=~/.local/bin sh`.

### Download from the latest release

Binaries for macOS and Linux (arm64, amd64) are on the [releases](https://github.com/withlogicco/ceryx-cli/releases) page as `ceryx_<os>_<arch>`:

```sh
os=$(uname -s | tr A-Z a-z); arch=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
curl -fsSL -o ceryx "https://github.com/withlogicco/ceryx-cli/releases/latest/download/ceryx_${os}_${arch}"
chmod +x ceryx && sudo mv ceryx /usr/local/bin/
```

### Install with Go

```sh
go install github.com/withlogicco/ceryx-cli/cmd/ceryx@latest
```

### Install with bin

With [bin](https://github.com/marcosnils/bin):

```sh
bin install github.com/withlogicco/ceryx-cli
```

## Configure

The CLI talks to `http://localhost:8080` by default. Point it elsewhere with `--api-url` or `CERYX_API_URL`:

```sh
export CERYX_API_URL=ceryx:8080
```

Responses are the API's JSON on stdout; errors go to stderr. Exit codes: `0` ok, `1` error, `2` usage, `3` not found.

## Routes

### Create a route

Proxy `app.example.com` to the `app` service on port `8000`:

```console
$ ceryx routes set app.example.com app:8000
{
"settings": {
"certificate_path": null,
"enforce_https": false,
"key_path": null,
"mode": "proxy",
"mtls_require_client_certificate": false,
"mtls_trusted_ca": null,
"mtls_verify_depth": null
},
"source": "app.example.com",
"target": "app:8000"
}
```

`set` creates the route or replaces it whole: settings you leave out get server defaults.

### Patch a route

Turn on HTTPS enforcement and keep everything else:

```console
$ ceryx routes patch app.example.com --enforce-https
{
"settings": {
"certificate_path": null,
"enforce_https": true,
"key_path": null,
"mode": "proxy",
"mtls_require_client_certificate": false,
"mtls_trusted_ca": null,
"mtls_verify_depth": null
},
"source": "app.example.com",
"target": "app:8000"
}
```

`patch` changes only the flags you pass and fails if the route does not exist:

```console
$ ceryx routes patch typo.example.com --enforce-https
error: No route found for typo.example.com (HTTP 404)
$ echo $?
3
```

Pass `--certificate-path ""` or `--key-path ""` to clear a value.

### Create a redirect

```console
$ ceryx routes set old.example.com https://app.example.com --mode redirect
{
"settings": {
"certificate_path": null,
"enforce_https": false,
"key_path": null,
"mode": "redirect",
"mtls_require_client_certificate": false,
"mtls_trusted_ca": null,
"mtls_verify_depth": null
},
"source": "old.example.com",
"target": "https://app.example.com"
}
```

### List routes

```console
$ ceryx routes list
[
{
"settings": {
"certificate_path": null,
"enforce_https": true,
"key_path": null,
"mode": "proxy",
"mtls_require_client_certificate": false,
"mtls_trusted_ca": null,
"mtls_verify_depth": null
},
"source": "app.example.com",
"target": "app:8000"
},
{
"settings": {
"certificate_path": null,
"enforce_https": false,
"key_path": null,
"mode": "redirect",
"mtls_require_client_certificate": false,
"mtls_trusted_ca": null,
"mtls_verify_depth": null
},
"source": "old.example.com",
"target": "https://app.example.com"
}
]
```

### Delete a route

Deleting prints nothing and succeeds even if the route is already gone:

```console
$ ceryx routes delete old.example.com
$ ceryx routes get old.example.com
error: No route found for old.example.com (HTTP 404)
$ echo $?
3
```

## Certificates

### Upload a certificate

```console
$ ceryx certs set '*.example.com' --cert cert.pem --key key.pem --meta '{"issuer":"internal"}'
{
"domain": "*.example.com",
"has_certificate": true,
"has_chain": false,
"meta": {
"issued_at": "2026-11-10T22:13:33Z",
"issuer": "internal",
"stored_at": 1794348813
}
}
```

Add `--chain chain.pem` for intermediates. Any file flag accepts `-` to read stdin.

### See which certificate a domain gets

```console
$ ceryx certs get app.example.com
{
"domain": "app.example.com",
"has_certificate": true,
"has_chain": false,
"meta": {
"issued_at": "2026-11-10T22:13:33Z",
"issuer": "internal",
"stored_at": 1794348813
}
}
```

### Patch, list and delete

```console
$ ceryx certs patch '*.example.com' --meta '{"issuer":"acme"}'
$ ceryx certs list
$ ceryx certs delete '*.example.com'
```

`certs patch` changes a stored certificate without uploading it again. `--cert` and `--key` go together, and a new certificate drops the old chain unless you pass `--chain`.

Run `ceryx <command> --help` for all flags.

## Development

Builds and tests run in Docker, so only Docker with buildx is needed:

```sh
docker buildx build --target test . # go vet + go test
docker buildx build --platform darwin/arm64 --output bin . # bin/ceryx for an Apple Silicon Mac
docker buildx build --platform darwin/arm64,linux/amd64 --output dist . # dist/<os>_<arch>/ceryx
```

In VS Code these are the `build`, `build: release binaries` and `test` tasks; `bin/` is on the `PATH` of VS Code terminals.
Loading
Loading