diff options
Diffstat
| -rw-r--r-- | .dockerignore | 15 | +15 −0 |
| -rw-r--r-- | .github/workflows/build.yml | 114 | +114 −0 |
| -rw-r--r-- | .gitignore | 1 | +1 −0 |
| -rw-r--r-- | REUSE.toml | 2 | +2 −0 |
| -rw-r--r-- | charts/mirum/Chart.yaml | 10 | +10 −0 |
| -rw-r--r-- | charts/mirum/templates/_helpers.tpl | 40 | +40 −0 |
| -rw-r--r-- | charts/mirum/templates/server-deployment.yaml | 102 | +102 −0 |
| -rw-r--r-- | charts/mirum/templates/server-httproute.yaml | 26 | +26 −0 |
| -rw-r--r-- | charts/mirum/templates/server-service.yaml | 23 | +23 −0 |
| -rw-r--r-- | charts/mirum/templates/worker-deployment.yaml | 87 | +87 −0 |
| -rw-r--r-- | charts/mirum/values.yaml | 53 | +53 −0 |
| -rw-r--r-- | cmd/mirum-server/Dockerfile | 20 | +20 −0 |
| -rw-r--r-- | cmd/mirum-worker/Dockerfile | 26 | +26 −0 |
| -rw-r--r-- | mise.toml | 2 | +1 −1 |
14 files changed, 520 insertions, 1 deletions
diff --git a/.dockerignore b/.dockerignore new file mode 100644 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# SPDX-License-Identifier: AGPL-3.0-or-later + +.git +.github +.task +build +cmd/mirum-agent/.zig-cache +cmd/mirum-agent/zig-out +cmd/mirum-agent/zig-pkg +cmd/mirum-server/apipb +cmd/mirum-server/static +cmd/mirum-server/web/gen +cmd/mirum-server/web/node_modules +internal/protocol/wirepb diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf36b5e..7e1bd0e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,6 +53,17 @@ jobs: - name: Tests run: task test + - name: Upload container binaries + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: container-binaries + path: | + build/mirum-server-linux-amd64 + build/mirum-server-linux-arm64 + build/mirum-worker-linux-amd64 + build/mirum-worker-linux-arm64 + if-no-files-found: error + - name: Publish if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') run: | @@ -77,3 +88,106 @@ jobs: S3_PUBLIC_URL: ${{ vars.S3_PUBLIC_URL }} S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }} S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }} + + oci: + name: OCI artifacts + needs: [build] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3 + with: + version: 2026.7.5 + experimental: true + install: false + + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: container-binaries + path: .container + + - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 + + - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + + - name: Determine artifact version + id: artifact + shell: bash + run: | + base_version=$(cat VERSION) + if [[ "$GITHUB_REF" == refs/heads/main ]]; then + version="$base_version-nightly.$(git log -1 --format=%ct)" + elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then + version="${GITHUB_REF_NAME#v}" + if [[ "$version" != "$base_version" ]]; then + echo "Tag version $version does not match VERSION $base_version" >&2 + exit 1 + fi + else + version="$base_version-pr.$GITHUB_RUN_NUMBER" + fi + echo "version=$version" >> "$GITHUB_OUTPUT" + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log Helm in to GHCR + if: github.event_name != 'pull_request' + env: + GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + printf '%s' "$GHCR_TOKEN" | mise x helm@4.1.1 -- \ + helm registry login ghcr.io --username "$GITHUB_ACTOR" --password-stdin + + - name: Build and publish OCI artifacts + shell: bash + run: | + version="${{ steps.artifact.outputs.version }}" + image_output=() + chart_output=() + + if [[ "${{ github.event_name }}" != pull_request ]]; then + image_output+=(--push) + chart_output+=(--push "oci://ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/charts") + fi + + for service in server worker; do + image="ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/mirum-$service" + tags=( + --tag "$image:sha-$GITHUB_SHA" + --tag "$image:$version" + ) + labels=( + --label "org.opencontainers.image.revision=$GITHUB_SHA" + --label "org.opencontainers.image.source=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" + --label "org.opencontainers.image.version=$version" + ) + + if [[ "$GITHUB_REF" == refs/heads/main ]]; then + tags+=(--tag "$image:nightly") + elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then + tags+=(--tag "$image:latest") + fi + + mise run container -- \ + --context . \ + --file "cmd/mirum-$service/Dockerfile" \ + --platform linux/amd64,linux/arm64 \ + --cache-scope "mirum-$service" \ + "${tags[@]}" "${labels[@]}" "${image_output[@]}" + done + + mise run chart -- \ + --chart charts/mirum \ + --version "$version" \ + --app-version "$version" \ + "${chart_output[@]}" diff --git a/.gitignore b/.gitignore index 149195c..437dc77 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /dev /build +/.container /.task mise.local.toml mise.*.local.toml diff --git a/REUSE.toml b/REUSE.toml index 850dd72..708bc13 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -14,6 +14,7 @@ path = [ "mise.lock", "packaging/dl/*", "packaging/logo.svg", + "charts/mirum/templates/*", "buf.*", ] SPDX-FileCopyrightText = "2026 Nikolay Govorov" @@ -29,6 +30,7 @@ SPDX-License-Identifier = "CC-BY-3.0" [[annotations]] path = [ "README.md", + "charts/mirum/README.md", "docs/**.md", ] SPDX-FileCopyrightText = "2026 Nikolay Govorov" diff --git a/charts/mirum/Chart.yaml b/charts/mirum/Chart.yaml new file mode 100644 --- /dev/null +++ b/charts/mirum/Chart.yaml @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# SPDX-License-Identifier: AGPL-3.0-or-later + +apiVersion: v2 +type: application +version: 0.1.0 + +name: mirum +home: https://github.com/dimidiumlabs/mirum +description: Mirum CI server and workers diff --git a/charts/mirum/templates/_helpers.tpl b/charts/mirum/templates/_helpers.tpl new file mode 100644 --- /dev/null +++ b/charts/mirum/templates/_helpers.tpl @@ -0,0 +1,40 @@ +{{/* SPDX-License-Identifier: AGPL-3.0-or-later */}} +{{/* vim: set filetype=helm: */}} +{{- define "mirum.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "mirum.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name (include "mirum.name" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{- define "mirum.labels" -}} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }} +app.kubernetes.io/name: {{ include "mirum.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{- define "mirum.componentLabels" -}} +{{ include "mirum.labels" .root }} +app.kubernetes.io/component: {{ .component }} +{{- end -}} + +{{- define "mirum.selectorLabels" -}} +app.kubernetes.io/name: {{ include "mirum.name" .root }} +app.kubernetes.io/instance: {{ .root.Release.Name }} +app.kubernetes.io/component: {{ .component }} +{{- end -}} + +{{- define "mirum.image" -}} +{{- if .image.digest -}} +{{- printf "%s@%s" .image.repository .image.digest -}} +{{- else -}} +{{- printf "%s:%s" .image.repository (default .root.Chart.AppVersion .image.tag) -}} +{{- end -}} +{{- end -}} diff --git a/charts/mirum/templates/server-deployment.yaml b/charts/mirum/templates/server-deployment.yaml new file mode 100644 --- /dev/null +++ b/charts/mirum/templates/server-deployment.yaml @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# SPDX-License-Identifier: AGPL-3.0-or-later + +{{- if and .Values.server.enabled (ne (int .Values.server.replicaCount) 1) }} +{{- fail "mirum: server.replicaCount must be 1 while the task queue is process-local" }} +{{- end }} +{{- if .Values.server.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "mirum.fullname" . }}-server + labels: + {{- include "mirum.componentLabels" (dict "root" . "component" "server") | nindent 4 }} +spec: + replicas: {{ .Values.server.replicaCount }} + strategy: + type: Recreate + selector: + matchLabels: + {{- include "mirum.selectorLabels" (dict "root" . "component" "server") | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.server.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "mirum.selectorLabels" (dict "root" . "component" "server") | nindent 8 }} + {{- with .Values.server.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + automountServiceAccountToken: false + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + securityContext: + runAsNonRoot: true + runAsUser: 10000 + runAsGroup: 10000 + fsGroup: 10000 + fsGroupChangePolicy: OnRootMismatch + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: server + image: {{ include "mirum.image" (dict "root" . "image" .Values.server.image) | quote }} + imagePullPolicy: {{ .Values.server.image.pullPolicy }} + args: ["daemon", "--config=/etc/mirum/secret/config.yaml"] + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + readOnlyRootFilesystem: true + ports: + - name: web + containerPort: {{ .Values.server.webPort }} + - name: grpc + containerPort: {{ .Values.server.grpcPort }} + {{- with .Values.server.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + startupProbe: + tcpSocket: { port: web } + failureThreshold: 30 + periodSeconds: 2 + readinessProbe: + tcpSocket: { port: web } + livenessProbe: + tcpSocket: { port: web } + periodSeconds: 20 + resources: + {{- toYaml .Values.server.resources | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/mirum/secret + readOnly: true + - name: run + mountPath: /run/mirum-server + volumes: + - name: config + secret: + secretName: {{ .Values.server.existingSecret }} + defaultMode: 0440 + - name: run + emptyDir: {} + {{- with .Values.server.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} + +# vim: set filetype=helm: diff --git a/charts/mirum/templates/server-httproute.yaml b/charts/mirum/templates/server-httproute.yaml new file mode 100644 --- /dev/null +++ b/charts/mirum/templates/server-httproute.yaml @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# SPDX-License-Identifier: AGPL-3.0-or-later + +{{- if and .Values.server.enabled .Values.server.route.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "mirum.fullname" . }} + labels: + {{- include "mirum.componentLabels" (dict "root" . "component" "server") | nindent 4 }} +spec: + {{- with .Values.server.route.parentRefs }} + parentRefs: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.server.route.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + - backendRefs: + - name: {{ include "mirum.fullname" . }} + port: {{ .Values.server.webPort }} +{{- end }} + +# vim: set filetype=helm: diff --git a/charts/mirum/templates/server-service.yaml b/charts/mirum/templates/server-service.yaml new file mode 100644 --- /dev/null +++ b/charts/mirum/templates/server-service.yaml @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# SPDX-License-Identifier: AGPL-3.0-or-later + +{{- if .Values.server.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "mirum.fullname" . }} + labels: + {{- include "mirum.componentLabels" (dict "root" . "component" "server") | nindent 4 }} +spec: + selector: + {{- include "mirum.selectorLabels" (dict "root" . "component" "server") | nindent 4 }} + ports: + - name: web + port: {{ .Values.server.webPort }} + targetPort: web + - name: grpc + port: {{ .Values.server.grpcPort }} + targetPort: grpc +{{- end }} + +# vim: set filetype=helm: diff --git a/charts/mirum/templates/worker-deployment.yaml b/charts/mirum/templates/worker-deployment.yaml new file mode 100644 --- /dev/null +++ b/charts/mirum/templates/worker-deployment.yaml @@ -0,0 +1,87 @@ +# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# SPDX-License-Identifier: AGPL-3.0-or-later + +{{- if .Values.worker.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "mirum.fullname" . }}-worker + labels: + {{- include "mirum.componentLabels" (dict "root" . "component" "worker") | nindent 4 }} +spec: + replicas: {{ .Values.worker.replicaCount }} + selector: + matchLabels: + {{- include "mirum.selectorLabels" (dict "root" . "component" "worker") | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.worker.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "mirum.selectorLabels" (dict "root" . "component" "worker") | nindent 8 }} + {{- with .Values.worker.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + automountServiceAccountToken: false + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + securityContext: + runAsNonRoot: true + runAsUser: 10000 + runAsGroup: 10000 + fsGroup: 10000 + fsGroupChangePolicy: OnRootMismatch + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: worker + image: {{ include "mirum.image" (dict "root" . "image" .Values.worker.image) | quote }} + imagePullPolicy: {{ .Values.worker.image.pullPolicy }} + args: ["--config=/etc/mirum/secret/config.yaml"] + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + readOnlyRootFilesystem: true + {{- with .Values.worker.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.worker.resources | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/mirum/secret + readOnly: true + - name: workspace + mountPath: /var/lib/mirum-worker + - name: tmp + mountPath: /tmp + volumes: + - name: config + secret: + secretName: {{ .Values.worker.existingSecret }} + defaultMode: 0440 + - name: workspace + emptyDir: {} + - name: tmp + emptyDir: {} + {{- with .Values.worker.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.worker.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.worker.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} + +# vim: set filetype=helm: diff --git a/charts/mirum/values.yaml b/charts/mirum/values.yaml new file mode 100644 --- /dev/null +++ b/charts/mirum/values.yaml @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# SPDX-License-Identifier: AGPL-3.0-or-later + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +server: + enabled: true + replicaCount: 1 + image: + repository: ghcr.io/dimidiumlabs/mirum-server + tag: "" + digest: "" + pullPolicy: IfNotPresent + # Must contain config.yaml and every file referenced by that config, normally + # grpc.crt and grpc.key. Keep database_uri, pepper, token, and webhook_secret + # in this Secret rather than Helm values. + existingSecret: mirum-server + webPort: 3000 + grpcPort: 2000 + route: + enabled: false + hostnames: [] + parentRefs: [] + resources: {} + extraEnv: [] + podAnnotations: {} + podLabels: {} + nodeSelector: {} + tolerations: [] + affinity: {} + +worker: + enabled: false + replicaCount: 1 + image: + repository: ghcr.io/dimidiumlabs/mirum-worker + tag: "" + digest: "" + pullPolicy: IfNotPresent + # Must contain config.yaml, the Ed25519 key referenced by key_file, and an + # optional CA file referenced by tls_ca. + existingSecret: mirum-worker + resources: {} + extraEnv: [] + podAnnotations: {} + podLabels: {} + nodeSelector: {} + tolerations: [] + affinity: {} + +terminationGracePeriodSeconds: 40 diff --git a/cmd/mirum-server/Dockerfile b/cmd/mirum-server/Dockerfile new file mode 100644 --- /dev/null +++ b/cmd/mirum-server/Dockerfile @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# SPDX-License-Identifier: AGPL-3.0-or-later + +FROM gcr.io/distroless/static-debian13:nonroot@sha256:1c2c046bc09ed40fad370b599a0b1ae7987f55b01e247cf27a7c27cd97e5bbc7 + +ARG TARGETARCH + +LABEL org.opencontainers.image.source="https://github.com/dimidiumlabs/mirum" \ + org.opencontainers.image.licenses="AGPL-3.0-or-later" \ + org.opencontainers.image.title="mirum-server" + +COPY --chown=root:root --chmod=0755 .container/mirum-server-linux-${TARGETARCH} /usr/local/bin/mirum-server +COPY LICENSE README.md /usr/share/doc/mirum/ + +USER 10000:10000 +EXPOSE 3000 2000 +ENTRYPOINT ["/usr/local/bin/mirum-server"] +CMD ["daemon", "--config=/etc/mirum/config.yaml"] + +# syntax=docker/dockerfile: diff --git a/cmd/mirum-worker/Dockerfile b/cmd/mirum-worker/Dockerfile new file mode 100644 --- /dev/null +++ b/cmd/mirum-worker/Dockerfile @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# SPDX-License-Identifier: AGPL-3.0-or-later + +FROM docker.io/library/alpine:3.23.5@sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40 + +ARG TARGETARCH + +LABEL org.opencontainers.image.source="https://github.com/dimidiumlabs/mirum" \ + org.opencontainers.image.licenses="AGPL-3.0-or-later" \ + org.opencontainers.image.title="mirum-worker" + +RUN apk add --no-cache bash ca-certificates git && \ + addgroup -g 10000 mirum-worker && \ + adduser -D -H -u 10000 -G mirum-worker -h /var/lib/mirum-worker \ + -s /sbin/nologin mirum-worker && \ + install -d -o mirum-worker -g mirum-worker -m 0750 /var/lib/mirum-worker + +COPY --chown=root:root --chmod=0755 .container/mirum-worker-linux-${TARGETARCH} /usr/local/bin/mirum-worker +COPY LICENSE README.md /usr/share/doc/mirum/ + +USER 10000:10000 +WORKDIR /var/lib/mirum-worker +ENTRYPOINT ["/usr/local/bin/mirum-worker"] +CMD ["--config=/etc/mirum/config.yaml"] + +# syntax=docker/dockerfile: diff --git a/mise.toml b/mise.toml index e23f620..2abf2b7 100644 --- a/mise.toml +++ b/mise.toml @@ -34,5 +34,5 @@ zig = "0.16.0" [task_config] dir = "{{cwd}}" includes = [ - "git::https://github.com/dimidiumlabs/infra.git//tasks?ref=4a4589fdb968686a7c91b380ff369aba23bd8662", + "git::https://github.com/dimidiumlabs/infra.git//tasks?ref=8bc35fe8be889c50db2d1fb4425cc1b4097dc6b8", ] |
