Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.git
.gitignore
README.md
node_modules
frontend/node_modules
frontend/dist
*.md
61 changes: 61 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "monthly"
time: "06:00"
commit-message:
prefix: "chore"
labels:
- "dependencies"
open-pull-requests-limit: 5
groups:
dependencies:
patterns:
- "*"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
time: "06:00"
commit-message:
prefix: "chore"
labels:
- "dependencies"
open-pull-requests-limit: 5
groups:
dependencies:
patterns:
- "*"

- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "monthly"
time: "06:00"
commit-message:
prefix: "chore"
labels:
- "dependencies"
open-pull-requests-limit: 5
groups:
dependencies:
patterns:
- "*"

- package-ecosystem: "npm"
directory: "/frontend"
schedule:
interval: "monthly"
time: "06:00"
commit-message:
prefix: "chore"
labels:
- "dependencies"
open-pull-requests-limit: 5
groups:
dependencies:
patterns:
- "*"
74 changes: 74 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Quality

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
backend:
name: Backend Quality Checks
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.25"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
go-version: "1.25"
go-version: "1.25.x"

cache: true

- name: Verify dependencies
run: go mod verify

- name: Build
run: go build -v ./...

- name: Check format
run: go mod tidy && gofmt -s -w . && git diff --exit-code
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe easier to use https://github.com/golangci/golangci-lint? It does do even more checks. Example configuration: https://github.com/AikidoSec/firewall-go/blob/main/.golangci.yml


- name: Check vet
run: go vet ./...

- name: Run Tests
run: go test -v -race -coverprofile=coverage.txt ./...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps nice if it is a separate job? Can of course be changed later.


frontend:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add to frontend PR later so it does not fail here?

name: Frontend Quality Checks
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Use Node.js 24.x
uses: actions/setup-node@v6
with:
node-version: 24.x
cache: 'npm'
cache-dependency-path: ./frontend/package-lock.json

- name: Setup Aikido Safe Chain
working-directory: ./frontend
run: |
npm i -g @aikidosec/safe-chain
safe-chain setup-ci
- name: Install dependencies
working-directory: ./frontend
run: npm ci --safe-chain-skip-minimum-package-age

- name: Run Linter
working-directory: ./frontend
run: node --run lint

- name: Check Formatting
working-directory: ./frontend
run: node --run check:format

- name: Type Checking
working-directory: ./frontend
run: node --run check:types
79 changes: 79 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Release Please

on:
push:
branches:
- main

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

permissions:
contents: write
pull-requests: write

jobs:
release-please:
runs-on: ubuntu-latest
outputs:
new-release-created: ${{ steps.release-please-action.outputs.releases_created }}
tag-name: ${{ steps.release-please-action.outputs.tag_name }}
steps:
- uses: googleapis/release-please-action@v4
id: release-please-action
with:
release-type: go

build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
needs: release-please
if: needs.release-please.outputs.new-release-created == 'true'
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}},value=${{ needs.release-please.outputs.tag-name }}
type=semver,pattern={{major}}.{{minor}}.{{patch}},value=${{ needs.release-please.outputs.tag-name }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.release-please.outputs.tag-name }}
type=semver,pattern={{major}},value=${{ needs.release-please.outputs.tag-name }}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: true
sbom: true
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# data directory for SQLite
data/
*.db

# Go
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out
go.work
73 changes: 73 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Build stage for frontend
FROM node:24-trixie-slim AS frontend-builder

WORKDIR /app/frontend

# Copy frontend package files
COPY frontend/package*.json ./

# Install dependencies
RUN npm install
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RUN npm install
RUN npm ci --ignore-scripts


# Copy frontend source
COPY frontend/ ./

# Build frontend
RUN npm run build

# Build stage for Go backend
FROM golang:1.25-alpine AS backend-builder

WORKDIR /app

# Install build dependencies for SQLite
RUN apk add --no-cache gcc musl-dev sqlite-dev

# Copy go mod files
COPY go.mod go.sum* ./

# Download dependencies
RUN go mod download

# Copy source code
COPY cmd/ ./cmd/
COPY internal/ ./internal/
COPY main.go ./

# Define build arguments
ARG VERSION=dev
ARG BUILD_TIME=unknown
ARG GIT_COMMIT=unknown

# Build the Go application with version information
RUN CGO_ENABLED=1 GOOS=linux go build \
-ldflags="-X 'orca-cd/internal/config.Version=${VERSION}' \
-X 'orca-cd/internal/config.BuildTime=${BUILD_TIME}' \
-X 'orca-cd/internal/config.GitCommit=${GIT_COMMIT}'" \
-o server .

# Final stage
FROM alpine:3.22

RUN apk --no-cache add ca-certificates sqlite-libs

WORKDIR /root/

# Copy the Go binary from backend builder
COPY --from=backend-builder /app/server .

# Copy the built frontend from frontend builder
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist

# Create data directory for SQLite database
RUN mkdir -p ./data

VOLUME ["/data"]

# Expose port 8080
EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["server", "health"]

# Run the server (root command starts the API)
CMD ["./server"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to add CLI options, e.g. for password reset, it might be nice to add the server to a sub command to display help if the root command is invoked?

61 changes: 61 additions & 0 deletions cmd/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package cmd
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to put all Go code in a folder "backend"?


import (
"encoding/json"
"fmt"
"net/http"
"os"
"time"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)

var healthCmd = &cobra.Command{
Use: "health",
Short: "Check the health of the API server",
Long: `Performs a health check by querying the /api/health endpoint.`,
Run: func(cmd *cobra.Command, args []string) {
port, _ := cmd.Flags().GetString("port")
url := fmt.Sprintf("http://localhost:%s/api/health", port)

log.Info().Str("url", url).Msg("Checking server health")

client := &http.Client{
Timeout: 5 * time.Second,
}

resp, err := client.Get(url)
if err != nil {
log.Error().Err(err).Msg("Health check failed")
os.Exit(1)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
log.Error().Int("status_code", resp.StatusCode).Msg("Health check failed")
os.Exit(1)
}

var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
log.Error().Err(err).Msg("Failed to parse response")
os.Exit(1)
}

log.Info().Msg("Server is healthy!")
log.Info().Interface("status", result["status"]).Msg("Health status")
if serverTime, ok := result["time"].(string); ok {
parsedTime, err := time.Parse(time.RFC3339Nano, serverTime)
if err == nil {
log.Info().Str("server_time", parsedTime.Format(time.RFC1123)).Msg("Server time")
} else {
log.Info().Interface("server_time", result["time"]).Msg("Server time")
}
}
},
}

func init() {
healthCmd.Flags().StringP("port", "p", "8080", "Port where the server is running")
}
Loading
Loading