Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Package workflow builds and smoke-tests the installable tarball.
name: Package

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Smoke-test package
run: make package-smoke GOOS=linux GOARCH=amd64
- name: Upload package artifact
uses: actions/upload-artifact@v4
with:
name: devrail-router-linux-amd64
path: dist/*.tar.gz
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ override.tf.json

# --- DevRail ---
.devrail-output/
bin/

# --- Secrets ---
.env
Expand Down
52 changes: 51 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
DEVRAIL_IMAGE ?= ghcr.io/devrail-dev/dev-toolchain:v1
DEVRAIL_FAIL_FAST ?= 0
DEVRAIL_LOG_FORMAT ?= json
VERSION ?= 0.1.0-dev
GOOS ?= $(shell go env GOOS 2>/dev/null || echo linux)
GOARCH ?= $(shell go env GOARCH 2>/dev/null || echo amd64)
DIST_DIR ?= dist
BIN_DIR ?= bin
PACKAGE_NAME := devrail-router_$(VERSION)_$(GOOS)_$(GOARCH)

DOCKER_RUN := docker run --rm \
-v "$$(pwd):/workspace" \
Expand Down Expand Up @@ -45,7 +51,7 @@ HAS_RUST := $(filter rust,$(LANGUAGES))
# ---------------------------------------------------------------------------
# .PHONY declarations
# ---------------------------------------------------------------------------
.PHONY: help lint format fix test security scan docs changelog check install-hooks init
.PHONY: help build clean lint format fix package package-smoke test security scan docs changelog check install-hooks init
.PHONY: _lint _format _fix _test _security _scan _docs _changelog _check _check-config _init

# ===========================================================================
Expand All @@ -58,12 +64,23 @@ help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

build: ## Build the devrail-router binary for GOOS/GOARCH
@mkdir -p "$(BIN_DIR)"
CGO_ENABLED=0 GOOS="$(GOOS)" GOARCH="$(GOARCH)" go build \
-trimpath \
-ldflags "-s -w -X main.version=$(VERSION)" \
-o "$(BIN_DIR)/devrail-router" \
./cmd/devrail-router

changelog: ## Generate CHANGELOG.md from conventional commits
$(DOCKER_RUN) make _changelog

check: ## Run all checks (lint, format, test, security, scan, docs)
$(DOCKER_RUN) make _check

clean: ## Remove build and package outputs
rm -rf "$(BIN_DIR)" "$(DIST_DIR)"

docs: ## Generate documentation
$(DOCKER_RUN) make _docs

Expand Down Expand Up @@ -101,6 +118,39 @@ install-hooks: ## Install pre-commit hooks
lint: ## Run all linters
$(DOCKER_RUN) make _lint

package: build ## Build a Linux/macOS tarball package
@rm -rf "$(DIST_DIR)/$(PACKAGE_NAME)"
@mkdir -p "$(DIST_DIR)/$(PACKAGE_NAME)"
cp "$(BIN_DIR)/devrail-router" "$(DIST_DIR)/$(PACKAGE_NAME)/devrail-router"
cp LICENSE README.md CHANGELOG.md "$(DIST_DIR)/$(PACKAGE_NAME)/"
mkdir -p "$(DIST_DIR)/$(PACKAGE_NAME)/configs" \
"$(DIST_DIR)/$(PACKAGE_NAME)/docs" \
"$(DIST_DIR)/$(PACKAGE_NAME)/packaging/systemd" \
"$(DIST_DIR)/$(PACKAGE_NAME)/packaging/linux"
cp configs/router.example.yaml "$(DIST_DIR)/$(PACKAGE_NAME)/configs/"
cp docs/architecture.md docs/packaging.md "$(DIST_DIR)/$(PACKAGE_NAME)/docs/"
cp packaging/systemd/devrail-router.service "$(DIST_DIR)/$(PACKAGE_NAME)/packaging/systemd/"
cp packaging/linux/install.sh "$(DIST_DIR)/$(PACKAGE_NAME)/packaging/linux/"
chmod 0755 "$(DIST_DIR)/$(PACKAGE_NAME)/devrail-router" \
"$(DIST_DIR)/$(PACKAGE_NAME)/packaging/linux/install.sh"
(cd "$(DIST_DIR)" && tar -czf "$(PACKAGE_NAME).tar.gz" "$(PACKAGE_NAME)")

package-smoke: package ## Build and smoke-test the tarball package
@set -e; \
tmp_dir=$$(mktemp -d); \
trap 'rm -rf "$$tmp_dir"' EXIT; \
tar -xzf "$(DIST_DIR)/$(PACKAGE_NAME).tar.gz" -C "$$tmp_dir"; \
host_goos=$$(go env GOHOSTOS); \
host_goarch=$$(go env GOHOSTARCH); \
if [ "$(GOOS)" = "$$host_goos" ] && [ "$(GOARCH)" = "$$host_goarch" ]; then \
"$$tmp_dir/$(PACKAGE_NAME)/devrail-router" version; \
"$$tmp_dir/$(PACKAGE_NAME)/devrail-router" check -config "$$tmp_dir/$(PACKAGE_NAME)/configs/router.example.yaml"; \
else \
echo "skipping binary execution for cross-built package $(GOOS)/$(GOARCH) on $$host_goos/$$host_goarch"; \
fi; \
bash -n "$$tmp_dir/$(PACKAGE_NAME)/packaging/linux/install.sh"; \
test -f "$$tmp_dir/$(PACKAGE_NAME)/packaging/systemd/devrail-router.service"

scan: ## Run universal scanners (trivy, gitleaks)
$(DOCKER_RUN) make _scan

Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ This repository is in early foundation work. The current service supports:
- OpenAI-compatible `/v1/*` request proxying
- model alias rewriting
- YAML configuration
- Linux/systemd packaging notes
- Linux tarball packaging
- Linux/systemd install script and unit

Routing policy, auth, telemetry, LM Studio lifecycle integration, and Omarchy
integration are planned next.
Expand Down Expand Up @@ -61,6 +62,15 @@ curl http://127.0.0.1:8080/v1/chat/completions \
}'
```

Build and smoke-test an installable Linux package:

```sh
make package-smoke GOOS=linux GOARCH=amd64
```

The package is written to `dist/` and includes the binary, example config,
systemd unit, and Linux installer.

## Configuration

See `configs/router.example.yaml`.
Expand Down
2 changes: 1 addition & 1 deletion cmd/devrail-router/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/devrail-dev/devrail-router/internal/server"
)

const version = "0.1.0-dev"
var version = "0.1.0-dev"

func main() {
os.Exit(run(os.Args[1:]))
Expand Down
45 changes: 45 additions & 0 deletions docs/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,51 @@ The primary Linux installation target is:

The systemd unit lives at `packaging/systemd/devrail-router.service`.

Build a tarball for the current platform:

```sh
make package
```

Build a Linux AMD64 tarball and run package smoke checks:

```sh
make package-smoke GOOS=linux GOARCH=amd64
```

The generated archive is written to `dist/` with this layout:

```text
devrail-router_<version>_<os>_<arch>/
devrail-router
configs/router.example.yaml
packaging/linux/install.sh
packaging/systemd/devrail-router.service
docs/
README.md
CHANGELOG.md
LICENSE
```

Install from an unpacked tarball:

```sh
sudo ./packaging/linux/install.sh
```

The installer creates the `devrail-router` service user and group when missing,
installs the binary, installs a default config only if `/etc/devrail/router.yaml`
does not already exist, installs the systemd unit, reloads systemd, and enables
the service. It does not start the service unless `START_SERVICE=1` is set.

Useful installer flags:

```sh
DRY_RUN=1 ./packaging/linux/install.sh
FORCE_CONFIG=1 sudo ./packaging/linux/install.sh
START_SERVICE=1 sudo ./packaging/linux/install.sh
```

## Container Image

A container image is useful for proxy-only deployments and CI smoke tests. It is
Expand Down
84 changes: 84 additions & 0 deletions packaging/linux/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
set -euo pipefail

SERVICE_NAME="devrail-router"
SERVICE_USER="${SERVICE_USER:-devrail-router}"
SERVICE_GROUP="${SERVICE_GROUP:-devrail-router}"
PREFIX="${PREFIX:-/usr/local}"
BIN_DIR="${BIN_DIR:-${PREFIX}/bin}"
CONFIG_DIR="${CONFIG_DIR:-/etc/devrail}"
STATE_DIR="${STATE_DIR:-/var/lib/devrail-router}"
SYSTEMD_DIR="${SYSTEMD_DIR:-/etc/systemd/system}"
ENABLE_SERVICE="${ENABLE_SERVICE:-1}"
START_SERVICE="${START_SERVICE:-0}"
FORCE_CONFIG="${FORCE_CONFIG:-0}"
DRY_RUN="${DRY_RUN:-0}"

script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
package_root="$(cd -- "${script_dir}/../.." && pwd)"
binary_src="${DEVRAIL_ROUTER_BINARY:-${package_root}/devrail-router}"
config_src="${DEVRAIL_ROUTER_CONFIG:-${package_root}/configs/router.example.yaml}"
unit_src="${DEVRAIL_ROUTER_SYSTEMD_UNIT:-${package_root}/packaging/systemd/devrail-router.service}"

if [[ $EUID -eq 0 ]]; then
sudo_cmd=()
else
sudo_cmd=(sudo)
fi

run() {
printf '+ %q' "$@"
printf '\n'
if [[ "${DRY_RUN}" == "1" ]]; then
return 0
fi
"${sudo_cmd[@]}" "$@"
}

need_file() {
local path="$1"
if [[ ! -f "${path}" ]]; then
echo "missing required file: ${path}" >&2
exit 1
fi
}

need_file "${binary_src}"
need_file "${config_src}"
need_file "${unit_src}"

if ! getent group "${SERVICE_GROUP}" >/dev/null 2>&1; then
run groupadd --system "${SERVICE_GROUP}"
fi

if ! id -u "${SERVICE_USER}" >/dev/null 2>&1; then
run useradd --system --gid "${SERVICE_GROUP}" --home-dir "${STATE_DIR}" --shell /usr/sbin/nologin "${SERVICE_USER}"
fi

run install -d -m 0755 "${BIN_DIR}" "${CONFIG_DIR}" "${SYSTEMD_DIR}"
run install -d -m 0750 -o "${SERVICE_USER}" -g "${SERVICE_GROUP}" "${STATE_DIR}"
run install -m 0755 "${binary_src}" "${BIN_DIR}/devrail-router"

if [[ ! -f "${CONFIG_DIR}/router.yaml" || "${FORCE_CONFIG}" == "1" ]]; then
run install -m 0640 -o root -g "${SERVICE_GROUP}" "${config_src}" "${CONFIG_DIR}/router.yaml"
else
echo "keeping existing ${CONFIG_DIR}/router.yaml"
fi

run install -m 0644 "${unit_src}" "${SYSTEMD_DIR}/${SERVICE_NAME}.service"

if command -v systemctl >/dev/null 2>&1; then
run systemctl daemon-reload
if [[ "${ENABLE_SERVICE}" == "1" ]]; then
run systemctl enable "${SERVICE_NAME}.service"
fi
if [[ "${START_SERVICE}" == "1" ]]; then
run systemctl restart "${SERVICE_NAME}.service"
fi
else
echo "systemctl not found; installed files but did not enable service"
fi

echo "DevRail Router installed."
echo "Config: ${CONFIG_DIR}/router.yaml"
echo "Binary: ${BIN_DIR}/devrail-router"
1 change: 1 addition & 0 deletions packaging/systemd/devrail-router.service
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Group=devrail-router
ExecStart=/usr/local/bin/devrail-router serve -config /etc/devrail/router.yaml
Restart=on-failure
RestartSec=5s
StateDirectory=devrail-router
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
Expand Down
Loading