Skip to content

Commit 8c7a80e

Browse files
committed
Github actions and workflow configuration
1 parent 3cf3c9b commit 8c7a80e

7 files changed

Lines changed: 269 additions & 21 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: "Docker Registry Login"
2+
description: "Login to Docker registries (GHCR and optionally Docker Hub)"
3+
4+
inputs:
5+
ghcr-token:
6+
description: "GitHub token for GHCR authentication"
7+
required: true
8+
ghcr-username:
9+
description: "GitHub username for GHCR"
10+
required: true
11+
dockerhub-username:
12+
description: "Docker Hub username (optional)"
13+
required: false
14+
default: ""
15+
dockerhub-token:
16+
description: "Docker Hub token (optional)"
17+
required: false
18+
default: ""
19+
20+
runs:
21+
using: "composite"
22+
steps:
23+
- name: 🔐 Log in to GitHub Container Registry
24+
uses: docker/login-action@v3
25+
with:
26+
registry: ghcr.io
27+
username: ${{ inputs.ghcr-username }}
28+
password: ${{ inputs.ghcr-token }}
29+
30+
- name: 🔐 Log in to Docker Hub
31+
if: inputs.dockerhub-username != ''
32+
uses: docker/login-action@v3
33+
with:
34+
registry: docker.io
35+
username: ${{ inputs.dockerhub-username }}
36+
password: ${{ inputs.dockerhub-token }}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: "Docker Metadata Extractor"
2+
description: "Extract Docker image metadata and tags"
3+
4+
inputs:
5+
service:
6+
description: "Service name"
7+
required: true
8+
registry:
9+
description: "Container registry"
10+
required: false
11+
default: "ghcr.io"
12+
image-prefix:
13+
description: "Image name prefix"
14+
required: false
15+
default: "nesohq/nesohq-issue-tracker"
16+
17+
outputs:
18+
tags:
19+
description: "Generated Docker tags"
20+
value: ${{ steps.meta.outputs.tags }}
21+
labels:
22+
description: "Generated Docker labels"
23+
value: ${{ steps.meta.outputs.labels }}
24+
25+
runs:
26+
using: "composite"
27+
steps:
28+
- name: 🏷️ Extract metadata
29+
id: meta
30+
uses: docker/metadata-action@v5
31+
with:
32+
images: |
33+
${{ inputs.registry }}/${{ inputs.image-prefix }}/${{ inputs.service }}
34+
tags: |
35+
type=ref,event=branch
36+
type=ref,event=pr
37+
type=semver,pattern={{version}}
38+
type=semver,pattern={{major}}.{{minor}}
39+
type=semver,pattern={{major}}
40+
type=sha,prefix={{branch}}-
41+
type=raw,value=latest,enable={{is_default_branch}}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: Build and Push Docker Images
2+
3+
on:
4+
push:
5+
branches: [master]
6+
tags:
7+
- 'v*.*.*'
8+
9+
env:
10+
REGISTRY_GHCR: ghcr.io
11+
IMAGE_PREFIX: nesohq/nesohq-issue-tracker
12+
13+
jobs:
14+
detect-changes:
15+
runs-on: ubuntu-latest
16+
outputs:
17+
client: ${{ steps.filter.outputs.client }}
18+
server: ${{ steps.filter.outputs.server }}
19+
steps:
20+
- uses: actions/checkout@v4
21+
22+
- name: Detect changed services
23+
id: filter
24+
uses: dorny/paths-filter@v3
25+
with:
26+
filters: |
27+
client:
28+
- 'client/**'
29+
server:
30+
- 'server/**'
31+
32+
build-and-push:
33+
needs: detect-changes
34+
if: github.event_name != 'pull_request'
35+
permissions:
36+
contents: read
37+
packages: write
38+
strategy:
39+
fail-fast: false
40+
matrix:
41+
service: [client, server]
42+
uses: ./.github/workflows/docker-build.yml
43+
with:
44+
service: ${{ matrix.service }}
45+
should-build: ${{ needs.detect-changes.outputs[matrix.service] == 'true' || github.ref_type == 'tag' }}
46+
secrets: inherit
47+
48+
# Trigger infrastructure deployment after successful builds
49+
update-deployment:
50+
needs: [detect-changes, build-and-push]
51+
# Trigger if at least one build succeeded (not cancelled or skipped)
52+
if: |
53+
always() &&
54+
github.ref == 'refs/heads/master' &&
55+
needs.build-and-push.result != 'cancelled' &&
56+
needs.build-and-push.result != 'skipped'
57+
runs-on: ubuntu-latest
58+
59+
steps:
60+
- name: 🚀 Trigger Infrastructure Deployment
61+
run: |
62+
curl -X POST \
63+
-H "Authorization: token ${{ secrets.INFRA_REPO_TOKEN }}" \
64+
-H "Accept: application/vnd.github.v3+json" \
65+
https://api.github.com/repos/opskraken/nesohq-infra/dispatches \
66+
-d '{"event_type":"deploy-staging"}'
67+
68+
- name: 📝 Deployment Summary
69+
run: |
70+
echo "## 🚀 Deployment Triggered" >> $GITHUB_STEP_SUMMARY
71+
echo "" >> $GITHUB_STEP_SUMMARY
72+
echo "**Status:** ✅ Infrastructure deployment triggered" >> $GITHUB_STEP_SUMMARY
73+
echo "**Repository:** nesohq/nesohq-issue-tracker" >> $GITHUB_STEP_SUMMARY
74+
echo "**Event:** deploy-nesohq-issue-tracker" >> $GITHUB_STEP_SUMMARY
75+
echo "" >> $GITHUB_STEP_SUMMARY
76+
echo "**Note:** Deployment triggered even if some builds failed" >> $GITHUB_STEP_SUMMARY
77+
echo "**Built Images:**" >> $GITHUB_STEP_SUMMARY
78+
echo '```' >> $GITHUB_STEP_SUMMARY
79+
echo "ghcr.io/nesohq/nesohq-issue-tracker/client:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
80+
echo "ghcr.io/nesohq/nesohq-issue-tracker/server:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
81+
echo '```' >> $GITHUB_STEP_SUMMARY
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: Docker Build and Push
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
service:
7+
required: true
8+
type: string
9+
description: 'Service name to build'
10+
should-build:
11+
required: true
12+
type: boolean
13+
description: 'Whether to build this service'
14+
secrets:
15+
DOCKERHUB_USERNAME:
16+
required: false
17+
DOCKERHUB_TOKEN:
18+
required: false
19+
20+
env:
21+
REGISTRY_GHCR: ghcr.io
22+
IMAGE_PREFIX: nesohq/nesohq-issue-tracker
23+
24+
jobs:
25+
build:
26+
runs-on: ubuntu-latest
27+
if: inputs.should-build
28+
29+
permissions:
30+
contents: read
31+
packages: write
32+
33+
steps:
34+
- name: 📥 Checkout code
35+
uses: actions/checkout@v4
36+
37+
- name: 🔧 Set up Docker Buildx
38+
uses: docker/setup-buildx-action@v3
39+
40+
- name: 🔐 Login to registries
41+
uses: ./.github/actions/docker-login
42+
with:
43+
ghcr-token: ${{ secrets.GITHUB_TOKEN }}
44+
ghcr-username: ${{ github.actor }}
45+
# Uncomment to enable Docker Hub
46+
# dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
47+
# dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
48+
49+
- name: 🏷️ Extract metadata
50+
id: meta
51+
uses: ./.github/actions/docker-metadata
52+
with:
53+
service: ${{ inputs.service }}
54+
registry: ${{ env.REGISTRY_GHCR }}
55+
image-prefix: ${{ env.IMAGE_PREFIX }}
56+
57+
- name: 🐳 Build and push Docker image
58+
uses: docker/build-push-action@v5
59+
with:
60+
context: ./${{ inputs.service }}
61+
file: ./${{ inputs.service }}/Dockerfile
62+
push: true
63+
tags: ${{ steps.meta.outputs.tags }}
64+
labels: ${{ steps.meta.outputs.labels }}
65+
cache-from: type=gha
66+
cache-to: type=gha,mode=max
67+
# Build only for amd64 for faster builds (add arm64 if needed)
68+
platforms: linux/amd64
69+
build-args: |
70+
GITHUB_CLIENT_ID=${{ secrets.GH_CLIENT_ID }}
71+
GITHUB_CLIENT_SECRET=${{ secrets.GH_CLIENT_SECRET }}
72+
73+
GITHUB_REDIRECT_URI=https://tracker.nesohq.org/auth/callback
74+
75+
CORS_ORIGIN=https://tracker.nesohq.org
76+
77+
- name: 📝 Generate image summary
78+
run: |
79+
echo "## 🐳 Docker Image Built" >> $GITHUB_STEP_SUMMARY
80+
echo "" >> $GITHUB_STEP_SUMMARY
81+
echo "**Service:** ${{ inputs.service }}" >> $GITHUB_STEP_SUMMARY
82+
echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
83+
echo '```' >> $GITHUB_STEP_SUMMARY
84+
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
85+
echo '```' >> $GITHUB_STEP_SUMMARY
86+
echo "" >> $GITHUB_STEP_SUMMARY
87+
echo "**Pull command:**" >> $GITHUB_STEP_SUMMARY
88+
echo '```bash' >> $GITHUB_STEP_SUMMARY
89+
echo "docker pull ${{ env.REGISTRY_GHCR }}/${{ env.IMAGE_PREFIX }}/${{ inputs.service }}:latest" >> $GITHUB_STEP_SUMMARY
90+
echo '```' >> $GITHUB_STEP_SUMMARY

