Compare commits
13
Commits
0.0.1
..
3e3a6752af
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e3a6752af | ||
|
|
5b07eae672 | ||
|
|
235e63bfc0 | ||
|
|
92380e534a | ||
|
|
4654a287a8 | ||
|
|
2427c07f4c | ||
|
|
7c1b893e09 | ||
|
|
4993fdca00 | ||
|
|
c4803ea5cb | ||
|
|
19bfa4cf28 | ||
|
|
e70d035672 | ||
|
|
d57320cb77 | ||
|
|
5feae97dd3 |
+1
-10
@@ -1,10 +1 @@
|
|||||||
{
|
{}
|
||||||
"hooks": {
|
|
||||||
"PostToolUse": [
|
|
||||||
{
|
|
||||||
"matcher": "Edit|Write",
|
|
||||||
"command": "php bin/phpunit 2>&1 | tail -20"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
APP_ENV=dev
|
APP_ENV=dev
|
||||||
APP_DEBUG=0
|
APP_DEBUG=1
|
||||||
APP_SECRET=changeme_replace_with_random_32char_string
|
APP_SECRET=changeme_replace_with_random_32char_string
|
||||||
|
|
||||||
# Comma-separated list of allowed hostnames. Leave empty to allow all hosts.
|
# Comma-separated list of allowed hostnames. Leave empty to allow all hosts.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Builds and pushes a multi-arch Docker image to the Gitea container registry
|
# Builds and pushes a multi-arch Docker image to the Gitea container registry.
|
||||||
# whenever a semver tag (v*.*.*) is pushed.
|
# Triggered manually via workflow_dispatch — enter an existing semver tag (e.g. 1.2.3)
|
||||||
|
# in the "Release tag" input. The workflow will fail early if the tag does not exist.
|
||||||
#
|
#
|
||||||
# One-time setup required:
|
# Requires a repository secret REGISTRY_TOKEN — a Gitea PAT with write:package scope.
|
||||||
# 1. Create a Gitea token with "package:write" scope.
|
# Create it at: Settings → Applications → Generate Token (scope: write:package)
|
||||||
# 2. Add it as a repository secret named GITEA_TOKEN
|
# Then add it: Repository → Settings → Secrets → Actions → REGISTRY_TOKEN
|
||||||
# (Repository → Settings → Secrets → Actions).
|
|
||||||
#
|
#
|
||||||
# After a successful run the image is available at:
|
# After a successful run the image is available at:
|
||||||
# <your-gitea-host>/<owner>/<repo>:<version>
|
# <your-gitea-host>/<owner>/<repo>:<version>
|
||||||
@@ -12,17 +12,33 @@
|
|||||||
name: Docker Publish
|
name: Docker Publish
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch:
|
||||||
tags:
|
inputs:
|
||||||
- 'v*.*.*'
|
tag:
|
||||||
|
description: 'Release tag (semver, e.g. 1.2.3)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-push:
|
build-push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Validate tag exists
|
||||||
|
run: |
|
||||||
|
if ! git rev-parse "refs/tags/${{ inputs.tag }}" >/dev/null 2>&1; then
|
||||||
|
echo "Error: tag '${{ inputs.tag }}' does not exist in this repository."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
git checkout "refs/tags/${{ inputs.tag }}"
|
||||||
|
|
||||||
# Strip the protocol from the server URL to get the registry hostname.
|
# Strip the protocol from the server URL to get the registry hostname.
|
||||||
# e.g. https://gitea.example.com → gitea.example.com
|
# e.g. https://gitea.example.com → gitea.example.com
|
||||||
@@ -30,17 +46,19 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "REGISTRY=$(echo '${{ gitea.server_url }}' | sed 's|https://||;s|http://||')" >> $GITHUB_ENV
|
echo "REGISTRY=$(echo '${{ gitea.server_url }}' | sed 's|https://||;s|http://||')" >> $GITHUB_ENV
|
||||||
|
|
||||||
# Generates OCI-compliant tags and labels from the git tag.
|
# Generates OCI-compliant tags and labels from the provided release tag.
|
||||||
# v1.2.3 → image tags: 1.2.3 / 1.2 / 1
|
# 1.2.3 → image tags: 1.2.3 / 1.2 / 1
|
||||||
- name: Extract Docker metadata
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ gitea.repository }}
|
images: ${{ env.REGISTRY }}/${{ gitea.repository }}
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}},value=${{ inputs.tag }}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}},value=${{ inputs.tag }}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}},value=${{ inputs.tag }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.source=${{ gitea.server_url }}/${{ gitea.repository }}
|
||||||
|
|
||||||
# QEMU enables emulation of arm64 on the amd64 runner.
|
# QEMU enables emulation of arm64 on the amd64 runner.
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
@@ -55,7 +73,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ gitea.actor }}
|
username: ${{ gitea.actor }}
|
||||||
password: ${{ secrets.GITEA_TOKEN }}
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
|
|||||||
@@ -117,6 +117,24 @@ Add to `.claude/settings.json` to run PHPUnit automatically after every file edi
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Gitea Workflows
|
||||||
|
|
||||||
|
Workflow files live in [.gitea/workflows/](.gitea/workflows/). This project uses Gitea Actions (GitHub Actions-compatible syntax).
|
||||||
|
|
||||||
|
**When editing workflow files:**
|
||||||
|
|
||||||
|
- Use `gitea.*` context variables, not `github.*` — e.g. `${{ gitea.server_url }}`, `${{ gitea.actor }}`, `${{ gitea.repository }}`
|
||||||
|
- `GITHUB_ENV` and `GITHUB_OUTPUT` are still the correct env file names (Gitea Actions re-uses these)
|
||||||
|
- Secrets are set under Repository → Settings → Secrets → Actions
|
||||||
|
- The registry hostname must be derived from `gitea.server_url` (strip the protocol prefix)
|
||||||
|
- Triggers use standard `on:` syntax; `tags: '*.*.*'` matches semver pushes without a `v` prefix
|
||||||
|
|
||||||
|
**Current workflows:**
|
||||||
|
|
||||||
|
| File | Trigger | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `docker-publish.yml` | Push tag `*.*.*` | Build & push multi-arch image to Gitea container registry |
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
|||||||
+6
-5
@@ -1,10 +1,9 @@
|
|||||||
FROM php:8.3-cli-alpine AS base
|
FROM dunglas/frankenphp:1-php8.4-alpine AS base
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
curl \
|
curl \
|
||||||
icu-libs \
|
icu-libs \
|
||||||
libzip \
|
libzip
|
||||||
&& docker-php-ext-install opcache
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -32,11 +31,12 @@ RUN apk add --no-cache ${PHPIZE_DEPS} linux-headers \
|
|||||||
&& docker-php-ext-enable xdebug \
|
&& docker-php-ext-enable xdebug \
|
||||||
&& apk del ${PHPIZE_DEPS}
|
&& apk del ${PHPIZE_DEPS}
|
||||||
COPY docker/php/xdebug.ini /usr/local/etc/php/conf.d/docker-xdebug.ini
|
COPY docker/php/xdebug.ini /usr/local/etc/php/conf.d/docker-xdebug.ini
|
||||||
|
COPY docker/frankenphp/Caddyfile.dev /etc/caddy/Caddyfile
|
||||||
COPY composer.json composer.lock* ./
|
COPY composer.json composer.lock* ./
|
||||||
RUN composer install --no-scripts --no-interaction --prefer-dist
|
RUN composer install --no-scripts --no-interaction --prefer-dist
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
ENV APP_ENV=dev APP_DEBUG=1
|
ENV APP_ENV=dev APP_DEBUG=1
|
||||||
CMD ["php", "-S", "0.0.0.0:8080", "-t", "public", "public/index.php"]
|
CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]
|
||||||
|
|
||||||
# ── final (prod) stage — no composer binary ────────────────────────────────────
|
# ── final (prod) stage — no composer binary ────────────────────────────────────
|
||||||
FROM base AS final
|
FROM base AS final
|
||||||
@@ -45,6 +45,7 @@ RUN addgroup -S app && adduser -S -G app app
|
|||||||
|
|
||||||
COPY --from=build /app/vendor /app/vendor
|
COPY --from=build /app/vendor /app/vendor
|
||||||
COPY . .
|
COPY . .
|
||||||
|
COPY docker/frankenphp/Caddyfile /etc/caddy/Caddyfile
|
||||||
|
|
||||||
RUN mkdir -p var/cache var/log \
|
RUN mkdir -p var/cache var/log \
|
||||||
&& chown -R app:app /app
|
&& chown -R app:app /app
|
||||||
@@ -54,4 +55,4 @@ USER app
|
|||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
ENV APP_ENV=prod APP_DEBUG=0
|
ENV APP_ENV=prod APP_DEBUG=0
|
||||||
|
|
||||||
CMD ["php", "-S", "0.0.0.0:8080", "-t", "public", "public/index.php"]
|
CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ A self-hosted Symfony service that merges contribution data from **GitHub**, **G
|
|||||||
https://your-host/graph.svg?theme=dark
|
https://your-host/graph.svg?theme=dark
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -27,15 +27,36 @@ https://your-host/graph.svg?theme=dark
|
|||||||
|
|
||||||
- Docker + Docker Compose
|
- Docker + Docker Compose
|
||||||
|
|
||||||
### 1. Clone and configure
|
### 1. Create a `docker-compose.yml`
|
||||||
|
|
||||||
```bash
|
Use the pre-built image — no need to clone the repo:
|
||||||
git clone https://git.arthurerlich.de/haylan/git-contribution-graph.git
|
|
||||||
cd git-contribution-graph
|
```yaml
|
||||||
cp .env .env.local
|
services:
|
||||||
|
graph:
|
||||||
|
image: git.arthurerlich.de/haylan/git-contribution-graph:latest
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
env_file:
|
||||||
|
- .env.local
|
||||||
|
volumes:
|
||||||
|
- cache:/var/www/html/var/cache
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
cache:
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit `.env.local` with your credentials:
|
The image is published to the Gitea container registry. Pull it manually with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull git.arthurerlich.de/haylan/git-contribution-graph:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Semver tags are published (`0`, `0.0`, `0.0.1`) alongside `latest` — see the [container registry](https://git.arthurerlich.de/haylan/-/packages/container/git-contribution-graph/latest) for all available tags.
|
||||||
|
|
||||||
|
### 2. Configure
|
||||||
|
|
||||||
|
Create a `.env.local` file next to your `docker-compose.yml`:
|
||||||
|
|
||||||
```dotenv
|
```dotenv
|
||||||
APP_SECRET=<generate with: openssl rand -hex 16>
|
APP_SECRET=<generate with: openssl rand -hex 16>
|
||||||
@@ -60,7 +81,7 @@ ALLOWED_HOSTS=
|
|||||||
|
|
||||||
Only configure the platforms you use — unused ones are silently skipped.
|
Only configure the platforms you use — unused ones are silently skipped.
|
||||||
|
|
||||||
### 2. Start
|
### 3. Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
@@ -68,10 +89,11 @@ docker compose up -d
|
|||||||
|
|
||||||
The service listens on **port 8080** by default. Put Traefik or nginx in front of it for HTTPS.
|
The service listens on **port 8080** by default. Put Traefik or nginx in front of it for HTTPS.
|
||||||
|
|
||||||
### 3. Health check
|
### 4. Verify
|
||||||
|
|
||||||
```
|
```bash
|
||||||
GET /health → {"status":"ok"}
|
curl http://localhost:8080/health
|
||||||
|
# {"status":"ok"}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
parameters:
|
||||||
|
env(APP_SECRET): ''
|
||||||
|
env(ALLOWED_HOSTS): ''
|
||||||
|
env(GITHUB_USER): ''
|
||||||
|
env(GITHUB_TOKEN): ''
|
||||||
|
env(GITLAB_USER): ''
|
||||||
|
env(GITLAB_TOKEN): ''
|
||||||
|
env(GITLAB_URL): ''
|
||||||
|
env(GITEA_USER): ''
|
||||||
|
env(GITEA_TOKEN): ''
|
||||||
|
env(GITEA_URL): ''
|
||||||
|
|
||||||
services:
|
services:
|
||||||
_defaults:
|
_defaults:
|
||||||
autowire: true
|
autowire: true
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
admin off
|
||||||
|
auto_https off
|
||||||
|
|
||||||
|
frankenphp {
|
||||||
|
worker /app/public/worker.php
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:8080 {
|
||||||
|
root * /app/public
|
||||||
|
php_server
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
admin off
|
||||||
|
auto_https off
|
||||||
|
debug
|
||||||
|
|
||||||
|
frankenphp
|
||||||
|
}
|
||||||
|
|
||||||
|
:8080 {
|
||||||
|
root * /app/public
|
||||||
|
php_server
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Kernel;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
require_once dirname(__DIR__).'/vendor/autoload.php';
|
||||||
|
|
||||||
|
$kernel = new Kernel($_SERVER['APP_ENV'] ?? 'prod', (bool) ($_SERVER['APP_DEBUG'] ?? false));
|
||||||
|
$kernel->boot();
|
||||||
|
|
||||||
|
$handler = static function () use ($kernel): void {
|
||||||
|
$request = Request::createFromGlobals();
|
||||||
|
$response = $kernel->handle($request);
|
||||||
|
$response->send();
|
||||||
|
$kernel->terminate($request, $response);
|
||||||
|
};
|
||||||
|
|
||||||
|
$maxRequests = (int) ($_SERVER['MAX_REQUESTS'] ?? 0);
|
||||||
|
$requestCount = 0;
|
||||||
|
|
||||||
|
while (\frankenphp_handle_request($handler)) {
|
||||||
|
if ($maxRequests > 0 && ++$requestCount >= $maxRequests) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$kernel->shutdown();
|
||||||
Reference in New Issue
Block a user