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
46 changes: 46 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Validate repository

on:
push:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: azure/setup-helm@v4.3.1
- name: Install ShellCheck
run: |
sudo apt-get update
sudo apt-get install --yes shellcheck
- name: Check shell scripts
run: shellcheck ./*.sh k8s-jobs/*.sh
- name: Validate Helm chart
run: |
helm lint helm/
helm template rt helm/ > /tmp/rt-rendered.yaml
helm template custom helm/ --set fullnameOverride=custom --set postgres.enabled=false --set db.host=db.example.test > /tmp/rt-external-db.yaml
helm template rt helm/ --set-json 'cronjobs=[{"name":"getmail","schedule":"* * * * *","command":["/usr/bin/getmail","--rcfile=/getmailrc"]}]' > /tmp/rt-cronjob.yaml
- name: Validate Kubernetes schemas
run: |
docker run --rm -i ghcr.io/yannh/kubeconform:v0.7.0 -strict -summary -ignore-missing-schemas < /tmp/rt-rendered.yaml
docker run --rm -i ghcr.io/yannh/kubeconform:v0.7.0 -strict -summary -ignore-missing-schemas < /tmp/rt-external-db.yaml
docker run --rm -i ghcr.io/yannh/kubeconform:v0.7.0 -strict -summary -ignore-missing-schemas < /tmp/rt-cronjob.yaml
- name: Prepare example Compose configuration
run: |
cp RT_SiteConfig.pm.example RT_SiteConfig.pm
cp Caddyfile.example Caddyfile
cp msmtp.conf.example msmtp/msmtp.conf
cp getmailrc.example getmail/getmailrc
cp crontab.example crontab
printf 'test' > pgadmin_password.secret
printf 'test' > postgres_password.secret
- name: Validate Compose models
run: |
docker compose config --quiet
docker compose -f docker-compose.yml -f docker-compose.dev.yml config --quiet
9 changes: 4 additions & 5 deletions .github/workflows/yamllint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: karancode/yamllint-github-action@master
with:
# fail on warnings and errors
yamllint_strict: true
yamllint_config_filepath: ".yamllint.yml"
- name: Install yamllint
run: pipx install yamllint
- name: Lint YAML
run: yamllint --strict --config-file .yamllint.yml .
14 changes: 1 addition & 13 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
*.sh
!dev.sh
!dev-helm.sh
!prod.sh
!k8s-jobs/install-ingress.sh
RT_SiteConfig.pm
*.pem
*.key
!dev.sh
!prod.sh
!logs_prod.sh
!bash_functions.sh
!restart_prod.sh
!cron_entrypoint.sh
Caddyfile
crontab
/certs/*
Expand All @@ -27,9 +16,8 @@ shredder/*.sql
*.env
.env
*.pgpass
*.json
.claude/settings.local.json
docker-compose.override.yml
*.patch
# Added by goreleaser init:
dist/
*.pm
46 changes: 0 additions & 46 deletions .goreleaser.yaml

This file was deleted.

31 changes: 31 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Repository Guidelines

## Project Structure & Module Organization

This repository packages Request Tracker (RT) and RTIR for Docker and Kubernetes. `Dockerfile` builds the image; `docker-compose.yml` defines production services, while `docker-compose.dev.yml` adds PostgreSQL and pgAdmin. Root shell scripts manage startup and logs. The Helm chart lives in `helm/`; database jobs are in `k8s-jobs/`. Files ending in `.example` are configuration templates.

## Build, Test, and Development Commands

- `./dev.sh` validates configuration, builds the image, and starts the development stack.
- `./prod.sh` pulls published images and recreates the production stack; use `./restart_prod.sh` to restart without pulling.
- `./logs_prod.sh` follows production service logs.
- `docker compose -f docker-compose.yml -f docker-compose.dev.yml config` validates the merged development configuration.
- `helm lint helm/` and `helm template rt helm/` validate and render the chart without modifying a cluster.

Before running the stack, copy the required templates to `RT_SiteConfig.pm`, `Caddyfile`, `msmtp/msmtp.conf`, `crontab`, and `getmail/getmailrc`. See `Readme.md` for development-only certificates and secrets.

## Coding Style & Naming Conventions

Shell scripts use Bash, `set -euf -o pipefail`, quoted expansions, four-space indentation, and `snake_case` functions. Use two-space indentation for YAML and preserve existing Helm Go-template conventions. Run `yamllint .`, `hadolint Dockerfile`, and `kube-linter lint helm/` when available. Keep version mappings synchronized between `Dockerfile`, `helm/Chart.yaml`, and CI workflows.

## Testing Guidelines

There is no standalone unit-test suite. Required checks are linting, image builds, Compose validation, and Helm rendering. For service changes, start the dev stack and inspect container health and logs. Test database initialization manifests in a disposable cluster.

## Commit & Pull Request Guidelines

History favors short subjects such as `Update Dockerfile` or `Fix RTIR version mapping`; dependency updates use `Bump <dependency> from <old> to <new>`. Keep commits focused. Pull requests should explain deployment impact, list validation performed, link issues, and call out configuration, port, image-tag, or migration changes. Include screenshots only for visible UI behavior.

## Security & Configuration

Never commit credentials, private keys, generated certificates, or live RT/mail configuration. Preserve the Caddy rule that blocks the unauthenticated mail-gateway endpoint on the public RT virtual host. Review volume permissions carefully: runtime data is expected to be owned by UID 1000 with restrictive modes.
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ docker compose run --rm rt bash -c 'cd /opt/rt && perl ./sbin/rt-validator --che
### Docker Image (Multi-stage Dockerfile)

1. **`msmtp-builder`** stage (debian:13-slim): Compiles msmtp from source with GPG verification against the upstream signing key.
2. **`builder`** stage (perl:5.42.2): Downloads and builds RT + RT-IR with GPG signature verification, installs CPAN dependencies, and installs all RT extensions. Build args: `RT_VERSION` (default 6.0.3) and `RTIR_VERSION` (default 6.0.3). The `ADDITIONAL_CPANM_ARGS` build arg is used in dev to pass `-n` (skip tests).
3. **Final image** (perl:5.42.2-slim): Copies compiled artifacts from builder stages, installs `getmail6` via `uv`, runs RT via `spawn-fcgi` on port 9000 (FastCGI). A final `rt-test-dependencies` check validates all Perl deps were copied correctly.
2. **`builder`** stage (perl:5.44.0): Downloads and builds RT + RT-IR with GPG signature verification, installs CPAN dependencies, and installs all RT extensions. Build args: `RT_VERSION` (default 6.0.3) and `RTIR_VERSION` (default 6.0.3). The `ADDITIONAL_CPANM_ARGS` build arg is used in dev to pass `-n` (skip tests).
3. **Final image** (perl:5.44.0-slim): Copies compiled artifacts from builder stages, installs `getmail6` via `uv`, runs RT via `spawn-fcgi` on port 9000 (FastCGI). A final `rt-test-dependencies` check validates all Perl deps were copied correctly.

The container exposes port 9000 (FastCGI) and uses a healthcheck via `cgi-fcgi`.

Expand Down
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,8 @@ COPY --chown=rt:rt --from=builder /opt/rt /opt/rt
# run a final dependency check if we copied all
RUN perl /opt/rt/sbin/rt-test-dependencies --with-pg --with-fastcgi --with-gpg --with-graphviz --with-gd

# uv and uvx (needed for getmail6)
COPY --from=docker.io/astral/uv:latest /uv /uvx /bin/
# uv and uvx (needed for getmail6); pin this independently from the base image.
COPY --from=docker.io/astral/uv:0.8.13 /uv /uvx /bin/

RUN true \
# msmtp config
Expand Down Expand Up @@ -320,7 +320,7 @@ EXPOSE 9000

# install getmail as the rt user
USER 1000
RUN uv tool install getmail6 \
RUN uv tool install getmail6==6.20.1 \
&& uv cache clean

USER 0
Expand Down
18 changes: 14 additions & 4 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,18 +410,24 @@ To include additional containers in this setup like pgadmin or change a default

## Kubernetes setup

The chart needs a Secret called `rt-db-creds` holding the database credentials
(keys `dbname`, `username`, `password`). Either create it yourself:
The chart scopes resource names to the Helm release. For release `rt`, it expects
`rt-request-tracker-db-creds` with keys `dbname`, `username`, and `password`:

```bash
kubectl create secret generic rt-db-creds \
kubectl create secret generic rt-request-tracker-db-creds \
--from-literal=dbname=rt \
--from-literal=username=rt \
--from-literal=password='changeme'
```

or let the chart manage it via values (`db.create=true`, `db.password=...`), or
point the chart at an existing Secret with `db.existingSecret`.
point the chart at an existing Secret with `db.existingSecret`. For an external
database, also set `postgres.enabled=false` and `db.host`.

For production mail settings, create a Secret with keys `msmtp` and `getmailrc`,
then set `mail.existingSecret`. Do not commit credentials in a values file. The
mailgate port is protected by a NetworkPolicy and only chart CronJobs are allowed
to connect by default.

```bash
helm install rt helm/
Expand All @@ -438,3 +444,7 @@ kubectl apply -f k8s-jobs/db-init.yaml
```bash
kubectl apply -f k8s-jobs/db-update.yaml
```

The standalone jobs assume release `rt`; adjust their image and resource names
for other releases. Back up PostgreSQL and RT data before upgrades. Chart PVCs
use Helm's keep policy, so uninstall leaves them for deliberate manual cleanup.
19 changes: 19 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Security Policy

## Reporting a Vulnerability

Do not open a public issue for a suspected vulnerability. Use GitHub's private
security-advisory reporting feature for this repository and include affected
versions, reproduction steps, impact, and any proposed mitigation.

Avoid including production credentials, private keys, customer data, or live
Request Tracker configuration in a report. Maintainers will acknowledge a
report, investigate it, and coordinate disclosure and remediation when the
issue is confirmed.

## Supported Versions

Security fixes are applied to the RT image versions currently built by
`.github/workflows/docker.yml`. Older image tags remain available but should not
be assumed to receive fixes. Prefer a concrete version tag over `latest` and
regularly rebuild or pull the selected tag to receive refreshed dependencies.
28 changes: 12 additions & 16 deletions dev-helm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@

set -euf -o pipefail

echo "uninstalling old stuff"
kubectl delete job --all --ignore-not-found
helm uninstall --ignore-not-found rt
kubectl delete secret --all --ignore-not-found
echo "sleeping 15 seconds to let things settle"
sleep 15
echo "installing new stuff"
kubectl create secret generic rt-db-creds \
NAMESPACE="${RT_DEV_NAMESPACE:-rt-dev}"
RELEASE="${RT_DEV_RELEASE:-rt}"

kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f -
kubectl -n "${NAMESPACE}" create secret generic "${RELEASE}-request-tracker-db-creds" \
--from-literal=dbname=rt \
--from-literal=username=rt \
--from-literal=password='rt'
helm install rt helm/
echo "sleeping 2 minutes to let the database come up"
sleep 120
echo "initializing the database"
kubectl apply -f k8s-jobs/db-init.yaml
echo "done"
kubectl get pods
--from-literal=password='rt' \
--dry-run=client -o yaml | kubectl apply -f -
helm upgrade --install "${RELEASE}" helm/ --namespace "${NAMESPACE}"
kubectl -n "${NAMESPACE}" rollout status "deployment/${RELEASE}-request-tracker-db" --timeout=180s
kubectl -n "${NAMESPACE}" apply -f k8s-jobs/db-init.yaml
kubectl -n "${NAMESPACE}" wait --for=condition=complete job/db-init-job --timeout=300s
kubectl -n "${NAMESPACE}" get pods
4 changes: 2 additions & 2 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ services:
restart: "no"

db:
image: postgres:latest
image: postgres:17.6
restart: "no"
environment:
POSTGRES_DB: rt
Expand All @@ -48,7 +48,7 @@ services:
- net

pgadmin:
image: dpage/pgadmin4:latest
image: dpage/pgadmin4:9.8
restart: "no"
ports:
- "127.0.0.1:8888:80"
Expand Down
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
x-app: &default-app
build:
context: .
image: firefart/requesttracker:latest
image: ${RT_IMAGE:-firefart/requesttracker:6.0.3}
restart: unless-stopped
configs:
- source: rt_site_config
Expand Down Expand Up @@ -31,7 +31,7 @@ services:
hostname: rt
deploy:
mode: replicated
replicas: 5
replicas: ${RT_REPLICAS:-5}
endpoint_mode: vip

cron:
Expand All @@ -58,7 +58,7 @@ services:
restart: true

caddy:
image: caddy:latest
image: caddy:2.10
hostname: caddy
restart: unless-stopped
ports:
Expand Down
2 changes: 1 addition & 1 deletion helm/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v2
name: request-tracker
description: A Helm chart for installing request tracker on Kubernetes/OpenShift clusters.
type: application
version: 0.2.0
version: 0.3.0
# the default container image tag is derived from this when rt.image.tag is empty
appVersion: "6.0.3"
sources:
Expand Down
29 changes: 12 additions & 17 deletions helm/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
1. Get the application URL by running these commands:
Request Tracker was installed as {{ include "request-tracker.fullname" . }}.

{{- if .Values.ingress.enabled }}
Configured ingress URLs:
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.caddy.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services rt)
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.caddy.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w rt'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} rt --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.caddy.service.port }}
{{- else if contains "ClusterIP" .Values.caddy.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name=rt,app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}
{{- else }}
To access it locally:
kubectl --namespace {{ .Release.Namespace }} port-forward service/{{ include "request-tracker.caddyName" . }} 8080:{{ .Values.caddy.service.port }}
echo http://127.0.0.1:8080
{{- end }}

The database and application-data PVCs have the Helm keep policy. Back them up
before upgrades and remove them manually only when their data is no longer needed.
Loading
Loading