Split the packages, publish releases from Gitea Actions, release 0.1.0
release / release (push) Successful in 7m7s

make deb now builds two packages: jarl-cli (the CLI, its completion and
the ACME hooks, no scripts) and jarl (the server), which depends on
jarl-cli of the same version.

Pushing a v* tag runs .gitea/workflows/release.yml: it tests, builds the
tarballs, the CLI for Linux, macOS and Windows (make cli-dist) and the
.debs for amd64 and arm64, then scripts/gitea-release.sh creates the
Gitea release and uploads the .debs to the Debian package registry, so
servers install and upgrade with apt. The stats package's inode lookup
is split by OS so the CLI builds for Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-09 08:40:56 +00:00
1 parent 293e6ddd33
commit 4d00be53c3
16 files changed
+449 -83

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
# Builds and publishes a release when a version tag (v0.2.0, v1.0.0-rc.1)
# is pushed:
# * a Gitea release with the CHANGELOG notes, the linux tarballs, the CLI
# for Linux, macOS and Windows, the .debs and SHA256SUMS;
# * the .debs (amd64, arm64) in the owner's Debian package registry.
#
# Needs a repository secret RELEASE_TOKEN: an access token of a user who
# can write to the repository, with the write:repository and write:package
# scopes. docs/development.md ("Releasing") has the steps.
name: release
on:
push:
tags: ["v*"]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Check the tag against VERSION and CHANGELOG.md
run: |
version=$(cat VERSION)
if [ "$GITHUB_REF_NAME" != "v$version" ]; then
echo "::error::tag $GITHUB_REF_NAME does not match VERSION ($version)"
exit 1
fi
if ! grep -q "^## \[$version\]" CHANGELOG.md; then
echo "::error::CHANGELOG.md has no '## [$version]' section"
exit 1
fi
- name: Tools
run: |
missing=
for t in jq dpkg-deb curl; do command -v $t >/dev/null || missing="$missing $t"; done
if [ -n "$missing" ]; then
sudo=; [ "$(id -u)" = 0 ] || sudo=sudo
$sudo apt-get update -qq && $sudo apt-get install -y -qq jq dpkg curl
fi
- name: Test
run: make test
- name: Build
run: |
export DEB_MAINTAINER="$(git log -1 --format='%an <%ae>')"
make dist
make cli-dist
make deb DEB_ARCH=amd64
make deb DEB_ARCH=arm64
- name: Publish
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: scripts/gitea-release.sh
+20 -7
View File
@@ -6,7 +6,9 @@ All notable changes to Jarl are documented here. The format follows
## [Unreleased] ## [Unreleased]
The first version, to be released as 0.1.0. ## [0.1.0] - 2026-10-09
The first version.
### Added ### Added
@@ -68,12 +70,23 @@ The first version, to be released as 0.1.0.
`socket`. The systemd unit creates `/run/jarl` (0750). `socket`. The systemd unit creates `/run/jarl` (0750).
- Jarl is released under the MIT License (`LICENSE`), which is installed - Jarl is released under the MIT License (`LICENSE`), which is installed
with the docs and is the Debian package's `copyright` file. with the docs and is the Debian package's `copyright` file.
- A Debian package: `make deb` builds `dist/jarl_<version>_<arch>.deb` with - Debian packages: `make deb` builds, with only `dpkg-deb` (no root) and
only `dpkg-deb` (no root), for the host or any other architecture for the host or any other architecture (`DEB_ARCH=arm64`), `jarl` (the
(`DEB_ARCH=arm64`). Installing it creates the `jarl` user and directories server: jarld, its units and docs) and `jarl-cli` (the `jarl` CLI, its
and an example `/etc/jarl/jarl.yaml`; upgrades restart a running jarld and completion and the ACME hooks), which `jarl` depends on. `jarl-cli` has no
never touch `/etc/jarl/jarl.yaml`; removing it stops jarld, and even a scripts, so it can go on any machine you manage Jarl from. Installing
purge keeps Jarl's database and the files named uses. `jarl` creates the `jarl` user and directories and an example
`/etc/jarl/jarl.yaml`; upgrades restart a running jarld and never touch
`/etc/jarl/jarl.yaml`; removing it stops jarld, and even a purge keeps
Jarl's database and the files named uses.
- Releases from Gitea Actions: pushing a `v<version>` tag runs
`.gitea/workflows/release.yml`, which tests, builds and publishes a Gitea
release (the CHANGELOG section as notes; the tarballs, the CLI for Linux,
macOS and Windows, the `.deb`s and `SHA256SUMS`), and uploads the `.deb`s
for amd64 and arm64 to the Gitea Debian registry, so servers install and
upgrade Jarl with apt. `make cli-dist` builds the standalone CLI files;
`scripts/gitea-release.sh` publishes. docs/development.md has the setup
(a runner and a `RELEASE_TOKEN` secret), docs/install.md the apt source.
- Bash completion for `jarl` and `jarld`, installed by `make install` and - Bash completion for `jarl` and `jarld`, installed by `make install` and
the Debian package. Besides commands and flags, it completes server and the Debian package. Besides commands and flags, it completes server and
group names from the CLI configuration, zone, view, ACL, TSIG key and group names from the CLI configuration, zone, view, ACL, TSIG key and
+16 -3
View File
@@ -6,9 +6,12 @@
# make e2e end-to-end test against a real named (scripts/e2e.sh) # make e2e end-to-end test against a real named (scripts/e2e.sh)
# sudo make install install binaries, bash completion, systemd units, # sudo make install install binaries, bash completion, systemd units,
# sysusers/tmpfiles files and an example /etc/jarl/jarl.yaml # sysusers/tmpfiles files and an example /etc/jarl/jarl.yaml
# make deb a Debian package in dist/ (DEB_ARCH=arm64 for another # make deb Debian packages in dist/ (DEB_ARCH=arm64 for another
# architecture); install it with 'sudo apt install ./dist/jarl_*.deb' # architecture): jarl (the server) and jarl-cli (the CLI,
# which jarl depends on); 'sudo apt install ./dist/jarl*_amd64.deb'
# make dist static binaries for linux/amd64 and linux/arm64 in dist/ # make dist static binaries for linux/amd64 and linux/arm64 in dist/
# make cli-dist the CLI alone for Linux, macOS and Windows in dist/
# (jarl-cli-<version>-<os>-<arch>[.exe])
VERSION ?= $(shell cat VERSION 2>/dev/null || echo 0.0.0-dev) VERSION ?= $(shell cat VERSION 2>/dev/null || echo 0.0.0-dev)
LDFLAGS := -s -w -X jarl/internal/version.Version=$(VERSION) LDFLAGS := -s -w -X jarl/internal/version.Version=$(VERSION)
@@ -34,7 +37,7 @@ JARL_BIN ?= bin/jarl
DEB_ARCH ?= $(shell dpkg --print-architecture 2>/dev/null || go env GOARCH) DEB_ARCH ?= $(shell dpkg --print-architecture 2>/dev/null || go env GOARCH)
DEB_VERSION ?= $(subst -,~,$(VERSION)) DEB_VERSION ?= $(subst -,~,$(VERSION))
.PHONY: all build completion test vet e2e install install-files uninstall deb dist clean .PHONY: all build completion test vet e2e install install-files uninstall deb dist cli-dist clean
all: build all: build
@@ -114,5 +117,15 @@ dist: build
tar -C dist -czf dist/jarl-$(VERSION)-linux-$$arch.tar.gz jarl-$(VERSION)-linux-$$arch || exit 1; \ tar -C dist -czf dist/jarl-$(VERSION)-linux-$$arch.tar.gz jarl-$(VERSION)-linux-$$arch || exit 1; \
done done
# The platforms cli-dist builds the CLI for.
CLI_PLATFORMS ?= linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64
cli-dist:
@mkdir -p dist
for p in $(CLI_PLATFORMS); do \
os=$${p%/*} arch=$${p#*/} ext=; [ $$os = windows ] && ext=.exe; \
GOOS=$$os GOARCH=$$arch go build $(GOFLAGS) -ldflags '$(LDFLAGS)' -o dist/jarl-cli-$(VERSION)-$$os-$$arch$$ext ./cmd/jarl || exit 1; \
done
clean: clean:
rm -rf bin dist rm -rf bin dist
+5 -3
View File
@@ -67,8 +67,9 @@ error: www.adbraun.lol has a CNAME, so it can't have any other records (found A)
fallback. fallback.
* One static Go binary for the daemon (web UI embedded, SQLite built in) and * One static Go binary for the daemon (web UI embedded, SQLite built in) and
one for the CLI, run by a sandboxed systemd service as an unprivileged one for the CLI, run by a sandboxed systemd service as an unprivileged
user. `make deb` builds a Debian package, and both come with bash user. Releases come as Debian packages in an apt repository (server and
completion. CLI separately) and as standalone CLI downloads for Linux, macOS and
Windows; both binaries have bash completion.
## Quick start ## Quick start
@@ -76,7 +77,8 @@ On a Debian/Ubuntu BIND 9.20 server ([install.md](docs/install.md) has the
details): details):
```sh ```sh
make deb && sudo apt install ./dist/jarl_*.deb # or: make && sudo make install, see install.md sudo apt install jarl # from the Gitea package registry (install.md shows adding it)
# or build it: make deb && sudo apt install ./dist/jarl*_amd64.deb, or make && sudo make install
sudo -u jarl jarld init # database + first administrator sudo -u jarl jarld init # database + first administrator
sudo -u jarl jarld import # read the current named.conf and zones sudo -u jarl jarld import # read the current named.conf and zones
sudo jarld adopt # switch named over (keeps a backup, rolls back on failure) sudo jarld adopt # switch named over (keeps a backup, rolls back on failure)
+18
View File
@@ -0,0 +1,18 @@
Package: jarl-cli
Version: @VERSION@
Architecture: @ARCH@
Maintainer: @MAINTAINER@
Installed-Size: @SIZE@
Replaces: jarl
Section: net
Priority: optional
Description: command line client for Jarl, the BIND 9 manager
jarl is the command line client for Jarl servers (jarld): zones,
records, views, ACLs, TSIG keys and options, changesets, users and
tokens, and query statistics, on one server or a group of them.
It talks to jarld's REST API with an API token, or, on the server
itself, to jarld's local socket.
.
Install this package alone to manage Jarl servers from another machine;
the jarl package (the server) depends on it. It also has the ACME DNS-01
hooks for acme.sh and lego in /usr/share/doc/jarl-cli/contrib.
@@ -3,7 +3,7 @@ Version: @VERSION@
Architecture: @ARCH@ Architecture: @ARCH@
Maintainer: @MAINTAINER@ Maintainer: @MAINTAINER@
Installed-Size: @SIZE@ Installed-Size: @SIZE@
Depends: systemd Depends: systemd, jarl-cli (= @VERSION@)
Recommends: bind9 (>= 1:9.20) Recommends: bind9 (>= 1:9.20)
Section: net Section: net
Priority: optional Priority: optional
@@ -14,7 +14,8 @@ Description: web UI, REST API and CLI for managing a BIND 9 server
loaded into named as one changeset that can be reverted. Users and API loaded into named as one changeset that can be reverted. Users and API
tokens have fine-grained permissions, and every change is audited. tokens have fine-grained permissions, and every change is audited.
. .
The package installs jarld (the daemon, as the systemd service jarl) and This package is the server: jarld (the daemon, as the systemd service
jarl (the CLI), with bash completion for both. jarld isn't started on a jarl) with its daily backup timer. The jarl CLI comes from jarl-cli,
first install: see /usr/share/doc/jarl/docs/install.md to create the which can also be installed on its own on other machines. jarld isn't
database and adopt the BIND configuration. started on a first install: see /usr/share/doc/jarl/docs/install.md to
create the database and adopt the BIND configuration.
+2 -2
View File
@@ -68,7 +68,7 @@ configuration, so `certbot renew` works unattended.
Install the DNS API hook that comes with Jarl: Install the DNS API hook that comes with Jarl:
```sh ```sh
cp contrib/acme.sh/dns_jarl.sh ~/.acme.sh/dnsapi/ # or from /usr/local/share/doc/jarl/contrib/ cp contrib/acme.sh/dns_jarl.sh ~/.acme.sh/dnsapi/ # or from /usr/share/doc/jarl-cli/contrib/ (jarl-cli package)
export JARL_SERVER=https://ns1.adbraun.lol:8053 export JARL_SERVER=https://ns1.adbraun.lol:8053
export JARL_TOKEN=$(cat /etc/jarl/certbot.token) export JARL_TOKEN=$(cat /etc/jarl/certbot.token)
acme.sh --issue --dns dns_jarl -d adbraun.lol -d '*.adbraun.lol' acme.sh --issue --dns dns_jarl -d adbraun.lol -d '*.adbraun.lol'
@@ -86,7 +86,7 @@ Jarl ships one, `contrib/lego/jarl-lego.sh`, which calls the `jarl` CLI
(so jarl must be installed on the same host): (so jarl must be installed on the same host):
```sh ```sh
sudo install -m 0755 contrib/lego/jarl-lego.sh /usr/local/bin/jarl-lego # or from /usr/local/share/doc/jarl/contrib/ sudo install -m 0755 contrib/lego/jarl-lego.sh /usr/local/bin/jarl-lego # or from /usr/share/doc/jarl-cli/contrib/ (jarl-cli package)
export EXEC_PATH=/usr/local/bin/jarl-lego export EXEC_PATH=/usr/local/bin/jarl-lego
export JARL_SERVER=https://ns1.adbraun.lol:8053 export JARL_SERVER=https://ns1.adbraun.lol:8053
export JARL_TOKEN=@/etc/jarl/certbot.token export JARL_TOKEN=@/etc/jarl/certbot.token
+4
View File
@@ -25,6 +25,10 @@ Besides commands and flags, Tab completes:
## Setup ## Setup
On the DNS server the CLI comes with Jarl. To use it from another machine,
install just the `jarl-cli` package there, or copy the static `jarl` binary
([install.md](install.md#just-the-cli-on-another-machine)).
```sh ```sh
jarl config set server https://ns1.adbraun.lol:8053 jarl config set server https://ns1.adbraun.lol:8053
jarl config set token # paste the token at the prompt jarl config set token # paste the token at the prompt
+38 -5
View File
@@ -49,7 +49,7 @@ make # bin/jarld, bin/jarl (CGO_ENABLED=0, version from ./VERSION),
# and bin/completion/{jarl,jarld} (bash completion) # and bin/completion/{jarl,jarld} (bash completion)
make test # go vet, gofmt check, go test ./... make test # go vet, gofmt check, go test ./...
make e2e # scripts/e2e.sh make e2e # scripts/e2e.sh
make deb # dist/jarl_<version>_<arch>.deb (DEB_ARCH=arm64 etc. cross-builds) make deb # dist/jarl_<version>_<arch>.deb (server) and jarl-cli_<version>_<arch>.deb (DEB_ARCH=arm64 etc. cross-builds)
``` ```
### The Debian package ### The Debian package
@@ -70,7 +70,7 @@ To try the package without installing it on your machine:
```sh ```sh
docker run --rm -it -v "$PWD/dist:/pkg:ro" debian:trixie \ docker run --rm -it -v "$PWD/dist:/pkg:ro" debian:trixie \
sh -c 'apt-get update && apt-get install -y /pkg/jarl_*_amd64.deb && bash' sh -c 'apt-get update && apt-get install -y /pkg/jarl_*_amd64.deb /pkg/jarl-cli_*_amd64.deb && bash'
``` ```
### Shell completion ### Shell completion
@@ -154,7 +154,40 @@ files are embedded at build time, so rebuild jarld after changes.
## Releasing ## Releasing
1. Move the "Unreleased" section of `CHANGELOG.md` under the new version 1. Move the "Unreleased" section of `CHANGELOG.md` under
and date. `## [<version>] - <date>`.
2. Put the version in `VERSION`. 2. Put the version in `VERSION`.
3. `make test && JARL_BIND_ROOT=... make e2e && make dist`. 3. `make test && JARL_BIND_ROOT=... make e2e` (CI runs only `make test`;
it has no BIND).
4. Commit, tag and push: `git tag v<version> && git push origin main v<version>`.
The tag starts `.gitea/workflows/release.yml`, which checks that the tag
matches `VERSION` and that `CHANGELOG.md` has the section, runs `make
test`, builds everything (`make dist`, `make cli-dist`, `make deb` for
amd64 and arm64) and runs `scripts/gitea-release.sh`. That creates the
Gitea release (the CHANGELOG section as notes; the tarballs, the CLI for
Linux, macOS and Windows, the `.deb`s and `SHA256SUMS` attached) and
uploads the `.deb`s to the owner's Debian registry, distribution `stable`,
component `main` ([install.md](install.md#from-the-package-registry)). A
version with a hyphen (`1.0.0-rc.1`) becomes a pre-release; its packages
get version `1.0.0~rc.1`, which apt sorts before `1.0.0`.
Pushing the same tag again (`git tag -f`, `git push -f`) re-runs it, and
the script replaces the attachments and packages, so a failed release can
simply be retried.
Once, on Gitea:
* Actions on for the repository (Settings → Repository → Actions), and a
runner with the `ubuntu-latest` label: act_runner's default, which runs
jobs in `docker.gitea.com/runner-images:ubuntu-latest`. The job
downloads Go itself (`actions/setup-go`, from `go.mod`), so the runner
needs internet access, or a mirror of the actions and Go.
* A repository secret `RELEASE_TOKEN` (Settings → Actions → Secrets): an
access token (User settings → Applications) with the `write:repository`
and `write:package` scopes, from a user who can write to the repository.
The job's automatic token can't be relied on to publish packages.
`scripts/gitea-release.sh` also runs by hand after the same builds, with
`GITEA_URL`, `GITEA_REPO` (`owner/name`) and `GITEA_TOKEN` set; it needs
`curl`, `jq` and `dpkg-deb`.
+84 -12
View File
@@ -12,6 +12,12 @@ to load Jarl's configuration the old one is put back automatically.
## 1. Build ## 1. Build
Each release is already built: the Debian packages are in the Gitea
package registry ([below](#from-the-package-registry)), and the release
page (https://gitea.adbraun.lol/adam/jarl/releases) has the packages, the
linux tarballs, and the CLI for Linux, macOS and Windows. Build it yourself
for an unreleased version.
Jarl is two static binaries with no runtime dependencies (SQLite is Jarl is two static binaries with no runtime dependencies (SQLite is
compiled in). Building needs Go 1.27 or newer, on any machine: compiled in). Building needs Go 1.27 or newer, on any machine:
@@ -30,23 +36,62 @@ package (below) and copy that.
There are two ways: a Debian package, which is easiest to upgrade and There are two ways: a Debian package, which is easiest to upgrade and
remove, or `make install`. remove, or `make install`.
### With the Debian package ### With the Debian packages
There are two packages:
| Package | Holds | Install it |
|---------|-------|-----------|
| `jarl` | the server: `jarld`, its systemd units, the docs | on the DNS server |
| `jarl-cli` | the `jarl` CLI, its bash completion, the ACME hooks | wherever you manage Jarl from; `jarl` depends on it |
#### From the package registry
Every release is published to Gitea's Debian registry (amd64 and arm64).
Add it to apt once, and apt then finds `jarl-cli` by itself and
`apt upgrade` brings new releases:
```sh ```sh
make deb # dist/jarl_0.1.0_amd64.deb sudo install -d /etc/apt/keyrings
make deb DEB_ARCH=arm64 # for another architecture (also armhf, i386, ...) sudo curl -fsSL https://gitea.adbraun.lol/api/packages/adam/debian/repository.key -o /etc/apt/keyrings/jarl.asc
sudo apt install ./dist/jarl_0.1.0_amd64.deb echo "deb [signed-by=/etc/apt/keyrings/jarl.asc] https://gitea.adbraun.lol/api/packages/adam/debian stable main" \
| sudo tee /etc/apt/sources.list.d/jarl.list
sudo apt update
sudo apt install jarl
``` ```
The packages are as visible as the Gitea account that owns them, not the
repository: with a public account, apt needs no login even if the
repository is private. For a private or limited account, give apt a token
with the `read:package` scope in `/etc/apt/auth.conf.d/jarl.conf` (mode
0600):
```
machine gitea.adbraun.lol/api/packages/adam/debian
login adam
password <token>
```
#### From files
Download the two `.deb` files from the release page, or build them:
```sh
make deb # dist/jarl_0.1.0_amd64.deb and dist/jarl-cli_0.1.0_amd64.deb
make deb DEB_ARCH=arm64 # for another architecture (also armhf, i386, ...)
sudo apt install ./dist/jarl_0.1.0_amd64.deb ./dist/jarl-cli_0.1.0_amd64.deb
```
Give apt both files: `jarl` needs `jarl-cli` of the same version.
`make deb` needs only `dpkg-deb` (no root, no debhelper) and cross-builds `make deb` needs only `dpkg-deb` (no root, no debhelper) and cross-builds
for any architecture Go supports. apt also installs `bind9` (recommended) for any architecture Go supports. apt also installs `bind9` (recommended)
if it's missing. Installing the package: if it's missing. Installing the server package:
* puts the binaries in `/usr/bin`, bash completion in * puts `jarld` in `/usr/bin`, its bash completion in
`/usr/share/bash-completion/completions`, the systemd units in `/usr/share/bash-completion/completions`, the systemd units in
`/usr/lib/systemd/system` (`jarl.service`, and `jarl-backup.service` `/usr/lib/systemd/system` (`jarl.service`, and `jarl-backup.service`
with its daily `jarl-backup.timer`), the sysusers and tmpfiles files with its daily `jarl-backup.timer`), the sysusers and tmpfiles files
in `/usr/lib`, and the docs and contrib files in `/usr/share/doc/jarl`; in `/usr/lib`, and the docs in `/usr/share/doc/jarl`;
* creates the `jarl` user (in group `bind`) and Jarl's directories, and * creates the `jarl` user (in group `bind`) and Jarl's directories, and
writes `/etc/jarl/jarl.yaml` from the example on the first install. No writes `/etc/jarl/jarl.yaml` from the example on the first install. No
file in `/etc` belongs to the package, so your configuration is never file in `/etc` belongs to the package, so your configuration is never
@@ -58,17 +103,44 @@ if it's missing. Installing the package:
* doesn't start jarld: continue with [3. Configure](#3-configure), then * doesn't start jarld: continue with [3. Configure](#3-configure), then
`systemctl enable --now jarl` in step 8. `systemctl enable --now jarl` in step 8.
**Upgrading**: build and install the newer package the same way. A running `jarl-cli` installs `/usr/bin/jarl`, its completion, and
jarld is restarted, and upgrades its database as it starts. `/usr/share/doc/jarl-cli` (the CLI, ACME and permissions docs, and the
acme.sh and lego hooks in `contrib/`).
**Upgrading**: `sudo apt update && sudo apt upgrade` with the registry, or
install the newer files the same way. A running
jarld is restarted, and upgrades its database as it starts. Up to 0.1.0
everything was one `jarl` package; installing the two new ones over it
moves `/usr/bin/jarl` to `jarl-cli` by itself.
**Removing** (`sudo apt remove jarl`) stops jarld and the backup timer and **Removing** (`sudo apt remove jarl`) stops jarld and the backup timer and
removes the program, but named keeps running with the configuration Jarl removes the server (the CLI stays until you remove `jarl-cli` too), but named keeps running with the configuration Jarl
last wrote. Even `apt purge jarl` keeps `/etc/jarl`, `/var/lib/jarl` (the last wrote. Even `apt purge jarl` keeps `/etc/jarl`, `/var/lib/jarl` (the
database), `/var/backups/jarl` and the database), `/var/backups/jarl` and the
files in `/etc/bind/jarl`, `/var/lib/bind/jarl` and `/var/cache/bind/jarl`, files in `/etc/bind/jarl`, `/var/lib/bind/jarl` and `/var/cache/bind/jarl`,
since named.conf may still include them. Remove them by hand once named no since named.conf may still include them. Remove them by hand once named no
longer uses them. longer uses them.
### Just the CLI, on another machine
To manage Jarl servers from your laptop or another server, install only
`jarl-cli`. It needs nothing else and changes nothing on the machine (no
users, services or directories):
```sh
sudo apt install jarl-cli # with the registry above; or: sudo apt install ./jarl-cli_0.1.0_amd64.deb
jarl config set server https://ns1.adbraun.lol:8053
jarl config set token
```
Elsewhere, download the CLI from the release page:
`jarl-cli-<version>-<os>-<arch>` for Linux, macOS (`darwin`) and Windows
(`.exe`), with their checksums in `SHA256SUMS`. It is one static file; put
it in your PATH as `jarl` (on macOS, `xattr -d com.apple.quarantine jarl`
lets an unsigned download run). `make cli-dist` builds the same files, and
`make` builds it for this machine in `bin/`. [cli.md](cli.md)
explains the setup, and several servers in one configuration.
### With make install ### With make install
On the DNS server, as root: On the DNS server, as root:
@@ -297,8 +369,8 @@ without dropping connections. More in [acme.md](acme.md).
## Upgrading ## Upgrading
```sh ```sh
make && sudo make install sudo apt update && sudo apt upgrade # Debian packages from the registry
sudo systemctl restart jarl make && sudo make install && sudo systemctl restart jarl # make install
``` ```
Database migrations run automatically at startup. Before an upgrade, take Database migrations run automatically at startup. Before an upgrade, take
+1 -1
View File
@@ -14,7 +14,7 @@
| Restart / reload TLS | `systemctl restart jarl` / `systemctl reload jarl` | | Restart / reload TLS | `systemctl restart jarl` / `systemctl reload jarl` |
| Work on the server itself | `sudo jarl ...`: root uses jarld's local socket and needs no token ([cli.md](cli.md#on-the-server-itself)) | | Work on the server itself | `sudo jarl ...`: root uses jarld's local socket and needs no token ([cli.md](cli.md#on-the-server-itself)) |
| Renew or replace a token | `jarl token rotate NAME --grace 1h` (same grants, new secret; [permissions.md](permissions.md#rotating-a-token)). `jarl token list --all --expiring` shows those that expire within 14 days | | Renew or replace a token | `jarl token rotate NAME --grace 1h` (same grants, new secret; [permissions.md](permissions.md#rotating-a-token)). `jarl token list --all --expiring` shows those that expire within 14 days |
| Upgrade Jarl | Debian package: build the new one and `sudo apt install ./dist/jarl_*.deb`; otherwise `make && sudo make install && sudo systemctl restart jarl`. jarld upgrades its database when it starts | | Upgrade Jarl | Debian packages: build the new ones and `sudo apt install ./dist/jarl*_amd64.deb` (server and CLI); otherwise `make && sudo make install && sudo systemctl restart jarl`. jarld upgrades its database when it starts |
Restarting jarld doesn't affect DNS: named keeps serving the files it Restarting jarld doesn't affect DNS: named keeps serving the files it
loaded. jarld is only needed to make changes. loaded. jarld is only needed to make changes.
+9
View File
@@ -0,0 +1,9 @@
//go:build !unix
package stats
import "io/fs"
// inode is 0 where there are no inodes (the CLI uses this package on
// Windows for its types; only jarld tails logs).
func inode(fs.FileInfo) uint64 { return 0 }
+15
View File
@@ -0,0 +1,15 @@
//go:build unix
package stats
import (
"io/fs"
"syscall"
)
func inode(fi fs.FileInfo) uint64 {
if st, ok := fi.Sys().(*syscall.Stat_t); ok {
return st.Ino
}
return 0
}
-8
View File
@@ -6,7 +6,6 @@ import (
"io" "io"
"io/fs" "io/fs"
"os" "os"
"syscall"
) )
// Tailer follows a log file that named rotates (renaming it to FILE.0, // Tailer follows a log file that named rotates (renaming it to FILE.0,
@@ -30,13 +29,6 @@ type Position struct {
Offset int64 `json:"offset"` Offset int64 `json:"offset"`
} }
func inode(fi fs.FileInfo) uint64 {
if st, ok := fi.Sys().(*syscall.Stat_t); ok {
return st.Ino
}
return 0
}
// Position returns the current position (of complete lines). // Position returns the current position (of complete lines).
func (t *Tailer) Position() Position { func (t *Tailer) Position() Position {
return Position{Inode: t.ino, Offset: t.offset - int64(len(t.partial))} return Position{Inode: t.ino, Offset: t.offset - int64(len(t.partial))}
+103
View File
@@ -0,0 +1,103 @@
#!/bin/sh
# Publishes a built release (make dist, make cli-dist, make deb for each
# architecture) to Gitea:
#
# * a release for tag v<VERSION> with the CHANGELOG section for VERSION as
# its notes, and every file in dist/ (tarballs, CLI binaries, .debs)
# plus a SHA256SUMS of them attached;
# * the .debs in the owner's Debian package registry, so servers can
# 'apt install jarl' and get updates with apt (docs/install.md).
#
# Run by .gitea/workflows/release.yml when a v* tag is pushed. Running it
# again for the same version replaces the attachments and packages.
#
# Variables:
# GITEA_URL e.g. https://gitea.example.com (in Actions:
# GITHUB_SERVER_URL)
# GITEA_REPO owner/name (in Actions: GITHUB_REPOSITORY)
# GITEA_TOKEN a token with write:repository and write:package
# VERSION default: the VERSION file
# DEB_DISTRIBUTION the apt distribution (default: stable)
# DEB_COMPONENT the apt component (default: main)
#
# Needs curl and jq.
set -eu
: "${GITEA_URL:=${GITHUB_SERVER_URL:-}}" "${GITEA_REPO:=${GITHUB_REPOSITORY:-}}"
: "${GITEA_URL:?set GITEA_URL}" "${GITEA_REPO:?set GITEA_REPO}" "${GITEA_TOKEN:?set GITEA_TOKEN}"
VERSION=${VERSION:-$(cat VERSION)}
DEB_DISTRIBUTION=${DEB_DISTRIBUTION:-stable}
DEB_COMPONENT=${DEB_COMPONENT:-main}
GITEA_URL=${GITEA_URL%/}
owner=${GITEA_REPO%%/*}
tag=v$VERSION
api=$GITEA_URL/api/v1/repos/$GITEA_REPO
# curl with the token; fails on HTTP errors except the codes in $ok.
call() {
ok=$1
shift
out=$(mktemp)
code=$(curl -sS -o "$out" -w '%{http_code}' -H "Authorization: token $GITEA_TOKEN" "$@")
case " 200 201 204 $ok " in
*" $code "*) cat "$out"; rm -f "$out"; return 0 ;;
esac
echo "gitea-release: HTTP $code from $*" >&2
cat "$out" >&2
echo >&2
rm -f "$out"
return 1
}
files=$(find dist -maxdepth 1 -type f ! -name SHA256SUMS | sort)
[ -n "$files" ] || { echo "gitea-release: nothing in dist/" >&2; exit 1; }
for f in $files; do
case $f in *"$VERSION"*) ;; *) echo "gitea-release: $f is not version $VERSION" >&2; exit 1 ;; esac
done
# shellcheck disable=SC2046 # file names have no spaces
(cd dist && sha256sum $(printf '%s\n' "$files" | sed 's|^dist/||')) > dist/SHA256SUMS
files="$files dist/SHA256SUMS"
# The release notes: the CHANGELOG section for this version.
notes=$(awk -v v="$VERSION" '
/^## \[/ { if (on) exit; on = index($0, "## [" v "]") == 1; next }
on' CHANGELOG.md)
[ -n "$notes" ] || notes="See CHANGELOG.md."
prerelease=false
case $VERSION in *-*) prerelease=true ;; esac
# The release (created, or found when publishing again).
release=$(call 404 "$api/releases/tags/$tag")
id=$(printf '%s\n' "$release" | jq -r '.id // empty')
if [ -z "$id" ]; then
body=$(jq -n --arg tag "$tag" --arg notes "$notes" --argjson pre "$prerelease" \
'{tag_name: $tag, name: ("Jarl " + $tag), body: $notes, prerelease: $pre}')
release=$(call "" -X POST -H 'Content-Type: application/json' -d "$body" "$api/releases")
id=$(printf '%s\n' "$release" | jq -r .id)
echo "gitea-release: created release $tag"
fi
for f in $files; do
name=${f#dist/}
old=$(printf '%s\n' "$release" | jq -r --arg n "$name" '.assets[]? | select(.name == $n) | .id')
[ -z "$old" ] || call "" -X DELETE "$api/releases/$id/assets/$old" >/dev/null
call "" -X POST -F "attachment=@$f" "$api/releases/$id/assets?name=$name" >/dev/null
echo "gitea-release: attached $name"
done
# The Debian packages. Gitea refuses a package version it already has
# (409), so publishing again deletes it first.
pool=$GITEA_URL/api/packages/$owner/debian/pool/$DEB_DISTRIBUTION/$DEB_COMPONENT
for f in dist/*.deb; do
[ -e "$f" ] || continue
# shellcheck disable=SC2016,SC2046 # dpkg-deb's fields; split into "$@"
set -- $(dpkg-deb --showformat '${Package} ${Version} ${Architecture}' --show "$f")
if ! call 409 --upload-file "$f" "$pool/upload" >/dev/null; then
echo "gitea-release: upload of $f failed" >&2
exit 1
fi
if [ "$code" = 409 ]; then
call "" -X DELETE "$pool/$1/$2/$3" >/dev/null
call "" --upload-file "$f" "$pool/upload" >/dev/null
fi
echo "gitea-release: uploaded $1 $2 $3 to $DEB_DISTRIBUTION/$DEB_COMPONENT"
done
+67 -37
View File
@@ -1,18 +1,28 @@
#!/bin/sh #!/bin/sh
# Builds a Debian package of Jarl: dist/jarl_<version>_<arch>.deb. # Builds Jarl's two Debian packages:
# Run through 'make deb' (which sets the variables below); needs only
# dpkg-deb, no root and no debhelper.
# #
# The package installs, with Debian's paths: # dist/jarl-cli_<version>_<arch>.deb the CLI, to install anywhere:
# /usr/bin/jarld, /usr/bin/jarl # /usr/bin/jarl, its bash completion, and /usr/share/doc/jarl-cli/
# /usr/share/bash-completion/completions/{jarl,jarld} # (README, CHANGELOG, the CLI, ACME and permissions docs, contrib/ with
# the acme.sh and lego hooks). No scripts: installing it changes nothing
# else on the machine.
# dist/jarl_<version>_<arch>.deb the server, which depends on
# jarl-cli of the same version:
# /usr/bin/jarld and its bash completion
# /usr/lib/systemd/system/{jarl.service,jarl-backup.service,jarl-backup.timer} # /usr/lib/systemd/system/{jarl.service,jarl-backup.service,jarl-backup.timer}
# /usr/lib/sysusers.d/jarl.conf, /usr/lib/tmpfiles.d/jarl.conf # /usr/lib/sysusers.d/jarl.conf, /usr/lib/tmpfiles.d/jarl.conf
# /usr/share/doc/jarl/ (README, CHANGELOG, docs, contrib) # /usr/share/doc/jarl/ (README, CHANGELOG, docs)
# and its scripts (deploy/debian) create the jarl user and directories and # Its scripts (deploy/debian) create the jarl user and directories and
# /etc/jarl/jarl.yaml on the first install, turn on the daily backup timer, # /etc/jarl/jarl.yaml on the first install, turn on the daily backup
# restart a running jarld on upgrades, and stop both on removal. No file in /etc belongs to the package, # timer, restart a running jarld on upgrades, and stop both on removal.
# so an upgrade never asks about configuration files. # No file in /etc belongs to the package, so an upgrade never asks
# about configuration files.
#
# Until 0.1.0 one package, jarl, held both; jarl-cli takes /usr/bin/jarl
# over from it (Replaces: jarl).
#
# Run through 'make deb' (which sets the variables below); needs only
# dpkg-deb, no root and no debhelper.
# #
# Variables: DEB_ARCH (Debian architecture, e.g. amd64, arm64, armhf), # Variables: DEB_ARCH (Debian architecture, e.g. amd64, arm64, armhf),
# DEB_VERSION, VERSION, LDFLAGS, GOFLAGS, and DEB_MAINTAINER (default: your # DEB_VERSION, VERSION, LDFLAGS, GOFLAGS, and DEB_MAINTAINER (default: your
@@ -41,26 +51,42 @@ if [ -z "${DEB_MAINTAINER:-}" ]; then
DEB_MAINTAINER="${name:-Jarl packager} <${email:-root@localhost}>" DEB_MAINTAINER="${name:-Jarl packager} <${email:-root@localhost}>"
fi fi
work=dist/deb/jarl_${DEB_VERSION}_${DEB_ARCH} work=dist/deb/${DEB_VERSION}_${DEB_ARCH}
root=$work/root stage=$work/stage
out=dist/jarl_${DEB_VERSION}_${DEB_ARCH}.deb
rm -rf "$work" rm -rf "$work"
mkdir -p "$root/DEBIAN" mkdir -p "$stage"
# Binaries for the package's architecture (bin/ holds the host's, which # Binaries for the package's architecture (bin/ holds the host's, which
# also generated the completion scripts). # also generated the completion scripts).
echo "mkdeb: building for linux/$goarch" echo "mkdeb: building for linux/$goarch"
# go reads GOFLAGS (-trimpath) from the environment. # go reads GOFLAGS (-trimpath) from the environment.
export GOFLAGS export GOFLAGS
GOOS=linux GOARCH=$goarch GOARM=$goarm CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o "$work/jarld" ./cmd/jarld GOOS=linux GOARCH=$goarch GOARM=$goarm CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o "$work/bin/jarld" ./cmd/jarld
GOOS=linux GOARCH=$goarch GOARM=$goarm CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o "$work/jarl" ./cmd/jarl GOOS=linux GOARCH=$goarch GOARM=$goarm CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o "$work/bin/jarl" ./cmd/jarl
make --no-print-directory install-files DESTDIR="$root" PREFIX=/usr \ # Everything 'make install' would install, then split in two.
UNITDIR=/usr/lib/systemd/system JARLD_BIN="$work/jarld" JARL_BIN="$work/jarl" >/dev/null make --no-print-directory install-files DESTDIR="$stage" PREFIX=/usr \
UNITDIR=/usr/lib/systemd/system JARLD_BIN="$work/bin/jarld" JARL_BIN="$work/bin/jarl" >/dev/null
cli=$work/jarl-cli
srv=$work/jarl
mkdir -p "$cli/DEBIAN" "$cli/usr/bin" "$cli/usr/share/bash-completion/completions" "$cli/usr/share/doc/jarl-cli/docs"
mv "$stage/usr/bin/jarl" "$cli/usr/bin/"
mv "$stage/usr/share/bash-completion/completions/jarl" "$cli/usr/share/bash-completion/completions/"
mv "$stage/usr/share/doc/jarl/contrib" "$cli/usr/share/doc/jarl-cli/"
cp "$stage/usr/share/doc/jarl/README.md" "$stage/usr/share/doc/jarl/CHANGELOG.md" "$cli/usr/share/doc/jarl-cli/"
for d in cli acme permissions; do
cp "$stage/usr/share/doc/jarl/docs/$d.md" "$cli/usr/share/doc/jarl-cli/docs/"
done
mv "$stage" "$srv"
mkdir -p "$srv/DEBIAN"
for f in postinst prerm postrm; do
install -m 0755 "deploy/debian/$f" "$srv/DEBIAN/$f"
done
# Debian's machine-readable copyright file, from LICENSE. # Debian's machine-readable copyright file, from LICENSE.
indent() { sed -e 's/^$/./' -e 's/^/ /' "$1"; } indent() { sed -e 's/^$/./' -e 's/^/ /' "$1"; }
{ copyright() {
echo "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/" echo "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/"
echo "Upstream-Name: jarl" echo "Upstream-Name: jarl"
echo echo
@@ -68,22 +94,26 @@ indent() { sed -e 's/^$/./' -e 's/^/ /' "$1"; }
echo "Copyright: $(sed -n 's/^Copyright (c) //p' LICENSE)" echo "Copyright: $(sed -n 's/^Copyright (c) //p' LICENSE)"
echo "License: Expat" echo "License: Expat"
indent LICENSE indent LICENSE
} > "$root/usr/share/doc/jarl/copyright" }
for f in postinst prerm postrm; do # build PACKAGE ROOT: writes the control file and md5sums and builds the .deb.
install -m 0755 "deploy/debian/$f" "$root/DEBIAN/$f" build() {
done pkg=$1 root=$2 out=dist/${1}_${DEB_VERSION}_${DEB_ARCH}.deb
# Directories 0755 and no group/other write, whatever the build's umask. copyright > "$root/usr/share/doc/$pkg/copyright"
find "$root" -type d -exec chmod 0755 {} + rm -f "$root/usr/share/doc/$pkg/LICENSE"
chmod -R go-w "$root" # Directories 0755 and no group/other write, whatever the build's umask.
size=$(du -sk --exclude=DEBIAN "$root" | cut -f1) find "$root" -type d -exec chmod 0755 {} +
sed -e "s/@VERSION@/$DEB_VERSION/" -e "s/@ARCH@/$DEB_ARCH/" -e "s/@SIZE@/$size/" \ chmod -R go-w "$root"
-e "s|@MAINTAINER@|$DEB_MAINTAINER|" deploy/debian/control.in > "$root/DEBIAN/control" size=$(du -sk --exclude=DEBIAN "$root" | cut -f1)
(cd "$root" && find . -path ./DEBIAN -prune -o -type f -print | sed 's|^\./||' | sort | xargs md5sum) > "$root/DEBIAN/md5sums" sed -e "s/@VERSION@/$DEB_VERSION/g" -e "s/@ARCH@/$DEB_ARCH/" -e "s/@SIZE@/$size/" \
chmod 0644 "$root/DEBIAN/control" "$root/DEBIAN/md5sums" -e "s|@MAINTAINER@|$DEB_MAINTAINER|" "deploy/debian/control-$pkg.in" > "$root/DEBIAN/control"
(cd "$root" && find . -path ./DEBIAN -prune -o -type f -print | sed 's|^\./||' | sort | xargs md5sum) > "$root/DEBIAN/md5sums"
# Files belong to root in the package, whoever builds it. chmod 0644 "$root/DEBIAN/control" "$root/DEBIAN/md5sums"
dpkg-deb --root-owner-group -Zxz --build "$root" "$out" >/dev/null # Files belong to root in the package, whoever builds it.
dpkg-deb --root-owner-group -Zxz --build "$root" "$out" >/dev/null
echo "mkdeb: wrote $out"
dpkg-deb --info "$out" | sed -n 's/^ \(Package\|Version\|Architecture\|Installed-Size\|Depends\):/ \1:/p'
}
build jarl-cli "$cli"
build jarl "$srv"
rm -rf dist/deb rm -rf dist/deb
echo "mkdeb: wrote $out"
dpkg-deb --info "$out" | sed -n 's/^ \(Package\|Version\|Architecture\|Installed-Size\):/ \1:/p'