Releases: l3wi/mc2
Releases · l3wi/mc2
Release list
0.1.0
Release Notes
Simpler SSH (ssh: true) + mc2 up prints SSH endpoints
ssh:accepts a boolean flag:ssh: trueenables the host-side SSH
front end with all defaults and authenticates with every registered key
(mc2 ssh key add …); the long map form still works and now treats an empty
authorizedKeysas "all registered keys" (fails closed if none).mc2 upprints declared SSH endpoints after the apply result: per
service the listenerbind:port(orautountil reconcile) and the
ingress.tcpentrypoint when a TCP route targets the service.
mc2 network + fabric→network rename
mc2 networkreplacesmc2 fabricand adds network summaries:- no args → table of every network (default + named) with stacks, services, instance counts.
mc2 network <name>→ that network's member instances and their expose + host ports.mc2 network <stack>/<service>/<ordinal>→ one instance's observed exposes/edges.- Backed by
GET /v1/networks(membership) andGET /v1/instances/{id}/network.
- "fabric" is gone from the language:
DesiredFabric→DesiredNetwork,
FabricTable→NetworkTable,build_network_desired,instance_fabric
table→instance_network(migration 008),/v1/instances/{id}/network,
mc2 network; docs/examples updated (03-service-fabric→03-networks,
smoke-fabric→smoke-networks). The east–west layer is now just
"networks":exposelisteners + default-allow +svc.<network>.svc.mc2DNS.
Compose parity round 2 — environment, string command, full healthcheck, depends_on, per-replica ports
environment:replacesenv:as the guest env key (map orKEY=VALUE
list). The oldenv:key is rejected by the canonical parser. An explicit
environment:entry overrides a collidingsecrets[].env(env wins; the
secret is dropped, not decrypted).commandstring form:command: 'echo "hi there"'is split shell-like
(quotes + backslash escapes) into argv; list form still works.healthcheckfull field set:timeout,retries(default 3),
start_period,disable— alongsidetestandinterval. The node runs
probes with a per-probe timeout, ignores failures duringstart_period, and
only marks the service unhealthy afterretriesconsecutive failures.depends_onstartup ordering: list form ([db]) or map form
({db: {condition: service_healthy}}).service_healthywaits until the
dependency's healthcheck passes (new persistedinstances.healthysignal).
Cross-stack refs, unknown conditions, and cycles are rejected at parse time.scale+ published ports: per-replica host-port allocation — a fixed
published: Pbecomes the blockP, P+1, …, P+N-1; target-only ports get a
distinct auto port per replica. Stable across re-applies.- Cross-service published-host-port conflicts are now rejected at apply (400);
published host ports are effectively unique server-wide. - Docs: new Environment variables vs secrets guide
clarifies the direct-environmentvs host-gatedsecrets[].envmechanisms
and the server-wide (not per-stack) scoping of the secret store.
mc2 exec + mc2 logs (see inside VMs)
mc2 exec <instance> <cmd…>runs a command inside the instance's
sandbox: captures stdout/stderr and mirrors them, then exits with the
command's exit code.POST /v1/instances/{id}/exec.mc2 logs <instance> [--tail N]prints recent sandbox logs
(runtime/exec/kernel) via the SDK's log registry (read_logs).
GET /v1/instances/{id}/logs.- Instances are addressed by UUID or
<stack>/<service>/<ordinal>; the
runtime is now created once inmc2-server::runand shared between the node
loop and the REST handlers. NodeRuntimegainedexec_with_outputreturning{exit_code, stdout, stderr}(healthexec_commanddelegates to it).mc2 logs --followstreams new log entries as they arrive (SSE:
GET /v1/instances/{id}/logs?follow=true;--tail N --followshows the
last N then continues from that cursor).mc2 execstdin: piped stdin is forwarded to the sandbox
(echo hi | mc2 exec demo/web/0 cat); interactive TTY stdin is untouched.
exec_with_outputfeeds stdin via the SDK'sstdin_bytes.
Compose-faithful networking (ports north-south, expose listeners, server-wide networks)
ports= north-south, compose-faithful:"5000:3001", target-only
"3001"(→ auto host port, resolved at apply and persisted), and a
hostname sugar"mcp.example.com:3000"(→ TLS ingress route for that
hostname,leresolver).ingressbackends resolve auto-allocated host
ports from the stored spec.expose= internal listeners, compose list form (expose: [5432]/
["5432"]) or map form — drives the fabric dataplane.- Server-wide named networks: a service's
networks: [name]joins a
server-wide network (no top-level declaration needed). Services in different
stacks on the same named network reach each other (default-allow) via
svc.<network>.svc.mc2DNS. The stack's implicit default network keeps
svc.<stack>.svc.mc2. - Fabric edges are network-scoped (cross-stack peers on a shared network are
reachable); splices stay shared per (service, port) with round-robin. - Documented limitation: exposed guest ports are effectively unique
server-wide (L4 splice on the shared loopback — same-port across different
networks can't be isolated, unlike Docker's kernel bridges).
Compose-style stack config (canonical parser, k8s wrapper removed)
- Stack YAML is now Docker Compose-shaped with a canonical parser:
#[serde(deny_unknown_fields)]on the schema, so any unrecognized key is
rejected (unknown field …) — no compatibility layer for the old k8s form. - Top-level
name:replacesapiVersion/kind/metadata.name(filled from
the file stem when missing). - Field renames:
replicas→scale,restartPolicy→restart
(no|on-failure|always|unless-stopped),resources→cpus+mem_limit
(accepts512m/1g/bytes),health→healthcheck(composetest/interval
withCMD/CMD-SHELLstripping),volumes[].mount→target. portsaccepts compose syntax:"18080:8000","ip:18080:8000",
"18080:8000/tcp", and long{target, published, protocol}. Target-only
(auto host port) is rejected with a clear message for now. Ports always bind
loopback.envaccepts a map or aKEY=VALUElist. Stackmetadata.labelsdropped.- Old k8s-style keys (
apiVersion,kind,metadata,replicas,
resources,restartPolicy,health,allow,mount,host/guest
ports) are rejected, not silently ignored. - Stored
spec_jsonnow uses the compose field names; pre-release, delete the
data dir (schema edited in place).
Fabric shared-backend splices (full-mesh round-robin)
- The service fabric is now a full mesh within the stack (default allow):
every service reaches everyexposed port of every peer service with no
declaration required — the L4 splice +*.svc.mc2DNS stay, only the policy
changed from default-deny to default-allow. - Removed the per-service
allow:field (hard cut, docker-pure); the
fabric no longer needs explicit client edges.exposeremains the
reachability gate (a service with noexposeis reachable by nothing). - Fabric splices are now shared per exposed (service, port) (was: one per
consumer), so multiple consumers and scaled clients no longer collide on the
host loopback. Each connection round-robins across Ready replicas. - Scheduler fabric affinity now co-locates with exposing peers (cross-node
splices remain unsupported). - Exposed guest ports are effectively unique server-wide: each stack
validates uniqueness at parse time, but the shared loopback splices make a
port already bound by another stack or network reportFailed(second
edge). Same-port isolation across networks isn't possible (no kernel
bridges).
Compose-language CLI (hard cut from kubectl verbs)
mc2 apply→mc2 up(no alias): publish desired state and converge —
idempotent, same semantics.mc2 down <stack>(new): tear down a stack — deletes the definition and
its instances (the node loop's GC removes the sandboxes); named volumes are
retained (Compose semantics).mc2 rm <stack> [--volumes](new):downplus optional named-volume
deletion (DELETE /v1/stacks/{name}?volumes=true; volume root =--volume-dir
or the msb default).mc2 config -f stack.yaml(new): validate and print the normalized
stack config (whatmc2 upwould send) — thecompose configequivalent.- Server-side:
DELETE /v1/stacks/{name}handler; storedelete_stack
(instances first, cascades ssh/fabric);AppState.volume_dirfor volume
cleanup. Internal REST stays/v1/stacks:apply.
Interactive setup wizard (mc2 setup)
- New interactive
mc2 setupwizard with two trees: Server (run on the
VPS) and Client (run locally).mc2 setup server/mc2 setup client
jump straight to a tree. - Server tree collects the server options step by step (bind, data dir,
API key on/off, public hostname, cert resolver, ingress dir), writes a
ready-to-run defaulttraefik.static.ymlonce (never clobbered), renders
the runnablemc2 server …command, and prints a numbered finish-setup
checklist (bootstrap token → start Traefik → DNS/ports → run the client
tree locally). - Client tree takes a context name, URL and API key (masked), saves and
activates the context, and optionally verifies the live connection. - Prompts use
dialoguer(arrow-key menus); non-TTY stdin fails with a
friendly "run it in a terminal" error instead of hanging. - Wizard only scaffolds config and prints instructions — it never starts the
server or Traefik (BYO Traefik, one-process philosophy).