From 110548cdf8e9017954923c0c6d6430f722d96270 Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Fri, 23 Oct 2026 09:53:30 +0200 Subject: [PATCH] Retire Plex direct deploy bootstrap and one-time migration files --- README.md | 24 +++--------- ci-deployer.yaml | 60 ---------------------------- create-ci-kubeconfig.sh | 48 ----------------------- migrate.sh | 50 ------------------------ migration-job.yaml | 86 ----------------------------------------- namespace.yaml | 6 --- test.sh | 29 -------------- 7 files changed, 5 insertions(+), 298 deletions(-) delete mode 100644 ci-deployer.yaml delete mode 100755 create-ci-kubeconfig.sh delete mode 100755 migrate.sh delete mode 100644 migration-job.yaml delete mode 100644 namespace.yaml delete mode 100755 test.sh diff --git a/README.md b/README.md index 998bf6f..81d667f 100644 --- a/README.md +++ b/README.md @@ -20,26 +20,12 @@ live on the retained 160 GiB `plex-config` RBD PVC. Transcodes use node-local ephemeral storage. The official Plex image is pinned to the same version as the migrated Arch installation. -## One-time migration +## Historical migration -`migrate.sh` stops the Arch service, confirms no Plex process remains, checks -both source SQLite databases, creates the PVC, and starts a one-shot copy Job: - -```sh -./migrate.sh -kubectl -n plex logs -f job/plex-archlinux-migration -kubectl -n plex wait --for=condition=complete \ - job/plex-archlinux-migration --timeout=12h -``` - -The Job refuses to overwrite a non-empty destination and compares regular-file -counts and byte totals before writing its completion marker. Copy I/O is -deliberately duty-cycle throttled so the migration cannot monopolize bcachefs -and Ceph. Do not delete the source `/var/lib/plex` tree until the Kubernetes -server has been verified and a separate backup exists. - -The historical migration scripts remain for recovery; routine deployment is -owned by Argo CD. Plex remains available at +The one-time Arch-to-Kubernetes copy tooling is preserved in Git history, not +as an active deployment path. The existing `plex-config` PVC must not be +deleted or recreated during upgrades. Routine deployment is owned by Argo CD. +Plex remains available at `https://plex.brunner.ninja` and directly on `192.168.0.2:32400`. ## Monitoring diff --git a/ci-deployer.yaml b/ci-deployer.yaml deleted file mode 100644 index ea57e18..0000000 --- a/ci-deployer.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: plex-deployer - namespace: plex -automountServiceAccountToken: false ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: plex-deployer - namespace: plex -rules: - - apiGroups: [""] - resources: ["configmaps"] - resourceNames: ["plex-scripts"] - verbs: ["get", "patch", "update"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - resourceNames: ["plex-config"] - verbs: ["get", "patch", "update"] - - apiGroups: [""] - resources: ["services"] - resourceNames: ["plex"] - verbs: ["get", "patch", "update"] - - apiGroups: ["apps"] - resources: ["deployments"] - resourceNames: ["plex"] - verbs: ["get", "patch", "update"] - - apiGroups: ["traefik.io"] - resources: ["ingressroutes"] - resourceNames: ["plex-brunner-ninja"] - verbs: ["get", "patch", "update"] - - apiGroups: ["monitoring.coreos.com"] - resources: ["servicemonitors", "prometheusrules"] - resourceNames: ["plex"] - verbs: ["get", "patch", "update"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: plex-deployer - namespace: plex -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: plex-deployer -subjects: - - kind: ServiceAccount - name: plex-deployer - namespace: plex ---- -apiVersion: v1 -kind: Secret -metadata: - name: plex-deployer-token - namespace: plex - annotations: - kubernetes.io/service-account.name: plex-deployer -type: kubernetes.io/service-account-token diff --git a/create-ci-kubeconfig.sh b/create-ci-kubeconfig.sh deleted file mode 100755 index 866330d..0000000 --- a/create-ci-kubeconfig.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) -namespace=plex -service_account=plex-deployer -secret=plex-deployer-token - -if [[ "$(kubectl config current-context)" != kubernetes-admin@kubernetes ]]; then - echo 'Run this only with the kubernetes-admin@kubernetes homelab context.' >&2 - exit 1 -fi -kubectl apply --filename "${project_dir}/namespace.yaml" >&2 -kubectl apply --filename "${project_dir}/ci-deployer.yaml" >&2 - -for attempt in {1..30}; do - if kubectl --namespace "${namespace}" get secret "${secret}" \ - --output=jsonpath='{.data.token}' 2>/dev/null | grep -q .; then - break - fi - if [[ "${attempt}" == 30 ]]; then - echo 'Timed out waiting for the service-account token.' >&2 - exit 1 - fi - sleep 1 -done - -workdir=$(mktemp --directory) -trap 'rm -rf "${workdir}"' EXIT -server=$(kubectl config view --minify --output=jsonpath='{.clusters[0].cluster.server}') -kubectl --namespace "${namespace}" get secret "${secret}" \ - --output=jsonpath='{.data.ca\.crt}' | base64 --decode > "${workdir}/ca.crt" -token=$(kubectl --namespace "${namespace}" get secret "${secret}" \ - --output=jsonpath='{.data.token}' | base64 --decode) - -export KUBECONFIG="${workdir}/config" -kubectl config set-cluster cluster --server="${server}" \ - --certificate-authority="${workdir}/ca.crt" --embed-certs=true >/dev/null -kubectl config set-credentials "${service_account}" --token="${token}" >/dev/null -kubectl config set-context plex --cluster=cluster --user="${service_account}" \ - --namespace="${namespace}" >/dev/null -kubectl config use-context plex >/dev/null - -echo 'Store this single line as the Gitea Actions secret KUBE_CONFIG_BASE64.' >&2 -echo 'Treat it as a password; do not commit it.' >&2 -base64 --wrap=0 "${KUBECONFIG}" -printf '\n' diff --git a/migrate.sh b/migrate.sh deleted file mode 100755 index 349e48a..0000000 --- a/migrate.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) -expected_context=kubernetes-admin@kubernetes -context=$(kubectl config current-context) - -if [[ "${context}" != "${expected_context}" ]]; then - echo "Expected Kubernetes context ${expected_context}, found ${context}." >&2 - exit 1 -fi - -sudo systemctl stop plexmediaserver.service -if sudo systemctl is-active --quiet plexmediaserver.service; then - echo 'plexmediaserver.service is still active; refusing to copy its database.' >&2 - exit 1 -fi -if pgrep -u plex >/dev/null; then - echo 'A process owned by plex is still running; refusing to copy its database.' >&2 - exit 1 -fi - -database_root='/var/lib/plex/Plex Media Server/Plug-in Support/Databases' -for database in \ - "${database_root}/com.plexapp.plugins.library.db" \ - "${database_root}/com.plexapp.plugins.library.blobs.db"; do - result=$(sudo -u plex '/usr/lib/plexmediaserver/Plex SQLite' "${database}" 'PRAGMA quick_check;') - if [[ "${result}" != ok ]]; then - echo "SQLite quick_check failed for ${database}: ${result}" >&2 - exit 1 - fi -done - -kubectl apply --filename "${project_dir}/namespace.yaml" -kubectl apply --filename "${project_dir}/plex.yaml" \ - --selector=app.kubernetes.io/component=state -kubectl --namespace plex wait \ - --for=jsonpath='{.status.phase}'=Bound \ - persistentvolumeclaim/plex-config \ - --timeout=10m - -if kubectl --namespace plex get job plex-archlinux-migration >/dev/null 2>&1; then - echo 'Job plex-archlinux-migration already exists; inspect it instead of overwriting it.' >&2 - exit 1 -fi -kubectl apply --filename "${project_dir}/migration-job.yaml" -echo 'Migration started. Follow it with:' -echo ' kubectl -n plex logs -f job/plex-archlinux-migration' -echo ' kubectl -n plex wait --for=condition=complete job/plex-archlinux-migration --timeout=12h' diff --git a/migration-job.yaml b/migration-job.yaml deleted file mode 100644 index 807e210..0000000 --- a/migration-job.yaml +++ /dev/null @@ -1,86 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: plex-archlinux-migration - namespace: plex - labels: - app.kubernetes.io/name: plex - app.kubernetes.io/component: migration -spec: - backoffLimit: 0 - template: - metadata: - labels: - app.kubernetes.io/name: plex - app.kubernetes.io/component: migration - spec: - restartPolicy: Never - nodeSelector: - kubernetes.io/hostname: arschrock - tolerations: - - key: node-role.kubernetes.io/control-plane - operator: Exists - effect: NoSchedule - containers: - - name: copy - image: docker.io/library/alpine:3.24.2 - imagePullPolicy: IfNotPresent - command: - - /bin/sh - - -ec - - | - marker=/target/.archlinux-migration-complete - source='/source/Plex Media Server' - destination='/target/Library/Application Support/Plex Media Server' - test -d "${source}" - if [ -e "${marker}" ]; then - echo 'Migration marker already exists; refusing to overwrite the PVC.' >&2 - exit 1 - fi - if find /target -mindepth 1 -maxdepth 1 ! -name lost+found -print -quit | grep -q .; then - echo 'The destination PVC is not empty; refusing to overwrite it.' >&2 - exit 1 - fi - mkdir -p '/target/Library/Application Support' - cp -a "${source}" '/target/Library/Application Support/' & - copy_pid=$! - while kill -0 "${copy_pid}" 2>/dev/null; do - kill -CONT "${copy_pid}" 2>/dev/null || true - sleep 0.2 - kill -STOP "${copy_pid}" 2>/dev/null || true - sleep 3.8 - done - kill -CONT "${copy_pid}" 2>/dev/null || true - wait "${copy_pid}" - sync -f /target - source_files=$(find "${source}" -type f | wc -l) - destination_files=$(find "${destination}" -type f | wc -l) - source_bytes=$(find "${source}" -type f -exec stat -c '%s' {} + | awk '{ total += $1 } END { print total + 0 }') - destination_bytes=$(find "${destination}" -type f -exec stat -c '%s' {} + | awk '{ total += $1 } END { print total + 0 }') - test "${source_files}" = "${destination_files}" - test "${source_bytes}" = "${destination_bytes}" - printf 'source_files=%s\nsource_bytes=%s\n' "${source_files}" "${source_bytes}" > "${marker}" - chown 964:964 /target "${marker}" - echo "Copied ${source_files} files (${source_bytes} bytes)." - resources: - requests: - cpu: 250m - memory: 256Mi - securityContext: - allowPrivilegeEscalation: false - runAsUser: 0 - runAsGroup: 0 - volumeMounts: - - name: source - mountPath: /source - readOnly: true - - name: target - mountPath: /target - volumes: - - name: source - hostPath: - path: /var/lib/plex - type: Directory - - name: target - persistentVolumeClaim: - claimName: plex-config diff --git a/namespace.yaml b/namespace.yaml deleted file mode 100644 index 6db70ee..0000000 --- a/namespace.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: plex - labels: - app.kubernetes.io/name: plex diff --git a/test.sh b/test.sh deleted file mode 100755 index 78bd31a..0000000 --- a/test.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) - -sh -n "${project_dir}/install.sh" -bash -n "${project_dir}/migrate.sh" -bash -n "${project_dir}/create-ci-kubeconfig.sh" - -if grep -q '/komposthaufen' "${project_dir}/install.sh"; then - echo 'install.sh must not inspect node-local media paths on the admin machine.' >&2 - exit 1 -fi -if grep -q 'plex-archlinux-migration' "${project_dir}/install.sh"; then - echo 'install.sh must not depend on the one-time migration Job.' >&2 - exit 1 -fi - -if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then - for manifest in namespace.yaml plex.yaml migration-job.yaml; do - docker run --rm --interactive \ - ghcr.io/yannh/kubeconform:v0.7.0 \ - -strict -ignore-missing-schemas -summary < "${project_dir}/${manifest}" - done - docker run --rm --interactive \ - ghcr.io/yannh/kubeconform:v0.7.0 \ - -strict -summary < "${project_dir}/ci-deployer.yaml" -fi