Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
13 changes: 0 additions & 13 deletions .dockerignore

This file was deleted.

24 changes: 23 additions & 1 deletion .github/workflows/cloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ name: Cloud

on:
workflow_call:
inputs:
release-tag:
description: Build this release tag and publish ghcr.io/getployz/ployz-cloud:<tag>
type: string
default: ''
secrets:
RAILWAY_TOKEN:
required: false
Expand Down Expand Up @@ -49,6 +54,8 @@ jobs:
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.release-tag }}
- uses: ./.github/actions/setup-cloud
- name: Verify image publication ordering
run: python3 scripts/test-publish-cloud-image.py
Expand All @@ -64,6 +71,8 @@ jobs:
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.release-tag }}
- uses: actions/setup-node@v6
with:
node-version: 22
Expand Down Expand Up @@ -109,11 +118,13 @@ jobs:
permissions:
contents: read
packages: write
needs: sdk
needs: [sdk, static, test]
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.release-tag }}
- uses: ./.github/actions/setup-cloud
- uses: actions/download-artifact@v4
with:
Expand All @@ -139,6 +150,15 @@ jobs:
GH_TOKEN: ${{ github.token }}
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: bash scripts/publish-cloud-image.sh
- name: Publish release image
if: inputs.release-tag != ''
env:
GH_TOKEN: ${{ github.token }}
IMAGE: ghcr.io/getployz/ployz-cloud:${{ inputs.release-tag }}
run: |
printf '%s' "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
docker tag ployz-cloud:ci "$IMAGE"
docker push "$IMAGE"


test:
Expand All @@ -147,6 +167,8 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.release-tag }}
- uses: ./.github/actions/setup-cloud
- uses: actions/download-artifact@v4
with:
Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/release-contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,6 @@ jobs:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.release-tag || github.sha }}
- if: github.event_name != 'pull_request'
run: bash scripts/test-cli-installer.sh
- if: github.event_name != 'pull_request'
run: bash scripts/test-daemon-lifecycle.sh
- uses: actions/download-artifact@v4
with:
pattern: release-linux-*
Expand Down
24 changes: 23 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,21 @@ jobs:
goreleaser-args: release --clean --skip=publish
release-tag: ${{ inputs.tag || github.ref_name }}

cloud:
needs: [tag]
uses: ./.github/workflows/cloud.yml
secrets:
KACHE_S3_READ_ACCESS_KEY_ID: ${{ secrets.KACHE_S3_READ_ACCESS_KEY_ID }}
KACHE_S3_READ_SECRET_ACCESS_KEY: ${{ secrets.KACHE_S3_READ_SECRET_ACCESS_KEY }}
permissions:
contents: read
actions: write
packages: write
with:
release-tag: ${{ inputs.tag || github.ref_name }}

