Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0fe2f78
feat(worker): implement DBBackup and OutboxPurge jobs as Temporal wor…
dargmuesli Aug 18, 2026
7684960
feat(worker): add Schedules, Prometheus metrics, entrypoint, and Dock…
dargmuesli Aug 18, 2026
eb2f9c1
refactor(worker): drop unused activity name constants
dargmuesli Aug 18, 2026
d6a57a5
chore: initial commit
dargmuesli Aug 19, 2026
f4c67ee
fix(worker): fix file descriptor leak in DBBackup activity
dargmuesli Aug 19, 2026
62127e0
fix(schedule): handle concurrent schedule creation gracefully
dargmuesli Aug 19, 2026
1407244
fix(config): URL-encode password in Postgres DSN
dargmuesli Aug 19, 2026
7939915
fix(worker): healthcheck falls back on missing config
dargmuesli Aug 19, 2026
22da16e
fix(sentrycrons): parse URL once and tighten status check
dargmuesli Aug 19, 2026
32e1f1a
refactor(activities): use strings.Builder in quoteIdent
dargmuesli Aug 19, 2026
2980c60
docs: fix missing space in metrics address
dargmuesli Aug 19, 2026
020c45a
test: add smoke tests for s3client and worker
dargmuesli Aug 19, 2026
8dfb40f
feat(worker): support multipart uploads for large DBBackup files
dargmuesli Aug 18, 2026
6df0e48
refactor(worker): load config via struct tags instead of hand-rolled …
dargmuesli Aug 19, 2026
64866bd
refactor(worker): use s3/transfermanager instead of hand-rolled multi…
dargmuesli Aug 19, 2026
4083925
fix(worker): address golangci-lint findings ahead of adopting it in CI
dargmuesli Aug 19, 2026
4a1be17
chore(worker): add CI, lint, and release tooling matching dargstack's…
dargmuesli Aug 19, 2026
f62e43e
chore(worker): rename module and repo references to maevsi/temporal
dargmuesli Aug 19, 2026
c3dc205
ci(worker): add alpha and beta release channels
dargmuesli Aug 19, 2026
8dccf20
chore: remove github templates and gitignore file
dargmuesli Aug 19, 2026
a1c713b
chore: dereference jobber
dargmuesli Aug 19, 2026
3925add
fix(config): quote DSN values instead of URL-encoding the password
dargmuesli Aug 28, 2026
5d267d4
fix(schedule): detect AlreadyExists via the SDK error type
dargmuesli Aug 28, 2026
a02b7c6
fix(sentrycrons): reject unusable check-in URLs at startup
dargmuesli Aug 28, 2026
4291cca
test(worker): restore env vars in the healthcheck config-error test
dargmuesli Aug 28, 2026
9002218
refactor(activities): drop redundant file closes in DBBackup
dargmuesli Aug 28, 2026
a672179
chore(deps): update prometheus/client_golang to v1.24.1
dargmuesli Aug 28, 2026
586f293
chore: add gitignore
dargmuesli Aug 28, 2026
12f5582
docs: correct check-in count and note sentry URL validation
dargmuesli Aug 28, 2026
bb5b154
fix(activities): normalize S3 keys built from S3_PREFIX
dargmuesli Aug 29, 2026
1e4ca60
test(activities): cover the multipart upload path
dargmuesli Aug 29, 2026
c8fb6c2
fix(activities): heartbeat while a single file uploads
dargmuesli Aug 29, 2026
02a793d
fix(metrics): reject a metrics path colliding with the health endpoint
dargmuesli Aug 29, 2026
e6fc409
fix(worker): fall back to the default address when METRICS_ADDR is empty
dargmuesli Aug 29, 2026
dca3a3c
ci(worker): run tests with the race detector
dargmuesli Aug 29, 2026
3169e60
fix(worker): give both healthchecks an interval and a start period
dargmuesli Aug 29, 2026
46bfb29
chore: relax the go directive to a minimum version
dargmuesli Aug 29, 2026
dd5556d
chore: add the Apache-2.0 license
dargmuesli Aug 29, 2026
b1c3305
docs: correct the multipart threshold and drop handoff notes
dargmuesli Aug 29, 2026
5f4be37
fix: correct gaps
dargmuesli Aug 29, 2026
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
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.git
.gitignore
.github
.vscode
.editorconfig
.releaserc.json
.renovaterc.json
# .golangci.yaml is needed for the lint stage, so it is not excluded here
bin
dist
*.md
CODEOWNERS
.env
.env.local
18 changes: 18 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.go]
indent_style = tab
tab_width = 4

