Skip to content
Open
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
8 changes: 8 additions & 0 deletions 03_container_layer/docker/admin/gitea/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Prevent secrets and editor artifacts from leaking into the build context.
.env
*.env
*.key
*.pem
*.p12
*.pfx
README.md
28 changes: 28 additions & 0 deletions 03_container_layer/docker/admin/gitea/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#
# ISSUE 141
#
# Copy to .env and fill in values before running: cp .env.example .env
# .env is gitignored — never commit real secrets.
#

# ── Gitea ──────────────────────────────────────────────────────────────────
GITEA_DOMAIN=localhost
GITEA_BASE_URL=http://localhost:3000

# Generate with: openssl rand -hex 32
GITEA_SECRET_KEY=please-change-me-in-production
# Generate with: gitea generate secret INTERNAL_TOKEN
GITEA_INTERNAL_TOKEN=please-change-me-in-production

# ── Initial admin (must match admins[0] in provisioning/users.yml) ─────────
GITEA_ADMIN_USER=gitea-admin
GITEA_ADMIN_PASS=Admin1234!

# ── PostgreSQL ──────────────────────────────────────────────────────────────
POSTGRES_USER=gitea
POSTGRES_PASSWORD=CHANGEME_replace_before_deploying
POSTGRES_DB=gitea

# ── Host ports ──────────────────────────────────────────────────────────────
HTTP_PORT=3000
SSH_PORT=2222
29 changes: 29 additions & 0 deletions 03_container_layer/docker/admin/gitea/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#
# ISSUE 141
#

#
# BUILDER — golang:alpine (same base as official Gitea Dockerfile)
# Installs yq for YAML parsing; copies and pre-validates provisioning scripts.
#
FROM golang:alpine AS builder

RUN apk add --no-cache jq && \
wget -q https://github.com/mikefarah/yq/releases/download/v4.44.1/yq_linux_amd64 \
-O /usr/bin/yq && chmod +x /usr/bin/yq

COPY provisioning/ /provisioning/
RUN chmod +x /provisioning/init.sh

#
# RUNTIME — gitea/gitea:latest (Alpine-based, ships the gitea CLI)
# Copies tooling and provisioning scripts from builder.
# Acts as the provisioner sidecar: creates users via CLI + SSH keys via REST API.
#
FROM gitea/gitea:latest AS runtime

COPY --from=builder /usr/bin/yq /usr/bin/yq
COPY --from=builder /usr/bin/jq /usr/bin/jq
COPY --from=builder /provisioning/ /provisioning/

ENTRYPOINT ["/provisioning/init.sh"]
89 changes: 89 additions & 0 deletions 03_container_layer/docker/admin/gitea/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#
# ISSUE 141
#

SERVICE = gitea
PROVISIONER = gitea-provisioner
DEBUG_SERVICE = $(SERVICE)-debug

.PHONY: up down stop build rebuild build-up rebuild-up reprovision \
logs-provisioner term term-debug-build clean print help

help:
@echo ""
@echo ""
@echo " Available : "
@echo ""
@echo " make up - run the full stack (db + gitea + provisioner) in background"
@echo " make down - stop and remove all containers"
@echo " make stop - stop $(SERVICE) only"
@echo ""
@echo " make build - build the provisioner image"
@echo " make build-up - build provisioner image then start the full stack"
@echo ""
@echo " make rebuild - full rebuild without cache"
@echo " make rebuild-up - full rebuild without cache then start"
@echo ""
@echo " make reprovision - remove the provisioning stamp and restart provisioner"
@echo " make logs-provisioner - tail provisioner output"
@echo ""
@echo " make term - open shell in $(SERVICE) container"
@echo " make term-debug-build - build and open shell in $(DEBUG_SERVICE)"
@echo " make clean - delete all containers, images, volumes and unused network"
@echo " make print - print this help"
@echo ""
@echo ""

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

up:
docker compose up -d
down:
docker compose down
stop:
docker compose stop $(SERVICE)

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

build:
docker compose build

rebuild:
docker compose down
docker compose build --no-cache

# # # # # # # # # # # #

build-up:
docker compose build
docker compose up -d

rebuild-up:
docker compose down
docker compose build --no-cache
docker compose up -d

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

reprovision:
docker exec $(SERVICE) rm -f /data/gitea/.provisioned
docker compose restart $(PROVISIONER)

logs-provisioner:
docker compose logs -f $(PROVISIONER)

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

clean:
docker compose down -v --rmi all

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

term:
docker exec -it $(SERVICE) /bin/sh

term-debug-build:
docker compose up --build -d debug
docker exec -it $(DEBUG_SERVICE) /bin/sh

print: help
129 changes: 129 additions & 0 deletions 03_container_layer/docker/admin/gitea/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Gitea — Standalone Docker Deployment