server/config/index.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ dotenv.config({ path: path.join(serverRoot, '..', '.env') })
1111
/** Server port (default 3001) */
1212
export const PORT = Number(process.env.PORT) || 3001
1313

14-
/** GitHub OAuth client ID */
15-
export const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID
14+
/** GH OAuth client ID */
15+
export const GH_CLIENT_ID = process.env.GH_CLIENT_ID
1616

17-
/** GitHub OAuth client secret */
18-
export const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET
17+
/** GH OAuth client secret */
18+
export const GH_CLIENT_SECRET = process.env.GH_CLIENT_SECRET
1919

20-
/** Optional canonical GitHub OAuth callback URL */
21-
export const GITHUB_REDIRECT_URI = process.env.GITHUB_REDIRECT_URI
20+
/** Optional canonical GH OAuth callback URL */
21+
export const GH_REDIRECT_URI = process.env.GH_REDIRECT_URI
2222

2323
function parseCsvEnv(value: string | undefined): string[] {
2424
return (value || '')
@@ -32,10 +32,10 @@ export const CORS_ORIGINS = parseCsvEnv(process.env.CORS_ORIGIN)
3232

3333
/** Log a warning if OAuth is not configured */
3434
export function warnIfOAuthNotConfigured(): void {
35-
if (!GITHUB_CLIENT_ID || !GITHUB_CLIENT_SECRET) {
35+
if (!GH_CLIENT_ID || !GH_CLIENT_SECRET) {
3636
console.warn(
37-
'Warning: GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET must be set for OAuth. ' +
38-
'Create a GitHub OAuth App at https://github.com/settings/developers'
37+
'Warning: GH_CLIENT_ID and GH_CLIENT_SECRET must be set for OAuth. ' +
38+
'Create a GH OAuth App at https://GH.com/settings/developers'
3939
)
4040
}
4141
}

server/routes/auth.routes.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import type { Request, Response } from 'express'
22
import { Router } from 'express'
33
import {
4-
GITHUB_CLIENT_ID,
5-
GITHUB_CLIENT_SECRET,
6-
GITHUB_REDIRECT_URI,
4+
GH_CLIENT_ID,
5+
GH_CLIENT_SECRET,
6+
GH_REDIRECT_URI,
77
} from '../config'
88
import {
99
exchangeCodeForToken,
@@ -21,14 +21,14 @@ const MAX_URI_LENGTH = 2048
2121
* Returns non-secret OAuth config required by the frontend.
2222
*/
2323
router.get('/config', (_req: Request, res: Response): void => {
24-
if (!GITHUB_CLIENT_ID) {
24+
if (!GH_CLIENT_ID) {
2525
res.status(500).json({ error: 'OAuth not configured' })
2626
return
2727
}
2828

2929
res.json({
30-
client_id: GITHUB_CLIENT_ID,
31-
redirect_uri: GITHUB_REDIRECT_URI || null,
30+
client_id: GH_CLIENT_ID,
31+
redirect_uri: GH_REDIRECT_URI || null,
3232
})
3333
})
3434

@@ -64,13 +64,13 @@ router.post('/exchange', async (req: Request, res: Response): Promise<void> => {
6464
}
6565
// Reject any redirect_uri that doesn't match the server's configured value.
6666
// This prevents an attacker from substituting a different URI in the exchange call.
67-
if (GITHUB_REDIRECT_URI && clientRedirectUri !== GITHUB_REDIRECT_URI) {
67+
if (GH_REDIRECT_URI && clientRedirectUri !== GH_REDIRECT_URI) {
6868
res.status(400).json({ error: 'redirect_uri mismatch' })
6969
return
7070
}
7171
}
7272

73-
if (!GITHUB_CLIENT_ID || !GITHUB_CLIENT_SECRET) {
73+
if (!GH_CLIENT_ID || !GH_CLIENT_SECRET) {
7474
res.status(500).json({ error: 'OAuth not configured' })
7575
return
7676
}
@@ -79,7 +79,7 @@ router.post('/exchange', async (req: Request, res: Response): Promise<void> => {
7979
try {
8080
const tokenData = await exchangeCodeForToken(
8181
code,
82-
GITHUB_REDIRECT_URI || undefined,
82+
GH_REDIRECT_URI || undefined,
8383
typeof codeVerifier === 'string' ? codeVerifier : undefined
8484
)
8585

server/services/github.service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } from '../config'
1+
import { GH_CLIENT_ID, GH_CLIENT_SECRET } from '../config'
22
import { GITHUB_TOKEN_URL, GITHUB_USER_URL } from '../constants'
33

44
export interface TokenResponse {
@@ -60,8 +60,8 @@ export async function exchangeCodeForToken(
6060
codeVerifier?: string
6161
): Promise<TokenResponse> {
6262
const body: Record<string, string> = {
63-
client_id: GITHUB_CLIENT_ID!,
64-
client_secret: GITHUB_CLIENT_SECRET!,
63+
client_id: GH_CLIENT_ID!,
64+
client_secret: GH_CLIENT_SECRET!,
6565
code,
6666
}
6767
if (redirectUri) body.redirect_uri = redirectUri

0 commit comments

Comments
 (0)