Skip to content
Merged
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
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
SHELL := /usr/bin/env bash
CONTROL_IMAGE ?= site-control:local

.PHONY: help install test test-db test-db-down test-supabase test-oss console homepage-check image chart-lint chart-render benchmark quickstart quickstart-status quickstart-access quickstart-token quickstart-clean standalone-install standalone-smoke standalone-uninstall
.PHONY: help install test test-db test-db-down test-supabase test-oss console homepage-check image chart-lint chart-render benchmark quickstart-doctor quickstart quickstart-status quickstart-access quickstart-token quickstart-clean standalone-install standalone-smoke standalone-uninstall

# Generated from the targets themselves rather than kept as a second list. The
# hand-written listing this replaces had already lost `help`, and nothing could
Expand Down Expand Up @@ -79,6 +79,9 @@ chart-render: ## — render the self-contained Helm package
benchmark: ## — run the fail-closed deterministic benchmark profile
uv run --locked --extra dev python scripts/run-benchmark.py --profile contract

quickstart-doctor: ## — check every host prerequisite and local port before the trial
scripts/quickstart-kubeadm.sh doctor

quickstart: ## — build and prove Site on a disposable kubeadm VM
scripts/quickstart-kubeadm.sh up

Expand Down
48 changes: 38 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,31 @@ environments, not a public hosting platform — [Known limitations](#known-limit

## See the proof locally

The fastest path is deliberately end to end. With Lima, Docker, `kubectl`, Helm, `uv`,
and Python 3.12+ installed, one command creates a disposable single-node Kubernetes
### Host prerequisites

The supported source-checkout trial runs on macOS or Linux with hardware virtualization,
outbound HTTPS, and enough capacity for a Lima VM configured with **6 CPUs, 8 GiB RAM,
and a 40 GiB sparse disk**. Install Git, Lima, Docker, `kubectl`, Helm, `curl`, `uv`,
`lsof`, and Python 3.12+. Docker must be running, not merely installed.

On macOS with Homebrew, the command-line dependencies are:

```bash
brew install git lima kubectl helm uv python lsof
brew install --cask docker
open -a Docker # wait until Docker reports that it is running
```

Linux users should install [Docker Engine](https://docs.docker.com/engine/install/),
[Lima](https://lima-vm.io/docs/installation/),
[`kubectl`](https://kubernetes.io/docs/tasks/tools/), and
[Helm](https://helm.sh/docs/intro/install/) using their distribution's supported path.
Run `make quickstart-doctor` at any time for an explicit dependency, Docker-daemon, Python,
and local-port check. The first uncached run downloads a VM and several container images;
depending on the network it can take 5–25 minutes and needs ports 18090–18098 and 18447.

The fastest path is deliberately end to end. With the prerequisites above, one command
creates a disposable single-node Kubernetes
cluster with `kubeadm`, builds the image from this checkout, installs Site and Prometheus,
deploys the included HTML example, and refuses to pass until both server-side HTTP
verification and the administrative observability APIs return real evidence:
Expand All @@ -46,9 +69,10 @@ Site kubeadm quickstart passed
observability: available
```

See the same application, dependency health, verification result, and metrics in the
management console. Keep the first command running; it opens the browser automatically
when possible:
`make quickstart` finishes after it has printed the verified public URL; it does not need
to stay open. To see the same application, dependency health, verification result, and
metrics in the management console, use two terminals. Keep terminal 1 running because it
owns the local console tunnel and opens the browser automatically when possible:

```bash
# terminal 1
Expand All @@ -58,16 +82,20 @@ make quickstart-access
make quickstart-token
```

Paste the second command's value into **Admin token** and choose **Enter the console**.
The included application is visible immediately under **Applications** as
Paste the second command's value into **管理员 token / Admin token** and choose
**进入控制台 / Enter the console**. Only paste this secret into the local console URL
printed by terminal 1; do not commit or share it. The included application is visible
immediately under **应用 / Applications** as
`local/local/hello-site`; its HTTP verification and digests are in **Run details**, and
its **Open** action reaches the browser-visible address. CPU/memory samples are under
**Monitor → Single application**. Use **Deploy application** to create or update an
application from an existing container image; choose **Public** to receive a browser URL,
or **Internal** when no public URL should exist. The token remains in
a Kubernetes Secret and is never written into this checkout or browser storage. Inspect
the running objects with `make quickstart-status`; remove only the `site-quickstart` Lima
VM and repository-local kubeconfig with `make quickstart-clean`.
the running objects with `make quickstart-status`. Pressing Ctrl-C in terminal 1 only
closes console access; the application and cluster keep running. `make quickstart-clean`
permanently deletes the disposable `site-quickstart` VM, all applications inside it, and
the repository-local kubeconfig; it does not touch another cluster or repository.

This trial harness is entirely owned by this repository: it uses Lima's default network,
bootstraps Kubernetes directly with kubeadm, and builds the control image locally. No
Expand Down Expand Up @@ -125,7 +153,7 @@ git clone https://github.com/hullwork/site.git
cd site
uv sync --locked --extra dev
make test-db # starts a throwaway PostgreSQL on 127.0.0.1:55439
make test # 1030 tests
make test # 1031 tests
make test-db-down
```

Expand Down
14 changes: 11 additions & 3 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,25 @@ workflow, plus a separate `site-values-VERSION.yaml` digest-pinned override and

## Local kubeadm environment

Prerequisites for the complete local trial: Lima, Docker, kubectl, Helm, uv, and
Python 3.12+. The repository creates one disposable VM and bootstraps Kubernetes
with kubeadm; it does not use a shared cluster or another checkout.
Prerequisites for the complete local trial: Lima, a running Docker daemon, kubectl, Helm,
curl, uv, lsof, and Python 3.12+. Run `make quickstart-doctor` for the executable preflight.
The VM allocates 6 CPUs, 8 GiB RAM, and a 40 GiB sparse disk; outbound HTTPS and host
ports 18090–18098 plus 18447 are required. The repository creates one disposable VM and
bootstraps Kubernetes with kubeadm; it does not use a shared cluster or another checkout.

```bash
make quickstart-doctor
make quickstart
make quickstart-status
make quickstart-access
make quickstart-token # run in a second terminal, then log in
make quickstart-clean
```

`make quickstart` exits after verification. `make quickstart-access` must remain running
while the console is in use; Ctrl-C stops only that tunnel. `make quickstart-clean`
permanently removes the disposable VM and all applications stored inside it.

The quickstart uses a single node to minimize first-run time. Production cluster
provisioning, upgrades, backup, and disaster recovery are deliberately not
implemented in this repository.
Expand Down
15 changes: 13 additions & 2 deletions docs/STANDALONE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,35 @@ For a first source-checkout trial, use the disposable kubeadm workflow from the
root:

```bash
make quickstart-doctor # checks commands, Docker, Python, and fixed host ports
make quickstart
make quickstart-access # terminal 1: opens and serves the console
make quickstart-token # terminal 2: paste into the Admin token field
make quickstart-token # terminal 2: paste into 管理员 token / Admin token
make quickstart-clean # deletes only the site-quickstart Lima VM and local state
```

The trial VM is configured for 6 CPUs, 8 GiB RAM, and a 40 GiB sparse disk. It needs
outbound HTTPS while the Ubuntu, Kubernetes, Cilium, and workload images download, plus
host ports 18090–18098 and 18447. A first uncached install may take 5–25 minutes.
`make quickstart` exits after printing the proof. Keep `make quickstart-access` running;
pressing Ctrl-C there closes only the console tunnel, not the cluster. In the default
Chinese UI choose **进入控制台**; in English choose **Enter the console**.

That path creates a single-node Lima VM, installs Kubernetes 1.36 with kubeadm, installs
a pinned Cilium release, and builds the control image locally. The Helm release supplies
its own local storage provisioner, enables Prometheus, deploys `examples/hello-site`, and
gates success on the deployment's HTTP status/body digest, a host-openable public URL with
the same body digest, plus the admin metrics and deployment-snapshot APIs. It needs no
pre-created Lima network, external image, or other repository. After login, the included
application appears under **Applications** as
application appears under **应用 / Applications** as
`local/local/hello-site`, including an **Open** action; use **Monitor → Single application**
for its resource samples. **Deploy application** creates or updates from an existing
container image: public applications receive a browser URL, while internal applications
are explicitly labeled as having no public URL.

`make quickstart-clean` is intentionally destructive only to the disposable trial: it
deletes that VM, every application inside it, and this checkout's local kubeconfig.

The lower-level path installs Site into the Kubernetes context selected by `kubectl`.
Review the current context before continuing:

Expand Down
81 changes: 71 additions & 10 deletions scripts/quickstart-kubeadm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,18 @@ started_at=$SECONDS

usage() {
cat <<'EOF'
Usage: scripts/quickstart-kubeadm.sh <up|status|access|token|clean>
Usage: scripts/quickstart-kubeadm.sh <doctor|up|status|access|token|clean>

doctor Check every host dependency and fixed local port before installation.
up Create one disposable Lima VM, bootstrap Kubernetes with kubeadm,
build Site from this checkout, deploy the example, and prove it.
status Show the node, Site workloads, example, verification, and monitoring.
access Forward and open the console at http://127.0.0.1:18091/console/.
token Print the disposable trial's local admin token for console login.
clean Delete only the site-quickstart VM and this checkout's .site-kubeadm state.

Prerequisites: Lima, Docker, kubectl, Helm, curl, uv, and Python 3.12+.
Prerequisites: Lima, a running Docker daemon, kubectl, Helm, curl, uv, lsof,
and Python 3.12+; the VM is configured for 6 CPUs, 8 GiB RAM, and a 40 GiB disk.
No other repository, pre-created Lima network, or published Site image is used.
EOF
}
Expand All @@ -44,10 +46,67 @@ require_command() {
}
}

check_prerequisites() {
local command_name missing=()
for command_name in limactl docker kubectl helm curl uv python3 lsof; do
command -v "$command_name" >/dev/null 2>&1 || missing+=("$command_name")
done
if ((${#missing[@]})); then
printf 'quickstart prerequisites are missing: %s\n' "${missing[*]}" >&2
echo 'Install guide: https://github.com/hullwork/site#host-prerequisites' >&2
exit 2
fi

python3 - <<'PY' || exit 2
import sys
if sys.version_info < (3, 12):
raise SystemExit(
f"Python 3.12+ is required; found {sys.version.split()[0]}. "
"See https://www.python.org/downloads/"
)
PY

if ! docker info >/dev/null 2>&1; then
echo 'Docker is installed, but its daemon is not reachable.' >&2
echo 'Start Docker Desktop (macOS) or the Docker service (Linux), then rerun make quickstart.' >&2
exit 2
fi

# These are the complete fixed-port contract of the reference VM. Checking
# all of them now avoids a Lima forwarding failure several minutes later.
if ! vm_running; then
local port busy=()
for port in "$api_port" "$console_port" 18090 18092 18093 18094 18095 18096 18097 18098; do
if lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then
busy+=("$port")
fi
done
if ((${#busy[@]})); then
printf 'quickstart host ports are already in use: %s\n' "${busy[*]}" >&2
echo 'Stop the process or older Site trial using those ports, then rerun make quickstart.' >&2
exit 2
fi
fi

cat <<'EOF'
Quickstart doctor passed
commands: available
Python: 3.12 or newer
Docker daemon: reachable
local ports: available or owned by this trial
VM allocation: 6 CPUs, 8 GiB RAM, 40 GiB sparse disk
network: outbound HTTPS is required while Lima, Kubernetes, Cilium, and workload images download
EOF
}

vm_exists() {
limactl list --quiet | grep -Fxq "$vm"
}

vm_running() {
vm_exists && limactl list "$vm" --format '{{.Status}}' 2>/dev/null | grep -Fxq Running
}

kube() {
kubectl --kubeconfig "$kubeconfig" --context "$context" "$@"
}
Expand Down Expand Up @@ -288,10 +347,11 @@ Site kubeadm quickstart passed
observability: available

Next:
make quickstart-access # http://127.0.0.1:$console_port/console/
make quickstart-token # paste into the console login form
terminal 1: make quickstart-access # keep it running; opens http://127.0.0.1:$console_port/console/
terminal 2: make quickstart-token # paste into "管理员 token / Admin token"
choose "进入控制台 / Enter the console"
make quickstart-status
make quickstart-clean
make quickstart-clean # permanently deletes this disposable VM and its applications
EOF
}

Expand Down Expand Up @@ -333,9 +393,11 @@ $opener

First login:
1. In another terminal, run: make quickstart-token
2. Paste that value into "Admin token", then choose "Enter the console".
2. Paste that value into "管理员 token / Admin token".
3. Choose "进入控制台 / Enter the console".

The example application is under "Applications" as local/local/hello-site.
The example application is under "应用 / Applications" as local/local/hello-site.
Its "打开 / Open" action reaches the public URL printed by make quickstart.
Press Ctrl-C here to stop console access without stopping the cluster.
EOF
wait "$pid" || true
Expand All @@ -360,10 +422,9 @@ clean() {
}

case "$action" in
doctor) check_prerequisites ;;
up)
for command_name in limactl docker kubectl helm curl uv python3 lsof; do
require_command "$command_name"
done
check_prerequisites
create_cluster
prove_site
;;
Expand Down
20 changes: 19 additions & 1 deletion tests/test_helm_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_quickstart_lima_template_needs_no_precreated_network(self) -> None:

def test_makefile_exposes_the_complete_kubeadm_trial_lifecycle(self) -> None:
makefile = (ROOT / "Makefile").read_text(encoding="utf-8")
for target in ("quickstart", "quickstart-status", "quickstart-access", "quickstart-token", "quickstart-clean"):
for target in ("quickstart-doctor", "quickstart", "quickstart-status", "quickstart-access", "quickstart-token", "quickstart-clean"):
self.assertRegex(makefile, rf"(?m)^{target}:")
script = (ROOT / "scripts" / "quickstart-kubeadm.sh").read_text(encoding="utf-8")
self.assertIn('open "$url"', script)
Expand All @@ -62,6 +62,24 @@ def test_makefile_exposes_the_complete_kubeadm_trial_lifecycle(self) -> None:
self.assertIn('deployment/sites-prometheus', script[dependency_wait:restart])
self.assertIn('--timeout=15m', script[dependency_wait:restart])

def test_quickstart_explains_every_newcomer_handoff(self) -> None:
script = (ROOT / "scripts" / "quickstart-kubeadm.sh").read_text(encoding="utf-8")
self.assertIn("Quickstart doctor passed", script)
self.assertIn("Docker is installed, but its daemon is not reachable", script)
self.assertIn("Python 3.12+ is required", script)
self.assertIn("管理员 token / Admin token", script)
self.assertIn("进入控制台 / Enter the console", script)
self.assertIn("without stopping the cluster", script)

readme = (ROOT / "README.md").read_text(encoding="utf-8")
for fact in (
"make quickstart-doctor",
"6 CPUs, 8 GiB RAM",
"5–25 minutes",
"permanently deletes",
):
self.assertIn(fact, readme)

def test_newcomer_docs_name_kubeadm_and_not_the_kind_tool(self) -> None:
docs = "\n".join(
(ROOT / path).read_text(encoding="utf-8")
Expand Down
7 changes: 5 additions & 2 deletions website/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ <h2 id="features-title">Small surface. Hard boundaries.<br />Ridiculous leverage
</section>

<section class="quickstart shell" id="quickstart">
<div class="quick-copy reveal"><div class="eyebrow"><span></span> Kubeadm-first quickstart</div><h2>One command.<br />A real cluster.<br />A live URL.</h2><p>The repository-owned trial creates a disposable Lima VM, bootstraps Kubernetes with kubeadm, installs Cilium and Site, deploys the included app, and refuses to pass without measured HTTP proof.</p><div class="requirements">Lima · Docker · kubectl · Helm · uv · Python 3.12+</div></div>
<div class="quick-copy reveal"><div class="eyebrow"><span></span> Kubeadm-first quickstart</div><h2>One command.<br />A real cluster.<br />A live URL.</h2><p>The repository-owned trial creates a disposable Lima VM, bootstraps Kubernetes with kubeadm, installs Cilium and Site, deploys the included app, and refuses to pass without measured HTTP proof.</p><div class="requirements">Git · Lima · running Docker · kubectl · Helm · uv · Python 3.12+<br />6 CPU · 8 GiB RAM · 40 GiB sparse disk · outbound HTTPS</div></div>
<div class="terminal reveal delay-one">
<div class="terminal-top"><div><i></i><i></i><i></i></div><span>site — zsh</span><button type="button" data-copy="# clone-and-run">Copy</button></div>
<pre id="clone-and-run"><code><span>$</span> git clone https://github.com/hullwork/site.git
Expand All @@ -155,7 +155,10 @@ <h2 id="features-title">Small surface. Hard boundaries.<br />Ridiculous leverage
phase: <strong>Running</strong>
HTTP verification: <strong>200</strong>
public URL: <u>http://127.0.0.1:18090</u>
public URL body: <strong>matches verification digest</strong></code></pre>
public URL body: <strong>matches verification digest</strong>

<span>$</span> make quickstart-access <b># keep terminal 1 open</b>
<span>$</span> make quickstart-token <b># run in terminal 2</b></code></pre>
<div class="terminal-status"><span><i></i> kubeadm cluster ready</span><span>proof, not promises</span></div>
</div>
</section>
Expand Down
Loading