diff --git a/README.md b/README.md index bba02e9..4047445 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ asciinema has two durable data sets: searchable terminal text, and stream state. Its 8 Gi RBD claim uses the retained `rook-ceph-block-ec` StorageClass. - The private Rook Ceph RGW bucket `asciinema` stores uploaded asciicast files. - Downloads use expiring pre-signed URLs through `https://s3.brunner.ninja`. + The server reads casts through the cluster-local RGW Service and serves them + from the asciinema endpoint. -The public RGW ingress rejects ExAws signatures for some mutating methods, so -`custom.exs` uses the cluster-local RGW Service for server-side writes, moves, -and deletes while generating browser-visible pre-signed GET URLs with the -public endpoint. This mirrors the internal/public endpoint split used by other -S3-backed applications in the cluster. +All S3 operations use the cluster-local RGW Service. Although the S3 adapter +generates a pre-signed URL when reading a recording, asciinema consumes that URL +server-side to populate its local cache; it is not sent to the browser. Keeping +the URL internal also avoids routing storage reads through Traefik. The application needs neither Redis nor Valkey. Its cache and temporary upload workspace are bounded `emptyDir` volumes and may be discarded when the pod diff --git a/asciinema.yaml b/asciinema.yaml index 223f879..f824cb8 100644 --- a/asciinema.yaml +++ b/asciinema.yaml @@ -11,12 +11,9 @@ data: TZ: Europe/Vienna DB_POOL_SIZE: "5" S3_BUCKET: asciinema - # Mutating requests bypass the ingress because RGW rejects some ExAws - # DELETE/HEAD signatures after they pass through the public proxy. + # asciinema downloads stored casts server-side before serving them. Keep all + # S3 operations on the cluster-local RGW endpoint; no public S3 URL is needed. S3_ENDPOINT: http://rook-ceph-rgw-my-store.rook-ceph.svc.cluster.local - # The custom adapter uses this endpoint only when producing browser-visible - # pre-signed GET URLs. - S3_PUBLIC_ENDPOINT: https://s3.brunner.ninja S3_REGION: keller SMTP_HOST: smtp-relay.gmail.com SMTP_PORT: "587" @@ -36,59 +33,6 @@ data: # instance should remain single-user. SIGN_UP_DISABLED: "false" --- -apiVersion: v1 -kind: ConfigMap -metadata: - name: asciinema-custom-config - namespace: asciinema - labels: - app.kubernetes.io/name: asciinema -data: - custom.exs: | - import Config - - # asciinema has one S3 endpoint setting for both internal operations and - # browser-facing pre-signed URLs. Ceph RGW in this cluster needs separate - # endpoints, so delegate all mutations to the upstream adapter and only - # customize URL generation. - defmodule Asciinema.FileStore.SplitEndpointS3 do - @behaviour Asciinema.FileStore - - alias ExAws.{Config, S3} - - @impl true - def uri(path) do - endpoint = System.fetch_env!("S3_PUBLIC_ENDPOINT") |> URI.parse() - - config = - :s3 - |> Config.new() - |> Map.put(:scheme, "#{endpoint.scheme}://") - |> Map.put(:host, endpoint.host) - |> Map.put(:port, endpoint.port) - - {:ok, url} = - S3.presigned_url(config, :get, bucket(), "uploads/" <> path) - - url - end - - @impl true - defdelegate put_file(dst_path, src_local_path, content_type), - to: Asciinema.FileStore.S3 - - @impl true - defdelegate move_file(from_path, to_path), to: Asciinema.FileStore.S3 - - @impl true - defdelegate delete_file(path), to: Asciinema.FileStore.S3 - - defp bucket, do: System.fetch_env!("S3_BUCKET") - end - - config :asciinema, Asciinema.FileStore, - adapter: Asciinema.FileStore.SplitEndpointS3 ---- apiVersion: apps/v1 kind: Deployment metadata: @@ -200,19 +144,12 @@ spec: volumeMounts: - name: cache mountPath: /var/cache/asciinema - - name: custom-config - mountPath: /opt/app/etc/custom.exs - subPath: custom.exs - readOnly: true - name: tmp mountPath: /tmp volumes: - name: cache emptyDir: sizeLimit: 2Gi - - name: custom-config - configMap: - name: asciinema-custom-config - name: tmp emptyDir: sizeLimit: 512Mi diff --git a/ci-deployer.yaml b/ci-deployer.yaml index d1fd8df..1dea1fa 100644 --- a/ci-deployer.yaml +++ b/ci-deployer.yaml @@ -20,7 +20,6 @@ rules: - configmaps resourceNames: - asciinema - - asciinema-custom-config verbs: - get - patch diff --git a/test.sh b/test.sh index fc1d85d..02be005 100755 --- a/test.sh +++ b/test.sh @@ -33,6 +33,18 @@ if grep -Eqi 'redis|valkey' \ exit 1 fi +if grep -Eq 'S3_PUBLIC_ENDPOINT|SplitEndpointS3|asciinema-custom-config' \ + "${project_dir}/asciinema.yaml"; then + echo "asciinema must use the cluster-local RGW endpoint for server-side reads." >&2 + exit 1 +fi + +if ! grep -Fq 'S3_ENDPOINT: http://rook-ceph-rgw-my-store.rook-ceph.svc.cluster.local' \ + "${project_dir}/asciinema.yaml"; then + echo "asciinema must use the cluster-local RGW Service." >&2 + exit 1 +fi + validate_file() { local manifest=$1 shift @@ -43,8 +55,12 @@ validate_file() { docker run --rm --interactive \ ghcr.io/yannh/kubeconform:v0.7.0@sha256:85dbef6b4b312b99133decc9c6fc9495e9fc5f92293d4ff3b7e1b30f5611823c \ -strict -summary "$@" < "${manifest}" + elif command -v podman >/dev/null 2>&1; then + podman run --rm --interactive \ + ghcr.io/yannh/kubeconform:v0.7.0@sha256:85dbef6b4b312b99133decc9c6fc9495e9fc5f92293d4ff3b7e1b30f5611823c \ + -strict -summary "$@" < "${manifest}" else - echo "Required command not found: install kubeconform or Docker" >&2 + echo "Required command not found: install kubeconform, Docker, or Podman" >&2 exit 1 fi }