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
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.git
.github
charts
docs
*.md
config.yaml
64 changes: 64 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Verify

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- run: go test ./...
- run: go vet ./...
- run: go build ./cmd/minigate
- uses: azure/setup-helm@v4
- run: helm lint charts/minigate --set existingSecret=minigate-config
- run: helm template minigate charts/minigate --set existingSecret=minigate-config --set replicaCount=3 --set ingress.enabled=true --set ingress.host=files.example.com --set ingress.tlsSecretName=files-tls --set serviceMonitor.enabled=true > /dev/null
- if: github.event_name == 'pull_request'
run: docker build -t minigate:ci .

release:
needs: go
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
concurrency:
group: minigate-release
cancel-in-progress: false
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '24'
- uses: azure/setup-helm@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IMAGE_NAME: ghcr.io/ehsanmsb/minigate
run: |
npx --yes \
-p semantic-release@25 \
-p @semantic-release/commit-analyzer@13 \
-p @semantic-release/release-notes-generator@14 \
-p @semantic-release/exec@7 \
-p @semantic-release/github@12 \
-p conventional-changelog-conventionalcommits@8 \
semantic-release
31 changes: 31 additions & 0 deletions .releaserc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
branches:
- main
tagFormat: v${version}
plugins:
- - '@semantic-release/commit-analyzer'
- preset: conventionalcommits
releaseRules:
- breaking: true
release: major
- type: feat
release: minor
- type: '*'
release: patch
- - '@semantic-release/release-notes-generator'
- preset: conventionalcommits
- - '@semantic-release/exec'
- analyzeCommitsCmd: "printf patch"
prepareCmd: |
set -eu
version="${nextRelease.version}"
mkdir -p dist
helm package charts/minigate --destination dist --version "$version" --app-version "$version"
docker buildx build --platform linux/amd64,linux/arm64 --push \
--tag "$IMAGE_NAME:$version" \
--tag "$IMAGE_NAME:latest" \
--label "org.opencontainers.image.source=https://github.com/$GITHUB_REPOSITORY" \
--label "org.opencontainers.image.version=$version" \
.
- - '@semantic-release/github'
- assets:
- dist/*.tgz
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM golang:1.25.3-alpine AS build

WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY cmd ./cmd
COPY internal ./internal
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /minigate ./cmd/minigate

FROM alpine:3.22
RUN apk add --no-cache ca-certificates && adduser -D -u 10001 minigate
USER minigate
COPY --from=build /minigate /usr/local/bin/minigate
EXPOSE 8080 9090
ENTRYPOINT ["/usr/local/bin/minigate"]
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# MiniGate

[Documentation](docs/README.md) · [Kubernetes deployment](docs/deployment.md) · [Releases](docs/releases.md)

MiniGate exposes selected private S3-compatible buckets through public, read-only URLs. Each configured name has its own storage endpoint and credentials. The only object operation is `GetObject`.

```text
Expand Down Expand Up @@ -40,7 +42,11 @@ Give each configured credential `GetObject` permission only for the intended pre
go run ./cmd/minigate -config config.yaml -addr :8080
```

The service accepts `GET /{name}/{object-key}` and `GET /health`. Other methods return 405. A missing object returns 404; storage failures return 502. `Range` requests are forwarded to storage for partial downloads. S3 content metadata is forwarded in the response.
Prometheus metrics are served separately at `127.0.0.1:9090/metrics` by default. Set `-metrics-addr` to change the listener. Metrics include `minigate_configured_buckets`, request counts and durations by HTTP status and method, plus Go runtime and process metrics. Health probes are excluded from request counts. Object keys and bucket names are not metric labels. Prometheus itself supplies `up` for each scrape target; `GET /health` remains the Kubernetes health check.

The service accepts `GET /{name}/{object-key}` and `GET /health`. Other methods return 405. A missing or inaccessible object returns 404; storage failures return 502. S3 can return `AccessDenied` for a missing object when credentials lack `ListBucket`, so MiniGate maps that error to 404. `Range` requests are forwarded to storage for partial downloads. S3 content metadata is forwarded in the response.

Logs are JSON on standard output. `MINIGATE_LOG_LEVEL` accepts `DEBUG`, `INFO`, `WARN`, or `ERROR` (default `WARN`). Unexpected storage and server failures are logged by default. Set `INFO` to also see access denials and invalid ranges, or `DEBUG` for one compact record per object request, including successful reads and expected 404s. Health probes are not logged. Routine request logs omit object keys and credentials.

HTTP routing uses [chi](https://github.com/go-chi/chi); storage access uses the official AWS SDK for Go v2. The HTTP server itself uses Go's standard `net/http` package.

Expand Down
6 changes: 6 additions & 0 deletions charts/minigate/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v2
name: minigate
description: Read-only gateway for private S3-compatible buckets
type: application
version: 1.0.0
appVersion: "1.0.0"
9 changes: 9 additions & 0 deletions charts/minigate/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{{- define "minigate.fullname" -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{- define "minigate.labels" -}}
app.kubernetes.io/name: minigate
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end -}}
64 changes: 64 additions & 0 deletions charts/minigate/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{{- if not .Values.existingSecret }}
{{- fail "existingSecret must name a Secret containing config.yaml" }}
{{- end }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "minigate.fullname" . }}
labels:
{{- include "minigate.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: minigate
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
{{- include "minigate.labels" . | nindent 8 }}
spec:
terminationGracePeriodSeconds: 35
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: minigate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args: ["-config", "/etc/minigate/config.yaml", "-metrics-addr", ":9090"]
env:
- name: MINIGATE_LOG_LEVEL
value: {{ .Values.logLevel | quote }}
ports:
- name: http
containerPort: 8080
- name: metrics
containerPort: 9090
readinessProbe:
httpGet:
path: /health
port: http
livenessProbe:
httpGet:
path: /health
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: config
mountPath: /etc/minigate
readOnly: true
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumes:
- name: config
secret:
secretName: {{ .Values.existingSecret | quote }}
items:
- key: config.yaml
path: config.yaml
31 changes: 31 additions & 0 deletions charts/minigate/templates/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{{- if .Values.ingress.enabled }}
{{- if not .Values.ingress.host }}
{{- fail "ingress.host is required when ingress.enabled=true" }}
{{- end }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "minigate.fullname" . }}
labels:
{{- include "minigate.labels" . | nindent 4 }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . | quote }}
{{- end }}
{{- with .Values.ingress.tlsSecretName }}
tls:
- hosts: [{{ $.Values.ingress.host | quote }}]
secretName: {{ . | quote }}
{{- end }}
rules:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "minigate.fullname" . }}
port:
name: http
{{- end }}
18 changes: 18 additions & 0 deletions charts/minigate/templates/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "minigate.fullname" . }}
labels:
{{- include "minigate.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
- name: metrics
port: {{ .Values.service.metricsPort }}
targetPort: metrics
selector:
app.kubernetes.io/name: minigate
app.kubernetes.io/instance: {{ .Release.Name }}
19 changes: 19 additions & 0 deletions charts/minigate/templates/servicemonitor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{- if .Values.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "minigate.fullname" . }}
labels:
{{- include "minigate.labels" . | nindent 4 }}
{{- with .Values.serviceMonitor.additionalLabels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
app.kubernetes.io/name: minigate
app.kubernetes.io/instance: {{ .Release.Name }}
endpoints:
- port: metrics
path: /metrics
{{- end }}
29 changes: 29 additions & 0 deletions charts/minigate/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
replicaCount: 1
logLevel: WARN

image:
repository: ghcr.io/ehsanmsb/minigate
tag: ""
pullPolicy: IfNotPresent

imagePullSecrets: []

# Create this Secret separately. Its config.yaml key contains S3 credentials.
existingSecret: ""

service:
type: ClusterIP
port: 80
metricsPort: 9090

serviceMonitor:
enabled: false
additionalLabels: {}

ingress:
enabled: false
className: ""
host: ""
tlsSecretName: ""

resources: {}
Loading
Loading