Issue: [#141](https://github.com/range42/range42-catalog/issues/141)

Standalone Gitea instance with automated user and SSH-key provisioning.
Registration is disabled by default; all accounts are declared in `provisioning/users.yml`.

---

## Prerequisites

- Docker 24+ with Compose v2
- `make`

---

## Quick Start

```bash
cp .env.example .env # edit secrets before deploying
make build-up # build provisioner image, start full stack
make logs-provisioner # watch bootstrap output
```

Gitea will be available at `http://localhost:3000` (or `GITEA_BASE_URL`).
SSH cloning: `git clone git@localhost:2222/<org>/<repo>.git`

---

## Build & Push

```bash
# Build only the provisioner image
make build

# Full rebuild (no cache)
make rebuild

# Push to a registry (replace tag as needed)
docker tag gitea-provisioner registry.example.com/range42/gitea-provisioner:latest
docker push registry.example.com/range42/gitea-provisioner:latest
```

---

## Declaring Users and SSH Keys

Edit `provisioning/users.yml` before the first `make up`:

```yaml
admins:
- username: gitea-admin
email: admin@range42.local
password: "Admin1234!"
ssh_keys:
- "ssh-ed25519 AAAA... user@host" # full public key string

users:
- username: trainee01
email: trainee01@range42.local
password: "Trainee1234!"
ssh_keys: [] # no SSH key for this user
```

- Add/remove entries to change the provisioned user set.
- `admins[]` entries receive Gitea admin privileges.
- `users[]` entries are regular accounts.
- `ssh_keys` is a list of raw public-key strings (same format as `~/.ssh/authorized_keys`).

**The provisioner runs only once** (guarded by `/data/gitea/.provisioned`).
To re-provision after changes, run:

```bash
make reprovision
```

---

## SSH Key Format

Accepted algorithms: `ssh-ed25519`, `ssh-rsa`, `ecdsa-sha2-nistp256/384/521`.

```
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... comment
```

Generate a new key pair:

```bash
ssh-keygen -t ed25519 -C "trainee01@range42" -f ~/.ssh/range42_trainee01
```

Paste the contents of `~/.ssh/range42_trainee01.pub` into the `ssh_keys` list.

---

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `GITEA_DOMAIN` | `localhost` | Public hostname |
| `GITEA_BASE_URL` | `http://localhost:3000` | Root URL shown in clone URLs |
| `GITEA_SECRET_KEY` | *(required)* | App secret — `openssl rand -hex 32` |
| `GITEA_INTERNAL_TOKEN` | *(required)* | Internal token — `gitea generate secret INTERNAL_TOKEN` |
| `GITEA_ADMIN_USER` | `gitea-admin` | Must match `admins[0].username` in `users.yml` |
| `GITEA_ADMIN_PASS` | `Admin1234!` | Must match `admins[0].password` in `users.yml` |
| `POSTGRES_USER` | `gitea` | DB user |
| `POSTGRES_PASSWORD` | `gitea` | DB password |
| `POSTGRES_DB` | `gitea` | DB name |
| `HTTP_PORT` | `3000` | Host port for HTTP |
| `SSH_PORT` | `2222` | Host port for SSH (avoids conflict with host sshd) |

---

## Troubleshooting

**Provisioner exits immediately with "Already provisioned"**
Remove the stamp and re-run: `make reprovision`

**`gitea admin user create` fails silently**
Check provisioner logs: `make logs-provisioner`
The stamp is NOT written on failure — restart the provisioner to retry.

**SSH key injection fails (HTTP 422)**
The key already exists in Gitea, or the key format is invalid.
Verify key format with `ssh-keygen -l -f <pubkey_file>`.

**Port 3000 already in use**
Set `HTTP_PORT=3001` (or any free port) in `.env`.
91 changes: 91 additions & 0 deletions 03_container_layer/docker/admin/gitea/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#
# ISSUE 141
#

services:

db:
image: postgres:16-alpine
container_name: gitea-db
environment:
POSTGRES_USER: ${POSTGRES_USER:-gitea}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in .env}
POSTGRES_DB: ${POSTGRES_DB:-gitea}
volumes:
- gitea-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-gitea}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

gitea: &main
image: gitea/gitea:latest
container_name: gitea
environment:
USER_UID: "1000"
USER_GID: "1000"
GITEA__database__DB_TYPE: postgres
GITEA__database__HOST: db:5432
GITEA__database__NAME: ${POSTGRES_DB:-gitea}
GITEA__database__USER: ${POSTGRES_USER:-gitea}
GITEA__database__PASSWD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in .env}
GITEA__server__DOMAIN: ${GITEA_DOMAIN:-localhost}
GITEA__server__ROOT_URL: ${GITEA_BASE_URL:-http://localhost:3000}
GITEA__server__HTTP_PORT: "3000"
GITEA__server__SSH_DOMAIN: ${GITEA_DOMAIN:-localhost}
GITEA__server__START_SSH_SERVER: "true"
GITEA__server__SSH_LISTEN_PORT: "22"
GITEA__server__SSH_PORT: "${SSH_PORT:-2222}"
GITEA__service__DISABLE_REGISTRATION: "true"
GITEA__security__INSTALL_LOCK: "true"
GITEA__security__SECRET_KEY: ${GITEA_SECRET_KEY:-please-change-me-in-production}
GITEA__security__INTERNAL_TOKEN: ${GITEA_INTERNAL_TOKEN:-please-change-me-in-production}
volumes:
- gitea-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "${HTTP_PORT:-3000}:3000"
- "${SSH_PORT:-2222}:22"
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/v1/version"]
interval: 15s
timeout: 10s
retries: 10
start_period: 30s
restart: unless-stopped
#
# #### #### do not remove.
#
# debug:
# <<: *main
# container_name: gitea-debug
# ports:
# - "60443:443"
# command: ["sleep", "infinity"]

provisioner:
build:
context: .
dockerfile: Dockerfile
container_name: gitea-provisioner
environment:
GITEA_URL: http://gitea:3000
GITEA_ADMIN_USER: ${GITEA_ADMIN_USER:-gitea-admin}
GITEA_ADMIN_PASS: ${GITEA_ADMIN_PASS:-Admin1234!}
USERS_FILE: /provisioning/users.yml
volumes:
- gitea-data:/data
depends_on:
gitea:
condition: service_healthy
restart: "no"

volumes:
gitea-data:
gitea-db-data:
Loading