[*.{yaml,yml}]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: [dargmuesli]
5 changes: 5 additions & 0 deletions .github/semantic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Always validate the PR title AND all the commits
titleAndCommits: true
# Allows use of Merge commits (eg on github: "Merge branch 'master' into feature/ride-unicorns")
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
allowMergeCommits: true
48 changes: 48 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: CI

on:
pull_request:
push:
branches:
- alpha
- beta
- main
- renovate/**

permissions: {}

jobs:
ci-optimization:
name: CI optimization
uses: dargmuesli/github-actions/.github/workflows/ci-optimization.yml@e9c9cf391baeb261cf62c0af6c4af2245bae0662 # 5.9.2
permissions:
pull-requests: read
release_semantic_dry:
needs: ci-optimization
if: needs.ci-optimization.outputs.continue == 'true'
name: Release (semantic, dry)
uses: dargmuesli/github-actions/.github/workflows/release-semantic.yml@e9c9cf391baeb261cf62c0af6c4af2245bae0662 # 5.9.2
permissions:
contents: write
id-token: write
secrets:
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
with:
DRY_RUN: true
build:
name: Build
uses: dargmuesli/github-actions/.github/workflows/docker.yml@e9c9cf391baeb261cf62c0af6c4af2245bae0662 # 5.9.2
needs: release_semantic_dry
permissions:
packages: write
with:
TAG: ${{ (needs.release_semantic_dry.outputs.new_release_version != null) && needs.release_semantic_dry.outputs.new_release_version || '' }}
release_semantic:
needs: build
name: Release (semantic)
uses: dargmuesli/github-actions/.github/workflows/release-semantic.yml@e9c9cf391baeb261cf62c0af6c4af2245bae0662 # 5.9.2
permissions:
contents: write
id-token: write
secrets:
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Build output.
/bin/
/dist/

# Local environment, which carries the Postgres/S3/Sentry credentials this worker reads.
.env
.env.local

# Go test and coverage artifacts.
*.test
*.out
36 changes: 36 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: "2"

run:
timeout: 5m

linters:
enable:
- errcheck
- govet
- staticcheck
- unused
- ineffassign
- misspell
- unconvert
- gocritic
- nolintlint
settings:
gocritic:
enabled-tags:
- diagnostic
- style
- performance
errcheck:
exclude-functions:
- (io.Closer).Close

formatters:
enable:
- gofmt
- goimports
settings:
goimports:
local-prefixes:
- github.com/maevsi/temporal

issues: {}
21 changes: 21 additions & 0 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits",
"presetConfig": {}
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits",
"presetConfig": {}
}
],
"@semantic-release/changelog",
"@semantic-release/git",
"@semantic-release/github"
]
}
4 changes: 4 additions & 0 deletions .renovaterc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>dargmuesli/renovate-config"]
}
7 changes: 7 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"github.vscode-github-actions",
"golang.go"
]
}
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[go]": {
"editor.defaultFormatter": "golang.go"
}
}
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @dargmuesli
25 changes: 25 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Contributing

## Prerequisites

- Go (see `go.mod` for the minimum version)
- Git
- [golangci-lint](https://golangci-lint.run/) v2, for local linting matching CI (`docker build --target lint .` runs the same check without a local install)

See the [README's Development section](README.md#development) for running the worker locally, and its [Tests section](README.md#tests) for the test suite.

## Local checks

```sh
go build ./...
go test -race ./...
golangci-lint run ./...
```

CI (`.github/workflows/ci.yaml`) runs the same three checks via the Docker `lint`, `test`, and `build` stages, gating the image build on all three passing.
The `test` stage runs the race detector too, so a data race fails CI rather than only showing up locally.
Lint is enforced at zero issues.

## Commits

Commits and PR titles must follow [Conventional Commits](https://www.conventionalcommits.org/) (enforced by the Semantic Pull Requests check, see `.github/semantic.yml`); releases and `CHANGELOG.md` entries are generated from them automatically.
122 changes: 122 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# syntax=docker/dockerfile:1

########################
# Create base.

FROM golang:1.26.6-alpine AS base

RUN apk add --no-cache ca-certificates

ARG GROUP_ID=1000
ARG USER_ID=1000
ARG USER_NAME=go

RUN addgroup -g $GROUP_ID -S $USER_NAME \
&& adduser -u $USER_ID -S -G $USER_NAME -h /home/$USER_NAME $USER_NAME

WORKDIR /srv/app/


########################
# Serve development.

FROM base AS development

ENV CGO_ENABLED=0

RUN mkdir -p /home/$USER_NAME/go/pkg/mod \
&& chown -R $USER_NAME:$USER_NAME /home/$USER_NAME /srv/app

VOLUME /home/$USER_NAME/go/pkg/mod
VOLUME /srv/app

USER $USER_NAME
CMD ["go", "run", "./cmd/worker"]
EXPOSE 9090
# Unlike production, this stage has a shell and busybox wget, so the probe is a plain HTTP request rather than the binary's own -healthcheck flag.
# Running that flag through "go run" would have to compile and link the whole worker, Temporal and AWS SDKs included, before the probe's own timeout, which a cold build cache cannot do; it would also re-link on every interval.
# Shortcut: this hardcodes the default metrics port from config.Metrics.Addr, so a dev container that moves the metrics server has to update this line too.
HEALTHCHECK --interval=30s --start-period=30s --timeout=5s CMD wget --quiet --spider http://127.0.0.1:9090/healthz || exit 1


########################
# Prepare environment.

FROM base AS prepare

COPY go.mod go.sum ./
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
go mod download

COPY ./ ./


########################
# Lint code.

FROM golangci/golangci-lint:v2.12.2-alpine AS lint

WORKDIR /srv/app/
COPY --from=prepare /srv/app/ ./
RUN golangci-lint run ./...


########################
# Test code.

FROM prepare AS test

# The race detector needs cgo, which on Alpine means a C toolchain.
# This is a leaf stage, so nothing it installs reaches the production image.
RUN apk add --no-cache gcc musl-dev

RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
CGO_ENABLED=1 go test -race -count=1 ./...


########################
# Build for production.

FROM prepare AS build

ENV CGO_ENABLED=0

RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
go build -trimpath -ldflags="-s -w" -o /srv/app/bin/worker ./cmd/worker


########################
# Collect results.

FROM base AS collect

RUN chown $USER_NAME:$USER_NAME .

COPY --from=build --chown=$USER_NAME /srv/app/bin/worker ./worker
COPY --from=lint /srv/app/go.mod /dev/null
COPY --from=test /srv/app/go.mod /dev/null


########################
# Serve production.
#
# The final image is FROM scratch: a statically linked Go binary needs nothing else at runtime, which keeps the attack surface and image size minimal.
# There is deliberately no shell, so HEALTHCHECK execs the binary itself with -healthcheck instead of curl/wget (see cmd/worker/main.go).

FROM scratch AS production

COPY --from=collect /etc/passwd /etc/passwd
COPY --from=collect /etc/group /etc/group
COPY --from=base /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=collect --chown=go /srv/app/worker /srv/app/worker

WORKDIR /srv/app/
USER go
ENTRYPOINT ["/srv/app/worker"]
EXPOSE 9090
HEALTHCHECK --interval=30s --start-period=30s --timeout=5s CMD ["/srv/app/worker", "-healthcheck"]
LABEL org.opencontainers.image.source="https://github.com/maevsi/temporal"
LABEL org.opencontainers.image.description="Temporal worker (Go) running jobs for the Vibetype platform."
Loading
Loading