publish:
needs: [tag, artifacts]
needs: [tag, artifacts, cloud]
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -69,3 +82,12 @@ jobs:
- run: bash scripts/publish-github-release.sh "$RELEASE_TAG"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Attach self-host Compose pinned to this release
working-directory: dashboard/self-host
run: |
sed "s|\${PLOYZ_CLOUD_VERSION:?[^}]*}|$RELEASE_TAG|" compose.yml > ployz-cloud-compose.yml
grep -Fq "ghcr.io/getployz/ployz-cloud:$RELEASE_TAG" ployz-cloud-compose.yml
cp .env.example ployz-cloud.env.example
gh release upload "$RELEASE_TAG" ployz-cloud-compose.yml ployz-cloud.env.example
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
*.node
node_modules/
/docs/*
!/docs/adr/
!/docs/adr/**
!/docs/agents/
!/docs/agents/**
!/docs/audits/
!/docs/audits/**
/.claude/*
!/.claude/skills/
/.scratch/
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Workspace

- `core/` owns the engine, CLI, daemon, SDK, native helpers, and releases. Run Cargo there; apply relevant `core/CODING_STANDARDS.md` rules when changing core code.
- `dashboard/` owns the hosted application, backend, workflows, and marketing. Run pnpm there.
- Before designing a feature, read the affected project's `DESIGN.md`. A change that fights one of its bets needs an ADR justifying the exception — or a redesign.
- `dashboard/` owns Ployz Cloud: web, worker, durable workflows, and self-host packaging. Run pnpm there.
- Before designing a feature, read the affected project's `DESIGN.md`. A change that fights one of its bets needs a `DESIGN.md` amendment justifying the exception — or a redesign.
- When changing domain behavior, terminology, or ownership boundaries, follow [docs/agents/domain.md](docs/agents/domain.md).

# Change workflow
Expand Down
2 changes: 1 addition & 1 deletion CONTEXT-MAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ At the boundary:
- Shared runtime bootstrap terms follow the Engine glossary; the Cloud glossary
supplies their product language.

Architectural decisions live in [docs/adr/](docs/adr/).
Design decisions live in each project's `DESIGN.md`; there is no separate ADR log.
39 changes: 0 additions & 39 deletions Dockerfile

This file was deleted.

25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
# Ployz

Two projects in one repository:
Ployz runs containerized Services across a Cluster of your own Docker Machines.
Cloud authors the Deploy Intent and drives the Cluster; the `ployz` CLI operates
the same Cluster over SSH contexts. Neither is a control plane.

| Project | Owns | Start here |
| --- | --- | --- |
| **Core** | Deployment engine, CLI, daemon, Rust SDK/WASM, native helpers, installer and releases | [core/README.md](core/README.md) |
| **Dashboard** | Hosted backend, web UI, durable workflows and marketing | [dashboard/README.md](dashboard/README.md) |
| **Core** | Engine, `ployz` CLI, `ployzd` daemon, internal SDK, installer, releases | [core/README.md](core/README.md) |
| **Dashboard** | Ployz Cloud: web, worker, durable workflows, self-hosting | [dashboard/README.md](dashboard/README.md) |

```text
core/ Cargo workspace and engine tooling
dashboard/ pnpm application
scripts/ Cross-project CI and SDK checks
.github/ Shared workflows
docs/ Shared domain guidance and decisions
core/ Cargo workspace, installer, release scripts
dashboard/ pnpm application; self-host/ holds the Compose deployment
Dockerfile.cloud Cloud runtime image, packaged by the Cloud CI job
scripts/ CI check selection
.github/ Workflows
docs/agents/ Agent guidance for domain changes
```

Run Cargo commands from `core/` and pnpm commands from `dashboard/`.
Dashboard links `@ployz/sdk` directly from `core/crates/ployz-sdk`; its dev and
build commands compile that SDK. Changes across the boundary land together.
Run Cargo commands from `core/` and pnpm commands from `dashboard/`. Dashboard
links `@ployz/sdk` from `core/crates/ployz-sdk`; changes across the boundary land
together. Releases: [core/docs/RELEASE.md](core/docs/RELEASE.md).

Core is licensed under [Apache-2.0](core/LICENSE). Dashboard is licensed under
[AGPL-3.0](dashboard/LICENSE).
Expand Down
2 changes: 1 addition & 1 deletion core/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Architectural bets and their red flags live in [DESIGN.md](DESIGN.md).

**Ployz**:
The product, CLI, and daemon in this repository.
_Avoid_: Uncloud, Ployz2
_Avoid_: Ployz2

**Cluster**:
The product-level mesh as observed from one entry machine. A Cluster is not a globally authoritative entity or complete view.
Expand Down
21 changes: 11 additions & 10 deletions core/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,21 @@ design, not filling a gap.

## Stable promise

From 0.2.0, the first stable release: a daemon keeps working, and can be
upgraded, across every later 0.x release without re-enrolling or reinstalling.
Versions follow semver; 1.0 may break the promise, and says so if it does.
The promise covers what a daemon carries or speaks — the replicated store, the
local Machine record, Machine RPC within `PROTOCOL_MAJOR`, the enrollment
protocol, and the release source. The CLI surface is a client courtesy with
ordinary deprecation, not a guarantee.
From 0.2.0: a daemon keeps working, and can be upgraded, across every later
0.x release without re-enrolling or reinstalling. Versions follow semver; only a
new major line (1.0 first) may break the promise, and it says so. The promise covers what a
daemon carries or speaks — the replicated store and its bodies, the local
Machine record, Machine RPC within `PROTOCOL_MAJOR`, the enrollment protocol,
and the release source. The CLI surface is a client courtesy with ordinary
deprecation, not a guarantee.

Frozen formats evolve **additively with tolerant readers**: rows and bodies only
gain fields; every new field is optional with a default; nothing is renamed or
repurposed; readers ignore unknown fields. Replicated bodies never use
`deny_unknown_fields` — a newer Machine's row must remain readable by an older
one in the same Cluster. Explicit security refusals are the only exception,
and never on replicated bodies: a section holding secret or key material, or
one in the same Cluster. Authored config and unrelated trust boundaries stay
strict. Within frozen formats, explicit security refusals are the only
exception, and never on replicated bodies: a section holding secret or key material, or
a request mode that selects verification, may refuse fields it does not
recognize and fail closed. There are no version gates and no store migrations;
a change that cannot be expressed additively waits for `PROTOCOL_MAJOR` 2.
Expand Down Expand Up @@ -163,7 +164,7 @@ unreachable.
controller and holds no runtime truth. SSH and the Management Capability use the
same Machine RPC connection seam. The daemon serves Machine RPC in-process on its
Management Identity, an iroh key that is not a mesh peer; clients reach it through
the self-hosted Ployz Relay, which sees ciphertext only, or a direct path.
the Ployz-hosted Ployz Relay, which sees ciphertext only, or a direct path.

**Why.** Reusing transport primitives removes a hosted protocol to maintain.
Cloud stores encrypted, Organization-scoped connection candidates associated with
Expand Down
47 changes: 39 additions & 8 deletions core/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Ployz Core

Deployment engine, CLI, daemon, and SDK for a cluster of Docker machines.
The hosted application lives in [dashboard/](../dashboard/README.md).
Deployment engine, `ployz` CLI, `ployzd` daemon, and internal SDK for a Cluster
of Docker Machines. Ployz Cloud lives in [dashboard/](../dashboard/README.md).

## Install

Expand All @@ -10,7 +10,31 @@ curl -fsSL https://ployz.sh | sh
brew install getployz/ployz/ployz
```

Release process: [docs/RELEASE.md](docs/RELEASE.md).
Releases and channels: [docs/RELEASE.md](docs/RELEASE.md). What a 0.x daemon
keeps working across: [DESIGN.md](DESIGN.md#stable-promise).

## CLI

The CLI operates a Cluster over SSH contexts. Cloud authors and deploys
Services; the CLI has no deploy, build, or image command.

```text
ployz
├── cloud enroll
├── machine init · add · ls · inspect · logs · rename · rm · rtt · update
│ upgrade [inspect] · build-cache-clear
├── service ls · inspect · logs · exec · scale · start · stop · rm
├── volume create · ls · inspect · rm
├── ingress config · deploy · logs
├── project ls · rm
├── ctx ls · show · use · rm · connection
├── proxy
├── ps
├── version
└── completion
```

`crates/ployz/tests/cli_shape.rs` pins this tree; there are no aliases.

`ployz machine add` saves subnet assignments beside its configuration file in
`<config-stem>.enrollment/` before publishing or joining. Commands using that
Expand All @@ -21,17 +45,28 @@ cannot establish a shared scope. Different computers, configuration stores, and
Cloud remain independent operators and can still choose overlapping subnets;
there is no automatic reclamation, cross-store synchronization, or subnet repair.

`ployzd` issues https Ingress certificates from the ACME directory named by
`PLOYZ_ACME_DIRECTORY` in the daemon environment:

| `PLOYZ_ACME_DIRECTORY` | Certificate issuance |
| --- | --- |
| unset | Let's Encrypt production |
| a URL | that ACME directory |
| empty | off |

## Workspace

Run Cargo and engine script commands from `core/`.

- `crates/ployz-core`: domain and wire contracts shared by both binaries
- `crates/ployz`: CLI for Linux, macOS, and Windows through WSL
- `crates/ployz-build`: BuildKit execution for one captured Build
- `crates/ployz-config-wasm`: the SDK's config ABI, compiled to WASM
- `crates/ployz-sdk`: internal workspace package `@ployz/sdk`, never published. napi serves Machine RPC; config runs on the `ployz-config-wasm` build in Node and the browser. Its TypeScript declarations are derived from the Rust wire types by `cargo test -p ployz --test sdk_payloads`
- `crates/ployzd`: Linux-only daemon
- `crates/ployz-testkit`: unpublished support crate used only by tests

Each release archive ships one binary. The remote management transport (iroh, via the self-hosted Ployz Relay) is in-process in `ployz`, `ployzd`, and the SDK; there is no helper process.
Each release archive ships one binary. The remote management transport (iroh, via the Ployz-hosted Ployz Relay) is in-process in `ployz`, `ployzd`, and the SDK; there is no helper process.

Cloud builds each Git Service on one Build Machine. The Service's build settings
select a Dockerfile or Railpack; a failed recipe never falls back to another.
Expand All @@ -54,10 +89,6 @@ variant's manifest, configuration and layers present in its containerd store: a
tag, an image index, or a listed-but-absent platform is not content, so a peer
that pulled one platform is never the source for another.

The [prototype findings](https://github.com/getployz/ployz2/blob/c3ca5519a4607256ffb28052d77a1c7d89f1bbe1/prototypes/railpack-transfer/FINDINGS.md)
preserve the evidence for this assembly approach. They used shipped beta binaries
and emulated ARM64; they do not qualify this implementation or native ARM64.

Service variables become build variables without changing runtime values; a
Railpack build command is passed as `RAILPACK_BUILD_CMD`. Values travel as
private secret mounts. Docker ignore patterns and Railpack's configured
Expand Down
2 changes: 0 additions & 2 deletions core/crates/ployz/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ pub mod env {
pub const CONNECT: &str = "PLOYZ_CONNECT";
pub const CONTEXT: &str = "PLOYZ_CONTEXT";
pub const DAEMON_VERSION: &str = "PLOYZ_DAEMON_VERSION";
pub const HEALTH_MONITOR_PERIOD: &str = "PLOYZ_HEALTH_MONITOR_PERIOD";
pub const SSH_CONTROL_PERSIST: &str = "PLOYZ_SSH_CONTROL_PERSIST";
}

#[must_use]
Expand Down
6 changes: 1 addition & 5 deletions core/crates/ployz/src/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,11 +303,7 @@ pub(crate) fn ssh_control_args(control_path: Option<&Path>) -> Vec<String> {
"-o".into(),
format!("ControlPath={}", path.display()),
"-o".into(),
format!(
"ControlPersist={}",
std::env::var(crate::cli::env::SSH_CONTROL_PERSIST)
.unwrap_or_else(|_| "10m".into())
),
"ControlPersist=10m".into(),
]);
}
args
Expand Down
Loading
Loading