From 58fa4b0af8fc506b13e2e4c6faa05dfa2f573eb9 Mon Sep 17 00:00:00 2001 From: Minseong Choi Date: Sat, 27 Jun 2026 00:40:39 +0900 Subject: [PATCH] feat(deploy): add one-line bootstrap installer and container image bootstrap.sh auto-detects the host package manager (apt/dnf) and installs whatever is missing: Docker, k3s, and PostgreSQL. It builds and imports the felis image, opens pg_hba to the pod CIDR, runs migrations, and applies the rendered control-plane bundle, leaving Web disabled pending 'felis setup'. The Dockerfile builds the distroless felis image; deploy/crd holds the MinecraftServer CRD. --- .dockerignore | 19 + Dockerfile | 35 ++ deploy/bootstrap.sh | 424 ++++++++++++++++++ .../felis.lolicon.best_minecraftservers.yaml | 391 ++++++++++++++++ 4 files changed, 869 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 deploy/bootstrap.sh create mode 100644 deploy/crd/felis.lolicon.best_minecraftservers.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..48d01c6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +# Keep the felis image build context tiny: the binary is pure Go (only +# internal/store/migrations/*.sql is embedded), so none of the web/plugin/doc +# assets are needed and shipping them would bloat the context and slow `docker +# build` on a small host. +.git +.claude +node_modules +panel +plugins +docs +*.md +deploy +Dockerfile +.dockerignore +.gitignore +*.pem +*.key +felis +felis.exe diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1e026f1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# Felis control-plane image. +# +# Builds the single multi-call `felis` binary (api / operator / migrate / reaper +# / restore / manifests / setup) as a static, CGO-free executable and ships it on +# a distroless base. Two contracts the rendered Deployments depend on: +# +# 1. The binary lives on PATH at /usr/local/bin/felis, because the bundle +# invokes it by bare name (`command: ["felis", ...]` in workloads.go). PATH +# is pinned explicitly so this holds regardless of base-image defaults. +# 2. The image is meant to be imported into a local containerd (k3s ctr import) +# and referenced by a NON-:latest tag (e.g. felis:demo). k8s then resolves +# the default IfNotPresent pull policy against the imported image instead of +# trying to pull it from a registry that does not exist yet. +# +# deploy/bootstrap.sh also extracts this same binary onto the host (docker cp) +# so `felis migrate up` and the `felis setup` TUI run with the identical build. + +FROM golang:1.26 AS build +WORKDIR /src +ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64 +# Prime the module cache first so source-only edits do not re-download deps. +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN go build -trimpath -ldflags="-s -w" -o /out/felis ./cmd/felis + +FROM gcr.io/distroless/static-debian12:nonroot +# Guarantee bare `felis` resolves no matter what PATH the base image ships. +ENV PATH=/usr/local/bin:/usr/bin:/bin +COPY --from=build /out/felis /usr/local/bin/felis +# distroless "nonroot" is uid 65532; the rendered PodSecurityContext pins +# runAsUser 1000 at deploy time, and a static binary needs no /etc/passwd entry, +# so either uid runs the same binary from a read-only root filesystem. +USER 65532:65532 +ENTRYPOINT ["felis"] diff --git a/deploy/bootstrap.sh b/deploy/bootstrap.sh new file mode 100644 index 0000000..1495c05 --- /dev/null +++ b/deploy/bootstrap.sh @@ -0,0 +1,424 @@ +#!/usr/bin/env bash +# +# Felis one-line bootstrap installer. +# +# curl -fsSL /deploy/bootstrap.sh | sudo bash +# +# Brings a fresh single-node Linux host from nothing to a running Felis control +# plane: it installs whatever is missing (picking dnf or apt by OS), provisions a +# swap file on tiny hosts, then configures Docker, k3s and PostgreSQL, builds and +# imports the felis image, runs database migrations and applies the rendered +# install bundle (CRD + namespaces + RBAC + NetworkPolicies + control-plane +# Deployments + in-cluster registry). +# +# By design it stops short of serving the web panel. After it finishes you run +# `felis setup` on the host (a TUI) to create the Owner account; the SysAdmin web +# surface only unlocks once Web Zero-Trust is configured. See deploy/README.md. +# +# The script is idempotent: re-running it converges rather than duplicating, and +# generated secrets are persisted to /etc/felis/secrets.env so reruns reuse them. +# +# Tunables (export before running to override the demo defaults): +# FELIS_REPO_URL git URL to build from (default: the upstream repo) +# FELIS_REF branch/tag/sha (default: main) +# FELIS_IMAGE local image tag (default: felis:demo — never :latest) +# FELIS_ROOT_DOMAIN deployment root domain (default: .nip.io) +# FELIS_EGRESS_MODE loadbalancer|nodeport (default: nodeport — no MetalLB on a demo box) +set -euo pipefail + +# --------------------------------------------------------------------------- +# Configuration & constants +# --------------------------------------------------------------------------- +FELIS_REPO_URL="${FELIS_REPO_URL:-https://github.com/MliroLirrorsIngenuity/Felis.git}" +FELIS_REF="${FELIS_REF:-main}" +FELIS_IMAGE="${FELIS_IMAGE:-felis:demo}" +FELIS_EGRESS_MODE="${FELIS_EGRESS_MODE:-nodeport}" + +CONTROL_NS="felis" +MINECRAFT_NS="minecraft" +BUILD_NS="felis-build" +POD_CIDR="10.42.0.0/16" # k3s default cluster CIDR +DB_NAME="felis" +DB_USER="felis" +REGISTRY_URL="registry.felis.svc:5000" + +STATE_DIR="/etc/felis" +SECRETS_ENV="${STATE_DIR}/secrets.env" +SRC_DIR="/opt/felis/src" +HOST_BIN="/usr/local/bin/felis" + +# --------------------------------------------------------------------------- +# Logging +# --------------------------------------------------------------------------- +log() { printf '\033[1;36m[felis]\033[0m %s\n' "$*"; } +ok() { printf '\033[1;32m[ ok ]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; } +die() { printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2; exit 1; } + +kube() { k3s kubectl "$@"; } + +# --------------------------------------------------------------------------- +# 0. Privilege & host facts +# --------------------------------------------------------------------------- +if [ "$(id -u)" -ne 0 ]; then + log "re-executing under sudo" + exec sudo -E bash "$0" "$@" +fi + +detect_os() { + [ -r /etc/os-release ] || die "cannot read /etc/os-release; unsupported host" + # shellcheck disable=SC1091 + . /etc/os-release + OS_ID="${ID:-unknown}" + OS_VERSION="${VERSION_ID:-unknown}" + if command -v apt-get >/dev/null 2>&1; then + PKG="apt" + elif command -v dnf >/dev/null 2>&1; then + PKG="dnf" + elif command -v yum >/dev/null 2>&1; then + PKG="yum" + else + die "no supported package manager (apt/dnf/yum) found on ${OS_ID} ${OS_VERSION}" + fi + log "host: ${PRETTY_NAME:-$OS_ID $OS_VERSION} (package manager: ${PKG})" +} + +detect_node_ip() { + NODE_IP="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')" + [ -n "${NODE_IP:-}" ] || NODE_IP="$(hostname -I 2>/dev/null | awk '{print $1}')" + [ -n "${NODE_IP:-}" ] || die "could not determine this host's primary IPv4 address" + FELIS_ROOT_DOMAIN="${FELIS_ROOT_DOMAIN:-${NODE_IP}.nip.io}" + log "node IP: ${NODE_IP} root domain: ${FELIS_ROOT_DOMAIN}" +} + +pkg_install() { + case "$PKG" in + apt) DEBIAN_FRONTEND=noninteractive apt-get install -y "$@" ;; + dnf) dnf install -y "$@" ;; + yum) yum install -y "$@" ;; + esac +} + +pkg_refresh_once() { + [ -n "${_PKG_REFRESHED:-}" ] && return 0 + case "$PKG" in + apt) DEBIAN_FRONTEND=noninteractive apt-get update -y ;; + dnf|yum) : ;; # dnf/yum refresh metadata on demand + esac + _PKG_REFRESHED=1 +} + +# --------------------------------------------------------------------------- +# 1. Swap — k3s + Postgres + a Go build will OOM on a <2 GiB box without it +# --------------------------------------------------------------------------- +ensure_swap() { + local mem_kb swap_kb + mem_kb="$(awk '/^MemTotal:/{print $2}' /proc/meminfo)" + swap_kb="$(awk '/^SwapTotal:/{print $2}' /proc/meminfo)" + if [ "${swap_kb:-0}" -gt 0 ]; then + ok "swap already present ($((swap_kb/1024)) MiB)" + return 0 + fi + if [ "${mem_kb:-0}" -ge 2097152 ]; then + ok "RAM $((mem_kb/1024)) MiB is sufficient; skipping swap" + return 0 + fi + log "low RAM ($((mem_kb/1024)) MiB) and no swap — creating a 2 GiB swap file" + if ! fallocate -l 2G /swapfile 2>/dev/null; then + dd if=/dev/zero of=/swapfile bs=1M count=2048 status=none + fi + chmod 600 /swapfile + mkswap /swapfile >/dev/null + swapon /swapfile + grep -q '^/swapfile ' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab + ok "2 GiB swap active" +} + +# --------------------------------------------------------------------------- +# 2. Base packages +# --------------------------------------------------------------------------- +install_base() { + pkg_refresh_once + pkg_install curl ca-certificates git openssl + ok "base tools present" +} + +# --------------------------------------------------------------------------- +# 3. Docker (used only to build & export the felis image; k3s uses containerd) +# --------------------------------------------------------------------------- +install_docker() { + if command -v docker >/dev/null 2>&1; then + ok "docker already installed" + else + log "installing docker via get.docker.com" + curl -fsSL https://get.docker.com | sh + fi + systemctl enable --now docker + ok "docker running" +} + +# --------------------------------------------------------------------------- +# 4. k3s — single node, trimmed for RAM. NetworkPolicy stays ENABLED on purpose: +# Felis's minecraft fence (default-deny + allow-rcon/allow-game) is a core +# security claim, so we must NOT pass --disable-network-policy. +# --------------------------------------------------------------------------- +install_k3s() { + if command -v k3s >/dev/null 2>&1; then + ok "k3s already installed" + else + log "installing k3s (no traefik/servicelb/metrics-server)" + curl -sfL https://get.k3s.io | \ + INSTALL_K3S_EXEC="--disable traefik --disable servicelb --disable metrics-server --write-kubeconfig-mode 644" \ + sh - + fi + systemctl enable --now k3s + export KUBECONFIG=/etc/rancher/k3s/k3s.yaml + log "waiting for the node to become Ready" + local i + for i in $(seq 1 60); do + if kube get nodes 2>/dev/null | grep -q ' Ready '; then + ok "k3s node Ready" + return 0 + fi + sleep 5 + done + kube get nodes || true + die "k3s node did not become Ready in time" +} + +# --------------------------------------------------------------------------- +# 5. Source + image build + host binary + containerd import +# --------------------------------------------------------------------------- +fetch_source() { + if [ -n "${FELIS_SKIP_FETCH:-}" ]; then + [ -d "$SRC_DIR" ] || die "FELIS_SKIP_FETCH set but ${SRC_DIR} does not exist" + ok "skipping fetch; using pre-staged source at ${SRC_DIR}" + return 0 + fi + if [ -d "${SRC_DIR}/.git" ]; then + log "updating source in ${SRC_DIR}" + git -C "$SRC_DIR" fetch --depth 1 origin "$FELIS_REF" + git -C "$SRC_DIR" checkout -f FETCH_HEAD + else + log "cloning ${FELIS_REPO_URL} (${FELIS_REF})" + mkdir -p "$(dirname "$SRC_DIR")" + git clone --depth 1 --branch "$FELIS_REF" "$FELIS_REPO_URL" "$SRC_DIR" 2>/dev/null \ + || git clone "$FELIS_REPO_URL" "$SRC_DIR" + fi + ok "source ready at ${SRC_DIR}" +} + +build_image() { + systemctl start docker + log "building ${FELIS_IMAGE} (this compiles the Go binary; first run is slow)" + docker build -t "$FELIS_IMAGE" "$SRC_DIR" + + log "extracting the felis binary onto the host (${HOST_BIN})" + local cid + cid="$(docker create "$FELIS_IMAGE")" + docker cp "${cid}:/usr/local/bin/felis" "$HOST_BIN" + docker rm "$cid" >/dev/null + chmod 0755 "$HOST_BIN" + + log "importing ${FELIS_IMAGE} into k3s containerd" + docker save "$FELIS_IMAGE" | k3s ctr images import - + + # Reclaim the ~150 MiB the docker daemon holds; reruns restart it on demand. + systemctl stop docker docker.socket 2>/dev/null || true + ok "image built, binary on host, image imported" +} + +# --------------------------------------------------------------------------- +# 6. PostgreSQL on the host (apt/dnf). felis-api pods reach it at :5432; +# migrations run from the host binary against 127.0.0.1. +# --------------------------------------------------------------------------- +install_postgres() { + if command -v psql >/dev/null 2>&1 && systemctl list-unit-files 2>/dev/null | grep -q '^postgresql'; then + ok "postgresql already installed" + else + log "installing postgresql" + case "$PKG" in + apt) pkg_install postgresql ;; + dnf) pkg_install postgresql-server postgresql ;; + yum) pkg_install postgresql-server postgresql ;; + esac + fi + # RHEL-family ships an uninitialised data dir. + if [ "$PKG" != "apt" ] && [ ! -f /var/lib/pgsql/data/PG_VERSION ]; then + log "initialising postgresql data directory" + if command -v postgresql-setup >/dev/null 2>&1; then + postgresql-setup --initdb || /usr/bin/postgresql-setup initdb + fi + fi + systemctl enable --now postgresql + ok "postgresql running" +} + +configure_postgres() { + local cfg hba + cfg="$(sudo -u postgres psql -tAc 'SHOW config_file;' 2>/dev/null || true)" + hba="$(sudo -u postgres psql -tAc 'SHOW hba_file;' 2>/dev/null || true)" + [ -n "$cfg" ] && [ -n "$hba" ] || die "could not query postgresql config/hba file paths" + + # Listen on all interfaces (applied on restart). ALTER SYSTEM is idempotent. + sudo -u postgres psql -v ON_ERROR_STOP=1 -c "ALTER SYSTEM SET listen_addresses = '*';" >/dev/null + + # Allow the host loopback, the pod CIDR, and the node IP (covers SNAT either way). + local line + for line in \ + "host ${DB_NAME} ${DB_USER} 127.0.0.1/32 scram-sha-256" \ + "host ${DB_NAME} ${DB_USER} ${POD_CIDR} scram-sha-256" \ + "host ${DB_NAME} ${DB_USER} ${NODE_IP}/32 scram-sha-256" ; do + grep -qF "$line" "$hba" || echo "$line" >> "$hba" + done + + # Role + database (idempotent), and (re)set the password to our generated one. + sudo -u postgres psql -v ON_ERROR_STOP=1 </dev/null +DO \$\$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${DB_USER}') THEN + CREATE ROLE ${DB_USER} LOGIN PASSWORD '${DB_PASSWORD}'; + END IF; +END +\$\$; +ALTER ROLE ${DB_USER} WITH LOGIN PASSWORD '${DB_PASSWORD}'; +SQL + if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'" | grep -q 1; then + sudo -u postgres createdb -O "$DB_USER" "$DB_NAME" + fi + + systemctl restart postgresql + ok "postgresql configured (listen=*, role/db '${DB_NAME}', pg_hba opened to pods)" +} + +# --------------------------------------------------------------------------- +# 7. Secrets + felis.toml (pod variant reaches Postgres at the node IP; host +# variant at 127.0.0.1 for migrations) +# --------------------------------------------------------------------------- +load_or_make_secrets() { + mkdir -p "$STATE_DIR" + chmod 0700 "$STATE_DIR" + if [ -f "$SECRETS_ENV" ]; then + # shellcheck disable=SC1090 + . "$SECRETS_ENV" + ok "reusing persisted secrets from ${SECRETS_ENV}" + fi + DB_PASSWORD="${DB_PASSWORD:-$(openssl rand -hex 24)}" + SERVICE_TOKEN="${SERVICE_TOKEN:-$(openssl rand -hex 32)}" + SESSION_SECRET="${SESSION_SECRET:-$(openssl rand -hex 32)}" + umask 077 + cat > "$SECRETS_ENV" < "$target" < 127.0.0.1)" + "$HOST_BIN" migrate up -config "${STATE_DIR}/felis.host.toml" + ok "migrations applied" +} + +deploy_bundle() { + export KUBECONFIG=/etc/rancher/k3s/k3s.yaml + write_felis_toml "${STATE_DIR}/felis.pod.toml" "${NODE_IP}" + + log "applying MinecraftServer CRD" + kube apply -f "${SRC_DIR}/deploy/crd/" + + log "ensuring namespaces" + local ns + for ns in "$CONTROL_NS" "$MINECRAFT_NS" "$BUILD_NS"; do + kube create namespace "$ns" --dry-run=client -o yaml | kube apply -f - + done + + log "provisioning felis-config + felis-service-token secrets (out-of-band, never in the bundle)" + kube -n "$CONTROL_NS" create secret generic felis-config \ + --from-file=felis.toml="${STATE_DIR}/felis.pod.toml" \ + --dry-run=client -o yaml | kube apply -f - + kube -n "$CONTROL_NS" create secret generic felis-service-token \ + --from-literal=token="${SERVICE_TOKEN}" \ + --dry-run=client -o yaml | kube apply -f - + + log "rendering + applying the control-plane bundle" + "$HOST_BIN" manifests \ + --felis-image "$FELIS_IMAGE" \ + --velocity-cidr "${NODE_IP}/32" \ + | kube apply -f - + + log "waiting for control-plane rollouts" + local d + for d in $(kube -n "$CONTROL_NS" get deploy -o name); do + kube -n "$CONTROL_NS" rollout status "$d" --timeout=180s || warn "rollout not complete: $d" + done +} + +# --------------------------------------------------------------------------- +# 9. Summary +# --------------------------------------------------------------------------- +summary() { + export KUBECONFIG=/etc/rancher/k3s/k3s.yaml + echo + ok "Felis control plane deployed." + echo + kube -n "$CONTROL_NS" get pods -o wide || true + echo + log "Web is intentionally NOT enabled yet." + log "Next: run 'sudo felis setup' on this host to create the Owner account." + log "The SysAdmin web surface unlocks only after Web Zero-Trust is configured." + echo +} + +main() { + detect_os + detect_node_ip + ensure_swap + install_base + load_or_make_secrets + install_docker + install_k3s + fetch_source + build_image + install_postgres + configure_postgres + run_migrations + deploy_bundle + summary +} + +main "$@" diff --git a/deploy/crd/felis.lolicon.best_minecraftservers.yaml b/deploy/crd/felis.lolicon.best_minecraftservers.yaml new file mode 100644 index 0000000..01ca290 --- /dev/null +++ b/deploy/crd/felis.lolicon.best_minecraftservers.yaml @@ -0,0 +1,391 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: minecraftservers.felis.lolicon.best +spec: + group: felis.lolicon.best + names: + kind: MinecraftServer + listKind: MinecraftServerList + plural: minecraftservers + singular: minecraftserver + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + MinecraftServer is the lifecycle source-of-truth for a single managed + Minecraft server (spec §4). The operator reconciles the StatefulSet, Service + and PVC from this object; readiness is gated exclusively on an RCON probe. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: MinecraftServerSpec is the desired state (spec §4 spec.*). + properties: + args: + description: Args are extra arguments appended after the jar. + items: + type: string + type: array + autostartPolicy: + description: AutostartPolicy controls who may wake the server (spec + §8). + enum: + - public + - allowlist + - ownerOnly + type: string + description: + description: Description is free-form operator/owner notes. + type: string + desiredState: + description: DesiredState toggles the server up or down (default Stopped). + enum: + - Running + - Stopped + type: string + displayName: + description: DisplayName is the human-facing name shown in the panel + and MOTD. + type: string + env: + description: Env are extra environment variables injected into the + server container. + items: + description: |- + EnvVar is a name/value pair injected into the server container. It is a + deliberately narrow subset of corev1.EnvVar (no valueFrom) so the CRD cannot + be used to exfiltrate arbitrary cluster secrets. + properties: + name: + type: string + value: + type: string + required: + - name + type: object + type: array + fallbackServer: + description: |- + FallbackServer is the Velocity server name traffic routes to while this + server is stopped or starting. + type: string + idle: + description: Idle configures empty-server auto-stop (spec §8). + properties: + autoStopEnabled: + description: AutoStopEnabled turns on idle auto-stop. + type: boolean + emptySecondsBeforeStop: + description: |- + EmptySecondsBeforeStop is how long the server may sit empty before the + operator scales it down. + format: int32 + type: integer + type: object + image: + description: Image is the fully-qualified container image (loader-agnostic). + type: string + jar: + description: |- + Jar is the server jar path/name inside the image, if the entrypoint + needs it explicitly. + type: string + javaFlags: + description: JavaFlags are additional JVM flags (e.g. Aikar's flags). + items: + type: string + type: array + javaMemory: + description: JavaMemory is the heap sizing passed as -Xmx/-Xms (e.g. + "4G"). + type: string + lifecycle: + description: Lifecycle tunes graceful shutdown (spec §7). + properties: + preStopSaveAndStop: + description: PreStopSaveAndStop enables the operator-injected + RCON save+stop preStop. + type: boolean + terminationGracePeriodSeconds: + description: TerminationGracePeriodSeconds is the pod grace period + (default 300). + format: int64 + type: integer + type: object + motd: + description: Motd holds the per-phase MOTD strings surfaced to status + pings. + properties: + failed: + type: string + running: + type: string + starting: + type: string + stopped: + type: string + type: object + onlineMode: + description: |- + OnlineMode mirrors server.properties online-mode. Wake/claim semantics + only hold when the Velocity proxy enforces online-mode=true (spec §8). + type: boolean + rcon: + description: |- + Rcon configures the RCON endpoint the operator probes for readiness and + uses for graceful shutdown (spec §5, §7). + properties: + enabled: + description: Enabled must be true for readiness probing and graceful + shutdown. + type: boolean + port: + description: Port is the RCON TCP port (default 25575). + format: int32 + type: integer + secretRef: + description: SecretRef points at the Secret holding the RCON password. + properties: + key: + type: string + name: + type: string + required: + - key + - name + type: object + type: object + reaperExempt: + description: ReaperExempt opts this server out of the world reaper + entirely (spec §18). + type: boolean + resources: + description: Resources are the container resource requests/limits. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + startup: + description: Startup bounds how long Starting may last before Failed + (spec §5). + properties: + readinessTimeoutSeconds: + description: ReadinessTimeoutSeconds is the budget for the first + successful RCON probe. + format: int32 + type: integer + timeoutSeconds: + description: TimeoutSeconds is the overall budget before the server + is marked Failed. + format: int32 + type: integer + type: object + storage: + description: Storage configures the world PVC. + properties: + retainOnDelete: + description: RetainOnDelete keeps the PVC when the MinecraftServer + is deleted. + type: boolean + size: + description: Size is the requested PVC capacity (e.g. "10Gi"). + type: string + storageClassName: + description: StorageClassName selects the StorageClass; empty + uses the default. + type: string + type: object + subdomain: + description: |- + Subdomain is the per-server label under the deployment zone. It is the + only routing identity; the operator never hardcodes the parent domain. + type: string + required: + - image + - subdomain + type: object + status: + description: MinecraftServerStatus is the observed state (spec §4 status.*). + properties: + conditions: + description: Conditions are the standard metav1 conditions (Ready, + RconReached, ...). + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + endpoint: + description: Endpoint is where the proxy should route traffic. + properties: + address: + description: Address is the host:port the proxy should dial. + type: string + mode: + description: Mode is "direct" or "fallback". + type: string + type: object + liveMotd: + description: LiveMotd is the MOTD currently advertised for the active + phase. + type: string + observedGeneration: + description: ObservedGeneration is the spec generation this status + reflects. + format: int64 + type: integer + phase: + description: Phase is the coarse lifecycle phase. + type: string + players: + description: Players is the last observed player count. + properties: + max: + format: int32 + type: integer + online: + format: int32 + type: integer + required: + - max + - online + type: object + ready: + description: |- + Ready is true only after a successful RCON probe (loader-agnostic; a + status ping is never sufficient — spec §5). + type: boolean + readySignalAt: + description: ReadySignalAt is when the first RCON probe succeeded. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {}