From 84ecbbffa36f758f7fa3d89d363f46388d678088 Mon Sep 17 00:00:00 2001 From: Johnny 5 Date: Sun, 7 Jun 2026 22:09:05 +0000 Subject: [PATCH] CI: GitHub Actions build and release workflows - build.yml: runs on push to main, builds Docker image + packs s9pk, uploads artifact - release.yml: runs on version tags (v*.*), builds and creates GitHub release with s9pk attached - Dockerfile: build context is repo root (docker build -f deploy/Dockerfile .) - Manifest: switched to dockerTag (pre-built image pattern) - Makefile + s9pk.mk: local build support --- .github/workflows/build.yml | 74 ++++++++++++++++++ .github/workflows/release.yml | 61 +++++++++++++++ deploy/.gitignore | 1 + deploy/Dockerfile | 13 ++-- deploy/Makefile | 19 +++++ deploy/s9pk.mk | 127 +++++++++++++++++++++++++++++++ deploy/startos/manifest/index.ts | 4 +- 7 files changed, 290 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml create mode 100644 deploy/Makefile create mode 100644 deploy/s9pk.mk diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..60eb84a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,74 @@ +name: Build Service + +on: + workflow_dispatch: + pull_request: + paths-ignore: ['*.md'] + branches: ['main'] + push: + paths-ignore: ['*.md'] + branches: ['main'] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + packages: read + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install system dependencies + run: sudo apt-get update && sudo apt-get install -y squashfs-tools-ng + + - name: Install start-cli + run: | + curl -fsSL -o start-cli https://github.com/Start9Labs/start-os/releases/download/v0.4.0-alpha.20/start-cli_x86_64-linux + chmod +x start-cli + sudo mv start-cli /usr/local/bin/ + start-cli --version + + - name: Initialize developer key + env: + DEV_KEY: ${{ secrets.DEV_KEY }} + run: | + if [ -n "$DEV_KEY" ]; then + mkdir -p ~/.startos + echo "$DEV_KEY" > ~/.startos/developer.key.pem + else + start-cli init-key + fi + + - name: Build Docker image + run: docker build -f deploy/Dockerfile -t ten31portal:local . + + - name: Install StartOS packaging deps + working-directory: deploy + run: npm ci + + - name: Build StartOS JavaScript + working-directory: deploy + run: npm run build + + - name: Pack s9pk + working-directory: deploy + run: start-cli s9pk pack --arch x86_64 --license ../LICENSE -o ten31portal.s9pk + + - name: Upload s9pk artifact + uses: actions/upload-artifact@v4 + with: + name: ten31portal-s9pk + path: deploy/ten31portal.s9pk diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e228529 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,61 @@ +name: Release Service + +on: + push: + tags: + - 'v*.*' + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install system dependencies + run: sudo apt-get update && sudo apt-get install -y squashfs-tools-ng + + - name: Install start-cli + run: | + curl -fsSL -o start-cli https://github.com/Start9Labs/start-os/releases/download/v0.4.0-alpha.20/start-cli_x86_64-linux + chmod +x start-cli + sudo mv start-cli /usr/local/bin/ + start-cli --version + + - name: Initialize developer key + env: + DEV_KEY: ${{ secrets.DEV_KEY }} + run: | + mkdir -p ~/.startos + echo "$DEV_KEY" > ~/.startos/developer.key.pem + + - name: Build Docker image + run: docker build -f deploy/Dockerfile -t ten31portal:local . + + - name: Install StartOS packaging deps + working-directory: deploy + run: npm ci + + - name: Build StartOS JavaScript + working-directory: deploy + run: npm run build + + - name: Pack s9pk + working-directory: deploy + run: start-cli s9pk pack --arch x86_64 --license ../LICENSE -o ten31portal.s9pk + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: deploy/ten31portal.s9pk + generate_release_notes: true diff --git a/deploy/.gitignore b/deploy/.gitignore index 30d033e..300d670 100644 --- a/deploy/.gitignore +++ b/deploy/.gitignore @@ -1,2 +1,3 @@ node_modules/ javascript/ +*.s9pk diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 25a2af6..d8f475b 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -1,18 +1,17 @@ +# Build context is the repo root (docker build -f deploy/Dockerfile .) + # --- Frontend build stage --- FROM node:20-slim AS frontend-build -WORKDIR /build/frontend +WORKDIR /build COPY frontend/package.json frontend/package-lock.json ./ RUN npm ci --include=dev COPY frontend/ ./ RUN npx vite build -# --- Backend stage --- +# --- Backend + runtime stage --- FROM python:3.11-slim -RUN apt-get update && apt-get install -y --no-install-recommends \ - && rm -rf /var/lib/apt/lists/* - WORKDIR /app # Install Python deps @@ -23,7 +22,7 @@ COPY backend/alembic.ini ./ RUN pip install --no-cache-dir . # Copy built frontend -COPY --from=frontend-build /build/frontend/dist ./static/ +COPY --from=frontend-build /build/dist ./static/ # Startup script COPY deploy/start.sh ./start.sh @@ -33,7 +32,7 @@ RUN chmod +x ./start.sh RUN mkdir -p /data ENV TEN31_DB_PATH=/data/portal.db -ENV TEN31_SESSION_SECRET=change-me +ENV TEN31_SESSION_SECRET=*** EXPOSE 8000 diff --git a/deploy/Makefile b/deploy/Makefile new file mode 100644 index 0000000..f5206f1 --- /dev/null +++ b/deploy/Makefile @@ -0,0 +1,19 @@ +# Ten31Portal StartOS package build +# Docker image must be built separately before packing: +# docker build -f deploy/Dockerfile -t ten31portal:local . (from repo root) +# cd deploy && make x86 + +LICENSE := ../LICENSE + +include s9pk.mk + +# Override the license path for s9pk pack +$(BASE_NAME).s9pk: $(INGREDIENTS) .git/HEAD .git/index + @$(MAKE) --no-print-directory ingredients + @echo " Packing '$@'..." + start-cli s9pk pack --license $(LICENSE) -o $@ + +$(BASE_NAME)_%.s9pk: $(INGREDIENTS) .git/HEAD .git/index + @$(MAKE) --no-print-directory ingredients + @echo " Packing '$@'..." + start-cli s9pk pack --arch=$* --license $(LICENSE) -o $@ diff --git a/deploy/s9pk.mk b/deploy/s9pk.mk new file mode 100644 index 0000000..45ebfc9 --- /dev/null +++ b/deploy/s9pk.mk @@ -0,0 +1,127 @@ +# ** Plumbing. DO NOT EDIT **. +# This file is imported by ./Makefile. Make edits there + +PACKAGE_ID := $(shell awk -F"'" '/id:/ {print $$2}' startos/manifest/index.ts) +INGREDIENTS := $(shell start-cli s9pk list-ingredients 2>/dev/null) +ARCHES ?= x86 arm riscv +TARGETS ?= arches +ifdef VARIANT +BASE_NAME := $(PACKAGE_ID)_$(VARIANT) +else +BASE_NAME := $(PACKAGE_ID) +endif + +.PHONY: all arches aarch64 x86_64 riscv64 arm arm64 x86 riscv arch/* clean install check-deps check-init package ingredients +.DELETE_ON_ERROR: +.SECONDARY: + +define SUMMARY + @manifest=$$(start-cli s9pk inspect $(1) manifest); \ + size=$$(du -h $(1) | awk '{print $$1}'); \ + title=$$(printf '%s' "$$manifest" | jq -r .title); \ + version=$$(printf '%s' "$$manifest" | jq -r .version); \ + arches=$$(printf '%s' "$$manifest" | jq -r '[.images[].arch // []] | flatten | unique | join(", ")'); \ + sdkv=$$(printf '%s' "$$manifest" | jq -r .sdkVersion); \ + gitHash=$$(printf '%s' "$$manifest" | jq -r .gitHash | sed -E 's/(.*-modified)$$/\x1b[0;31m\1\x1b[0m/'); \ + printf "\n"; \ + printf "\033[1;32m✅ Build Complete!\033[0m\n"; \ + printf "\n"; \ + printf "\033[1;37m📦 $$title\033[0m \033[36mv$$version\033[0m\n"; \ + printf "───────────────────────────────\n"; \ + printf " \033[1;36mFilename:\033[0m %s\n" "$(1)"; \ + printf " \033[1;36mSize:\033[0m %s\n" "$$size"; \ + printf " \033[1;36mArch:\033[0m %s\n" "$$arches"; \ + printf " \033[1;36mSDK:\033[0m %s\n" "$$sdkv"; \ + printf " \033[1;36mGit:\033[0m %s\n" "$$gitHash"; \ + echo "" +endef + +all: $(TARGETS) + +arches: $(ARCHES) + +universal: $(BASE_NAME).s9pk + $(call SUMMARY,$<) + +arch/%: $(BASE_NAME)_%.s9pk + $(call SUMMARY,$<) + +x86 x86_64: arch/x86_64 +arm arm64 aarch64: arch/aarch64 +riscv riscv64: arch/riscv64 + +$(BASE_NAME).s9pk: $(INGREDIENTS) .git/HEAD .git/index + @$(MAKE) --no-print-directory ingredients + @echo " Packing '$@'..." + start-cli s9pk pack -o $@ + +$(BASE_NAME)_%.s9pk: $(INGREDIENTS) .git/HEAD .git/index + @$(MAKE) --no-print-directory ingredients + @echo " Packing '$@'..." + start-cli s9pk pack --arch=$* -o $@ + +ingredients: $(INGREDIENTS) + @echo " Re-evaluating ingredients..." + +install: | check-deps check-init + @HOST=$$(awk -F'/' '/^host:/ {print $$3}' ~/.startos/config.yaml); \ + if [ -z "$$HOST" ]; then \ + echo "Error: You must define \"host: http://server-name.local\" in ~/.startos/config.yaml"; \ + exit 1; \ + fi; \ + S9PK=$$(ls -t *.s9pk 2>/dev/null | head -1); \ + if [ -z "$$S9PK" ]; then \ + echo "Error: No .s9pk file found. Run 'make' first."; \ + exit 1; \ + fi; \ + printf "\n🚀 Installing %s to %s ...\n" "$$S9PK" "$$HOST"; \ + start-cli package install -s "$$S9PK" + +publish: | all + @REGISTRY=$$(awk -F'/' '/^registry:/ {print $$3}' ~/.startos/config.yaml); \ + if [ -z "$$REGISTRY" ]; then \ + echo "Error: You must define \"registry: https://my-registry.tld\" in ~/.startos/config.yaml"; \ + exit 1; \ + fi; \ + S3BASE=$$(awk -F'/' '/^s9pk-s3base:/ {print $$3}' ~/.startos/config.yaml); \ + if [ -z "$$S3BASE" ]; then \ + echo "Error: You must define \"s3base: https://s9pks.my-s3-bucket.tld\" in ~/.startos/config.yaml"; \ + exit 1; \ + fi; \ + command -v s3cmd >/dev/null || \ + (echo "Error: s3cmd not found. It must be installed to publish using s3." && exit 1); \ + printf "\n🚀 Publishing to %s; indexing on %s ...\n" "$$S3BASE" "$$REGISTRY"; \ + for s9pk in *.s9pk; do \ + age=$$(( $$(date +%s) - $$(stat -c %Y "$$s9pk") )); \ + if [ "$$age" -gt 3600 ]; then \ + printf "\033[1;33m⚠️ %s is %d minutes old. Publish anyway? [y/N] \033[0m" "$$s9pk" "$$((age / 60))"; \ + read -r ans; \ + case "$$ans" in [yY]*) ;; *) echo "Skipping $$s9pk"; continue ;; esac; \ + fi; \ + start-cli s9pk publish "$$s9pk"; \ + done + +check-deps: + @command -v start-cli >/dev/null || \ + (echo "Error: start-cli not found. Please see https://docs.start9.com/latest/developer-guide/sdk/installing-the-sdk" && exit 1) + @command -v npm >/dev/null || \ + (echo "Error: npm not found. Please install Node.js and npm." && exit 1) + +check-init: + @if [ ! -f ~/.startos/developer.key.pem ]; then \ + echo "Initializing StartOS developer environment..."; \ + start-cli init-key; \ + fi + +javascript/index.js: $(shell find startos -type f) tsconfig.json node_modules + npm run build + +node_modules: package-lock.json + npm ci + +package-lock.json: package.json + npm i + +clean: + @echo "Cleaning up build artifacts..." + @rm -rf $(PACKAGE_ID).s9pk $(PACKAGE_ID)_x86_64.s9pk $(PACKAGE_ID)_aarch64.s9pk $(PACKAGE_ID)_riscv64.s9pk javascript node_modules diff --git a/deploy/startos/manifest/index.ts b/deploy/startos/manifest/index.ts index b0389cd..ab01d21 100644 --- a/deploy/startos/manifest/index.ts +++ b/deploy/startos/manifest/index.ts @@ -16,8 +16,8 @@ export const manifest = setupManifest({ volumes: ['main'], images: { main: { - source: { dockerBuild: { dockerfile: './Dockerfile', workdir: '.' } }, - arch: ['x86_64', 'aarch64'], + source: { dockerTag: 'ten31portal:local' }, + arch: ['x86_64'], }, }, alerts: {