Skip to content
Merged
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
153 changes: 91 additions & 62 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ permissions:

jobs:
agent:
runs-on: ubuntu-latest
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- goos: linux
goarch: amd64
runner: blacksmith-2vcpu-ubuntu-2404
- goos: linux
goarch: arm64
runner: blacksmith-2vcpu-ubuntu-2404-arm

steps:
- name: Checkout
Expand Down Expand Up @@ -52,20 +54,25 @@ jobs:
retention-days: 1

cli:
runs-on: ubuntu-latest
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- goos: darwin
goarch: amd64
runner: blacksmith-2vcpu-ubuntu-2404
- goos: darwin
goarch: arm64
runner: blacksmith-2vcpu-ubuntu-2404-arm
- goos: linux
goarch: amd64
runner: blacksmith-2vcpu-ubuntu-2404
- goos: linux
goarch: arm64
runner: blacksmith-2vcpu-ubuntu-2404-arm
- goos: windows
goarch: amd64
runner: blacksmith-2vcpu-ubuntu-2404

steps:
- name: Checkout
Expand Down Expand Up @@ -108,8 +115,8 @@ jobs:
retention-days: 1

release:
needs: [agent, cli, web, registry, updater]
runs-on: ubuntu-latest
needs: [agent, cli, containers]
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Download agent artifacts
uses: actions/download-artifact@v8
Expand Down Expand Up @@ -245,14 +252,49 @@ jobs:
git pull --rebase origin main
done

