Add Gold network package catalog dropdown support #153
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build And Publish Images | |
| on: | |
| push: | |
| branches: | |
| - main | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| env: | |
| REGISTRY: ghcr.io | |
| FRONTEND_IMAGE: ghcr.io/paradox1612/streambridge-frontend | |
| BACKEND_IMAGE: ghcr.io/paradox1612/streambridge-backend | |
| PUBLIC_BASE_URL: ${{ vars.PUBLIC_BASE_URL }} | |
| jobs: | |
| build-and-push: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| backend_changed: ${{ steps.changes.outputs.backend }} | |
| frontend_changed: ${{ steps.changes.outputs.frontend }} | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 2 | |
| - name: Detect image build changes | |
| id: changes | |
| uses: dorny/paths-filter@v4 | |
| with: | |
| filters: | | |
| backend: | |
| - 'backend/**' | |
| frontend: | |
| - 'frontend-next/**' | |
| - name: Log in to Docker Hub | |
| uses: docker/login-action@v4 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| continue-on-error: true | |
| - name: Set up Docker Buildx | |
| id: buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Docker metadata for backend | |
| if: steps.changes.outputs.backend == 'true' | |
| id: backend-meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ${{ env.BACKEND_IMAGE }} | |
| tags: | | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| type=sha,format=short | |
| type=ref,event=tag | |
| - name: Docker metadata for frontend | |
| if: steps.changes.outputs.frontend == 'true' | |
| id: frontend-meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ${{ env.FRONTEND_IMAGE }} | |
| tags: | | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| type=sha,format=short | |
| type=ref,event=tag | |
| - name: Build and push backend | |
| if: steps.changes.outputs.backend == 'true' | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: ./backend | |
| file: ./backend/Dockerfile | |
| push: true | |
| tags: ${{ steps.backend-meta.outputs.tags }} | |
| labels: ${{ steps.backend-meta.outputs.labels }} | |
| cache-from: type=gha,scope=backend-image | |
| cache-to: type=gha,scope=backend-image,mode=max | |
| - name: Cache frontend Buildx layers | |
| if: steps.changes.outputs.frontend == 'true' | |
| uses: actions/cache@v5 | |
| with: | |
| path: ${{ runner.temp }}/.buildx-cache-frontend-next | |
| key: ${{ runner.os }}-buildx-frontend-next-${{ hashFiles('frontend-next/package-lock.json', 'frontend-next/Dockerfile', 'frontend-next/next.config.ts') }} | |
| restore-keys: | | |
| ${{ runner.os }}-buildx-frontend-next- | |
| - name: Cache frontend Docker cache mounts | |
| if: steps.changes.outputs.frontend == 'true' | |
| id: frontend-cache-mounts | |
| uses: actions/cache@v5 | |
| with: | |
| path: ${{ runner.temp }}/.buildx-cache-mounts-frontend-next | |
| key: ${{ runner.os }}-buildx-cache-mounts-frontend-next-${{ hashFiles('frontend-next/package-lock.json', 'frontend-next/Dockerfile', 'frontend-next/next.config.ts') }} | |
| restore-keys: | | |
| ${{ runner.os }}-buildx-cache-mounts-frontend-next- | |
| - name: Restore frontend Docker cache mounts | |
| if: steps.changes.outputs.frontend == 'true' | |
| uses: reproducible-containers/buildkit-cache-dance@4db69aec1c9d8c63a6412a678820f69d2c48569b | |
| with: | |
| builder: ${{ steps.buildx.outputs.name }} | |
| cache-dir: ${{ runner.temp }}/.buildx-cache-mounts-frontend-next | |
| dockerfile: frontend-next/Dockerfile | |
| skip-extraction: ${{ steps.frontend-cache-mounts.outputs.cache-hit }} | |
| - name: Validate frontend public URL | |
| if: steps.changes.outputs.frontend == 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| test -n "${PUBLIC_BASE_URL:-}" || { echo "Set the PUBLIC_BASE_URL repository variable before building the frontend image."; exit 1; } | |
| - name: Build and push frontend | |
| if: steps.changes.outputs.frontend == 'true' | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: ./frontend-next | |
| file: ./frontend-next/Dockerfile | |
| push: true | |
| tags: ${{ steps.frontend-meta.outputs.tags }} | |
| labels: ${{ steps.frontend-meta.outputs.labels }} | |
| build-args: | | |
| NEXT_PUBLIC_API_URL=${{ env.PUBLIC_BASE_URL }} | |
| NEXT_PUBLIC_SITE_URL=${{ env.PUBLIC_BASE_URL }} | |
| NEXT_PUBLIC_GOOGLE_CLIENT_ID=${{ secrets.NEXT_PUBLIC_GOOGLE_CLIENT_ID }} | |
| cache-from: | | |
| type=gha,scope=frontend-next-image | |
| type=local,src=${{ runner.temp }}/.buildx-cache-frontend-next | |
| cache-to: | | |
| type=gha,scope=frontend-next-image,mode=max | |
| type=local,dest=${{ runner.temp }}/.buildx-cache-frontend-next-new,mode=max | |
| - name: Move frontend Buildx cache | |
| if: steps.changes.outputs.frontend == 'true' | |
| shell: bash | |
| run: | | |
| rm -rf "${{ runner.temp }}/.buildx-cache-frontend-next" | |
| mv "${{ runner.temp }}/.buildx-cache-frontend-next-new" "${{ runner.temp }}/.buildx-cache-frontend-next" | |
| build-cloudstream-plugin: | |
| name: Build CloudStream Plugin | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 2 # needed for paths-filter to diff against previous commit | |
| - name: Check if plugin code changed | |
| id: changes | |
| uses: dorny/paths-filter@v4 | |
| with: | |
| filters: | | |
| plugin: | |
| - 'cloudstream-plugin/**' | |
| - name: Set up JDK 17 | |
| if: steps.changes.outputs.plugin == 'true' | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: "17" | |
| distribution: "temurin" | |
| - name: Set up Android SDK | |
| if: steps.changes.outputs.plugin == 'true' | |
| uses: android-actions/setup-android@v4 | |
| - name: Install Android SDK packages | |
| if: steps.changes.outputs.plugin == 'true' | |
| shell: bash | |
| run: | | |
| sdkmanager --install \ | |
| "platform-tools" \ | |
| "platforms;android-33" \ | |
| "build-tools;33.0.2" | |
| - name: Cache Gradle | |
| if: steps.changes.outputs.plugin == 'true' | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('cloudstream-plugin/**/*.gradle*', 'cloudstream-plugin/**/gradle-wrapper.properties') }} | |
| restore-keys: ${{ runner.os }}-gradle- | |
| - name: Validate plugin public URL | |
| if: steps.changes.outputs.plugin == 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| test -n "${PUBLIC_BASE_URL:-}" || { echo "Set the PUBLIC_BASE_URL repository variable before building the CloudStream plugin."; exit 1; } | |
| - name: Build .cs3 plugin | |
| if: steps.changes.outputs.plugin == 'true' | |
| run: | | |
| cd cloudstream-plugin | |
| chmod +x gradlew | |
| ./gradlew StreamBridgePlugin:make makePluginsJson | |
| env: | |
| ANDROID_HOME: ${{ env.ANDROID_HOME }} | |
| ANDROID_SDK_ROOT: ${{ env.ANDROID_SDK_ROOT }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| PUBLIC_BASE_URL: ${{ env.PUBLIC_BASE_URL }} | |
| - name: Copy artifacts into repo | |
| if: steps.changes.outputs.plugin == 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p builds | |
| shopt -s globstar nullglob | |
| artifacts=(cloudstream-plugin/**/build/*.cs3) | |
| if [ ${#artifacts[@]} -eq 0 ]; then | |
| echo "No .cs3 artifact produced by the build — failing the job." | |
| exit 1 | |
| fi | |
| cp "${artifacts[@]}" builds/ | |
| cp cloudstream-plugin/plugins.json builds/plugins.json | |
| - name: Commit and push built artifacts | |
| if: steps.changes.outputs.plugin == 'true' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add builds/ | |
| git diff --cached --quiet || git commit -m "chore: CloudStream plugin build ${{ github.sha }}" | |
| git push | |
| deploy-k3s: | |
| name: Deploy To K3s | |
| needs: build-and-push | |
| if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main') | |
| runs-on: | |
| - self-hosted | |
| - k3s | |
| permissions: | |
| contents: read | |
| packages: read | |
| concurrency: | |
| group: streambridge-k3s-deploy | |
| cancel-in-progress: true | |
| env: | |
| KUBECONFIG_PATH: ${{ vars.K3S_KUBECONFIG_PATH }} | |
| VALUES_FILE: ${{ vars.K3S_VALUES_FILE || '/runner-secrets/streambridge-values.yaml' }} | |
| RELEASE_NAME: ${{ vars.K3S_RELEASE_NAME || 'streambridge' }} | |
| RELEASE_NAMESPACE: ${{ vars.K3S_NAMESPACE || 'streambridge' }} | |
| PUBLIC_BASE_URL: ${{ vars.PUBLIC_BASE_URL }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Set up kubectl | |
| uses: azure/setup-kubectl@v5 | |
| - name: Set up Helm | |
| uses: azure/setup-helm@v5 | |
| - name: Validate local deploy prerequisites | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| test -f "$VALUES_FILE" | |
| test -n "${PUBLIC_BASE_URL:-}" || { echo "Set the PUBLIC_BASE_URL repository variable before building or deploying."; exit 1; } | |
| if [ -n "${KUBECONFIG_PATH:-}" ]; then | |
| test -f "$KUBECONFIG_PATH" | |
| export KUBECONFIG="$KUBECONFIG_PATH" | |
| fi | |
| kubectl version --client | |
| helm version | |
| - name: Build Helm dependencies | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| helm dependency build ./deployment/helm/streambridge | |
| - name: Deploy Helm release with commit image tags | |
| shell: bash | |
| env: | |
| BACKEND_CHANGED: ${{ needs.build-and-push.outputs.backend_changed }} | |
| FRONTEND_CHANGED: ${{ needs.build-and-push.outputs.frontend_changed }} | |
| PAYGATE_WALLET_ADDRESS: ${{ secrets.PAYGATE_WALLET_ADDRESS }} | |
| PIA_USERNAME: ${{ secrets.PIA_USERNAME }} | |
| PIA_PASSWORD: ${{ secrets.PIA_PASSWORD }} | |
| TWENTY_API_KEY: ${{ secrets.TWENTY_API_KEY }} | |
| TWENTY_APP_SECRET: ${{ secrets.TWENTY_APP_SECRET }} | |
| TWENTY_ACCESS_TOKEN_SECRET: ${{ secrets.TWENTY_ACCESS_TOKEN_SECRET }} | |
| TWENTY_LOGIN_TOKEN_SECRET: ${{ secrets.TWENTY_LOGIN_TOKEN_SECRET }} | |
| TWENTY_REFRESH_TOKEN_SECRET: ${{ secrets.TWENTY_REFRESH_TOKEN_SECRET }} | |
| TWENTY_FILE_TOKEN_SECRET: ${{ secrets.TWENTY_FILE_TOKEN_SECRET }} | |
| TWO_CAPTCHA_API_KEY: ${{ secrets.TWO_CAPTCHA_API_KEY }} | |
| GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} | |
| RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} | |
| RESEND_FROM_EMAIL: ${{ secrets.RESEND_FROM_EMAIL }} | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${KUBECONFIG_PATH:-}" ]; then | |
| export KUBECONFIG="$KUBECONFIG_PATH" | |
| fi | |
| SHORT_SHA="${GITHUB_SHA::7}" | |
| IMAGE_TAG="sha-${SHORT_SHA}" | |
| current_tag() { | |
| local deployment_name="$1" | |
| local container_name="$2" | |
| local image | |
| image="$(kubectl get deployment "$deployment_name" -n "$RELEASE_NAMESPACE" -o jsonpath="{.spec.template.spec.containers[?(@.name==\"$container_name\")].image}" 2>/dev/null || true)" | |
| if [ -z "$image" ]; then | |
| return 0 | |
| fi | |
| printf '%s\n' "${image##*:}" | |
| } | |
| BACKEND_TAG="$IMAGE_TAG" | |
| FRONTEND_TAG="$IMAGE_TAG" | |
| if [ "${BACKEND_CHANGED}" != "true" ]; then | |
| BACKEND_TAG="$(current_tag "$RELEASE_NAME"-streambridge-backend backend)" | |
| BACKEND_TAG="${BACKEND_TAG:-$IMAGE_TAG}" | |
| fi | |
| if [ "${FRONTEND_CHANGED}" != "true" ]; then | |
| FRONTEND_TAG="$(current_tag "$RELEASE_NAME"-streambridge-frontend frontend)" | |
| FRONTEND_TAG="${FRONTEND_TAG:-$IMAGE_TAG}" | |
| fi | |
| # Enable VPN only when PIA credentials are present | |
| PIA_ARGS="" | |
| VPN_ARG="--set vpn.enabled=false" | |
| if [ -n "${PIA_USERNAME:-}" ] && [ -n "${PIA_PASSWORD:-}" ]; then | |
| PIA_ARGS="--set-string backend.secrets.piaUsername=${PIA_USERNAME} --set-string backend.secrets.piaPassword=${PIA_PASSWORD}" | |
| VPN_ARG="--set vpn.enabled=true" | |
| fi | |
| PAYGATE_ARGS="" | |
| if [ -n "${PAYGATE_WALLET_ADDRESS:-}" ]; then | |
| PAYGATE_ARGS="--set-string backend.secrets.paygateWalletAddress=${PAYGATE_WALLET_ADDRESS} --set-string backend.env.paygateEnabled=true" | |
| fi | |
| TWENTY_ARGS="" | |
| if [ -n "${TWENTY_API_KEY:-}" ]; then | |
| TWENTY_ARGS="--set-string backend.secrets.twentyApiKey=${TWENTY_API_KEY}" | |
| fi | |
| TWO_CAPTCHA_ARGS="" | |
| if [ -n "${TWO_CAPTCHA_API_KEY:-}" ]; then | |
| TWO_CAPTCHA_ARGS="--set-string backend.secrets.twoCaptchaApiKey=${TWO_CAPTCHA_API_KEY}" | |
| fi | |
| GOOGLE_ARGS="" | |
| if [ -n "${GOOGLE_CLIENT_ID:-}" ]; then | |
| GOOGLE_ARGS="--set-string backend.secrets.googleClientId=${GOOGLE_CLIENT_ID}" | |
| fi | |
| RESEND_ARGS="" | |
| if [ -n "${RESEND_API_KEY:-}" ]; then | |
| RESEND_ARGS="--set-string backend.secrets.resendApiKey=${RESEND_API_KEY}" | |
| if [ -n "${RESEND_FROM_EMAIL:-}" ]; then | |
| RESEND_ARGS+=" --set-string backend.secrets.resendFromEmail=${RESEND_FROM_EMAIL}" | |
| fi | |
| fi | |
| TWENTY_SECRET_ARGS="" | |
| if [ -n "${TWENTY_APP_SECRET:-}" ]; then | |
| TWENTY_SECRET_ARGS=" --set-string twenty.secrets.appSecret=${TWENTY_APP_SECRET}" | |
| TWENTY_SECRET_ARGS+=" --set-string twenty.secrets.accessTokenSecret=${TWENTY_ACCESS_TOKEN_SECRET}" | |
| TWENTY_SECRET_ARGS+=" --set-string twenty.secrets.loginTokenSecret=${TWENTY_LOGIN_TOKEN_SECRET}" | |
| TWENTY_SECRET_ARGS+=" --set-string twenty.secrets.refreshTokenSecret=${TWENTY_REFRESH_TOKEN_SECRET}" | |
| TWENTY_SECRET_ARGS+=" --set-string twenty.secrets.fileTokenSecret=${TWENTY_FILE_TOKEN_SECRET}" | |
| fi | |
| helm upgrade --install "$RELEASE_NAME" ./deployment/helm/streambridge \ | |
| --namespace "$RELEASE_NAMESPACE" \ | |
| --create-namespace \ | |
| -f "$VALUES_FILE" \ | |
| --set-string frontend.image.tag="$FRONTEND_TAG" \ | |
| --set-string backend.image.tag="$BACKEND_TAG" \ | |
| --set-string backend.env.frontendUrl="$PUBLIC_BASE_URL" \ | |
| --set-string backend.env.baseUrl="$PUBLIC_BASE_URL" \ | |
| --set-string backend.env.apiBaseUrl="$PUBLIC_BASE_URL" \ | |
| $VPN_ARG \ | |
| $PIA_ARGS \ | |
| $PAYGATE_ARGS \ | |
| $TWENTY_ARGS \ | |
| $TWO_CAPTCHA_ARGS \ | |
| $TWENTY_SECRET_ARGS \ | |
| $GOOGLE_ARGS \ | |
| $RESEND_ARGS | |
| - name: Wait for rollout | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${KUBECONFIG_PATH:-}" ]; then | |
| export KUBECONFIG="$KUBECONFIG_PATH" | |
| fi | |
| kubectl rollout status deployment/"$RELEASE_NAME"-streambridge-backend -n "$RELEASE_NAMESPACE" --timeout=300s | |
| kubectl rollout status deployment/"$RELEASE_NAME"-streambridge-frontend -n "$RELEASE_NAMESPACE" --timeout=300s | |
| if kubectl get deployment/"$RELEASE_NAME"-streambridge-scheduler -n "$RELEASE_NAMESPACE" >/dev/null 2>&1; then | |
| kubectl rollout status deployment/"$RELEASE_NAME"-streambridge-scheduler -n "$RELEASE_NAMESPACE" --timeout=300s | |
| fi |