chore: init
This commit is contained in:
126
.gitea/workflows/build-release.yml
Normal file
126
.gitea/workflows/build-release.yml
Normal file
@@ -0,0 +1,126 @@
|
||||
name: build-release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "mistral.rs upstream tag"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
plan:
|
||||
runs-on: fedora
|
||||
outputs:
|
||||
flavours: ${{ steps.plan.outputs.flavours }}
|
||||
version: ${{ steps.plan.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: plan
|
||||
run: |
|
||||
version="${TAG#v}"
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
# Emit flavours as a JSON array for matrix consumption
|
||||
flavours=$(yq -o=json -I=0 '.flavours' flavours.yml)
|
||||
echo "flavours=${flavours}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
TAG: ${{ inputs.tag }}
|
||||
|
||||
build:
|
||||
needs: plan
|
||||
runs-on: cuda-13.0
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavour: ${{ fromJSON(needs.plan.outputs.flavours) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Clone mistral.rs at tag
|
||||
run: |
|
||||
git clone --depth 1 --branch "${{ inputs.tag }}" \
|
||||
https://github.com/EricLBuehler/mistral.rs.git src/
|
||||
|
||||
- name: Build
|
||||
run: ./script/build-binary.sh
|
||||
env:
|
||||
FLAVOUR_NAME: ${{ matrix.flavour.name }}
|
||||
CUDA_HOME: ${{ matrix.flavour.cuda_home }}
|
||||
CARGO_FEATURES: ${{ matrix.flavour.cargo_features }}
|
||||
CUDA_COMPUTE_CAP: ${{ matrix.flavour.compute_caps }}
|
||||
SRC_DIR: src
|
||||
|
||||
- name: Upload binary artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: mistralrs-server-${{ matrix.flavour.name }}
|
||||
path: artifacts/mistralrs-server-${{ matrix.flavour.name }}
|
||||
retention-days: 1
|
||||
|
||||
package:
|
||||
needs: [plan, build]
|
||||
runs-on: fedora
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavour: ${{ fromJSON(needs.plan.outputs.flavours) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
#- name: Install build tools
|
||||
# run: sudo dnf install -y rpm-build rpmdevtools systemd-rpm-macros
|
||||
|
||||
- name: Download binary
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: mistralrs-server-${{ matrix.flavour.name }}
|
||||
path: artifacts/
|
||||
|
||||
- name: Build RPM
|
||||
run: |
|
||||
rpmdev-setuptree
|
||||
cp artifacts/mistralrs-server-${{ matrix.flavour.name }} ~/rpmbuild/SOURCES/
|
||||
cp rpm/systemd/mistralrs@.service ~/rpmbuild/SOURCES/
|
||||
cp rpm/systemd/mistralrs@.conf.example ~/rpmbuild/SOURCES/
|
||||
rpmbuild -bb rpm/mistralrs.spec \
|
||||
--define "mistralrs_version ${{ needs.plan.outputs.version }}" \
|
||||
--define "mistralrs_flavour ${{ matrix.flavour.name }}"
|
||||
|
||||
- name: Upload RPM
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: rpm-${{ matrix.flavour.name }}
|
||||
path: ~/rpmbuild/RPMS/x86_64/*.rpm
|
||||
retention-days: 7
|
||||
|
||||
publish:
|
||||
needs: [plan, package]
|
||||
runs-on: fedora
|
||||
# concurrency ensures only one publish runs at a time — repo metadata
|
||||
# corruption is a nightmare if two createrepo_c processes race.
|
||||
concurrency:
|
||||
group: rpm-publish
|
||||
cancel-in-progress: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
#- name: Install tools
|
||||
# run: sudo dnf install -y createrepo_c rpm-sign rsync
|
||||
|
||||
- name: Download all RPMs
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: rpms/
|
||||
pattern: rpm-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Import signing key
|
||||
run: |
|
||||
echo "${{ secrets.RPM_SIGNING_KEY }}" | gpg --batch --import
|
||||
echo "%_gpg_name ${{ secrets.RPM_SIGNING_KEY_ID }}" > ~/.rpmmacros
|
||||
|
||||
- name: Sign and publish
|
||||
run: ./script/publish-repo.sh rpms/
|
||||
env:
|
||||
RSYNC_TARGET: ${{ secrets.RSYNC_TARGET }}
|
||||
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
|
||||
43
.gitea/workflows/poll-upstream.yml
Normal file
43
.gitea/workflows/poll-upstream.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
name: poll-upstream
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "*/15 * * * *"
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: fedora
|
||||
steps:
|
||||
- name: Get upstream latest tag
|
||||
id: upstream
|
||||
run: |
|
||||
tag=$(curl -sSfL \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
https://api.github.com/repos/EricLBuehler/mistral.rs/releases/latest \
|
||||
| jq -r .tag_name)
|
||||
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
|
||||
echo "Upstream latest: ${tag}"
|
||||
|
||||
- name: Get published version from our repo
|
||||
id: published
|
||||
run: |
|
||||
# Query our own dnf repo. If the version is there, we've already built it.
|
||||
# Strip leading 'v' because RPM versions don't use it.
|
||||
version="${UPSTREAM_TAG#v}"
|
||||
if curl -sSfI "https://rpm.lair.cafe/mistralrs/fedora-43/x86_64/mistralrs-server-cuda13-fa-${version}-1.fc43.x86_64.rpm" | grep -q '^HTTP.*200'; then
|
||||
echo "already_built=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "already_built=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
env:
|
||||
UPSTREAM_TAG: ${{ steps.upstream.outputs.tag }}
|
||||
|
||||
- name: Trigger build workflow
|
||||
if: steps.published.outputs.already_built == 'false'
|
||||
run: |
|
||||
curl -sSfL -X POST \
|
||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||
-H 'Accept: application/json' \
|
||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/workflows/build-release.yml/dispatches" \
|
||||
-d "{\"ref\":\"main\",\"inputs\":{\"tag\":\"${{ steps.upstream.outputs.tag }}\"}}"
|
||||
55
CLAUDE.md
Normal file
55
CLAUDE.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Purpose
|
||||
|
||||
This repo packages [mistral.rs](https://github.com/EricLBuehler/mistral.rs) (a Rust LLM inference server) into RPMs for Fedora 43 / x86_64. It does **not** contain the mistral.rs source — it clones upstream at a given tag, cross-compiles with CUDA, and produces signed RPMs published to a self-hosted dnf repo at `rpm.lair.cafe`.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Pipeline flow
|
||||
|
||||
1. **poll-upstream** (`.gitea/workflows/poll-upstream.yml`) — cron every 15 min, checks GitHub for latest mistral.rs release tag. If the corresponding RPM doesn't exist on `rpm.lair.cafe`, triggers `build-release`.
|
||||
2. **build-release** (`.gitea/workflows/build-release.yml`) — three-stage pipeline:
|
||||
- **plan** — reads `flavours.yml`, emits a JSON matrix of flavours + stripped version.
|
||||
- **build** — runs on a `cuda-13.0` runner. Clones upstream at tag, calls `script/build-binary.sh` to `cargo build --release --locked` with flavour-specific CUDA features.
|
||||
- **package** — runs `rpmbuild -bb rpm/mistralrs.spec` with `--define` for version and flavour.
|
||||
- **publish** — GPG-signs RPMs, rsyncs to `rpm.lair.cafe`, runs `createrepo_c --update`. Uses concurrency group `rpm-publish` to prevent metadata races.
|
||||
|
||||
### Flavours
|
||||
|
||||
Defined in `flavours.yml`. Each flavour specifies a name, `cuda_home`, `cargo_features`, and `compute_caps`. The RPM spec uses `update-alternatives` so multiple flavours can coexist, with priority: base=10, fa=20, nccl=30.
|
||||
|
||||
### Key files
|
||||
|
||||
- `flavours.yml` — flavour matrix definition (drives CI matrix)
|
||||
- `rpm/mistralrs.spec` — RPM spec (binary-only package, no rebuild)
|
||||
- `rpm/systemd/mistralrs@.service` — templated systemd unit (`@BINARY@` and `@FLAVOUR@` are sed-replaced during rpmbuild)
|
||||
- `rpm/systemd/mistralrs@.conf.example` — example env file for instances
|
||||
- `script/build-binary.sh` — compiles mistralrs-server with cargo (requires `FLAVOUR_NAME`, `CUDA_HOME`, `CARGO_FEATURES`, `CUDA_COMPUTE_CAP`, `SRC_DIR` env vars)
|
||||
- `script/publish-repo.sh` — signs RPMs and rsyncs to the repo server
|
||||
- `script/setup/` — one-time infra setup scripts (DNS, TLS cert, nginx) for `rpm.lair.cafe` on host `oolon`
|
||||
|
||||
## Commands
|
||||
|
||||
Build a binary locally (requires CUDA toolkit):
|
||||
```bash
|
||||
FLAVOUR_NAME=cuda13 CUDA_HOME=/usr/local/cuda-13.0 CARGO_FEATURES="cuda cudnn flash-attn nccl" CUDA_COMPUTE_CAP=120 SRC_DIR=./src ./script/build-binary.sh
|
||||
```
|
||||
|
||||
Build an RPM from a pre-built binary:
|
||||
```bash
|
||||
rpmdev-setuptree
|
||||
cp artifacts/mistralrs-server-cuda13 ~/rpmbuild/SOURCES/
|
||||
cp rpm/systemd/mistralrs@.service ~/rpmbuild/SOURCES/
|
||||
cp rpm/systemd/mistralrs@.conf.example ~/rpmbuild/SOURCES/
|
||||
rpmbuild -bb rpm/mistralrs.spec --define "mistralrs_version 0.7.0" --define "mistralrs_flavour cuda13"
|
||||
```
|
||||
|
||||
## Infrastructure
|
||||
|
||||
- CI runs on Gitea Actions (self-hosted), not GitHub Actions
|
||||
- RPM repo hosted at `rpm.lair.cafe` on host `oolon.kosherinata.internal`
|
||||
- TLS via Let's Encrypt with Cloudflare DNS challenge
|
||||
- Publish uses rsync over SSH as `gitea_ci` user
|
||||
36
asset/nginx/rpm.lair.cafe.conf
Normal file
36
asset/nginx/rpm.lair.cafe.conf
Normal file
@@ -0,0 +1,36 @@
|
||||
server {
|
||||
server_name rpm.lair.cafe;
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/rpm.lair.cafe/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/rpm.lair.cafe/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ecdh_curve X25519:secp256r1:secp384r1;
|
||||
|
||||
root /var/www/rpm;
|
||||
|
||||
autoindex on;
|
||||
autoindex_exact_size off;
|
||||
autoindex_localtime on;
|
||||
|
||||
types {
|
||||
application/x-rpm rpm;
|
||||
application/xml xml;
|
||||
}
|
||||
default_type application/octet-stream;
|
||||
|
||||
location ~ \.rpm$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location ~ /repodata/ {
|
||||
expires -1;
|
||||
add_header Cache-Control "no-cache, must-revalidate";
|
||||
}
|
||||
|
||||
location = /RPM-GPG-KEY-mistralrs {
|
||||
default_type text/plain;
|
||||
}
|
||||
}
|
||||
5
flavours.yml
Normal file
5
flavours.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
flavours:
|
||||
- name: cuda13
|
||||
cuda_home: /usr/local/cuda-13.0
|
||||
cargo_features: "cuda cudnn flash-attn nccl"
|
||||
compute_caps: "120"
|
||||
100
rpm/mistralrs.spec
Normal file
100
rpm/mistralrs.spec
Normal file
@@ -0,0 +1,100 @@
|
||||
%global _build_id_links none
|
||||
%global debug_package %{nil}
|
||||
%global __strip /usr/bin/true
|
||||
|
||||
# Passed in via --define at rpmbuild time
|
||||
%{!?mistralrs_version: %global mistralrs_version 0.7.0}
|
||||
%{!?mistralrs_flavour: %global mistralrs_flavour cuda13}
|
||||
|
||||
Name: mistralrs-server-%{mistralrs_flavour}
|
||||
Version: %{mistralrs_version}
|
||||
Release: 1%{?dist}
|
||||
Summary: Fast, flexible LLM inference server (mistral.rs, %{mistralrs_flavour} flavour)
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/EricLBuehler/mistral.rs
|
||||
|
||||
# Pre-built binary (produced in the build job, not rebuilt here)
|
||||
Source0: mistralrs-server-%{mistralrs_flavour}
|
||||
Source1: mistralrs@.service
|
||||
Source2: mistralrs@.conf.example
|
||||
|
||||
ExclusiveArch: x86_64
|
||||
|
||||
# Runtime requirements. We link against the CUDA runtime; consumers must have
|
||||
# a matching CUDA installation or the rpmfusion nvidia driver's cuda-libs.
|
||||
# We don't hard-require it at the RPM level because consumers may have CUDA
|
||||
# from multiple sources (nvidia direct, rpmfusion, etc.) — failing to load
|
||||
# libcuda.so at runtime gives a clearer error than RPM dep resolution would.
|
||||
Requires: systemd
|
||||
|
||||
# Flavours are mutually exclusive with other flavours of themselves at the
|
||||
# same install path, but you can install cuda13, cuda13-fa, cuda13-fa-nccl
|
||||
# side by side — they all get separate /opt paths.
|
||||
Provides: mistralrs-server = %{version}-%{release}
|
||||
|
||||
%description
|
||||
mistral.rs is a blazingly fast LLM inference engine written in Rust.
|
||||
This package provides the %{mistralrs_flavour} flavour, built with features:
|
||||
cuda, cudnn, and optionally flash-attn and nccl depending on flavour name.
|
||||
|
||||
Binary installs to /opt/mistralrs/%{mistralrs_flavour}/bin/ and can coexist
|
||||
with other flavours. Use `update-alternatives --config mistralrs-server` to
|
||||
select the default /usr/bin/mistralrs-server symlink target.
|
||||
|
||||
%prep
|
||||
# Nothing to unpack; Source0 is the binary itself
|
||||
cp %{SOURCE0} .
|
||||
cp %{SOURCE1} .
|
||||
cp %{SOURCE2} .
|
||||
|
||||
%build
|
||||
# Already built
|
||||
|
||||
%install
|
||||
install -D -m 0755 mistralrs-server-%{mistralrs_flavour} \
|
||||
%{buildroot}/opt/mistralrs/%{mistralrs_flavour}/bin/mistralrs-server
|
||||
install -D -m 0644 mistralrs@.service \
|
||||
%{buildroot}%{_unitdir}/mistralrs-%{mistralrs_flavour}@.service
|
||||
install -D -m 0644 mistralrs@.conf.example \
|
||||
%{buildroot}%{_sysconfdir}/mistralrs/%{mistralrs_flavour}.conf.example
|
||||
|
||||
# Patch the unit to point at this flavour's binary
|
||||
sed -i "s|@BINARY@|/opt/mistralrs/%{mistralrs_flavour}/bin/mistralrs-server|g" \
|
||||
%{buildroot}%{_unitdir}/mistralrs-%{mistralrs_flavour}@.service
|
||||
sed -i "s|@FLAVOUR@|%{mistralrs_flavour}|g" \
|
||||
%{buildroot}%{_unitdir}/mistralrs-%{mistralrs_flavour}@.service
|
||||
|
||||
%post
|
||||
# Register this flavour as an alternative for /usr/bin/mistralrs-server.
|
||||
# Priority = 10 for cuda13, 20 for cuda13-fa, 30 for cuda13-fa-nccl so that
|
||||
# "more featureful" wins by default. Consumers can override with
|
||||
# `update-alternatives --config mistralrs-server`.
|
||||
priority=10
|
||||
case "%{mistralrs_flavour}" in
|
||||
*nccl*) priority=30 ;;
|
||||
*fa*) priority=20 ;;
|
||||
esac
|
||||
update-alternatives --install /usr/bin/mistralrs-server mistralrs-server \
|
||||
/opt/mistralrs/%{mistralrs_flavour}/bin/mistralrs-server "${priority}"
|
||||
|
||||
%systemd_post mistralrs-%{mistralrs_flavour}@.service
|
||||
|
||||
%preun
|
||||
%systemd_preun mistralrs-%{mistralrs_flavour}@.service
|
||||
|
||||
%postun
|
||||
if [ $1 -eq 0 ]; then
|
||||
update-alternatives --remove mistralrs-server \
|
||||
/opt/mistralrs/%{mistralrs_flavour}/bin/mistralrs-server
|
||||
fi
|
||||
%systemd_postun_with_restart mistralrs-%{mistralrs_flavour}@.service
|
||||
|
||||
%files
|
||||
/opt/mistralrs/%{mistralrs_flavour}/
|
||||
%{_unitdir}/mistralrs-%{mistralrs_flavour}@.service
|
||||
%{_sysconfdir}/mistralrs/%{mistralrs_flavour}.conf.example
|
||||
|
||||
%changelog
|
||||
* Thu Apr 23 2026 Robin Thijssen <grenade@lair.cafe> - %{mistralrs_version}-1
|
||||
- Automated build for %{mistralrs_flavour} flavour
|
||||
10
rpm/systemd/mistralrs@.conf.example
Normal file
10
rpm/systemd/mistralrs@.conf.example
Normal file
@@ -0,0 +1,10 @@
|
||||
# Configuration for a mistralrs instance.
|
||||
# Copy to /etc/mistralrs/<instance>.conf and edit.
|
||||
|
||||
MISTRALRS_ARGS="--port 1234 plain -m openai/gpt-oss-20b --isq Q4K"
|
||||
|
||||
# HuggingFace token for gated models
|
||||
# HF_TOKEN=hf_xxxx
|
||||
|
||||
# Where model weights are cached
|
||||
HF_HOME=/var/cache/mistralrs/hf
|
||||
29
rpm/systemd/mistralrs@.service
Normal file
29
rpm/systemd/mistralrs@.service
Normal file
@@ -0,0 +1,29 @@
|
||||
[Unit]
|
||||
Description=mistral.rs inference server (@FLAVOUR@, instance %i)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=mistralrs
|
||||
Group=mistralrs
|
||||
SupplementaryGroups=video render
|
||||
EnvironmentFile=/etc/mistralrs/%i.conf
|
||||
ExecStart=@BINARY@ $MISTRALRS_ARGS
|
||||
Restart=on-failure
|
||||
RestartSec=10s
|
||||
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
ReadWritePaths=/var/lib/mistralrs /var/cache/mistralrs
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
|
||||
StateDirectory=mistralrs
|
||||
CacheDirectory=mistralrs
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
25
script/build-binary.sh
Executable file
25
script/build-binary.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
: "${FLAVOUR_NAME:?}"
|
||||
: "${CUDA_HOME:?}"
|
||||
: "${CARGO_FEATURES:?}"
|
||||
: "${CUDA_COMPUTE_CAP:?}"
|
||||
: "${SRC_DIR:?}"
|
||||
|
||||
export PATH="${CUDA_HOME}/bin:${PATH}"
|
||||
export LD_LIBRARY_PATH="${CUDA_HOME}/targets/x86_64-linux/lib:${CUDA_HOME}/lib64:${LD_LIBRARY_PATH:-}"
|
||||
|
||||
cd "${SRC_DIR}"
|
||||
|
||||
# --locked ensures Cargo.lock is respected; fails loud if it's out of sync
|
||||
# rather than silently resolving to different versions.
|
||||
cargo build --release --locked --features "${CARGO_FEATURES}"
|
||||
|
||||
mkdir -p ../artifacts
|
||||
cp target/release/mistralrs-server "../artifacts/mistralrs-server-${FLAVOUR_NAME}"
|
||||
|
||||
# Also grab the other binaries if you want them
|
||||
cp target/release/mistralrs "../artifacts/mistralrs-${FLAVOUR_NAME}" 2>/dev/null || true
|
||||
|
||||
echo "Built $(../artifacts/mistralrs-server-${FLAVOUR_NAME} --version 2>&1 | head -1)"
|
||||
24
script/publish-repo.sh
Executable file
24
script/publish-repo.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
RPM_DIR="${1:?usage: $0 <rpm-directory>}"
|
||||
REMOTE_DIR="/var/www/rpm/mistralrs/fedora-43/x86_64"
|
||||
|
||||
# sign each rpm with the imported gpg key
|
||||
for rpm in "${RPM_DIR}"/*.rpm; do
|
||||
rpm --addsign "${rpm}"
|
||||
done
|
||||
|
||||
install --directory --mode 700 ~/.ssh
|
||||
echo "${RSYNC_SSH_KEY}" | install --mode 600 /dev/stdin ~/.ssh/id_ed25519
|
||||
ssh-keyscan -H oolon.kosherinata.internal > ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
rsync \
|
||||
--archive \
|
||||
--verbose \
|
||||
--chmod D755,F644 \
|
||||
"${RPM_DIR}/"*.rpm \
|
||||
"${RSYNC_TARGET}:${REMOTE_DIR}/"
|
||||
ssh "${RSYNC_TARGET}" "cd ${REMOTE_DIR} && createrepo_c --update ."
|
||||
|
||||
echo "Published $(ls ${RPM_DIR}/*.rpm | wc -l) RPMs"
|
||||
17
script/setup/cert.sh
Executable file
17
script/setup/cert.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
tld=lair.cafe
|
||||
fqdn=rpm.${tld}
|
||||
sudo certbot certonly \
|
||||
-m ops@${tld} \
|
||||
--agree-tos \
|
||||
--no-eff-email \
|
||||
--noninteractive \
|
||||
--cert-name ${fqdn} \
|
||||
--expand \
|
||||
--allow-subset-of-names \
|
||||
--key-type ecdsa \
|
||||
--dns-cloudflare \
|
||||
--dns-cloudflare-credentials /root/.cloudflare/${tld} \
|
||||
--dns-cloudflare-propagation-seconds 60 \
|
||||
-d ${fqdn}
|
||||
44
script/setup/dns.sh
Executable file
44
script/setup/dns.sh
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cloudflare_api_token=$(cat ~/.cloudflare/lair.cafe | cut -d ' ' -f 3)
|
||||
cloudflare_dns_zone_name=lair.cafe
|
||||
cloudflare_dns_record_name=rpm.${cloudflare_dns_zone_name}
|
||||
cloudflare_dns_record_type=CNAME
|
||||
cloudflare_dns_record_content=bl.thgttg.com
|
||||
cloudflare_dns_zone_id=$(curl \
|
||||
--silent \
|
||||
--request GET \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Authorization: Bearer ${cloudflare_api_token}" \
|
||||
--url "https://api.cloudflare.com/client/v4/zones?name=${cloudflare_dns_zone_name}&status=active" \
|
||||
| jq -r '.result[0].id//empty')
|
||||
if [ -z ${cloudflare_dns_zone_id} ]; then
|
||||
echo "cloudflare dns zone not found"
|
||||
exit 1
|
||||
else
|
||||
echo "cloudflare dns zone found: ${cloudflare_dns_zone_name} (${cloudflare_dns_zone_id})"
|
||||
fi
|
||||
cloudflare_dns_record_id=$(curl \
|
||||
--silent \
|
||||
--request GET \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Authorization: Bearer ${cloudflare_api_token}" \
|
||||
--url "https://api.cloudflare.com/client/v4/zones/${cloudflare_dns_zone_id}/dns_records?type=${cloudflare_dns_record_type}&name=${cloudflare_dns_record_name}" \
|
||||
| jq -r '.result[0].id//empty')
|
||||
if [ -z ${cloudflare_dns_record_id} ] && curl \
|
||||
--silent \
|
||||
--request POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Authorization: Bearer ${cloudflare_api_token}" \
|
||||
--data "{\"type\":\"${cloudflare_dns_record_type}\",\"name\":\"${cloudflare_dns_record_name}\",\"content\":\"${cloudflare_dns_record_content}\",\"ttl\":1,\"proxied\":false}" \
|
||||
--url "https://api.cloudflare.com/client/v4/zones/${cloudflare_dns_zone_id}/dns_records"; then
|
||||
echo "${cloudflare_dns_record_name} ${cloudflare_dns_record_type} record created with content: ${cloudflare_dns_record_content} in zone: ${cloudflare_dns_zone_name} (${cloudflare_dns_zone_id}), record: ${cloudflare_dns_record_name} (${cloudflare_dns_record_id})"
|
||||
elif curl \
|
||||
--silent \
|
||||
--request PUT \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Authorization: Bearer ${cloudflare_api_token}" \
|
||||
--data "{\"type\":\"${cloudflare_dns_record_type}\",\"name\":\"${cloudflare_dns_record_name}\",\"content\":\"${cloudflare_dns_record_content}\",\"ttl\":1,\"proxied\":false}" \
|
||||
--url "https://api.cloudflare.com/client/v4/zones/${cloudflare_dns_zone_id}/dns_records/${cloudflare_dns_record_id}"; then
|
||||
echo "${cloudflare_dns_record_name} ${cloudflare_dns_record_type} record updated with content: ${cloudflare_dns_record_content} in zone: ${cloudflare_dns_zone_name} (${cloudflare_dns_zone_id}), record: ${cloudflare_dns_record_name} (${cloudflare_dns_record_id})"
|
||||
fi
|
||||
48
script/setup/nginx.sh
Executable file
48
script/setup/nginx.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
script_dir="$(dirname "$0")"
|
||||
|
||||
nginx_conf_local_path="${script_dir}/../../asset/nginx/rpm.lair.cafe.conf"
|
||||
nginx_conf_remote_path="/etc/nginx/sites-available/rpm.lair.cafe.conf"
|
||||
nginx_host=oolon
|
||||
if [ ! -s ~/.ssh/id_gitea_ci.pub ]; then
|
||||
echo "gitea_ci ssh key not found in ~/.ssh/id_gitea_ci.pub"
|
||||
exit 1
|
||||
fi
|
||||
gitea_ssh_key=$(cat ~/.ssh/id_gitea_ci.pub)
|
||||
|
||||
if rsync \
|
||||
--archive \
|
||||
--compress \
|
||||
--verbose \
|
||||
${nginx_conf_local_path} \
|
||||
${nginx_host}:${nginx_conf_remote_path}; then
|
||||
echo "sync'd ${nginx_conf_local_path} to ${nginx_host}:${nginx_conf_remote_path}"
|
||||
else
|
||||
echo "failed to sync ${nginx_conf_local_path} to ${nginx_host}:${nginx_conf_remote_path}"
|
||||
exit 1
|
||||
fi
|
||||
if ssh ${nginx_host} "id gitea_ci &> /dev/null || sudo useradd --system --create-home --home-dir /var/lib/gitea_ci gitea_ci"; then
|
||||
echo "gitea_ci user created or observed on ${nginx_host}"
|
||||
if ssh ${nginx_host} "sudo --user gitea_ci install --directory --mode 0700 /var/lib/gitea_ci/.ssh && echo '${gitea_ssh_key}' | sudo --user gitea_ci install --mode 0600 /dev/stdin /var/lib/gitea_ci/.ssh/authorized_keys"; then
|
||||
echo "gitea_ci ssh key installed on ${nginx_host}"
|
||||
else
|
||||
echo "failed to install gitea_ci ssh key on ${nginx_host}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "failed to create or observe gitea_ci user on ${nginx_host}"
|
||||
exit 1
|
||||
fi
|
||||
if ssh ${nginx_host} "sudo install --directory /var/www/rpm && sudo setfacl -R -m u:gitea_ci:rwx /var/www/rpm/ && sudo chcon -Rt httpd_sys_content_t /var/www/rpm/"; then
|
||||
echo "rpm repo directory created and permissions set on ${nginx_host}"
|
||||
else
|
||||
echo "failed to create rpm repo directory on ${nginx_host}"
|
||||
exit 1
|
||||
fi
|
||||
if ssh ${nginx_host} "sudo ln -sf ${nginx_conf_remote_path} ${nginx_conf_remote_path/available/enabled} && sudo nginx -t ${nginx_conf_remote_path} && sudo systemctl reload nginx"; then
|
||||
echo "nginx config reload on ${nginx_host} successful"
|
||||
else
|
||||
echo "nginx config reload on ${nginx_host} failed"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user