# Running Zurg in Google Cloud

Zurg leverages support for .STRM files and WebDAV to create a shareable media library; it pairs very well with Infuse. (This is a low cost, low complexity deployment that is not suited for users of Emby, Jelly, Plex, *arr.)

  • Zurg is available in a private repository: debridmediamanager/zurg
  • Google Artifact Registry mirrors this private repository to directly deploy the latest image from GitHub Container Registry to Google Cloud Run.
  • Google Cloud Run serves Zurg in a low-resource instance using a saved secret in Secret Manager as the configuration file.

# Prerequisites

# Add a private remote repository to Artifact Registry

Cloud Run does not directly support deploying private images from GitHub Container Registry. Private remote remote repositories must instead be mirrored in Artifact Registry.

  1. Create a new project:

    gcloud projects create --name="Zurg"

    Set your active Project ID and set related variables:

    gcloud projects list
    printf "Google Cloud Project ID: "; read PROJECT_ID; export PROJECT_ID;
    PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
    echo Google Cloud Project Number: ${PROJECT_NUMBER}
    gcloud config set project ${PROJECT_ID}
  2. Configure Artifact Registry and Secret Manager:

    Enable the API for each service:

    gcloud services enable artifactregistry.googleapis.com
    gcloud services enable secretmanager.googleapis.com

    Add your GitHub Personal Access Token to Secret Manager:

    read -s -p "GitHub token (hidden): " GITHUB_TOKEN; echo
    gcloud secrets describe github-token >/dev/null 2>&1 \
    || gcloud secrets create github-token --quiet; printf "%s" "$GITHUB_TOKEN" \
     | gcloud secrets versions add github-token --data-file=- --quiet
    unset GITHUB_TOKEN

    Grant Artifact Registry service agent permission to access this secret:

    gcloud secrets add-iam-policy-binding github-token \
      --member="serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-artifactregistry.iam.gserviceaccount.com" \
      --role="roles/secretmanager.secretAccessor" \
      --project=${PROJECT_ID}
  3. Create an Artifact Registry remote repository:

    Set your GitHub username as a temporary variable:

     printf "GitHub username: "; read GITHUB_USERNAME; export GITHUB_USERNAME

    Select a Google Cloud Region that meets your low latency or data residency requirements. Set this region (for example us-central1) as a variable:

     printf "Google Cloud Region: "; read REGION_ID; export REGION_ID

    Create a remote repository named github-remote that acts as a pull-through cache for GitHub Container Registry using the token stored in Secret Manager:

     gcloud artifacts repositories create github-remote \
       --repository-format=docker \
       --mode=REMOTE_REPOSITORY \
       --location=${REGION_ID} \
       --description="remote repository for ghcr.io with authentication" \
       --remote-repo-config-desc="GitHub Container Registry" \
       --remote-docker-repo=https://ghcr.io \
       --remote-username=GITHUB_USERNAME \
       --remote-password-secret-version=projects/${PROJECT_ID}/secrets/github-token/versions/latest

    Verify the remote repository:

     gcloud artifacts repositories list --location=$REGION_ID
  4. Consider disabling vulnerability scans for this project:

You now have a new Google Cloud project with a remote repository in Artifact Registry that securely pulls images from ghcr.io using a GitHub API token stored in Secret Manager.

# Configure Cloud Run and Deploy Zurg

  1. Set your active Project ID and set related variables:

    ```bash
    gcloud projects list
    printf "Google Cloud Project ID: "; read PROJECT_ID; export PROJECT_ID;
    PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
    echo Google Cloud Project Number: ${PROJECT_NUMBER}
    gcloud config set project ${PROJECT_ID}
    ```
  2. Initialize Cloud Run:

    Enable the API for Cloud Run:

     gcloud services enable run.googleapis.com

    Initialize zurg as a Cloud Run service with a temporary image (and default service account):

     gcloud run deploy zurg \
     	--image=gcr.io/cloudrun/hello \
     	--platform=managed \
     	--region=$REGION_ID \
     	--allow-unauthenticated
  3. Configure Zurg:

    Create or edit config.yml locally. Save the contents to a new (or updated) secret in Secret Manager:

     gcloud secrets describe zurg-config-yaml >/dev/null 2>&1 \
     || gcloud secrets create zurg-config-yaml \
     	--data-file=config.yml \
     	--replication-policy=user-managed \
     	--locations=$REGION_ID \
     && gcloud secrets versions add zurg-config-yaml \
     	--data-file=config.yml

    Grant permission to Cloud Run service agent to access the secret:

     SERVICE_ACCOUNT=$(gcloud run services describe zurg  \
     	--region us-central1 \
     	--format='value(spec.template.spec.serviceAccountName)')
     gcloud projects add-iam-policy-binding $PROJECT_NUMBER \
     	--member="serviceAccount:${SERVICE_ACCOUNT}" \
     	--role="roles/secretmanager.secretAccessor" \
     	--condition="expression=true,title=unconditional-access,description=granting permanent access for Cloud Run to read secrets"

# Deploy Zurg to Cloud Run

Deploy (or update) zurg with the :latest release and serve a saved secret as the configuration file:

gcloud run deploy zurg \
	--project=${PROJECT_ID} \
	--region=${REGION_ID} \
	--image=${REGION_ID}-docker.pkg.dev/${PROJECT_ID}/github-remote/ghcr.io/debridmediamanager/zurg:latest \
	--port=9999 \
	--memory=128Mi \
	--min-instances=1 \
	--max-instances=2 \
	--allow-unauthenticated \
	--no-cpu-boost \
	--set-env-vars=LOG_LEVEL=INFO \
	--set-secrets=/app/config.yml=projects/${PROJECT_NUMBER}/secrets/zurg-config-yaml:latest \
	--args=--config,/app/config.yml

[!warning]  You may need to direct all traffic to the latest revision:
gcloud run services update-traffic zurg \
--to-latest --region=$REGION_ID

# Update Zurg

Artifact Registry remote repositories are pull-through caches: they fetch a tag on first pull and continue to serve a cached copy.

To force Artifact Registry to fetch the :latest version delete the existing :latest tag before deploying to Cloud Run:

gcloud artifacts docker images delete \
	${REGION_ID}-docker.pkg.dev/${PROJECT_ID}/github-remote/ghcr.io/debridmediamanager/zurg:latest \
	--delete-tags

# Recent Updates

  • Set min-instances=1 to avoid cold start delays and max-instances=2 to avoid unexpected billing:

      gcloud run services update zurg \
    	--region=$REGION_ID \
      	--min-instances=1 \
      	--max-instances=2

# Future Considerations

  • There appears to be no billing impact to having Zurg make a basic API call every 15 seconds (default).
  • Longterm testing is required to determine to what extent memory=528Mi and execution-environment=gen2 would impact billing.