web:
runs-on: ubuntu-latest
container:
name: Build ${{ matrix.image }} (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- image: web
context: web
arch: amd64
platform: linux/amd64
runner: blacksmith-2vcpu-ubuntu-2404
- image: web
context: web
arch: arm64
platform: linux/arm64
runner: blacksmith-2vcpu-ubuntu-2404-arm
- image: registry
context: registry
arch: amd64
platform: linux/amd64
runner: blacksmith-2vcpu-ubuntu-2404
- image: registry
context: registry
arch: arm64
platform: linux/arm64
runner: blacksmith-2vcpu-ubuntu-2404-arm
- image: updater
context: deployment/updater
arch: amd64
platform: linux/amd64
runner: blacksmith-2vcpu-ubuntu-2404
- image: updater
context: deployment/updater
arch: arm64
platform: linux/arm64
runner: blacksmith-2vcpu-ubuntu-2404-arm

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Set up Docker builder
uses: useblacksmith/setup-docker-builder@v1

- name: Login to GHCR
uses: docker/login-action@v4
Expand All @@ -262,55 +304,45 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v7
id: build
uses: useblacksmith/build-push-action@v2
with:
context: web
push: true
tags: |
ghcr.io/${{ github.repository }}/web:${{ github.ref_name }}
ghcr.io/${{ github.repository }}/web:tip
build-args: |
APP_VERSION=${{ github.ref_name }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha,scope=web
cache-to: type=gha,mode=max,scope=web

registry:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
context: ${{ matrix.context }}
tags: ghcr.io/${{ github.repository }}/${{ matrix.image }}
platforms: ${{ matrix.platform }}
build-args: ${{ matrix.image == 'web' && format('APP_VERSION={0}', github.ref_name) || '' }}
outputs: type=image,push-by-digest=true,name-canonical=true,push=true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Export digest
run: |
mkdir -p /tmp/digests
touch "/tmp/digests/${DIGEST#sha256:}"
env:
DIGEST: ${{ steps.build.outputs.digest }}

- name: Login to GHCR
uses: docker/login-action@v4
- name: Upload digest
uses: actions/upload-artifact@v7
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
name: digest-${{ matrix.image }}-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

- name: Build and push
uses: docker/build-push-action@v7
with:
context: registry
push: true
tags: |
ghcr.io/${{ github.repository }}/registry:${{ github.ref_name }}
ghcr.io/${{ github.repository }}/registry:tip
platforms: linux/amd64,linux/arm64
cache-from: type=gha,scope=registry
cache-to: type=gha,mode=max,scope=registry

updater:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
containers:
name: Publish ${{ matrix.image }} manifest
needs: container
runs-on: blacksmith-2vcpu-ubuntu-2404
strategy:
matrix:
image: [web, registry, updater]

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: digest-${{ matrix.image }}-*
merge-multiple: true

- name: Login to GHCR
uses: docker/login-action@v4
Expand All @@ -319,14 +351,11 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v7
with:
context: deployment/updater
push: true
tags: |
ghcr.io/${{ github.repository }}/updater:${{ github.ref_name }}
ghcr.io/${{ github.repository }}/updater:tip
platforms: linux/amd64,linux/arm64
cache-from: type=gha,scope=updater
cache-to: type=gha,mode=max,scope=updater
- name: Create and push multi-platform manifest
working-directory: /tmp/digests
run: |
image="ghcr.io/${{ github.repository }}/${{ matrix.image }}"
docker buildx imagetools create \
--tag "$image:${{ github.ref_name }}" \
--tag "$image:tip" \
$(printf "$image@sha256:%s " *)
2 changes: 1 addition & 1 deletion .github/workflows/web-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ concurrency:

jobs:
build:
runs-on: ubuntu-latest
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 30
defaults:
run:
Expand Down
8 changes: 7 additions & 1 deletion web/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ FROM node:24-slim AS deps
WORKDIR /app
RUN corepack enable pnpm && corepack prepare pnpm@11 --activate
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY patches ./patches
RUN pnpm install --frozen-lockfile

FROM node:24-slim AS builder
Expand All @@ -17,7 +18,12 @@ RUN npx next build

FROM node:24-slim AS drizzle
WORKDIR /drizzle
RUN npm install drizzle-kit drizzle-orm
RUN apt-get update \
&& apt-get install -y --no-install-recommends patch \
&& rm -rf /var/lib/apt/lists/*
RUN npm install drizzle-kit@0.31.10 drizzle-orm@0.45.2
COPY patches/drizzle-kit@0.31.10.patch /tmp/drizzle-kit.patch
RUN patch --directory=node_modules/drizzle-kit --strip=1 < /tmp/drizzle-kit.patch

FROM node:24-slim AS runner
WORKDIR /app
Expand Down
5 changes: 1 addition & 4 deletions web/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,12 +603,9 @@ export const serviceRevisions = pgTable(
.notNull(),
},
(table) => [
// The database constraint is physically (id, service_id), but drizzle-kit
// 0.31.10 reports it as (service_id, id) during push diffing. Keep this order
// to avoid a drop/recreate that fails because foreign keys depend on it.
unique("service_revisions_id_service_id_unique").on(
table.serviceId,
table.id,
table.serviceId,
),
index("service_revisions_service_created_id_idx").on(
table.serviceId,
Expand Down
22 changes: 11 additions & 11 deletions web/lib/cli-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,34 +252,34 @@ function getUnsupportedReason(
placementReplicaCount: number,
) {
if (service.sourceType !== "image") {
return "tc only supports image-backed services. This service uses an unsupported source.";
return "Techulus Cloud only supports image-backed services. This service uses an unsupported source.";
}

if (service.stateful || volumeCount > 0) {
return "tc does not support stateful services or volumes. Manage this service from the web UI.";
return "Techulus Cloud does not support stateful services or volumes. Manage this service from the web UI.";
}

if (ports.some((port) => port.protocol !== "http")) {
return "tc only supports HTTP ports. This service has TCP or UDP ports configured.";
return "Techulus Cloud only supports HTTP ports. This service has TCP or UDP ports configured.";
}

if (ports.some((port) => port.isPublic && !port.domain)) {
return "tc requires every public HTTP port to have a domain.";
return "Techulus Cloud requires every public HTTP port to have a domain.";
}

if (placementReplicaCount < 1) {
return "tc requires manual server placement to be configured in the web UI before deploy.";
return "Techulus Cloud requires manual server placement to be configured in the web UI before deploy.";
}

if (placementReplicaCount > 10) {
return "tc only supports manually placed replica counts between 1 and 10.";
return "Techulus Cloud only supports manually placed replica counts between 1 and 10.";
}

const hasCpu = service.resourceCpuLimit !== null;
const hasMemory = service.resourceMemoryLimitMb !== null;

if (hasCpu !== hasMemory) {
return "tc requires both CPU and memory limits to be set together.";
return "Techulus Cloud requires both CPU and memory limits to be set together.";
}

return null;
Expand Down Expand Up @@ -609,7 +609,7 @@ function assertManifestReplicaCount(
) {
if (placementReplicaCount !== desiredReplicaCount) {
throw new Error(
`tc cannot change server placement. Update placement in the web UI so it has ${desiredReplicaCount} replica${desiredReplicaCount === 1 ? "" : "s"}, or update replicas.count to match the current manual placement of ${placementReplicaCount}.`,
`Techulus Cloud cannot change server placement. Update placement in the web UI so it has ${desiredReplicaCount} replica${desiredReplicaCount === 1 ? "" : "s"}, or update replicas.count to match the current manual placement of ${placementReplicaCount}.`,
);
}
}
Expand All @@ -620,14 +620,14 @@ export async function applyManifest(
const project = await findProjectByManifest(manifest);
if (!project) {
throw new Error(
"Project not found. Create the service and select server placement in the web UI before using tc.",
"Project not found. Create the service and select server placement in the web UI before using Techulus Cloud.",
);
}

const environment = await findEnvironmentByManifest(project.id, manifest);
if (!environment) {
throw new Error(
"Environment not found. Create the service and select server placement in the web UI before using tc.",
"Environment not found. Create the service and select server placement in the web UI before using Techulus Cloud.",
);
}

Expand All @@ -640,7 +640,7 @@ export async function applyManifest(

if (!service) {
throw new Error(
"tc cannot create services because server placement must be selected in the web UI.",
"Techulus Cloud cannot create services because server placement must be selected in the web UI.",
);
}

Expand Down
4 changes: 2 additions & 2 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"clsx": "^2.1.1",
"cron-parser": "^5.4.0",
"cronstrue": "^3.9.0",
"drizzle-orm": "^0.45.1",
"drizzle-orm": "^0.45.2",
"inngest": "^4.3.0",
"input-otp": "^1.4.2",
"ip-address": "^10.1.0",
Expand Down Expand Up @@ -62,7 +62,7 @@
"@types/react": "19.2.17",
"@types/react-dom": "19.2.3",
"@types/validator": "^13.15.10",
"drizzle-kit": "^0.31.8",
"drizzle-kit": "^0.31.10",
"eslint": "^9",
"eslint-config-next": "16.2.9",
"tailwindcss": "^4",
Expand Down
Loading
Loading