Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
self-hosted-runner:
labels:
- ci-pool-typescript
- ci-pool-go
- ci-pool-ops
185 changes: 55 additions & 130 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: CI

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

Expand All @@ -12,71 +13,60 @@ concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
GOFLAGS: -mod=readonly
GOTOOLCHAIN: local

jobs:
contract:
uses: dinglebear-ai/workflows/.github/workflows/fleet-contract.yml@66e64b9f31de7ac1f9aa8c9f87ede9bbec5eae1d
with:
profile: go
implementation-ref: 66e64b9f31de7ac1f9aa8c9f87ede9bbec5eae1d

frontend:
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- name: Assert submitted revision
run: test "$(git rev-parse HEAD)" = "$GITHUB_SHA"
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 24.18.0
cache: npm
cache-dependency-path: web/package-lock.json
- run: npm ci --prefix web --no-audit --no-fund
- run: npm run lint --prefix web
- run: npm run typecheck --if-present --prefix web
- run: npm run test:coverage --prefix web
- run: npm audit --prefix web --omit=dev --audit-level=high
- run: npm run build --prefix web
- run: node web/scripts/build-embed.mjs --apply --skip-build
- run: ./scripts/check-web-assets.sh
- name: Embedded assets match frontend source
run: git diff --exit-code -- public/
- run: npx playwright install --with-deps chromium
- run: npm run test:e2e --prefix web
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: frontend-test-artifacts-${{ github.sha }}
path: |
web/coverage/
web/playwright-report/
web/test-results/
if-no-files-found: warn
retention-days: 14
uses: dinglebear-ai/workflows/.github/workflows/fast-node.yml@66e64b9f31de7ac1f9aa8c9f87ede9bbec5eae1d
with:
node-version: "24.18.0"
working-directory: web
cache-dependency-path: web/package-lock.json
install-command: npm ci --no-audit --no-fund
audit-command: npm audit --omit=dev --audit-level=high
lint-command: npm run lint
typecheck-command: npm run typecheck
test-command: npm run test:coverage
build-command: npm run build
contract-command: >-
node scripts/build-embed.mjs --apply --skip-build &&
../scripts/check-web-assets.sh &&
git diff --exit-code -- ../public/
timeout-minutes: 25

backend:
runs-on: ubuntu-24.04
timeout-minutes: 45
uses: dinglebear-ai/workflows/.github/workflows/fast-go.yml@66e64b9f31de7ac1f9aa8c9f87ede9bbec5eae1d
with:
go-version: "1.26.5"
generate-command: go generate -x ./server/...
vet-command: go vet ./server/... ./cmd/...
test-command: >-
go test -count=1 ./server/... ./cmd/... &&
CGO_ENABLED=1 go test -race -count=1 ./server/... ./cmd/...
govulncheck-version: "v1.6.0"
timeout-minutes: 45

backend-evidence:
name: Backend coverage and benchmarks
runs-on: [self-hosted, ci-pool-go]
timeout-minutes: 30
env:
GOFLAGS: -mod=readonly
GOTOOLCHAIN: local
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
- name: Assert submitted revision
run: test "$(git rev-parse HEAD)" = "$GITHUB_SHA"
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: 1.26.5
cache-dependency-path: go.sum
- name: Native build dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libjpeg-dev libtiff-dev libpng-dev libwebp-dev libraw-dev libheif-dev \
libgif-dev libvips-dev libavcodec-dev libavdevice-dev libavfilter-dev \
libavformat-dev libswresample-dev libswscale-dev libavutil-dev
go-version: "1.26.5"
cache: true
- run: go mod download && go mod verify
- run: go generate -x ./server/...
- run: go vet ./server/... ./cmd/...
- run: go test -count=1 -covermode=atomic -coverprofile=coverage-go.out ./server/... ./cmd/...
- run: ./scripts/check-go-coverage.sh coverage-go.out
- name: Critical-package coverage ratchet
Expand All @@ -86,11 +76,7 @@ jobs:
./server/pkg/workflow/trigger ./server/plugin/plg_handler_mcp \
./server/plugin/plg_handler_console ./server/plugin/plg_backend_dav
GO_COVERAGE_MIN=16.6 ./scripts/check-go-coverage.sh coverage-go-critical.out
- run: CGO_ENABLED=1 go test -race -count=1 ./server/... ./cmd/...
- run: go test -run '^$' -bench . -benchmem ./server/... ./cmd/... | tee benchmarks.txt
- run: go run golang.org/x/vuln/cmd/govulncheck@v1.6.0 ./server/... ./cmd/...
- name: Module files remain readonly
run: git diff --exit-code -- go.mod go.sum
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: benchmarks-${{ github.sha }}
Expand All @@ -101,75 +87,14 @@ jobs:
if-no-files-found: error
retention-days: 30

