diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index a0d9a9b..d2ff775 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -8,15 +8,9 @@ on: workflow_dispatch: workflow_call: outputs: - api_image: - description: "The full API image tag" - value: ${{ jobs.build-api.outputs.image_tag }} - queue_image: - description: "The full Queue image tag" - value: ${{ jobs.build-queue.outputs.image_tag }} - ui_image: - description: "The full UI image tag" - value: ${{ jobs.build-ui.outputs.image_tag }} + image_tag: + description: "The tag for all images in this run" + value: ${{ jobs.setup.outputs.version }} jobs: setup: @@ -34,11 +28,6 @@ jobs: build-api: needs: setup runs-on: ubuntu-latest - outputs: - image_tag: ${{ steps.image_tag.outputs.value }} - permissions: - contents: read - packages: write steps: - name: Checkout code uses: actions/checkout@v4 @@ -76,11 +65,6 @@ jobs: build-queue: needs: setup runs-on: ubuntu-latest - outputs: - image_tag: ${{ steps.image_tag.outputs.value }} - permissions: - contents: read - packages: write steps: - name: Checkout code uses: actions/checkout@v4 @@ -118,11 +102,6 @@ jobs: build-ui: needs: setup runs-on: ubuntu-latest - outputs: - image_tag: ${{ steps.image_tag.outputs.value }} - permissions: - contents: read - packages: write steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-ui.yml similarity index 84% rename from .github/workflows/build-web.yml rename to .github/workflows/build-ui.yml index 6523cdb..9760e7f 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-ui.yml @@ -1,4 +1,4 @@ -name: build web +name: build ui on: push: @@ -9,11 +9,6 @@ on: jobs: build: runs-on: ubuntu-latest - - defaults: - run: - working-directory: packages/web - steps: - uses: actions/checkout@v4 @@ -24,14 +19,14 @@ jobs: cache: "yarn" cache-dependency-path: | yarn.lock - packages/web/yarn.lock + services/barn-ui/yarn.lock # Cache the build output directory - name: Cache build output uses: actions/cache@v4 with: path: | - packages/web/.svelte-kit + services/barn-ui/.svelte-kit key: ${{ runner.os }}-web-build-${{ github.sha }} restore-keys: | ${{ runner.os }}-web-build- @@ -42,7 +37,7 @@ jobs: with: path: | node_modules - packages/web/node_modules + services/barn-ui/node_modules key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-node-modules- diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 4558da1..cecfc48 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -1,4 +1,4 @@ -name: Deploy to Staging +name: deploy - staging on: workflow_dispatch: @@ -14,7 +14,7 @@ jobs: deploy: runs-on: ubuntu-latest environment: staging - + needs: build steps: - name: Checkout code uses: actions/checkout@v4 @@ -22,7 +22,7 @@ jobs: - name: Install doctl uses: digitalocean/action-doctl@v2 with: - token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + token: ${{ secrets.DO_REGISTRY_KEY }} - name: Save DigitalOcean kubeconfig run: doctl kubernetes cluster kubeconfig save ${{ vars.CLUSTER_NAME }} @@ -30,9 +30,11 @@ jobs: - name: Update kustomization run: | cd k8s/overlays/staging - kustomize edit set image api=${{ needs.build.outputs.api_image }} + kustomize edit set image api=${{ secrets.DO_REGISTRY }}/${{ vars.API_IMAGE }}:${{ needs.build.outputs.image_tag }} + kustomize edit set image queue=${{ secrets.DO_REGISTRY }}/${{ vars.QUEUE_IMAGE }}:${{ needs.build.outputs.image_tag }} + kustomize edit set image ui=${{ secrets.DO_REGISTRY }}/${{ vars.UI_IMAGE }}:${{ needs.build.outputs.image_tag }} - name: Deploy to staging run: | kubectl apply -k k8s/overlays/staging - kubectl rollout status deployment/api -n staging + kubectl rollout status deployment/farmhand-api-staging -n staging --timeout=2m diff --git a/Cargo.lock b/Cargo.lock index 1a12cae..50589e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -80,7 +80,7 @@ checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" name = "api" version = "0.1.0" dependencies = [ - "axum 0.7.7", + "axum 0.7.9", "axum-extra", "chrono", "db", @@ -180,9 +180,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core 0.4.5", @@ -687,6 +687,7 @@ name = "forge" version = "0.1.0" dependencies = [ "anyhow", + "axum 0.7.9", "db", "queue", "tokio", diff --git a/README.md b/README.md index 15a6010..9dd23ab 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # farmhand [![build api](https://github.com/sneakycrow/farmhand/actions/workflows/build-api.yml/badge.svg)](https://github.com/sneakycrow/farmhand/actions/workflows/build-api.yml) -[![build web](https://github.com/sneakycrow/farmhand/actions/workflows/build-web.yml/badge.svg)](https://github.com/sneakycrow/farmhand/actions/workflows/build-web.yml) +[![build ui](https://github.com/sneakycrow/farmhand/actions/workflows/build-ui.yml/badge.svg)](https://github.com/sneakycrow/farmhand/actions/workflows/build-ui.yml) [![build queue](https://github.com/sneakycrow/farmhand/actions/workflows/build-queue.yml/badge.svg)](https://github.com/sneakycrow/farmhand/actions/workflows/build-queue.yml) Farmhand is a powerful, open-source clip and VOD management system built for creators and artists who want more control over their content. diff --git a/config/queue.Dockerfile b/config/queue.Dockerfile index 6b323e1..b373508 100644 --- a/config/queue.Dockerfile +++ b/config/queue.Dockerfile @@ -40,5 +40,8 @@ RUN apt-get update && apt-get install -y \ # Copy the binary from builder COPY --from=builder /usr/local/bin/forge /usr/local/bin/forge +# Expose the health check server +EXPOSE 8080 + # Set the entrypoint ENTRYPOINT ["/usr/local/bin/forge"] diff --git a/k8s/base/api/configmap.yaml b/k8s/base/api/configmap.yaml index 8185c0d..b846f77 100644 --- a/k8s/base/api/configmap.yaml +++ b/k8s/base/api/configmap.yaml @@ -3,4 +3,8 @@ kind: ConfigMap metadata: name: farmhand-api-config data: + FRONTEND_URL: "https://staging.farmhand.witchscrow.com" + RUST_LOG: "api=debug,db=debug,queue=debug,tower_http=debug,axum::rejection=trace" + FFMPEG_LOCATION: "/opt/homebrew/bin/ffmpeg" # TODO: Make this is the right path for docker context + TWITCH_REDIRECT_URI: "https://staging.api.farmhand.witchscrow.com/auth/twitch/callback" LOG_LEVEL: "api=debug" diff --git a/k8s/base/api/deployment.yaml b/k8s/base/api/deployment.yaml index f0e5066..22e8558 100644 --- a/k8s/base/api/deployment.yaml +++ b/k8s/base/api/deployment.yaml @@ -18,7 +18,28 @@ spec: - containerPort: 3000 envFrom: - configMapRef: - name: api-config + name: farmhand-api-config + env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: farmhand-db + key: uri + - name: TWITCH_CLIENT_ID + valueFrom: + secretKeyRef: + name: twitch-credentials + key: client-id + - name: TWITCH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: twitch-credentials + key: client-secret + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: jwt + key: secret resources: requests: cpu: "100m" diff --git a/k8s/base/api/kustomization.yaml b/k8s/base/api/kustomization.yaml index d9af2e9..ab071dc 100644 --- a/k8s/base/api/kustomization.yaml +++ b/k8s/base/api/kustomization.yaml @@ -5,8 +5,3 @@ resources: - deployment.yaml - service.yaml - configmap.yaml - -images: - - name: api - newName: placeholder - newTag: placeholder diff --git a/k8s/base/queue/configmap.yaml b/k8s/base/queue/configmap.yaml new file mode 100644 index 0000000..95a5825 --- /dev/null +++ b/k8s/base/queue/configmap.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: farmhand-queue-config +data: + LOG_LEVEL: "queue=debug" diff --git a/k8s/base/queue/deployment.yaml b/k8s/base/queue/deployment.yaml new file mode 100644 index 0000000..caab2d2 --- /dev/null +++ b/k8s/base/queue/deployment.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: farmhand-queue +spec: + selector: + matchLabels: + app: farmhand-queue + template: + metadata: + labels: + app: farmhand-queue + spec: + containers: + - name: farmhand-queue + image: queue + ports: + - containerPort: 8080 + envFrom: + - configMapRef: + name: farmhand-queue-config + env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: farmhand-db + key: uri + resources: + requests: + cpu: "100m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 diff --git a/k8s/base/queue/kustomization.yaml b/k8s/base/queue/kustomization.yaml new file mode 100644 index 0000000..92585b0 --- /dev/null +++ b/k8s/base/queue/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - configmap.yaml diff --git a/k8s/base/ui/configmap.yaml b/k8s/base/ui/configmap.yaml new file mode 100644 index 0000000..781f08f --- /dev/null +++ b/k8s/base/ui/configmap.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: farmhand-ui-config +data: + API_URL: "https://staging.api.farmhand.witchscrow.com" diff --git a/k8s/base/ui/deployment.yaml b/k8s/base/ui/deployment.yaml new file mode 100644 index 0000000..7c0ddf2 --- /dev/null +++ b/k8s/base/ui/deployment.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: farmhand-ui +spec: + selector: + matchLabels: + app: farmhand-ui + template: + metadata: + labels: + app: farmhand-ui + spec: + containers: + - name: farmhand-ui + image: ui + ports: + - containerPort: 3000 + envFrom: + - configMapRef: + name: farmhand-ui-config + resources: + requests: + cpu: "100m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" + readinessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 10 diff --git a/k8s/base/ui/kustomization.yaml b/k8s/base/ui/kustomization.yaml new file mode 100644 index 0000000..ab071dc --- /dev/null +++ b/k8s/base/ui/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - service.yaml + - configmap.yaml diff --git a/k8s/base/ui/service.yaml b/k8s/base/ui/service.yaml new file mode 100644 index 0000000..716d4e5 --- /dev/null +++ b/k8s/base/ui/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: farmhand-ui +spec: + selector: + app: farmhand-ui + ports: + - port: 80 + targetPort: 3000 + type: ClusterIP diff --git a/k8s/overlays/staging/ingress.yaml b/k8s/overlays/staging/ingress.yaml new file mode 100644 index 0000000..403d84b --- /dev/null +++ b/k8s/overlays/staging/ingress.yaml @@ -0,0 +1,78 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: farmhand-ui + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + nginx.ingress.kubernetes.io/proxy-body-size: "50m" + # Cloudflare specific settings + nginx.ingress.kubernetes.io/use-forwarded-headers: "true" + nginx.ingress.kubernetes.io/enable-real-ip: "true" + nginx.ingress.kubernetes.io/real-ip-header: "X-Forwarded-For" + nginx.ingress.kubernetes.io/proxy-real-ip-cidr: "173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22" +spec: + ingressClassName: nginx + tls: + - hosts: + - staging.farmhand.witchscrow.com + secretName: farmhand-ui-staging-tls + rules: + - host: staging.farmhand.witchscrow.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: farmhand-ui-staging + port: + number: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: farmhand-api + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + nginx.ingress.kubernetes.io/proxy-body-size: "50m" + nginx.ingress.kubernetes.io/use-forwarded-headers: "true" + nginx.ingress.kubernetes.io/enable-real-ip: "true" + nginx.ingress.kubernetes.io/real-ip-header: "X-Forwarded-For" + nginx.ingress.kubernetes.io/proxy-real-ip-cidr: "173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22" + nginx.ingress.kubernetes.io/configuration-snippet: | + proxy_set_header Authorization $http_authorization; + proxy_pass_header Authorization; + proxy_set_header X-Forwarded-Proto https; + nginx.ingress.kubernetes.io/proxy-set-headers: "ingress-nginx/custom-headers" +spec: + ingressClassName: nginx + tls: + - hosts: + - staging.api.farmhand.witchscrow.com + secretName: farmhand-api-staging-tls + rules: + - host: staging.api.farmhand.witchscrow.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: farmhand-api-staging + port: + number: 80 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: custom-headers + namespace: ingress-nginx +data: + X-Forwarded-Proto: https + X-Real-IP: $remote_addr + X-Forwarded-For: $proxy_add_x_forwarded_for + Authorization: $http_authorization diff --git a/k8s/overlays/staging/kustomization.yaml b/k8s/overlays/staging/kustomization.yaml index 7411124..e4601c1 100644 --- a/k8s/overlays/staging/kustomization.yaml +++ b/k8s/overlays/staging/kustomization.yaml @@ -6,11 +6,14 @@ nameSuffix: -staging resources: - ../../base/api + - ../../base/queue + - ../../base/ui + - ingress.yaml images: - name: api - newName: placeholder - newTag: placeholder + - name: queue + - name: ui labels: - includeSelectors: true diff --git a/services/barn-ui/src/lib/server/users.ts b/services/barn-ui/src/lib/server/users.ts index f784145..a1b8467 100644 --- a/services/barn-ui/src/lib/server/users.ts +++ b/services/barn-ui/src/lib/server/users.ts @@ -8,20 +8,38 @@ export enum UserError { export const getTokenIdentity = async (token: string): Promise => { try { - // Fetch user data from your API + console.log('Making request to:', `${env.API_URL}/user/me`); + console.log('With token:', token.substring(0, 10) + '...'); // Log partial token for safety + + const headers = { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json' + }; + const response = await fetch(`${env.API_URL}/user/me`, { - headers: { - Authorization: `Bearer ${token}` - } + method: 'GET', + headers + }); + + // Log the complete request details + console.log('Request details:', { + url: `${env.API_URL}/user/me`, + method: 'GET', + headers: headers }); if (response.ok) { const userData: User = await response.json(); return userData; } else { + console.error('Response status:', response.status); + console.error('Response headers:', response.headers); + const errorText = await response.text(); + console.error('Error response:', errorText); throw UserError.INVALID_TOKEN; } } catch (e) { + console.error('Error in getTokenIdentity:', e); if (e === UserError.INVALID_TOKEN) { throw e; } diff --git a/services/barn-ui/src/routes/(guest)/health/+server.ts b/services/barn-ui/src/routes/(guest)/health/+server.ts new file mode 100644 index 0000000..3de85b7 --- /dev/null +++ b/services/barn-ui/src/routes/(guest)/health/+server.ts @@ -0,0 +1,8 @@ +export function GET() { + return new Response('OK', { + status: 200, + headers: { + 'Content-Type': 'text/plain' + } + }); +} diff --git a/services/barn-ui/src/routes/(guest)/login/+page.svelte b/services/barn-ui/src/routes/(guest)/login/+page.svelte index 6865807..02e70b4 100644 --- a/services/barn-ui/src/routes/(guest)/login/+page.svelte +++ b/services/barn-ui/src/routes/(guest)/login/+page.svelte @@ -13,7 +13,7 @@ clearInterval(interval); goto('/', { invalidateAll: true }); } - }, 50); + }, 250); return () => clearInterval(interval); }); diff --git a/services/forge-queue/Cargo.toml b/services/forge-queue/Cargo.toml index f4d4abb..4a096c4 100644 --- a/services/forge-queue/Cargo.toml +++ b/services/forge-queue/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = "1" +axum = { version = "0.7.9", features = ["tracing"] } db = { path = "../../packages/db" } queue = { path = "../../packages/queue" } tokio = { version = "1", features = ["full"] } diff --git a/services/forge-queue/src/main.rs b/services/forge-queue/src/main.rs index f3b7241..b2eb49a 100644 --- a/services/forge-queue/src/main.rs +++ b/services/forge-queue/src/main.rs @@ -14,6 +14,15 @@ async fn main() -> anyhow::Result<()> { .with(tracing_subscriber::fmt::layer()) .init(); + // Spawn a health check server + tokio::spawn(async { + let app = axum::Router::new().route("/health", axum::routing::get(|| async { "OK" })); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); + tracing::info!("Health check server listening on port 8080"); + axum::serve(listener, app).await.unwrap(); + }); + // Get database connection pool using the db package let db_pool = connect_to_database().await?; diff --git a/services/silo-api/src/main.rs b/services/silo-api/src/main.rs index 2774cb9..4628380 100644 --- a/services/silo-api/src/main.rs +++ b/services/silo-api/src/main.rs @@ -88,15 +88,16 @@ async fn main() { middleware::auth::auth_middleware, )), ) - .route( - "/upload", - post(routes::upload::upload_video) - .layer(DefaultBodyLimit::max(UPLOAD_CHUNK_SIZE * 8)) - .layer(axum_mw::from_fn_with_state( - state.clone(), - middleware::auth::auth_middleware, - )), - ) + // TODO: Update this to use Backblaze instead + // .route( + // "/upload", + // post(routes::upload::upload_video) + // .layer(DefaultBodyLimit::max(UPLOAD_CHUNK_SIZE * 8)) + // .layer(axum_mw::from_fn_with_state( + // state.clone(), + // middleware::auth::auth_middleware, + // )), + // ) .nest( "/video", Router::new() diff --git a/services/silo-api/src/middleware/auth.rs b/services/silo-api/src/middleware/auth.rs index 7e2069b..ce357e7 100644 --- a/services/silo-api/src/middleware/auth.rs +++ b/services/silo-api/src/middleware/auth.rs @@ -27,6 +27,7 @@ pub async fn auth_middleware( // This middleware allows for optional users, so we just return early if no auth headers are found None => { tracing::debug!("No auth headers, skipping user lookup"); + tracing::debug!("Headers: {:#?}", req.headers()); req.extensions_mut().insert(None::); return Ok(next.run(req).await); }