From f88b8e099b3d438fbcccac93a490746e60ff3a0e Mon Sep 17 00:00:00 2001 From: Daniel Arroyo Date: Thu, 30 Jul 2026 12:42:10 -0400 Subject: [PATCH] Add .deb packaging, GitHub Actions CI/CD, and release workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Makefile: add 'deb' and 'release' targets; fix migrate/create syntax - scripts/build-deb.sh: build .deb with systemd unit, postinst/prerm/postrm - .github/workflows/ci.yml: go build, test, lint on PR/push to main - .github/workflows/release.yml: multi-platform build (linux/darwin/windows × amd64/arm64), .deb for Linux, tarballs, draft GitHub Release on tag v* - README.md: add CI/Release badges, Install section with .deb and binary release instructions --- .github/workflows/ci.yml | 52 ++++++++++++++ .github/workflows/release.yml | 126 ++++++++++++++++++++++++++++++++++ Makefile | 24 +++++-- README.md | 60 ++++++++++++++-- scripts/build-deb.sh | 116 +++++++++++++++++++++++++++++++ 5 files changed, 367 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100755 scripts/build-deb.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..aba0744 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache: true + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: web/frontend/package-lock.json + + - name: Install frontend deps + run: cd web/frontend && npm ci + + - name: Build frontend + run: cd web/frontend && npm run build + + - name: Download Go deps + run: go mod download + + - name: Build + run: go build ./... + + - name: Vet + run: go vet ./... + + - name: Test + run: go test -race -coverprofile=coverage.out ./... + + - name: Lint + run: | + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin + $(go env GOPATH)/bin/golangci-lint run ./... + + - name: Upload coverage + uses: actions/upload-artifact@v4 + with: + name: coverage + path: coverage.out diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..77ea297 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,126 @@ +name: Release + +on: + push: + tags: ['v*'] + +concurrency: + group: release + cancel-in-progress: false + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - goos: linux + goarch: amd64 + deb: true + - goos: linux + goarch: arm64 + deb: true + - goos: darwin + goarch: amd64 + deb: false + - goos: darwin + goarch: arm64 + deb: false + - goos: windows + goarch: amd64 + deb: false + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache: true + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: web/frontend/package-lock.json + + - name: Install frontend deps + run: cd web/frontend && npm ci + + - name: Build frontend + run: cd web/frontend && npm run build + + - name: Build binary + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: 0 + run: | + mkdir -p dist + VERSION="${GITHUB_REF_NAME#v}" + go build -ldflags="-s -w -X main.Version=${VERSION}" \ + -o "dist/llamalink-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.goos == 'windows' && '.exe' || '' }}" \ + ./cmd/llamalink + + - name: Build .deb (Linux only) + if: matrix.deb + env: + DEB_ARCH: ${{ matrix.goarch }} + run: | + sudo apt-get update -qq && sudo apt-get install -y -qq dpkg fakeroot + ./scripts/build-deb.sh "${GITHUB_REF_NAME#v}" "${{ matrix.goarch }}" + + - name: Package artifacts + run: | + cd dist + EXT="" + if [ "${{ matrix.goos }}" = "windows" ]; then EXT=".zip"; fi + if [ "${{ matrix.goos }}" = "windows" ]; then + zip "llamalink-${{ matrix.goos }}-${{ matrix.goarch }}.zip" \ + "llamalink-${{ matrix.goos }}-${{ matrix.goarch }}.exe" + else + tar -czf "llamalink-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz" \ + "llamalink-${{ matrix.goos }}-${{ matrix.goarch }}" + if [ "${{ matrix.deb }}" = "true" ]; then + cp *.deb "llamalink-${{ matrix.goos }}-${{ matrix.goarch }}.deb" + fi + fi + + - uses: actions/upload-artifact@v4 + with: + name: llamalink-${{ matrix.goos }}-${{ matrix.goarch }} + path: dist/* + + release: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: + path: artifacts + pattern: llamalink-* + + - name: Merge artifacts + run: | + mkdir -p release + cp artifacts/*/* release/ + + - name: List artifacts + run: ls -lh release/ + + - name: Create checksums + run: | + cd release + sha256sum * > llamalink_checksums.txt + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + name: ${{ github.ref_name }} + draft: true + prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }} + generate_release_notes: true + files: release/* diff --git a/Makefile b/Makefile index 99b1275..939be49 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build run test lint clean migrate dev prod deps fmt +.PHONY: build run test lint clean migrate dev prod deps fmt deb release # Binary name BINARY=llamalink @@ -8,7 +8,9 @@ LDFLAGS=-ldflags "-X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME)" # Directories BUILD_DIR=./bin +DIST_DIR=./dist FRONTEND_DIR=./web/frontend +DEB_ARCH=$(shell dpkg --print-architecture 2>/dev/null || echo "amd64") # Go parameters GOCMD=go @@ -60,7 +62,7 @@ deps: ## clean: Remove build artifacts clean: - rm -rf $(BUILD_DIR) + rm -rf $(BUILD_DIR) $(DIST_DIR) rm -f coverage.out rm -f *.db @@ -72,8 +74,9 @@ migrate/up: migrate/down: migrate -path internal/db/migrations -database "$(DATABASE_URL)" down -## migrate/create: Create a new migration -migrate/create NAME=add_users_table: +## migrate/create: Create a new migration (Usage: make migrate/create NAME=add_users_table) +migrate/create: + @if [ -z "$(NAME)" ]; then echo "Usage: make migrate/create NAME=add_users_table"; exit 1; fi migrate create -path internal/db/migrations -ext .sql -dir internal/db/migrations $(NAME) ## frontend/install: Install frontend dependencies @@ -99,6 +102,19 @@ docker/run: ## docker/build/run: Build and run with docker-compose docker/build/run: docker/build docker/run +## deb: Build .deb package (requires dpkg-deb, Linux only) +deb: build + @echo "Building .deb package..." + @./scripts/build-deb.sh $(VERSION) $(DEB_ARCH) + +## release: Build all release artifacts locally +release: + @echo "Building release artifacts for $(VERSION)..." + @mkdir -p $(DIST_DIR) + $(MAKE) build + $(MAKE) deb + @echo "Release artifacts in $(DIST_DIR)/" + ## help: Show this help help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/README.md b/README.md index d3671ce..8f24117 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # LlamaLink +[![CI](https://github.com/llamalink/llamalink/actions/workflows/ci.yml/badge.svg)](https://github.com/llamalink/llamalink/actions/workflows/ci) +[![Release](https://img.shields.io/github/v/release/llamalink/llamalink?include_prereleases&label=latest)](https://github.com/llamalink/llamalink/releases/latest) + **API Gateway en Go** para ejecutar modelos de lenguaje (LLMs) de forma local usando **llama.cpp**, con autenticación por API keys, rate limiting, cuotas de uso y panel de administración Vue 3. ## Características @@ -105,6 +108,46 @@ docker compose -f deploy/docker-compose.yml up -d ADMIN_TOKEN=mi-token-secreto docker compose -f deploy/docker-compose.yml up -d ``` +## Install + +### Binary releases (all platforms) + +Download pre-built binaries from [GitHub Releases](https://github.com/llamalink/llamalink/releases): + +```bash +# Linux/macOS/Windows — pick your arch +curl -L https://github.com/llamalink/llamalink/releases/latest/download/llamalink-linux-amd64.tar.gz | tar -xz +./llamalink +``` + +### .deb package (Debian/Ubuntu) + +```bash +# Download latest .deb +curl -L https://github.com/llamalink/llamalink/releases/latest/download/llamalink_VERSION_amd64.deb \ + -o llamalink.deb + +# Install +sudo dpkg -i llamalink.deb + +# Configure +sudo nano /etc/llamalink/env +sudo systemctl enable llamalink +sudo systemctl start llamalink +``` + +### Build from source + +```bash +git clone https://github.com/llamalink/llamalink.git +cd llamalink +make deps build + +# Or with .deb package +make deb +sudo dpkg -i dist/llamalink_*.deb +``` + ## Admin Panel Accede a `http://localhost:8000/admin/` para el panel Vue 3: @@ -139,14 +182,17 @@ Accede a `http://localhost:8000/admin/` para el panel Vue 3: ## Makefile ```bash -make build # Build binario -make run # Build y ejecutar -make dev # go run (desarrollo) -make test # Tests -make lint # golangci-lint -make clean # Limpiar build -make migrate/up # Correr migrations +make build # Build binario +make run # Build y ejecutar +make dev # go run (desarrollo) +make test # Tests +make lint # golangci-lint +make clean # Limpiar build +make migrate/up # Correr migrations make frontend/build # Build Vue SPA +make deb # Build .deb package (Linux, requires dpkg-deb) +make release # Build .deb + local release artifacts +make docker/build # Build imagen Docker ``` ## Arquitectura diff --git a/scripts/build-deb.sh b/scripts/build-deb.sh new file mode 100755 index 0000000..111ba35 --- /dev/null +++ b/scripts/build-deb.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# build-deb.sh — Build a .deb package for LlamaLink +set -euo pipefail + +VERSION="${1:-}" +ARCH="${2:-$(dpkg --print-architecture 2>/dev/null || echo "amd64")}" + +if [[ -z "$VERSION" ]]; then + VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev") + VERSION="${VERSION#v}" +fi + +if ! command -v dpkg-deb &>/dev/null; then + echo "Error: dpkg-deb not found. Install with: sudo apt install dpkg" >&2 + exit 1 +fi + +if [[ "$(uname)" != "Linux" ]]; then + echo "Error: .deb packages can only be built on Linux." >&2 + exit 1 +fi + +DEB_VERSION="${VERSION}-1" +BUILD_DIR="dist/deb-build" +PKG_DIR="${BUILD_DIR}/llamalink-${DEB_VERSION}_${ARCH}" + +echo "==> Building llamalink_${DEB_VERSION}_${ARCH}.deb" + +rm -rf "$BUILD_DIR" +mkdir -p "$PKG_DIR"/{DEBIAN,usr/local/bin,etc/llamalink,etc/systemd/system,opt/llamalink/data,var/log/llamalink} + +# Binary (must exist) +if [[ ! -f "bin/llamalink" ]]; then + echo "Error: bin/llamalink not found. Run 'make build' first." >&2 + exit 1 +fi +cp bin/llamalink "$PKG_DIR/usr/local/bin/" +chmod 755 "$PKG_DIR/usr/local/bin/llamalink" + +# Config template +cp .env.example "$PKG_DIR/etc/llamalink/env" +chmod 644 "$PKG_DIR/etc/llamalink/env" + +# Systemd unit +cp deploy/llamalink.service "$PKG_DIR/etc/systemd/system/llamalink.service" + +# DEBIAN/control +cat > "$PKG_DIR/DEBIAN/control" < +Description: API Gateway for local LLM inference via llama.cpp + Provides a REST API with API key authentication, rate limiting, + and multi-model hot-swap for llama.cpp server instances. +Homepage: https://github.com/llamalink/llamalink +Depends: libc6 +EOF + +# DEBIAN/conffiles +echo "/etc/llamalink/env" > "$PKG_DIR/DEBIAN/conffiles" + +# DEBIAN/postinst +cat > "$PKG_DIR/DEBIAN/postinst" <<'POSTINST' +#!/bin/sh +set -e +if [ "$1" = "configure" ]; then + if ! id llamalink > /dev/null 2>&1; then + useradd --system --no-create-home --shell /usr/sbin/nologin llamalink 2>/dev/null || true + fi + mkdir -p /opt/llamalink/data /var/log/llamalink + chown llamalink:llamalink /opt/llamalink/data /var/log/llamalink 2>/dev/null || true + if command -v systemctl >/dev/null 2>&1; then + systemctl daemon-reload 2>/dev/null || true + fi +fi +POSTINST +chmod 755 "$PKG_DIR/DEBIAN/postinst" + +# DEBIAN/prerm +cat > "$PKG_DIR/DEBIAN/prerm" <<'PRERM' +#!/bin/sh +set -e +if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then + if command -v systemctl >/dev/null 2>&1; then + systemctl stop llamalink.service 2>/dev/null || true + systemctl disable llamalink.service 2>/dev/null || true + fi +fi +PRERM +chmod 755 "$PKG_DIR/DEBIAN/prerm" + +# DEBIAN/postrm +cat > "$PKG_DIR/DEBIAN/postrm" <<'POSTRM' +#!/bin/sh +set -e +if [ "$1" = "purge" ]; then + rm -rf /opt/llamalink/data /var/log/llamalink + if id llamalink > /dev/null 2>&1; then + userdel llamalink 2>/dev/null || true + fi + if command -v systemctl >/dev/null 2>&1; then + systemctl daemon-reload 2>/dev/null || true + fi +fi +POSTRM +chmod 755 "$PKG_DIR/DEBIAN/postrm" + +# Build .deb +mkdir -p dist +dpkg-deb --build "$PKG_DIR" "dist/llamalink_${DEB_VERSION}_${ARCH}.deb" + +echo "==> Created dist/llamalink_${DEB_VERSION}_${ARCH}.deb" +rm -rf "$BUILD_DIR"