sbom:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: 1.26.5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 24.18.0
cache: npm
cache-dependency-path: web/package-lock.json
- run: make sbom
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: cyclonedx-sbom-${{ github.sha }}
path: dist/*.cdx.json
if-no-files-found: error
retention-days: 30

image:
needs: [frontend, backend]
runs-on: ubuntu-24.04
timeout-minutes: 45
gate:
name: CI
if: always()
needs: [contract, frontend, backend, backend-evidence]
runs-on: [self-hosted, ci-pool-ops]
timeout-minutes: 2
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
file: docker/Dockerfile
push: false
load: true
build-args: BUILD_REF=${{ github.sha }}
tags: filestash:${{ github.sha }}
# Docker's load exporter cannot import the manifest list produced by
# attestations. The dedicated sbom job publishes validated CycloneDX.
provenance: false
sbom: false
- name: Smoke-test image startup
shell: bash
run: |
set -euo pipefail
name=filestash-smoke
trap 'docker rm -f "$name" >/dev/null 2>&1 || true' EXIT
docker run -d --name "$name" "filestash:${{ github.sha }}" >/dev/null
for _ in {1..45}; do
state=$(docker inspect --format '{{.State.Status}}' "$name")
health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{end}}' "$name")
if [[ "$health" == "healthy" ]]; then
exit 0
fi
if [[ "$state" == "exited" || "$health" == "unhealthy" ]]; then
docker logs "$name"
exit 1
fi
sleep 2
done
docker logs "$name"
exit 1
- uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: filestash:${{ github.sha }}
format: table
exit-code: "1"
ignore-unfixed: true
severity: CRITICAL,HIGH
- name: Require every fast lane
env:
RESULTS: ${{ join(needs.*.result, ' ') }}
run: test "$RESULTS" = "success success success success"
109 changes: 109 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: Release

on:
release:
types: [published]

permissions: {}

concurrency:
group: filestash-release-${{ github.event.release.tag_name }}
cancel-in-progress: false

jobs:
web:
permissions:
contents: read
uses: dinglebear-ai/workflows/.github/workflows/hosted-web-release.yml@66e64b9f31de7ac1f9aa8c9f87ede9bbec5eae1d
with:
checkout-ref: ${{ github.event.release.tag_name }}
node-version: "24.18.0"
working-directory: web
cache-dependency-path: web/package-lock.json
install-command: npm ci --no-audit --no-fund
coverage-command: npm run test:coverage
build-command: >-
npm run build &&
node scripts/build-embed.mjs --apply --skip-build &&
../scripts/check-web-assets.sh
e2e-command: npm run test:e2e
artifact-name: filestash-web-${{ github.event.release.tag_name }}
artifact-path: public/
diagnostic-path: |
web/coverage/
web/playwright-report/
web/test-results/
timeout-minutes: 45

container:
needs: web
permissions:
contents: read
packages: write
attestations: write
id-token: write
uses: dinglebear-ai/workflows/.github/workflows/hosted-container-release.yml@66e64b9f31de7ac1f9aa8c9f87ede9bbec5eae1d
with:
checkout-ref: ${{ github.event.release.tag_name }}
image: ghcr.io/${{ github.repository }}
release-tag: ${{ github.event.release.tag_name }}
dockerfile: docker/Dockerfile
build-args: BUILD_REF=${{ github.sha }}
smoke-command: |
set -euo pipefail
name="filestash-release-smoke-${GITHUB_RUN_ID}"
trap 'docker rm -f "$name" >/dev/null 2>&1 || true' EXIT
docker run -d --name "$name" "$IMAGE_REF" >/dev/null
for _ in {1..45}; do
health="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{end}}' "$name")"
[[ "$health" == healthy ]] && exit 0
[[ "$health" == unhealthy ]] && break
sleep 2
done
docker logs "$name"
exit 1
cache-scope: filestash-release
timeout-minutes: 60
secrets:
REGISTRY_USERNAME: ${{ github.actor }}
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}

sbom:
needs: web
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
ref: ${{ github.event.release.tag_name }}
persist-credentials: false
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: "1.26.5"
cache: true
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: "24.18.0"
cache: npm
cache-dependency-path: web/package-lock.json
- run: make sbom
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: cyclonedx-sbom-${{ github.event.release.tag_name }}
path: dist/*.cdx.json
if-no-files-found: error
retention-days: 30

publish:
needs: [container, sbom]
permissions:
contents: write
attestations: write
id-token: write
uses: dinglebear-ai/workflows/.github/workflows/github-release.yml@66e64b9f31de7ac1f9aa8c9f87ede9bbec5eae1d
with:
release-tag: ${{ github.event.release.tag_name }}
artifact-pattern: cyclonedx-sbom-*
artifact-path: release-artifacts
2 changes: 1 addition & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pipeline {
sh '''
docker buildx build \
--file docker/Dockerfile \
--platform linux/amd64,linux/arm64 \
--platform linux/amd64 \
--build-arg BUILD_REF=${REVISION} \
--provenance=mode=max \
--sbom=true \
Expand Down
6 changes: 6 additions & 0 deletions docs/frontend-migration.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
title: Next.js Frontend Migration
created: 2026-07-30
updated: 2026-07-30
---

# Next.js Frontend Migration

The React/Aurora frontend is a static export embedded in the Filestash Go binary.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ require (
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.40.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/grpc v1.82.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
modernc.org/libc v1.72.1 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -332,12 +332,12 @@ google.golang.org/api v0.276.0 h1:nVArUtfLEihtW+b0DdcqRGK1xoEm2+ltAihyztq7MKY=
google.golang.org/api v0.276.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI=
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
Expand Down
Loading
Loading