From 99e1245ed3e42a73bf19f17de97c3f587aa9e3ed Mon Sep 17 00:00:00 2001 From: Georg K Date: Fri, 12 Jun 2026 05:45:10 +0300 Subject: [PATCH] feat: migrate to Gitea, productize Helm chart, unify version - CI: replace GitLab pipeline with Gitea Actions (.gitea/workflows/release.yaml); publish .deb/image/hosted/chart to Gitea registries; gate image+hosted on .deb (needs + registry pre-check) - drop connectone/Nexus; apt now from the Gitea Debian registry - single version source (/VERSION=3.2.8); CI enforces .env/Chart consistency - restructure: build/->packaging/deb, root Dockerfile->packaging/image, hosted/->packaging/hosted, .kube/->charts/freeradius - Helm chart: documented values, DB password via Secret/$ENV, configurable scheduling/persistence; remove plaintext creds and hostPath PV - gitignore *.env_cnf; docs for hosted (RU)/helm/root + agent KB sync Co-Authored-By: Claude Opus 4.8 --- .dockerignore | 7 +- .gitea/workflows/release.yaml | 162 ++++++++++++++++++ .gitignore | 5 +- .gitlab-ci.yml | 48 ------ .kube/Chart.yaml | 4 - .kube/templates/00-pullsecret.yaml | 8 - .kube/templates/deployment.yaml | 70 -------- .kube/templates/service.yaml | 19 -- .kube/templates/volumes.yaml | 26 --- .kube/values.yaml | 20 --- AGENTS.md | 60 +++++++ CLAUDE.md | 1 + Dockerfile | 30 ---- README.md | 95 ++++++++-- VERSION | 1 + agent/INDEX.md | 104 +++++++++++ build/upload.sh | 9 - charts/freeradius/.helmignore | 9 + charts/freeradius/Chart.yaml | 11 ++ charts/freeradius/README.md | 83 +++++++++ charts/freeradius/templates/NOTES.txt | 20 +++ charts/freeradius/templates/_helpers.tpl | 63 +++++++ .../freeradius/templates/configmap.yaml | 22 ++- charts/freeradius/templates/deployment.yaml | 93 ++++++++++ charts/freeradius/templates/pvc.yaml | 18 ++ charts/freeradius/templates/secret.yaml | 12 ++ charts/freeradius/templates/service.yaml | 29 ++++ charts/freeradius/values.yaml | 74 ++++++++ hosted/README.md | 14 -- {build => packaging/deb}/.dockerignore | 0 {build => packaging/deb}/Dockerfile | 6 +- {build => packaging/deb}/local-noble.list | 0 packaging/deb/upload.sh | 41 +++++ {hosted => packaging/hosted}/.env | 0 {hosted => packaging/hosted}/.env_cnf_example | 0 {hosted => packaging/hosted}/Dockerfile | 0 packaging/hosted/README.md | 120 +++++++++++++ .../hosted}/config/clients.conf | 0 .../hosted}/config/dictionary | 0 .../hosted}/config/mods-enabled/raw | 0 .../hosted}/config/mods-enabled/rest | 1 - .../hosted}/config/mods-enabled/sql | 0 .../hosted}/config/radiusd.conf | 0 .../hosted}/config/sites-enabled/default | 0 .../config/sites-enabled/dynamic-clients | 0 .../hosted}/config/systemd/freeradius.service | 0 .../hosted}/cron/radius-renew.sh | 0 {hosted => packaging/hosted}/install.sh | 26 ++- {hosted => packaging/hosted}/update.sh | 0 packaging/image/Dockerfile | 55 ++++++ .../image/entrypoint.sh | 0 .../image/local-noble.list | 0 52 files changed, 1079 insertions(+), 287 deletions(-) create mode 100644 .gitea/workflows/release.yaml delete mode 100644 .gitlab-ci.yml delete mode 100644 .kube/Chart.yaml delete mode 100644 .kube/templates/00-pullsecret.yaml delete mode 100644 .kube/templates/deployment.yaml delete mode 100644 .kube/templates/service.yaml delete mode 100644 .kube/templates/volumes.yaml delete mode 100644 .kube/values.yaml create mode 100644 AGENTS.md create mode 100644 CLAUDE.md delete mode 100644 Dockerfile create mode 100644 VERSION create mode 100644 agent/INDEX.md delete mode 100644 build/upload.sh create mode 100644 charts/freeradius/.helmignore create mode 100644 charts/freeradius/Chart.yaml create mode 100644 charts/freeradius/README.md create mode 100644 charts/freeradius/templates/NOTES.txt create mode 100644 charts/freeradius/templates/_helpers.tpl rename .kube/templates/cm-configs.yaml => charts/freeradius/templates/configmap.yaml (88%) create mode 100644 charts/freeradius/templates/deployment.yaml create mode 100644 charts/freeradius/templates/pvc.yaml create mode 100644 charts/freeradius/templates/secret.yaml create mode 100644 charts/freeradius/templates/service.yaml create mode 100644 charts/freeradius/values.yaml delete mode 100644 hosted/README.md rename {build => packaging/deb}/.dockerignore (100%) rename {build => packaging/deb}/Dockerfile (84%) rename {build => packaging/deb}/local-noble.list (100%) create mode 100644 packaging/deb/upload.sh rename {hosted => packaging/hosted}/.env (100%) rename {hosted => packaging/hosted}/.env_cnf_example (100%) rename {hosted => packaging/hosted}/Dockerfile (100%) create mode 100644 packaging/hosted/README.md rename {hosted => packaging/hosted}/config/clients.conf (100%) rename {hosted => packaging/hosted}/config/dictionary (100%) rename {hosted => packaging/hosted}/config/mods-enabled/raw (100%) rename {hosted => packaging/hosted}/config/mods-enabled/rest (99%) rename {hosted => packaging/hosted}/config/mods-enabled/sql (100%) rename {hosted => packaging/hosted}/config/radiusd.conf (100%) rename {hosted => packaging/hosted}/config/sites-enabled/default (100%) rename {hosted => packaging/hosted}/config/sites-enabled/dynamic-clients (100%) rename {hosted => packaging/hosted}/config/systemd/freeradius.service (100%) rename {hosted => packaging/hosted}/cron/radius-renew.sh (100%) rename {hosted => packaging/hosted}/install.sh (73%) rename {hosted => packaging/hosted}/update.sh (100%) create mode 100644 packaging/image/Dockerfile rename entrypoint.sh => packaging/image/entrypoint.sh (100%) rename local-noble.list => packaging/image/local-noble.list (100%) diff --git a/.dockerignore b/.dockerignore index 35cd0c4..4e4d03a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ .git/ .idea/ +.gitea/ *.md -.kube/ -build/ -.kube/ \ No newline at end of file +charts/ +packaging/deb/ +agent/ \ No newline at end of file diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml new file mode 100644 index 0000000..f11209a --- /dev/null +++ b/.gitea/workflows/release.yaml @@ -0,0 +1,162 @@ +name: release + +# Manual release pipeline (Gitea Actions). +# +# Order / gating (".deb is the source of truth, hosted/image must not build without it"): +# prepare ──► build-deb ──► build-image ──► package-helm +# └──► package-hosted +# build-image and package-hosted additionally verify the .deb exists in the +# registry before doing anything (belt-and-suspenders gate). +# +# Required repo secrets: +# PACKAGES_USER Gitea username (token owner) +# PACKAGES_TOKEN Gitea access token with write:package (+ read:package) + +on: + workflow_dispatch: + inputs: + rlm_raw_branch: + description: rlm_raw module branch + required: false + default: v3.2.4 + +concurrency: + group: release + cancel-in-progress: false + +env: + REGISTRY: git.ahax86.ru + OWNER: pub + IMAGE: git.ahax86.ru/pub/freeradius + DEB_REGISTRY: https://git.ahax86.ru/api/packages/pub/debian + DEB_DISTRIBUTION: noble + DEB_COMPONENT: main + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.v.outputs.version }} + radius_tag: ${{ steps.v.outputs.radius_tag }} + pkg_version: ${{ steps.v.outputs.pkg_version }} + steps: + - uses: actions/checkout@v4 + - id: v + name: Resolve version from /VERSION + run: | + set -euo pipefail + version="$(tr -d ' \n\r' < VERSION)" + radius_tag="release_$(echo "$version" | tr '.' '_')" + pkg_version="${version}+git" + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "radius_tag=$radius_tag" >> "$GITHUB_OUTPUT" + echo "pkg_version=$pkg_version" >> "$GITHUB_OUTPUT" + echo "version=$version tag=$radius_tag pkg=$pkg_version" + - name: Check version consistency + run: | + set -euo pipefail + version="$(tr -d ' \n\r' < VERSION)" + env_tag="$(grep -E '^FREERADIUS_TAG=' packaging/hosted/.env | cut -d= -f2 | tr -d ' \r')" + chart_app="$(grep -E '^appVersion:' charts/freeradius/Chart.yaml | sed -E 's/appVersion:[[:space:]]*"?([^"]+)"?/\1/' | tr -d ' \r')" + [ "$env_tag" = "$version" ] || { echo "hosted/.env FREERADIUS_TAG=$env_tag != VERSION=$version"; exit 1; } + [ "$chart_app" = "$version" ] || { echo "Chart appVersion=$chart_app != VERSION=$version"; exit 1; } + echo "Versions consistent: $version" + + build-deb: + needs: [prepare] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build .deb builder image + run: | + docker build packaging/deb -f packaging/deb/Dockerfile \ + --build-arg RADIUS_TAG=${{ needs.prepare.outputs.radius_tag }} \ + --build-arg RLM_RAW_BRANCH=${{ inputs.rlm_raw_branch }} \ + -t freerad-builder:${{ needs.prepare.outputs.version }} + - name: Upload .deb to Gitea Debian registry + env: + DEB_USER: ${{ secrets.PACKAGES_USER }} + DEB_PASS: ${{ secrets.PACKAGES_TOKEN }} + run: | + docker run --rm \ + -e DEB_USER -e DEB_PASS \ + -e DEB_REGISTRY="${{ env.DEB_REGISTRY }}" \ + -e DEB_DISTRIBUTION="${{ env.DEB_DISTRIBUTION }}" \ + -e DEB_COMPONENT="${{ env.DEB_COMPONENT }}" \ + freerad-builder:${{ needs.prepare.outputs.version }} ./upload.sh + + build-image: + needs: [prepare, build-deb] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Verify .deb is published + env: + DEB_USER: ${{ secrets.PACKAGES_USER }} + DEB_PASS: ${{ secrets.PACKAGES_TOKEN }} + run: | + set -euo pipefail + url="${{ env.DEB_REGISTRY }}/dists/${{ env.DEB_DISTRIBUTION }}/${{ env.DEB_COMPONENT }}/binary-amd64/Packages" + curl -fsSL --user "$DEB_USER:$DEB_PASS" "$url" \ + | grep -q "Version: ${{ needs.prepare.outputs.pkg_version }}" \ + || { echo "Package ${{ needs.prepare.outputs.pkg_version }} not found in registry"; exit 1; } + - name: Login to container registry + run: echo "${{ secrets.PACKAGES_TOKEN }}" | docker login ${{ env.REGISTRY }} -u "${{ secrets.PACKAGES_USER }}" --password-stdin + - name: Build and push image + run: | + set -euo pipefail + docker build -f packaging/image/Dockerfile \ + --build-arg PKG_VERSION=${{ needs.prepare.outputs.pkg_version }} \ + -t ${{ env.IMAGE }}:${{ needs.prepare.outputs.version }} \ + -t ${{ env.IMAGE }}:latest . + docker push ${{ env.IMAGE }}:${{ needs.prepare.outputs.version }} + docker push ${{ env.IMAGE }}:latest + + package-hosted: + needs: [prepare, build-deb] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Verify .deb is published + env: + DEB_USER: ${{ secrets.PACKAGES_USER }} + DEB_PASS: ${{ secrets.PACKAGES_TOKEN }} + run: | + set -euo pipefail + url="${{ env.DEB_REGISTRY }}/dists/${{ env.DEB_DISTRIBUTION }}/${{ env.DEB_COMPONENT }}/binary-amd64/Packages" + curl -fsSL --user "$DEB_USER:$DEB_PASS" "$url" \ + | grep -q "Version: ${{ needs.prepare.outputs.pkg_version }}" \ + || { echo "Package ${{ needs.prepare.outputs.pkg_version }} not found in registry"; exit 1; } + - name: Package and upload hosted bundle + env: + PKG_USER: ${{ secrets.PACKAGES_USER }} + PKG_TOKEN: ${{ secrets.PACKAGES_TOKEN }} + run: | + set -euo pipefail + chmod +x packaging/hosted/install.sh packaging/hosted/update.sh + tar -czf hosted.tar.gz -C packaging hosted + ver="${{ needs.prepare.outputs.version }}" + curl -fsSL --user "$PKG_USER:$PKG_TOKEN" \ + --upload-file hosted.tar.gz \ + "https://${{ env.REGISTRY }}/api/packages/${{ env.OWNER }}/generic/hosted/${ver}/hosted.tar.gz" + + package-helm: + needs: [prepare, build-image] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install helm + run: curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + - name: Lint chart + run: helm lint charts/freeradius --set db.password=ci + - name: Package and push chart + env: + PKG_USER: ${{ secrets.PACKAGES_USER }} + PKG_TOKEN: ${{ secrets.PACKAGES_TOKEN }} + run: | + set -euo pipefail + helm package charts/freeradius --app-version "${{ needs.prepare.outputs.version }}" -d dist + chart="$(ls dist/*.tgz)" + curl -fsSL --user "$PKG_USER:$PKG_TOKEN" -X POST \ + --upload-file "$chart" \ + "https://${{ env.REGISTRY }}/api/packages/${{ env.OWNER }}/helm/api/charts" diff --git a/.gitignore b/.gitignore index 78f43e9..b84569d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .idea/ pkg/ -bin/ \ No newline at end of file +bin/ + +# Local secrets for the hosted install — never commit (only *.env_cnf_example is tracked) +*.env_cnf diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index ded348d..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,48 +0,0 @@ -variables: - CI_REGISTRY: registry.connectone.pro - CONTAINER_IMAGE: ${CI_REGISTRY}/${CI_PROJECT_PATH}:${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHORT_SHA} - RADIUS_TAG: "release_3_2_7" - -stages: - - release - - release-deb - - release-hosted - -release: - stage: release - image: docker:latest - before_script: - - docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY} - script: - - docker build -t ${CONTAINER_IMAGE} . - - docker push ${CONTAINER_IMAGE} - when: manual - only: - - main - -release-deb: - stage: release-deb - image: docker:latest - variables: - DOCKER_BUILDKIT: 1 - script: - - cd build - - docker build --build-arg RADIUS_TAG=${RADIUS_TAG} . -t freerad_builder:3.2.x -f Dockerfile - - docker run -e DEB_USER=${DEB_USER} -e DEB_PASS="${DEB_PASS}" freerad_builder:3.2.x ./upload.sh - when: manual - only: - - main - -release-hosted: - stage: release-hosted - image: alpine:latest - variables: - VERSION: "${RADIUS_TAG}" - script: - - apk add --no-cache curl - - chmod a+x hosted/install.sh hosted/update.sh - - tar -czvf hosted.tar.gz hosted - - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file hosted.tar.gz "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/hosted/${VERSION}/hosted.tar.gz"' - when: manual - only: - - main diff --git a/.kube/Chart.yaml b/.kube/Chart.yaml deleted file mode 100644 index 480b503..0000000 --- a/.kube/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v2 -name: freeradius -version: 1.0.1 -appVersion: 3.2.0 \ No newline at end of file diff --git a/.kube/templates/00-pullsecret.yaml b/.kube/templates/00-pullsecret.yaml deleted file mode 100644 index 92024b7..0000000 --- a/.kube/templates/00-pullsecret.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{.Release.Name}}-registry-key - namespace: {{.Release.Namespace}} -data: - .dockerconfigjson: "{{.Values.registryAuthBase64}}" -type: kubernetes.io/dockerconfigjson diff --git a/.kube/templates/deployment.yaml b/.kube/templates/deployment.yaml deleted file mode 100644 index e9d4d1c..0000000 --- a/.kube/templates/deployment.yaml +++ /dev/null @@ -1,70 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{.Release.Name}} - namespace: {{.Release.Namespace}} -spec: - replicas: 1 - selector: - matchLabels: - app: {{.Release.Name}} - template: - metadata: - labels: - app: {{.Release.Name}} - spec: - nodeSelector: - freeradius: enabled - tolerations: - - key: freeradius - operator: Equal - value: enabled - effect: NoSchedule - imagePullSecrets: - - name: {{.Release.Name}}-registry-key -{{/* securityContext:*/}} -{{/* runAsUser: 108*/}} -{{/* runAsGroup: 112*/}} -{{/* fsGroup: 108*/}} - containers: - - name: {{.Release.Name}} - image: {{.Values.image.name}} - imagePullPolicy: {{.Values.image.pullPolicy}} - env: - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - - name: TZ - value: UTC - - name: HOSTNAME - value: freeradius - args: ["freeradius", "-X"] - ports: - - containerPort: {{.Values.ports.main}} - - containerPort: {{.Values.ports.second}} - volumeMounts: - - mountPath: /etc/freeradius/certs - name: freeradius-cert-data - readOnly: false - - name: configs-volume - mountPath: "/configs/default" - subPath: default - readOnly: true - - name: configs-volume - mountPath: "/configs/sql" - subPath: sql - readOnly: true - - name: configs-volume - mountPath: "/configs/rest" - subPath: rest - readOnly: true - volumes: - - name: configs-volume - configMap: - name: {{ .Release.Name }}-configs - defaultMode: 488 - - name: freeradius-cert-data - persistentVolumeClaim: - claimName: freeradius-data-claim diff --git a/.kube/templates/service.yaml b/.kube/templates/service.yaml deleted file mode 100644 index 6d832c0..0000000 --- a/.kube/templates/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{.Release.Name}}-external - namespace: {{.Release.Namespace}} -spec: - type: NodePort - externalTrafficPolicy: Local - ports: - - port: {{.Values.ports.main}} - nodePort: {{.Values.ports.main}} - protocol: UDP - name: freerad-first - - port: {{.Values.ports.second}} - nodePort: {{.Values.ports.second}} - protocol: UDP - name: freerad-second - selector: - app: {{ .Chart.Name }} diff --git a/.kube/templates/volumes.yaml b/.kube/templates/volumes.yaml deleted file mode 100644 index 46dd907..0000000 --- a/.kube/templates/volumes.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: v1 -kind: PersistentVolume -metadata: - name: freeradius-data-disk - namespace: {{.Release.Namespace}} -spec: - capacity: - storage: 10Mi - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - hostPath: - path: {{.Values.storageBase}}/{{.Chart.Name}} ---- -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: freeradius-data-claim - namespace: {{.Release.Namespace}} -spec: - volumeName: freeradius-data-disk - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Mi \ No newline at end of file diff --git a/.kube/values.yaml b/.kube/values.yaml deleted file mode 100644 index bee6c9a..0000000 --- a/.kube/values.yaml +++ /dev/null @@ -1,20 +0,0 @@ -image: - name: ${CONTAINER_IMAGE} - pullPolicy: "IfNotPresent" -registryAuthBase64: ${REGISTRY_AUTH} -storageBase: ${STORAGE_BASE} - -ports: - main: 31812 - second: 31813 - -db: - host: "postgresql-pgbouncer" - port: 6432 - user: "admin" - password: "radius_db" - name: "radius_db" - -api: - host: "hotspotter" - port: 8080 \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..69c6a11 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,60 @@ +# AGENTS.md + +## Что это + +Репозиторий упаковки и развёртывания кастомной сборки FreeRADIUS 3.2.x для +hotspot/captive-portal сценариев. Прикладного кода нет: это сборочные Dockerfile, +shell-скрипты, Helm/k8s-манифесты и +конфиги FreeRADIUS. Профиль каркаса: infra-lite. + +Главный контекст: [agent/INDEX.md](agent/INDEX.md). Читать перед любыми изменениями. + +Сборка и поставка - через Gitea (`git.ahax86.ru`, owner `pub`): Debian-реестр для .deb, +container registry для образа, Helm-реестр для чарта, generic-пакет для hosted-бандла. +Nexus и GitLab больше не используются. + +## Порядок чтения в начале сессии + +1. `AGENTS.md` (этот файл). +2. `agent/INDEX.md` - назначение, артефакты, режимы развёртывания, версии, риски. + +## Структура репозитория + +``` +VERSION ← единый источник версии FreeRADIUS (3.2.8) +.gitea/workflows/release.yaml ← ручной релиз (Gitea Actions): prepare/build-deb/build-image/package-hosted/package-helm +packaging/ + deb/ ← сборка .deb из исходников FreeRADIUS + rlm_raw, upload в Debian-реестр Gitea + image/ ← рантайм-образ (Dockerfile + entrypoint.sh + local-noble.list); ставит .deb, печёт конфиг + hosted/ ← bare-metal/VM установка (install.sh, update.sh, systemd, cron, config) + config/ ← radiusd.conf, dictionary, clients.conf, mods-enabled/*, sites-enabled/* + .env / .env_cnf* ← версия пакета и подстановки (хосты, порты, креды БД) +charts/ + freeradius/ ← продуктовый Helm-чарт (Deployment, ConfigMap, Secret, PVC, Service) +``` + +## Инструменты и команды + +- Сборка/выкладка - только через Gitea Actions (`.gitea/workflows/release.yaml`, `workflow_dispatch`). +- Версия задаётся в `VERSION`; CI выводит из неё тег/пин/appVersion и падает при рассинхроне. +- Локальная сборка .deb: `docker build packaging/deb -f packaging/deb/Dockerfile --build-arg RADIUS_TAG=release_3_2_8 --build-arg RLM_RAW_BRANCH=v3.2.4 -t freerad-builder:3.2.8` +- Локальная сборка образа: `docker build -f packaging/image/Dockerfile --build-arg PKG_VERSION=3.2.8+git -t freeradius:3.2.8 .` (контекст = корень). +- Тест hosted: `cd packaging/hosted && docker build . -t hosted:test -f Dockerfile` +- Установка на ноду (bare-metal): `packaging/hosted/install.sh` (root), обновление версии: `packaging/hosted/update.sh`. +- Чарт: `helm lint charts/freeradius --set db.password=test`, `helm template charts/freeradius --set db.password=test`. +- Линтеров/тестов кода в репозитории нет. + +## Правила репо + +1. Ничего не публиковать (push, PR, CI-стейдж, деплой) без явной команды пользователя. +2. `git add` строго поимённо. Никогда `git add .` или `git add -A`. +3. `.env` / `.env_cnf` не читать целиком и не коммитить - там креды БД. Проверять + переменную через grep по `^VAR=`. `.env_cnf` хранит секреты и не должен попадать в git. +4. Деструктивные действия с прод-нодами, БД и k8s - только с явного подтверждения. +5. Изменился конфиг, версия, хост или инвариант - синхронно отразить в `agent/INDEX.md`. +6. Долгоживущие решения фиксируются в `agent/INDEX.md`. Журнал сессий не ведётся по умолчанию. +7. Документация краткая и строгая: без галлюцинаций, факты берём из кода и конфигов. + +## Язык + +Отвечать по-русски, кратко, без длинных тире. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index ffbfd53..0000000 --- a/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM ubuntu:24.04 -WORKDIR /tmp -ARG out_dir=/etc/freeradius -COPY local-noble.list /etc/apt/sources.list.d/ubuntu.sources -RUN apt-get update && apt-get install -y apt-transport-https ca-certificates curl software-properties-common gpg -RUN curl -fsSL http://connectone.pro/gpg | gpg --dearmor -o /usr/share/keyrings/connectone.gpg && \ - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/connectone.gpg] \ - https://nexus.connectone.pro/repository/radius bionic main" | tee /etc/apt/sources.list.d/connectone.list \ - > /dev/null -RUN apt-get update && apt-get install -y libfreeradius3=3.2.8+git freeradius-common=3.2.8+git \ - freeradius-postgresql=3.2.8+git freeradius-rest=3.2.8+git freeradius-utils=3.2.8+git freeradius=3.2.8+git && \ - apt autoremove --purge -y && apt-get clean -COPY hosted /tmp/hosted -RUN cp hosted/config/radiusd.conf $out_dir/radiusd.conf && \ - cp hosted/config/dictionary $out_dir/dictionary && \ - cp hosted/config/clients.conf $out_dir/clients.conf && \ - cp hosted/config/mods-enabled/raw $out_dir/mods-enabled/raw && \ - cp hosted/config/sites-enabled/dynamic-clients $out_dir/sites-enabled/dynamic-clients && \ - rm -f /etc/freeradius/sites-enabled/inner-tunnel && \ - rm -f /etc/freeradius/mods-enabled/eap \ -RUN rm -f /etc/freeradius/mods-enabled/delay && \ - ln -s $out_dir/mods-available/delay $out_dir/mods-enabled/delay -ADD entrypoint.sh /usr/bin/entrypoint.sh -RUN chmod a+x /usr/bin/entrypoint.sh && chmod -R 750 $out_dir && chown -R freerad:root $out_dir && \ - mv /etc/freeradius/certs /tmp && \ - rm -rf /tmp/hosted - -WORKDIR /etc/freeradius -ENTRYPOINT ["/usr/bin/entrypoint.sh"] -CMD ["freeradius", "-f"] diff --git a/README.md b/README.md index a2922f3..aafb5b2 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,87 @@ -# freeradius +# FreeRADIUS -build: -```bash -cd build -docker build . -t freerad_builder:3.2.x -f Dockerfile_build -docker run -e DEB_USER=${DEB_USER} DEB_PASS="${DEB_PASS}" freerad_builder:3.2.x ./upload.sh +Packaging and delivery of a custom FreeRADIUS 3.2.x build for +hotspot / captive-portal scenarios. There is no application code here — the repo +builds upstream FreeRADIUS with the extra `rlm_raw` module and ships it to two +runtimes (bare-metal and Kubernetes) via a Gitea-hosted registry. + +## Single source of version truth + +[`VERSION`](VERSION) holds the FreeRADIUS release (`3.2.8`). CI derives everything +from it: the build tag `release_3_2_8`, the package pin `3.2.8+git`, the chart +`appVersion`. CI fails if `packaging/hosted/.env` or `charts/freeradius/Chart.yaml` +drift from `VERSION`. + +## Layout + +``` +VERSION single source of the FreeRADIUS version +.gitea/workflows/release.yaml manual release pipeline (Gitea Actions) +packaging/ + deb/ build the .deb from FreeRADIUS source + rlm_raw, upload to registry + image/ runtime container image (installs the .deb, bakes configs) + hosted/ bare-metal / VM install (install.sh, update.sh, config, cron) +charts/ + freeradius/ productized Helm chart ``` -label nodes: -```bash -kubectl label node connect-prod-0 freeradius=enabled +## Artifacts (Gitea, owner `pub`) + +| Artifact | Registry | +|----------|----------| +| `.deb` packages | `https://git.ahax86.ru/api/packages/pub/debian` (`noble main`) | +| Container image | `git.ahax86.ru/pub/freeradius:` | +| Hosted bundle | `.../api/packages/pub/generic/hosted//hosted.tar.gz` | +| Helm chart | `https://git.ahax86.ru/api/packages/pub/helm` | + +## Release flow (`.gitea/workflows/release.yaml`, manual) + +``` +prepare ──► build-deb ──► build-image ──► package-helm + └──► package-hosted ``` -remove -```bash +The `.deb` is the prerequisite: `build-image` and `package-hosted` both `needs: +build-deb` **and** verify the pinned version exists in the Debian registry +before running. Trigger from Gitea → Actions → `release` → *Run workflow*. -sudo apt-mark unhold libfreeradius3 freeradius-common freeradius-postgresql freeradius-rest freeradius-utils freeradius -sudo dpkg --force-overwrite --purge freeradius-common freeradius-config freeradius-python3 freeradius libfreeradius3 freeradius-postgresql freeradius-rest freeradius-utils && sudo rm -rf /etc/freeradius && sudo rm -rf /usr/lib/freeradius -``` \ No newline at end of file +### CI setup (Gitea) + +1. **Runner.** Enable Actions for the repo and register an `ubuntu-latest` runner + (`act_runner`) with Docker available. If your runner uses a different label, + change `runs-on` in the workflow. +2. **Access token.** Gitea → avatar → *Settings* → *Applications* → + *Manage Access Tokens* → *Generate New Token*. Scopes: `write:package` and + `read:package`. Copy the token (shown once). +3. **Repo secrets.** Repo → *Settings* → *Actions* → *Secrets* → *Add Secret*: + - `PACKAGES_USER` — your Gitea username (the token owner). + - `PACKAGES_TOKEN` — the token from step 2. + +These secrets are used to push to the container / Debian / generic / Helm +registries and to read the Debian `Packages` index in the gate check. + +## Local builds (no upload) + +```bash +# .deb builder image +docker build packaging/deb -f packaging/deb/Dockerfile \ + --build-arg RADIUS_TAG=release_3_2_8 --build-arg RLM_RAW_BRANCH=v3.2.4 \ + -t freerad-builder:3.2.8 + +# runtime image (context = repo root; pulls .deb from the Debian registry) +docker build -f packaging/image/Dockerfile --build-arg PKG_VERSION=3.2.8+git -t freeradius:3.2.8 . + +# chart +helm lint charts/freeradius --set db.password=test +helm template freeradius charts/freeradius --set db.password=test +``` + +## Deployment docs + +- Bare-metal / VM: [packaging/hosted/README.md](packaging/hosted/README.md) +- Kubernetes (Helm): [charts/freeradius/README.md](charts/freeradius/README.md) + +## Internals + +See [agent/INDEX.md](agent/INDEX.md) for invariants, hosts/ports, backends and +known constraints. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..f092941 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +3.2.8 diff --git a/agent/INDEX.md b/agent/INDEX.md new file mode 100644 index 0000000..7a6c1ba --- /dev/null +++ b/agent/INDEX.md @@ -0,0 +1,104 @@ +# FreeRADIUS - обзор инфраструктуры + +Профиль: infra-lite. Бизнес-логику самого FreeRADIUS здесь не описываем. + +## Назначение + +Сборка и поставка кастомного FreeRADIUS 3.2.x для hotspot/captive-portal сценариев. +Репозиторий не содержит сервиса - он упаковывает апстримный FreeRADIUS с +доп. модулем `rlm_raw` и накладывает конфиги под две среды выполнения. + +## Версия - единый источник + +`VERSION` (корень) = `3.2.8`. Из него CI выводит: +- `RADIUS_TAG=release_3_2_8` (тег исходников FreeRADIUS для сборки .deb), +- пин пакетов `3.2.8+git` (образ и hosted), +- `appVersion` чарта. + +CI (`prepare`) падает, если `packaging/hosted/.env` (`FREERADIUS_TAG`) или +`charts/freeradius/Chart.yaml` (`appVersion`) разошлись с `VERSION`. Ветка +`rlm_raw` - отдельный параметр (`workflow_dispatch` input, дефолт `v3.2.4`). + +## Артефакты (Gitea, owner `pub`) + +| Артефакт | Реестр | +|----------|--------| +| `.deb` | `https://git.ahax86.ru/api/packages/pub/debian` (distribution `noble`, component `main`) | +| Образ | `git.ahax86.ru/pub/freeradius:` (+ `latest`) | +| Hosted-бандл | `.../api/packages/pub/generic/hosted//hosted.tar.gz` | +| Helm-чарт | `https://git.ahax86.ru/api/packages/pub/helm` | + +Соглашение: локальный alias helm-репо - `ahax86` (`helm repo add ahax86 ...`). + +## Релизный поток (`.gitea/workflows/release.yaml`, `workflow_dispatch`) + +``` +prepare ──► build-deb ──► build-image ──► package-helm + └──► package-hosted +``` + +- `.deb` - источник истины: `build-image` и `package-hosted` имеют `needs: build-deb` + И дополнительно проверяют, что версия `3.2.8+git` уже опубликована в Debian-реестре + (GET `dists/noble/main/binary-amd64/Packages`), иначе fail. +- Авторизация: репо-секреты `PACKAGES_USER` / `PACKAGES_TOKEN` (Gitea token, scope + `write:package`). +- Раннер: `ubuntu-latest` с доступным Docker. + +## Режимы развёртывания + +- Kubernetes (`charts/freeradius/`): продуктовый Helm-чарт. Образ из `build-image`. + Конфиги `default`/`rest` - в ConfigMap; `sql` - тоже в ConfigMap, но пароль БД не + хранится там, а подставляется в рантайме через `$ENV{DB_PASSWORD}` из Secret + (`db.password` или `db.existingSecret`). `entrypoint.sh` копирует `/configs/*` в + `/etc/freeradius`. Сертификаты на PVC (`persistence`). nodeSelector/tolerations - + конфигурируемые (по умолчанию пусто). Установка - из Helm-реестра Gitea. +- Hosted / bare-metal (`packaging/hosted/install.sh`, root): ставит запиненные .deb из + Debian-реестра Gitea (`apt-mark hold`), подставляет хосты/порты/креды в конфиги через + `sed` из `.env_cnf`, ставит systemd-юнит и cron на ежемесячный перевыпуск сертификатов + (`cron/radius-renew.sh`). `update.sh` бампит версию пакета (`unhold` -> install -> hold). + Для приватного реестра - `DEB_AUTH="user:token"`. + +## Внешние зависимости и хосты + +- Gitea: `git.ahax86.ru`, owner `pub`. Образы - container registry, .deb - Debian-реестр, + чарт - Helm-реестр, hosted - generic-пакет. GPG-ключ apt-репо: + `https://git.ahax86.ru/api/packages/pub/debian/repository.key`. +- PostgreSQL `radius_db` (схема RADIUS: `radacct`, `radcheck`, `nas`, ...). В k8s - + `postgresql-pgbouncer:6432`, юзер `admin`; в hosted - из `.env_cnf`. +- REST API `hotspotter` (k8s: `hotspotter:8080`, эндпоинт `radius/auth`). Используется + как fallback-авторизация NAS, когда клиента нет в таблице `nas`. + +## Сетевые порты + +- Auth/acct: 31812 / 31813 (см. site `default`, `values.yaml` `service.*`, `.env_cnf`). +- В k8s публикуются Service'ом (по умолчанию NodePort 31812/31813). + +## Инварианты + +- Клиенты (NAS) резолвятся динамически: site `dynamic-clients` ищет NAS по `shortname` + (= `NAS-Identifier`) в таблице `nas`; если нет - идёт `rest` к `hotspotter`. Статический + `clients.conf` минимален. +- Включён модуль `delay` (delay_reject); удалены `eap`, `inner-tunnel`, `proxy_rate_limit` + (синхронно в образе и hosted). +- Модуль `rlm_raw` обязателен: конфиги используют `%{raw:...}` (Called-Station-Id, + NAS-Identifier). Без него сборка/конфиг не работают. +- Сертификаты в k8s переживают рестарты через PVC; `entrypoint.sh` копирует дефолтные + только если каталог `certs` пуст. + +## Риски и known warts + +- Образ при сборке тянет .deb из Debian-реестра Gitea - предполагается публичный read для + owner `pub`. Если реестр приватный, сборку образа надо чинить (read-токен/buildkit secret), + а на нодах нужен `DEB_AUTH` для apt. +- Реестр может отклонить повторную заливку той же версии .deb (релиз - переиздание версии + требует bump `VERSION`). +- Пароль БД: в hosted - в `.env_cnf` (gitignored, `*.env_cnf`); в k8s - в Secret. В git не + попадает. + +## Рабочие правила + +- Менять конфиги/версии осознанно: одно изменение часто нужно отразить и в чарте + (`charts/freeradius/`), и в hosted (`packaging/hosted/`), и в образе (`packaging/image/`). +- Версию менять только в `VERSION` (плюс синхронно `.env`/`Chart.yaml`, иначе CI упадёт). +- Прод-выкладка только вручную через Gitea Actions и только по команде пользователя. +- Долгоживущие решения дописывать сюда. Держать обзор кратким и актуальным. diff --git a/build/upload.sh b/build/upload.sh deleted file mode 100644 index dd77238..0000000 --- a/build/upload.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -e -DEB_REPO=${DEB_REPO:-"https://nexus.connectone.pro/repository/radius/"} - -for file in *.deb -do - curl -u "${DEB_USER}:${DEB_PASS}" -H "Content-Type: multipart/form-data" --data-binary "@./$file" "${DEB_REPO}" -done; # file \ No newline at end of file diff --git a/charts/freeradius/.helmignore b/charts/freeradius/.helmignore new file mode 100644 index 0000000..f0ea0eb --- /dev/null +++ b/charts/freeradius/.helmignore @@ -0,0 +1,9 @@ +# Patterns to ignore when building Helm packages. +.DS_Store +.git/ +.gitignore +*.tmp +*.bak +*.orig +*.swp +README.md.tmpl diff --git a/charts/freeradius/Chart.yaml b/charts/freeradius/Chart.yaml new file mode 100644 index 0000000..bdcc9ff --- /dev/null +++ b/charts/freeradius/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: freeradius +description: Custom FreeRADIUS 3.2.x with the rlm_raw module, PostgreSQL backend and REST-based dynamic clients. +type: application +# Chart version (semver). Bump on chart changes. +version: 1.2.0 +# Application version. Kept in sync with the repo-wide /VERSION by CI. +appVersion: "3.2.8" +home: https://git.ahax86.ru/pub/freeradius +sources: + - https://git.ahax86.ru/pub/freeradius diff --git a/charts/freeradius/README.md b/charts/freeradius/README.md new file mode 100644 index 0000000..f076aca --- /dev/null +++ b/charts/freeradius/README.md @@ -0,0 +1,83 @@ +# FreeRADIUS Helm chart + +Deploys the custom FreeRADIUS 3.2.x build — with the `rlm_raw` +module, PostgreSQL backend and REST-based dynamic clients — to Kubernetes. + +The chart is published to the Gitea Helm registry by CI: +`https://git.ahax86.ru/api/packages/pub/helm` + +## Prerequisites + +- The container image must be published (CI job `build-image`): + `git.ahax86.ru/pub/freeradius:`. +- A reachable PostgreSQL with the RADIUS schema (`radacct`, `radcheck`, `nas`, ...). +- The REST `hotspotter` service (fallback NAS authorization). +- If the image registry is private: a pre-created `docker-registry` Secret, + referenced via `imagePullSecrets`. + +## Install + +```bash +helm repo add --username --password \ + ahax86 https://git.ahax86.ru/api/packages/pub/helm +helm repo update + +helm upgrade --install freeradius ahax86/freeradius \ + --namespace radius --create-namespace \ + --set db.host=postgresql-pgbouncer \ + --set db.password= +``` + +Use an existing Secret for the DB password instead of `--set db.password`: + +```bash +helm upgrade --install freeradius ahax86/freeradius -n radius \ + --set db.existingSecret=freeradius-db --set db.passwordKey=password +``` + +`db.password` (or `db.existingSecret`) is required — install fails otherwise. + +## How config is delivered + +- `default` (virtual server) and `rest` (hotspotter) render into a ConfigMap. +- `sql` renders into the same ConfigMap but the password is **not** stored there: + it is read at runtime via `$ENV{DB_PASSWORD}`, injected from the DB Secret. +- The entrypoint copies `/configs/{default,sql,rest}` into `/etc/freeradius`. +- Certificates live on a PVC and survive restarts. + +## Values + +| Key | Default | Description | +|-----|---------|-------------| +| `replicaCount` | `1` | Pod replicas. | +| `image.repository` | `git.ahax86.ru/pub/freeradius` | Image repo. | +| `image.tag` | `""` | Empty → chart `appVersion`. | +| `image.pullPolicy` | `IfNotPresent` | | +| `imagePullSecrets` | `[]` | Names of pull secrets. | +| `args` | `[freeradius, -f]` | Use `[freeradius, -X]` for debug. | +| `service.type` | `NodePort` | `ClusterIP` / `NodePort` / `LoadBalancer`. | +| `service.authPort` / `service.acctPort` | `31812` / `31813` | UDP ports. | +| `service.authNodePort` / `service.acctNodePort` | `31812` / `31813` | NodePort only. | +| `service.externalTrafficPolicy` | `Local` | NodePort only. | +| `db.host` / `db.port` | `postgresql-pgbouncer` / `6432` | PostgreSQL. | +| `db.user` / `db.name` | `admin` / `radius_db` | | +| `db.password` | `""` | Required unless `db.existingSecret`. | +| `db.existingSecret` / `db.passwordKey` | `""` / `db-password` | Use an existing Secret. | +| `api.host` / `api.port` | `hotspotter` / `8080` | REST hotspotter. | +| `persistence.enabled` | `true` | Cert PVC (else `emptyDir`). | +| `persistence.existingClaim` | `""` | Use a pre-created PVC. | +| `persistence.storageClass` | `""` | | +| `persistence.size` | `32Mi` | | +| `resources` / `nodeSelector` / `tolerations` / `affinity` | `{}` / `{}` / `[]` / `{}` | Scheduling. | +| `env` | `{TZ: UTC}` | Extra container env. | + +> Note: the previous deployment pinned `nodeSelector freeradius=enabled` and a +> matching toleration. These are no longer hardcoded — set them via +> `nodeSelector` / `tolerations` if your nodes are tainted. + +## Render locally + +```bash +helm template freeradius charts/freeradius --set db.password=test | less +helm lint charts/freeradius --set db.password=test +``` diff --git a/charts/freeradius/templates/NOTES.txt b/charts/freeradius/templates/NOTES.txt new file mode 100644 index 0000000..f8bd006 --- /dev/null +++ b/charts/freeradius/templates/NOTES.txt @@ -0,0 +1,20 @@ +FreeRADIUS ({{ .Chart.AppVersion }}) released as {{ .Release.Name }} in namespace {{ .Release.Namespace }}. + +Image: {{ include "freeradius.image" . }} +Service: {{ include "freeradius.fullname" . }} ({{ .Values.service.type }}) + auth: UDP {{ .Values.service.authPort }}{{ if eq .Values.service.type "NodePort" }} (nodePort {{ .Values.service.authNodePort }}){{ end }} + acct: UDP {{ .Values.service.acctPort }}{{ if eq .Values.service.type "NodePort" }} (nodePort {{ .Values.service.acctNodePort }}){{ end }} + +Backends: + PostgreSQL: {{ .Values.db.host }}:{{ .Values.db.port }} db={{ .Values.db.name }} user={{ .Values.db.user }} + REST (hotspotter): http://{{ .Values.api.host }}:{{ .Values.api.port }}/ + +{{- if and (not .Values.db.existingSecret) (not .Values.db.password) }} + +WARNING: db.password is empty and no db.existingSecret was provided. +Set one of them, e.g.: helm upgrade {{ .Release.Name }} ... --set db.password= +{{- end }} + +Check status: + kubectl -n {{ .Release.Namespace }} rollout status deploy/{{ include "freeradius.fullname" . }} + kubectl -n {{ .Release.Namespace }} logs deploy/{{ include "freeradius.fullname" . }} diff --git a/charts/freeradius/templates/_helpers.tpl b/charts/freeradius/templates/_helpers.tpl new file mode 100644 index 0000000..59696ed --- /dev/null +++ b/charts/freeradius/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* Chart name */}} +{{- define "freeradius.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Fully qualified app name */}} +{{- define "freeradius.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* Common labels */}} +{{- define "freeradius.labels" -}} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{ include "freeradius.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* Selector labels */}} +{{- define "freeradius.selectorLabels" -}} +app.kubernetes.io/name: {{ include "freeradius.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* Container image ref (tag defaults to appVersion) */}} +{{- define "freeradius.image" -}} +{{- $tag := .Values.image.tag | default .Chart.AppVersion -}} +{{- printf "%s:%s" .Values.image.repository $tag -}} +{{- end -}} + +{{/* Name of the Secret holding the DB password */}} +{{- define "freeradius.dbSecretName" -}} +{{- if .Values.db.existingSecret -}} +{{- .Values.db.existingSecret -}} +{{- else -}} +{{- printf "%s-db" (include "freeradius.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* Key inside the DB Secret */}} +{{- define "freeradius.dbSecretKey" -}} +{{- .Values.db.passwordKey | default "db-password" -}} +{{- end -}} + +{{/* Name of the PVC used for certs */}} +{{- define "freeradius.pvcName" -}} +{{- if .Values.persistence.existingClaim -}} +{{- .Values.persistence.existingClaim -}} +{{- else -}} +{{- printf "%s-certs" (include "freeradius.fullname" .) -}} +{{- end -}} +{{- end -}} diff --git a/.kube/templates/cm-configs.yaml b/charts/freeradius/templates/configmap.yaml similarity index 88% rename from .kube/templates/cm-configs.yaml rename to charts/freeradius/templates/configmap.yaml index 7c44c4e..c91a82e 100644 --- a/.kube/templates/cm-configs.yaml +++ b/charts/freeradius/templates/configmap.yaml @@ -1,17 +1,17 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ .Release.Name }}-configs + name: {{ include "freeradius.fullname" . }}-configs namespace: {{ .Release.Namespace }} labels: - app: clickhouse-operator + {{- include "freeradius.labels" . | nindent 4 }} data: default: | server default { listen { type = auth ipaddr = * - port = {{.Values.ports.main}} + port = {{ .Values.service.authPort }} limit { max_connections = 16 lifetime = 0 @@ -21,7 +21,7 @@ data: listen { ipaddr = * - port = {{.Values.ports.second}} + port = {{ .Values.service.acctPort }} type = acct limit { @@ -99,7 +99,6 @@ data: } } - sql: | sql { dialect = "postgresql" @@ -108,11 +107,11 @@ data: send_application_name = yes } - server = "{{.Values.db.host}}" - port = {{.Values.db.port}} - login = "{{.Values.db.user}}" - password = "{{.Values.db.password}}" - radius_db = "{{.Values.db.name}}" + server = "{{ .Values.db.host }}" + port = {{ .Values.db.port }} + login = "{{ .Values.db.user }}" + password = "$ENV{DB_PASSWORD}" + radius_db = "{{ .Values.db.name }}" acct_table1 = "radacct" acct_table2 = "radacct" @@ -145,7 +144,7 @@ data: rest { tls { } - connect_uri = "http://{{.Values.api.host}}:{{.Values.api.port}}/" + connect_uri = "http://{{ .Values.api.host }}:{{ .Values.api.port }}/" authorize { uri = "${..connect_uri}radius/auth" method = 'post' @@ -167,4 +166,3 @@ data: idle_timeout = 60 } } - diff --git a/charts/freeradius/templates/deployment.yaml b/charts/freeradius/templates/deployment.yaml new file mode 100644 index 0000000..ee42740 --- /dev/null +++ b/charts/freeradius/templates/deployment.yaml @@ -0,0 +1,93 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "freeradius.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "freeradius.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "freeradius.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "freeradius.selectorLabels" . | nindent 8 }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ include "freeradius.name" . }} + image: {{ include "freeradius.image" . | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: {{ toJson .Values.args }} + env: + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "freeradius.dbSecretName" . }} + key: {{ include "freeradius.dbSecretKey" . }} + {{- range $k, $v := .Values.env }} + - name: {{ $k }} + value: {{ $v | quote }} + {{- end }} + ports: + - name: auth + containerPort: {{ .Values.service.authPort }} + protocol: UDP + - name: acct + containerPort: {{ .Values.service.acctPort }} + protocol: UDP + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: certs + mountPath: /etc/freeradius/certs + - name: configs + mountPath: /configs/default + subPath: default + readOnly: true + - name: configs + mountPath: /configs/sql + subPath: sql + readOnly: true + - name: configs + mountPath: /configs/rest + subPath: rest + readOnly: true + volumes: + - name: configs + configMap: + name: {{ include "freeradius.fullname" . }}-configs + - name: certs + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ include "freeradius.pvcName" . }} + {{- else }} + emptyDir: {} + {{- end }} diff --git a/charts/freeradius/templates/pvc.yaml b/charts/freeradius/templates/pvc.yaml new file mode 100644 index 0000000..5f99fea --- /dev/null +++ b/charts/freeradius/templates/pvc.yaml @@ -0,0 +1,18 @@ +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "freeradius.pvcName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "freeradius.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.accessMode }} + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} +{{- end }} diff --git a/charts/freeradius/templates/secret.yaml b/charts/freeradius/templates/secret.yaml new file mode 100644 index 0000000..7962645 --- /dev/null +++ b/charts/freeradius/templates/secret.yaml @@ -0,0 +1,12 @@ +{{- if not .Values.db.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-db" (include "freeradius.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "freeradius.labels" . | nindent 4 }} +type: Opaque +stringData: + {{ include "freeradius.dbSecretKey" . }}: {{ required "db.password is required (or set db.existingSecret)" .Values.db.password | quote }} +{{- end }} diff --git a/charts/freeradius/templates/service.yaml b/charts/freeradius/templates/service.yaml new file mode 100644 index 0000000..26ec1f0 --- /dev/null +++ b/charts/freeradius/templates/service.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "freeradius.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "freeradius.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + {{- if eq .Values.service.type "NodePort" }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} + {{- end }} + ports: + - name: auth + port: {{ .Values.service.authPort }} + targetPort: auth + protocol: UDP + {{- if eq .Values.service.type "NodePort" }} + nodePort: {{ .Values.service.authNodePort }} + {{- end }} + - name: acct + port: {{ .Values.service.acctPort }} + targetPort: acct + protocol: UDP + {{- if eq .Values.service.type "NodePort" }} + nodePort: {{ .Values.service.acctNodePort }} + {{- end }} + selector: + {{- include "freeradius.selectorLabels" . | nindent 4 }} diff --git a/charts/freeradius/values.yaml b/charts/freeradius/values.yaml new file mode 100644 index 0000000..a77293a --- /dev/null +++ b/charts/freeradius/values.yaml @@ -0,0 +1,74 @@ +# Default values for the freeradius chart. +# This is a YAML-formatted file. All keys are documented below. + +replicaCount: 1 + +image: + # Container image built by this repo and pushed to the Gitea registry. + repository: git.ahax86.ru/pub/freeradius + # Image tag. Empty -> defaults to the chart appVersion (e.g. 3.2.8). + tag: "" + pullPolicy: IfNotPresent + +# Names of pre-created docker-registry secrets used to pull the image. +imagePullSecrets: [] +# - name: gitea-registry + +nameOverride: "" +fullnameOverride: "" + +# Run arguments for the container. Use ["freeradius","-X"] for debug logging. +args: + - freeradius + - -f + +# Auth / accounting service. FreeRADIUS uses UDP. +service: + # ClusterIP | NodePort | LoadBalancer + type: NodePort + authPort: 31812 + acctPort: 31813 + # nodePort values are used only when type: NodePort. + authNodePort: 31812 + acctNodePort: 31813 + externalTrafficPolicy: Local + +# PostgreSQL backend (RADIUS schema: radacct, radcheck, nas, ...). +# The password is injected into the pod as the DB_PASSWORD env var and read by +# FreeRADIUS via $ENV{DB_PASSWORD}; it is never written into a ConfigMap. +db: + host: postgresql-pgbouncer + port: 6432 + user: admin + name: radius_db + # Provide the password at install time (never commit a real value), e.g. + # helm install ... --set db.password=... + password: "" + # Or reference an existing Secret instead of db.password: + existingSecret: "" + passwordKey: db-password + +# REST fallback authorization (hotspotter): used when a NAS is not in the `nas` table. +api: + host: hotspotter + port: 8080 + +# Certificate persistence. Certs are generated on first start and must survive +# restarts. Mounted at /etc/freeradius/certs. +persistence: + enabled: true + # Use a pre-created PVC instead of letting the chart create one. + existingClaim: "" + storageClass: "" + accessMode: ReadWriteOnce + size: 32Mi + +# Extra environment variables for the container. +env: + TZ: UTC + +resources: {} +podAnnotations: {} +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/hosted/README.md b/hosted/README.md deleted file mode 100644 index 540316e..0000000 --- a/hosted/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# hosted - -to test - -build -```bash -docker build . -t hosted:test -f Dockerfile -``` - -run - -```bash -docker run -ti -p 1812:1812/UDP -p 1813:1813/UDP hosted:test /bin/bash -``` \ No newline at end of file diff --git a/build/.dockerignore b/packaging/deb/.dockerignore similarity index 100% rename from build/.dockerignore rename to packaging/deb/.dockerignore diff --git a/build/Dockerfile b/packaging/deb/Dockerfile similarity index 84% rename from build/Dockerfile rename to packaging/deb/Dockerfile index 3137517..40ac759 100644 --- a/build/Dockerfile +++ b/packaging/deb/Dockerfile @@ -10,8 +10,10 @@ COPY local-noble.list /etc/apt/sources.list.d/ubuntu.sources RUN apt-get update && apt-get install -y devscripts equivs git quilt gcc # -# Setup build envs -ARG RADIUS_TAG="release_3_2_5" +# Setup build envs. +# Defaults match the repo-wide /VERSION (3.2.8). CI overrides RADIUS_TAG +# from VERSION; RLM_RAW_BRANCH is the rlm_raw module branch for this release. +ARG RADIUS_TAG="release_3_2_8" ARG RLM_RAW_BRANCH="v3.2.4" ENV RADIUS_REPO="https://github.com/FreeRADIUS/freeradius-server.git" diff --git a/build/local-noble.list b/packaging/deb/local-noble.list similarity index 100% rename from build/local-noble.list rename to packaging/deb/local-noble.list diff --git a/packaging/deb/upload.sh b/packaging/deb/upload.sh new file mode 100644 index 0000000..618cae5 --- /dev/null +++ b/packaging/deb/upload.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# +# Uploads built .deb packages to a Gitea Debian package registry. +# +# Required env: +# DEB_USER - Gitea username (token owner) +# DEB_PASS - Gitea access token with the write:package scope +# +# Optional env (defaults target git.ahax86.ru / owner "pub"): +# DEB_REGISTRY - registry base URL +# DEB_DISTRIBUTION - apt distribution (default: noble) +# DEB_COMPONENT - apt component (default: main) +# +set -euo pipefail + +DEB_REGISTRY="${DEB_REGISTRY:-https://git.ahax86.ru/api/packages/pub/debian}" +DEB_DISTRIBUTION="${DEB_DISTRIBUTION:-noble}" +DEB_COMPONENT="${DEB_COMPONENT:-main}" + +if [ -z "${DEB_USER:-}" ] || [ -z "${DEB_PASS:-}" ]; then + echo "DEB_USER and DEB_PASS must be set" >&2 + exit 1 +fi + +upload_url="${DEB_REGISTRY}/pool/${DEB_DISTRIBUTION}/${DEB_COMPONENT}/upload" + +shopt -s nullglob +debs=(*.deb) +if [ "${#debs[@]}" -eq 0 ]; then + echo "No .deb files found to upload" >&2 + exit 1 +fi + +for file in "${debs[@]}"; do + echo "Uploading ${file} -> ${upload_url}" + curl -fsSL --user "${DEB_USER}:${DEB_PASS}" \ + --upload-file "./${file}" \ + "${upload_url}" +done + +echo "Uploaded ${#debs[@]} package(s)" diff --git a/hosted/.env b/packaging/hosted/.env similarity index 100% rename from hosted/.env rename to packaging/hosted/.env diff --git a/hosted/.env_cnf_example b/packaging/hosted/.env_cnf_example similarity index 100% rename from hosted/.env_cnf_example rename to packaging/hosted/.env_cnf_example diff --git a/hosted/Dockerfile b/packaging/hosted/Dockerfile similarity index 100% rename from hosted/Dockerfile rename to packaging/hosted/Dockerfile diff --git a/packaging/hosted/README.md b/packaging/hosted/README.md new file mode 100644 index 0000000..87d93f5 --- /dev/null +++ b/packaging/hosted/README.md @@ -0,0 +1,120 @@ +# Hosted установка (bare-metal / VM) + +## Для чего этот репозиторий + +Это сборка и поставка кастомного **FreeRADIUS 3.2.x** для hotspot / captive-portal +сценариев. Прикладного кода здесь нет: репозиторий собирает апстримный FreeRADIUS с +дополнительным модулем `rlm_raw`, накладывает конфиги и доставляет результат в двух +видах - bare-metal (этот каталог) и Kubernetes (Helm-чарт в `charts/freeradius`). + +## Как это работает + +1. Точки доступа / hotspot-шлюзы (NAS) шлют RADIUS-запросы. Статически клиенты **не** + прописаны - они резолвятся динамически. `clients.conf` объявляет сеть `0.0.0.0/0` + как динамическую (`client dynamic`). +2. Когда приходит пакет от неизвестного NAS, FreeRADIUS вызывает виртуальный сервер + `dynamic_clients` (`sites-enabled/dynamic-clients`). Тот ищет NAS по `NAS-Identifier` + в таблице `nas` PostgreSQL: если запись есть - берёт из БД `secret`/`shortname` и + определяет клиента на лету; если нет - идёт в REST к сервису `hotspotter`. +3. Авторизация абонента идёт в site `default` через SQL (схема RADIUS: `radcheck`, + `radreply`, `radacct`, ...) и/или REST. Hotspotter получает `hotspot-group` + (= `Called-Station-Id`) и `hotspot-id` (= `NAS-Identifier`) и решает, пускать ли. +4. Профиль урезан под задачу: отключены `eap`, `inner-tunnel`, `proxy_rate_limit`, + включён `delay_reject`. + +## Зачем нужен rlm_raw + +`NAS-Identifier` и `Called-Station-Id` - строковые RADIUS-атрибуты, в которые +hotspot-шлюзы кладут идентификатор точки/группы. Это значение должно совпадать +**байт-в-байт** с `nas.shortname` в БД и с записями в hotspotter. + +Штатный xlat `%{NAS-Identifier}` отдаёт значение уже после разбора и нормализации +FreeRADIUS (атрибут декодируется по словарю, к строке применяется обработка). Для +произвольных байтов и нестандартных форматов это меняет значение и ломает поиск. + +Модуль `rlm_raw` добавляет xlat `%{raw:...}`, который возвращает атрибут ровно так, +как его прислал NAS, без перекодирования. Все ключевые конфиги (`dynamic-clients`, +`mods-enabled/rest`) построены на `%{raw:Called-Station-Id}` и `%{raw:NAS-Identifier}`, +поэтому модуль обязателен - без него конфиг просто не загрузится. Именно поэтому +FreeRADIUS пересобирается из исходников с `rlm_raw`, а не ставится из стандартного apt. + +## Что в этом каталоге + +| Файл | Назначение | +|------|------------| +| `.env` | Версия пакета: `FREERADIUS_TAG` (ставится как `+git`). | +| `.env_cnf_example` | Шаблон для `.env_cnf`. Скопировать и заполнить. | +| `.env_cnf` | Локальные секреты/хосты. **В git не попадает** (gitignore). | +| `config/` | radiusd.conf, dictionary, clients.conf, mods/sites. | +| `install.sh` | Первичная установка (root). | +| `update.sh` | Смена версии пакета (root). | +| `cron/radius-renew.sh` | Ежемесячный перевыпуск сертификатов. | + +Этот бандл публикуется CI как generic-пакет: +`https://git.ahax86.ru/api/packages/pub/generic/hosted//hosted.tar.gz` + +## Требования + +- Ubuntu 24.04 (noble), root. +- Доступ к `git.ahax86.ru`, к PostgreSQL и к REST-сервису `hotspotter`. +- Нужная версия `.deb` уже опубликована в Debian-реестре Gitea (CI собирает её первой). + +## Установка + +```bash +cp .env_cnf_example .env_cnf +$EDITOR .env_cnf # заполнить хосты/порты/креды +sudo ./install.sh +``` + +`install.sh` подключает Debian-реестр Gitea, ставит запиненные пакеты (`apt-mark hold`), +рендерит `sites-enabled/default`, `mods-enabled/rest` и `mods-enabled/sql` из `.env_cnf`, +копирует статические конфиги, включает `delay_reject`, ставит systemd-юнит и cron. + +Переменные `.env_cnf`: + +| Переменная | Смысл | +|------------|-------| +| `FR_MAIN_PORT` / `FR_ACC_PORT` | UDP-порты auth / accounting. | +| `FR_API_HOST` / `FR_API_PORT` | Endpoint REST hotspotter. | +| `FR_PG_HOST` / `FR_PG_PORT` | Хост / порт PostgreSQL. | +| `FR_PG_USER` / `FR_PG_PASS` / `FR_PG_DB` | Креды / база PostgreSQL. | + +### Приватный реестр + +Если реестр `pub` не публичный, перед запуском задайте токен: + +```bash +export DEB_AUTH=":" +sudo -E ./install.sh +``` + +Прочие переопределения (значения по умолчанию): +`DEB_REGISTRY=https://git.ahax86.ru/api/packages/pub/debian`, +`DEB_DISTRIBUTION=noble`, `DEB_COMPONENT=main`. + +## Смена версии + +```bash +# поменять FREERADIUS_TAG в .env, затем: +sudo ./update.sh +``` + +`update.sh`: `apt-mark unhold` → установка нужной версии → `apt-mark hold` → перезапуск. + +## Удаление + +```bash +sudo apt-mark unhold libfreeradius3 freeradius-common freeradius-postgresql \ + freeradius-rest freeradius-utils freeradius +sudo apt-get purge -y freeradius freeradius-common freeradius-config \ + freeradius-python3 libfreeradius3 freeradius-postgresql freeradius-rest freeradius-utils +sudo rm -rf /etc/freeradius /usr/lib/freeradius +``` + +## Проверка в Docker + +```bash +docker build . -t hosted:test -f Dockerfile +docker run -ti -p 1812:1812/udp -p 1813:1813/udp hosted:test /bin/bash +``` diff --git a/hosted/config/clients.conf b/packaging/hosted/config/clients.conf similarity index 100% rename from hosted/config/clients.conf rename to packaging/hosted/config/clients.conf diff --git a/hosted/config/dictionary b/packaging/hosted/config/dictionary similarity index 100% rename from hosted/config/dictionary rename to packaging/hosted/config/dictionary diff --git a/hosted/config/mods-enabled/raw b/packaging/hosted/config/mods-enabled/raw similarity index 100% rename from hosted/config/mods-enabled/raw rename to packaging/hosted/config/mods-enabled/raw diff --git a/hosted/config/mods-enabled/rest b/packaging/hosted/config/mods-enabled/rest similarity index 99% rename from hosted/config/mods-enabled/rest rename to packaging/hosted/config/mods-enabled/rest index a41443f..1aceb70 100644 --- a/hosted/config/mods-enabled/rest +++ b/packaging/hosted/config/mods-enabled/rest @@ -46,7 +46,6 @@ rest { # # If you wish to disable this pre-caching and reachability check, # comment out the configuration item below. - #connect_uri = "http://connectone.me:31668" connect_uri = "http://#API_HOST#:#API_PORT#/" # diff --git a/hosted/config/mods-enabled/sql b/packaging/hosted/config/mods-enabled/sql similarity index 100% rename from hosted/config/mods-enabled/sql rename to packaging/hosted/config/mods-enabled/sql diff --git a/hosted/config/radiusd.conf b/packaging/hosted/config/radiusd.conf similarity index 100% rename from hosted/config/radiusd.conf rename to packaging/hosted/config/radiusd.conf diff --git a/hosted/config/sites-enabled/default b/packaging/hosted/config/sites-enabled/default similarity index 100% rename from hosted/config/sites-enabled/default rename to packaging/hosted/config/sites-enabled/default diff --git a/hosted/config/sites-enabled/dynamic-clients b/packaging/hosted/config/sites-enabled/dynamic-clients similarity index 100% rename from hosted/config/sites-enabled/dynamic-clients rename to packaging/hosted/config/sites-enabled/dynamic-clients diff --git a/hosted/config/systemd/freeradius.service b/packaging/hosted/config/systemd/freeradius.service similarity index 100% rename from hosted/config/systemd/freeradius.service rename to packaging/hosted/config/systemd/freeradius.service diff --git a/hosted/cron/radius-renew.sh b/packaging/hosted/cron/radius-renew.sh similarity index 100% rename from hosted/cron/radius-renew.sh rename to packaging/hosted/cron/radius-renew.sh diff --git a/hosted/install.sh b/packaging/hosted/install.sh similarity index 73% rename from hosted/install.sh rename to packaging/hosted/install.sh index 5b8dd2e..0d7a7d7 100644 --- a/hosted/install.sh +++ b/packaging/hosted/install.sh @@ -14,13 +14,27 @@ out_dir=/etc/freeradius echo "Installing packages..." apt-get update -apt-get install -y apt-transport-https ca-certificates curl software-properties-common +apt-get install -y apt-transport-https ca-certificates curl -echo "Installing freeradius..." -curl -fsSL http://connectone.pro/gpg | gpg --dearmor -o /usr/share/keyrings/connectone.gpg && \ - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/connectone.gpg] \ - https://nexus.connectone.pro/repository/radius bionic main" | tee /etc/apt/sources.list.d/connectone.list \ - > /dev/null +echo "Registering Gitea Debian registry..." +DEB_REGISTRY="${DEB_REGISTRY:-https://git.ahax86.ru/api/packages/pub/debian}" +DEB_DISTRIBUTION="${DEB_DISTRIBUTION:-noble}" +DEB_COMPONENT="${DEB_COMPONENT:-main}" + +install -d /etc/apt/keyrings +curl -fsSL "${DEB_REGISTRY}/repository.key" -o /etc/apt/keyrings/gitea-pub.asc + +apt_list=/etc/apt/sources.list.d/gitea-freeradius.list +if [ -n "${DEB_AUTH:-}" ]; then + # Private registry: embed "user:token" in the source URL (file kept root-only). + apt_uri="${DEB_REGISTRY%%://*}://${DEB_AUTH}@${DEB_REGISTRY#*://}" + echo "deb [signed-by=/etc/apt/keyrings/gitea-pub.asc] ${apt_uri} ${DEB_DISTRIBUTION} ${DEB_COMPONENT}" >"$apt_list" + chmod 600 "$apt_list" +else + echo "deb [signed-by=/etc/apt/keyrings/gitea-pub.asc] ${DEB_REGISTRY} ${DEB_DISTRIBUTION} ${DEB_COMPONENT}" >"$apt_list" +fi + +echo "Installing freeradius ${FREERADIUS_TAG}..." apt-get update && apt-get install -y libfreeradius3=${FREERADIUS_TAG}+git freeradius-common=${FREERADIUS_TAG}+git \ freeradius-postgresql=${FREERADIUS_TAG}+git freeradius-rest=${FREERADIUS_TAG}+git \ freeradius-utils=${FREERADIUS_TAG}+git freeradius=${FREERADIUS_TAG}+git diff --git a/hosted/update.sh b/packaging/hosted/update.sh similarity index 100% rename from hosted/update.sh rename to packaging/hosted/update.sh diff --git a/packaging/image/Dockerfile b/packaging/image/Dockerfile new file mode 100644 index 0000000..2235a5a --- /dev/null +++ b/packaging/image/Dockerfile @@ -0,0 +1,55 @@ +# Runtime image for the custom FreeRADIUS build. +# Installs the pre-built .deb packages from the Gitea Debian registry and bakes +# the shared configs. Build context MUST be the repo root: +# docker build -f packaging/image/Dockerfile -t . +# +# Version is pinned via PKG_VERSION (CI derives it from /VERSION, e.g. 3.2.8+git). +FROM ubuntu:24.04 +WORKDIR /tmp +ARG out_dir=/etc/freeradius +ARG PKG_VERSION=3.2.8+git + +# Gitea Debian package registry (override for a different instance/owner). +ARG DEB_REGISTRY=https://git.ahax86.ru/api/packages/pub/debian +ARG DEB_DISTRIBUTION=noble +ARG DEB_COMPONENT=main + +COPY packaging/image/local-noble.list /etc/apt/sources.list.d/ubuntu.sources +RUN apt-get update && apt-get install -y apt-transport-https ca-certificates curl gpg + +# Register the Gitea Debian registry (public read assumed for owner "pub"). +RUN install -d /etc/apt/keyrings && \ + curl -fsSL "${DEB_REGISTRY}/repository.key" -o /etc/apt/keyrings/gitea-pub.asc && \ + echo "deb [signed-by=/etc/apt/keyrings/gitea-pub.asc] ${DEB_REGISTRY} ${DEB_DISTRIBUTION} ${DEB_COMPONENT}" \ + > /etc/apt/sources.list.d/gitea-freeradius.list + +RUN apt-get update && apt-get install -y \ + libfreeradius3=${PKG_VERSION} freeradius-common=${PKG_VERSION} \ + freeradius-postgresql=${PKG_VERSION} freeradius-rest=${PKG_VERSION} \ + freeradius-utils=${PKG_VERSION} freeradius=${PKG_VERSION} && \ + apt autoremove --purge -y && apt-get clean && \ + rm -f /etc/apt/sources.list.d/gitea-freeradius.list + +# Bake the shared configs (kept under packaging/hosted/config). +COPY packaging/hosted/config /tmp/config +RUN cp /tmp/config/radiusd.conf $out_dir/radiusd.conf && \ + cp /tmp/config/dictionary $out_dir/dictionary && \ + cp /tmp/config/clients.conf $out_dir/clients.conf && \ + cp /tmp/config/mods-enabled/raw $out_dir/mods-enabled/raw && \ + cp /tmp/config/sites-enabled/dynamic-clients $out_dir/sites-enabled/dynamic-clients && \ + rm -f $out_dir/sites-enabled/inner-tunnel && \ + rm -f $out_dir/mods-enabled/eap && \ + rm -f $out_dir/mods-enabled/proxy_rate_limit + +# Enable delay_reject. +RUN rm -f $out_dir/mods-enabled/delay && \ + ln -s $out_dir/mods-available/delay $out_dir/mods-enabled/delay + +ADD packaging/image/entrypoint.sh /usr/bin/entrypoint.sh +RUN chmod a+x /usr/bin/entrypoint.sh && chmod -R 750 $out_dir && chown -R freerad:root $out_dir && \ + mv /etc/freeradius/certs /tmp && \ + rm -rf /tmp/config + +WORKDIR /etc/freeradius +ENTRYPOINT ["/usr/bin/entrypoint.sh"] +CMD ["freeradius", "-f"] diff --git a/entrypoint.sh b/packaging/image/entrypoint.sh similarity index 100% rename from entrypoint.sh rename to packaging/image/entrypoint.sh diff --git a/local-noble.list b/packaging/image/local-noble.list similarity index 100% rename from local-noble.list rename to packaging/image/local-noble.list