From 16772c67b731df238dd72f9c47785724c4487fd8 Mon Sep 17 00:00:00 2001 From: Hayden McAfee Date: Sun, 4 Jan 2026 11:06:35 -0800 Subject: [PATCH 1/4] Add dockerfile --- .dockerignore | 28 ++++++++++++++++++++++++++++ .env.example | 3 +++ .gitignore | 3 ++- Dockerfile | 35 +++++++++++++++++++++++++++++++++++ docker-compose.yml | 13 +++++++++++++ nginx.conf | 28 ++++++++++++++++++++++++++++ src/ts/Application.ts | 3 ++- webpack.config.js | 8 +++++++- 8 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 nginx.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f0e2f9c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +# Dependencies +node_modules +npm-debug.log + +# Git +.git +.gitignore +.github + +# Build outputs +dist/index.js +dist/index.js.map + +# Development +.vscode +.idea + +# Testing +coverage +test + +# Documentation +README.md +LICENSE + +# Misc +*.md +.DS_Store diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..950ec85 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# API Configuration +# The base URL for the Purdue.io API +API_URL=https://api.purdue.io/odata diff --git a/.gitignore b/.gitignore index 02907f1..f91ab0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dist/index.js dist/index.js.map -node_modules \ No newline at end of file +node_modules +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7f4e50f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# Stage 1: Build the application +FROM node:18-alpine AS builder + +# Build argument for API URL +ARG API_URL=https://api.purdue.io/odata + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production=false + +# Copy source code +COPY . . + +# Build the application with API_URL environment variable +ENV API_URL=${API_URL} +RUN npm run publish + +# Stage 2: Serve with nginx +FROM nginx:alpine + +# Copy nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Copy built application from builder stage +COPY --from=builder /app/dist /usr/share/nginx/html + +# Expose port 80 +EXPOSE 80 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3788159 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' + +services: + web: + build: + context: . + dockerfile: Dockerfile + args: + - API_URL=${API_URL:-https://api.purdue.io/odata} + ports: + - "8080:80" + restart: unless-stopped + container_name: purdueio-browser diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..c7da6f7 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,28 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Cache static assets + location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # SPA fallback - serve index.html for all routes + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/src/ts/Application.ts b/src/ts/Application.ts index 2a5050c..1085dda 100644 --- a/src/ts/Application.ts +++ b/src/ts/Application.ts @@ -28,7 +28,8 @@ export class Application public static run(): Application { - let dataSource = new PurdueApiDataSource("https://api.purdue.io/odata"); + const apiUrl = process.env.API_URL || "https://api.purdue.io/odata"; + let dataSource = new PurdueApiDataSource(apiUrl); let returnVal = new Application(dataSource); returnVal.start(); return returnVal; diff --git a/webpack.config.js b/webpack.config.js index ec3eaed..c60c437 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,5 @@ const path = require('path'); +const webpack = require('webpack'); var config = { entry: './src/index.ts', @@ -18,7 +19,12 @@ var config = { output: { filename: 'index.js', path: path.resolve(__dirname, 'dist'), - } + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.API_URL': JSON.stringify(process.env.API_URL || 'https://api.purdue.io/odata') + }) + ] }; module.exports = (env, argv) => { From 7a22eaf703ccbe7fb6a8234a95e798dfa7602172 Mon Sep 17 00:00:00 2001 From: Hayden McAfee Date: Sun, 4 Jan 2026 11:11:31 -0800 Subject: [PATCH 2/4] Add GHA workflows --- .github/workflows/docker-pr.yml | 45 ++++++++++ .github/workflows/docker-publish.yml | 126 +++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 .github/workflows/docker-pr.yml create mode 100644 .github/workflows/docker-publish.yml diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml new file mode 100644 index 0000000..9f17153 --- /dev/null +++ b/.github/workflows/docker-pr.yml @@ -0,0 +1,45 @@ +name: Docker PR Verification + +on: + pull_request: + branches: [ main ] + paths: + - 'Dockerfile' + - 'docker-compose.yml' + - 'nginx.conf' + - '.dockerignore' + - 'src/**' + - 'package*.json' + - 'webpack.config.js' + - 'tsconfig.json' + +jobs: + verify: + name: Verify Docker Build + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: false + tags: purdueio-browser:pr-${{ github.event.pull_request.number }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Test image runs + run: | + docker run -d --name test-container -p 8080:80 purdueio-browser:pr-${{ github.event.pull_request.number }} + sleep 5 + curl -f http://localhost:8080 || exit 1 + docker stop test-container diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..b46f02c --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,126 @@ +name: Docker Publish + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Tag to build and push' + required: true + default: 'latest' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + name: Build Docker Images + runs-on: ${{ matrix.os }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + platform: linux/amd64 + - os: ubuntu-24.04-arm + platform: linux/arm64 + + steps: + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + name: Merge Docker Images and Push + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + needs: + - build + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=tag + type=raw,value=latest,enable={{is_default_branch}} + type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }} + + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} From f7a17741111277e0487ad8857377728affc872a3 Mon Sep 17 00:00:00 2001 From: Hayden McAfee Date: Sun, 4 Jan 2026 11:17:20 -0800 Subject: [PATCH 3/4] Simplify publish workflow --- .github/workflows/docker-publish.yml | 94 ++++------------------------ 1 file changed, 13 insertions(+), 81 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index b46f02c..123754e 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -15,33 +15,14 @@ env: IMAGE_NAME: ${{ github.repository }} jobs: - build: - name: Build Docker Images - runs-on: ${{ matrix.os }} + build-and-push: + name: Build and Push Docker Image + runs-on: ubuntu-latest permissions: contents: read packages: write - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - platform: linux/amd64 - - os: ubuntu-24.04-arm - platform: linux/arm64 steps: - - name: Prepare - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: Docker metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Checkout code uses: actions/checkout@v4 @@ -55,56 +36,6 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push by digest - id: build - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,push-by-digest=true,name-canonical=true,push=true - - - name: Export digest - run: | - mkdir -p ${{ runner.temp }}/digests - digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-${{ env.PLATFORM_PAIR }} - path: ${{ runner.temp }}/digests/* - if-no-files-found: error - retention-days: 1 - - merge: - name: Merge Docker Images and Push - runs-on: ubuntu-latest - permissions: - packages: write - contents: read - needs: - - build - steps: - - name: Download digests - uses: actions/download-artifact@v4 - with: - path: ${{ runner.temp }}/digests - pattern: digests-* - merge-multiple: true - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Docker metadata id: meta uses: docker/metadata-action@v5 @@ -115,12 +46,13 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }} - - name: Create manifest list and push - working-directory: ${{ runner.temp }}/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) - - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max From ee4d87b96e5469ade32b7233e0944db9b2151596 Mon Sep 17 00:00:00 2001 From: Hayden McAfee Date: Sun, 4 Jan 2026 11:21:39 -0800 Subject: [PATCH 4/4] Fix workflows --- .github/workflows/docker-pr.yml | 1 + .github/workflows/main.yml | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index 9f17153..3df5a12 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -33,6 +33,7 @@ jobs: context: . file: ./Dockerfile push: false + load: true tags: purdueio-browser:pr-${{ github.event.pull_request.number }} cache-from: type=gha cache-to: type=gha,mode=max diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 50343ae..73aa763 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,26 +9,28 @@ on: jobs: build: runs-on: ubuntu-latest - + steps: - - uses: actions/checkout@v2 + - name: Checkout code + uses: actions/checkout@v4 - - name: Use Node.js 14.x - uses: actions/setup-node@v1 + - name: Use Node.js 18.x + uses: actions/setup-node@v4 with: - node-version: 14.x + node-version: 18.x + cache: 'npm' - - name: Restore Packages - run: npm install + - name: Install dependencies + run: npm ci - - name: Build and Publish + - name: Build run: npm run publish - + - name: Test run: npm run test - - name: Upload Artifact - uses: actions/upload-artifact@v2 + - name: Upload build artifacts + uses: actions/upload-artifact@v4 with: name: Browser path: dist