Split the packages, publish releases from Gitea Actions, release 0.1.0
release / release (push) Successful in 7m7s
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:
1 parent
293e6ddd33
commit
4d00be53c3
16 files changed
+449
-83
No files matched your search
@@ -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
@@ -6,7 +6,9 @@ All notable changes to Jarl are documented here. The format follows
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
The first version, to be released as 0.1.0.
|
||||
## [0.1.0] - 2026-10-09
|
||||
|
||||
The first version.
|
||||
|
||||
### Added
|
||||
|
||||
@@ -68,12 +70,23 @@ The first version, to be released as 0.1.0.
|
||||
`socket`. The systemd unit creates `/run/jarl` (0750).
|
||||
- Jarl is released under the MIT License (`LICENSE`), which is installed
|
||||
with the docs and is the Debian package's `copyright` file.
|
||||
- A Debian package: `make deb` builds `dist/jarl_<version>_<arch>.deb` with
|
||||
only `dpkg-deb` (no root), for the host or any other architecture
|
||||
(`DEB_ARCH=arm64`). Installing it 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.
|
||||
- Debian packages: `make deb` builds, with only `dpkg-deb` (no root) and
|
||||
for the host or any other architecture (`DEB_ARCH=arm64`), `jarl` (the
|
||||
server: jarld, its units and docs) and `jarl-cli` (the `jarl` CLI, its
|
||||
completion and the ACME hooks), which `jarl` depends on. `jarl-cli` has no
|
||||
scripts, so it can go on any machine you manage Jarl from. Installing
|
||||
`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
|
||||
the Debian package. Besides commands and flags, it completes server and
|
||||
group names from the CLI configuration, zone, view, ACL, TSIG key and
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
# make e2e end-to-end test against a real named (scripts/e2e.sh)
|
||||
# sudo make install install binaries, bash completion, systemd units,
|
||||
# sysusers/tmpfiles files and an example /etc/jarl/jarl.yaml
|
||||
# make deb a Debian package in dist/ (DEB_ARCH=arm64 for another
|
||||
# architecture); install it with 'sudo apt install ./dist/jarl_*.deb'
|
||||
# make deb Debian packages in dist/ (DEB_ARCH=arm64 for another
|
||||
# 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 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)
|
||||
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_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
|
||||
|
||||
@@ -114,5 +117,15 @@ dist: build
|
||||
tar -C dist -czf dist/jarl-$(VERSION)-linux-$$arch.tar.gz jarl-$(VERSION)-linux-$$arch || exit 1; \
|
||||
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:
|
||||
rm -rf bin dist
|
||||
@@ -67,8 +67,9 @@ error: www.adbraun.lol has a CNAME, so it can't have any other records (found A)
|
||||
fallback.
|
||||
* 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
|
||||
user. `make deb` builds a Debian package, and both come with bash
|
||||
completion.
|
||||
user. Releases come as Debian packages in an apt repository (server and
|
||||
CLI separately) and as standalone CLI downloads for Linux, macOS and
|
||||
Windows; both binaries have bash completion.
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -76,7 +77,8 @@ On a Debian/Ubuntu BIND 9.20 server ([install.md](docs/install.md) has the
|
||||
details):
|
||||
|
||||
```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 import # read the current named.conf and zones
|
||||
sudo jarld adopt # switch named over (keeps a backup, rolls back on failure)
|
||||
|
||||
@@ -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@
|
||||
Maintainer: @MAINTAINER@
|
||||
Installed-Size: @SIZE@
|
||||
Depends: systemd
|
||||
Depends: systemd, jarl-cli (= @VERSION@)
|
||||
Recommends: bind9 (>= 1:9.20)
|
||||
Section: net
|
||||
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
|
||||
tokens have fine-grained permissions, and every change is audited.
|
||||
.
|
||||
The package installs jarld (the daemon, as the systemd service jarl) and
|
||||
jarl (the CLI), with bash completion for both. jarld isn't started on a
|
||||
first install: see /usr/share/doc/jarl/docs/install.md to create the
|
||||
database and adopt the BIND configuration.
|
||||
This package is the server: jarld (the daemon, as the systemd service
|
||||
jarl) with its daily backup timer. The jarl CLI comes from jarl-cli,
|
||||
which can also be installed on its own on other machines. jarld isn't
|
||||
started on a first install: see /usr/share/doc/jarl/docs/install.md to
|
||||
create the database and adopt the BIND configuration.
|
||||
+2
-2
@@ -68,7 +68,7 @@ configuration, so `certbot renew` works unattended.
|
||||
Install the DNS API hook that comes with Jarl:
|
||||
|
||||
```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_TOKEN=$(cat /etc/jarl/certbot.token)
|
||||
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):
|
||||
|
||||
```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 JARL_SERVER=https://ns1.adbraun.lol:8053
|
||||
export JARL_TOKEN=@/etc/jarl/certbot.token
|
||||
|
||||
@@ -25,6 +25,10 @@ Besides commands and flags, Tab completes:
|
||||
|
||||
## 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
|
||||
jarl config set server https://ns1.adbraun.lol:8053
|
||||
jarl config set token # paste the token at the prompt
|
||||
|
||||
+38
-5
@@ -49,7 +49,7 @@ make # bin/jarld, bin/jarl (CGO_ENABLED=0, version from ./VERSION),
|
||||
# and bin/completion/{jarl,jarld} (bash completion)
|
||||
make test # go vet, gofmt check, go test ./...
|
||||
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
|
||||
@@ -70,7 +70,7 @@ To try the package without installing it on your machine:
|
||||
|
||||
```sh
|
||||
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
|
||||
@@ -154,7 +154,40 @@ files are embedded at build time, so rebuild jarld after changes.
|
||||
|
||||
## Releasing
|
||||
|
||||
1. Move the "Unreleased" section of `CHANGELOG.md` under the new version
|
||||
and date.
|
||||
1. Move the "Unreleased" section of `CHANGELOG.md` under
|
||||
`## [<version>] - <date>`.
|
||||
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
@@ -12,6 +12,12 @@ to load Jarl's configuration the old one is put back automatically.
|
||||
|
||||
## 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
|
||||
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
|
||||
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
|
||||
make deb # dist/jarl_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
|
||||
sudo install -d /etc/apt/keyrings
|
||||
sudo curl -fsSL https://gitea.adbraun.lol/api/packages/adam/debian/repository.key -o /etc/apt/keyrings/jarl.asc
|
||||
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
|
||||
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/lib/systemd/system` (`jarl.service`, and `jarl-backup.service`
|
||||
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
|
||||
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
|
||||
@@ -58,17 +103,44 @@ if it's missing. Installing the package:
|
||||
* doesn't start jarld: continue with [3. Configure](#3-configure), then
|
||||
`systemctl enable --now jarl` in step 8.
|
||||
|
||||
**Upgrading**: build and install the newer package the same way. A running
|
||||
jarld is restarted, and upgrades its database as it starts.
|
||||
`jarl-cli` installs `/usr/bin/jarl`, its completion, and
|
||||
`/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
|
||||
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
|
||||
database), `/var/backups/jarl` and the
|
||||
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
|
||||
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
|
||||
|
||||
On the DNS server, as root:
|
||||
@@ -297,8 +369,8 @@ without dropping connections. More in [acme.md](acme.md).
|
||||
## Upgrading
|
||||
|
||||
```sh
|
||||
make && sudo make install
|
||||
sudo systemctl restart jarl
|
||||
sudo apt update && sudo apt upgrade # Debian packages from the registry
|
||||
make && sudo make install && sudo systemctl restart jarl # make install
|
||||
```
|
||||
|
||||
Database migrations run automatically at startup. Before an upgrade, take
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
| 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)) |
|
||||
| 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
|
||||
loaded. jarld is only needed to make changes.
|
||||
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Tailer follows a log file that named rotates (renaming it to FILE.0,
|
||||
@@ -30,13 +29,6 @@ type Position struct {
|
||||
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).
|
||||
func (t *Tailer) Position() Position {
|
||||
return Position{Inode: t.ino, Offset: t.offset - int64(len(t.partial))}
|
||||
|
||||
Executable
+103
@@ -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
@@ -1,18 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Builds a Debian package of Jarl: dist/jarl_<version>_<arch>.deb.
|
||||
# Run through 'make deb' (which sets the variables below); needs only
|
||||
# dpkg-deb, no root and no debhelper.
|
||||
# Builds Jarl's two Debian packages:
|
||||
#
|
||||
# The package installs, with Debian's paths:
|
||||
# /usr/bin/jarld, /usr/bin/jarl
|
||||
# /usr/share/bash-completion/completions/{jarl,jarld}
|
||||
# dist/jarl-cli_<version>_<arch>.deb the CLI, to install anywhere:
|
||||
# /usr/bin/jarl, its bash completion, and /usr/share/doc/jarl-cli/
|
||||
# (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/sysusers.d/jarl.conf, /usr/lib/tmpfiles.d/jarl.conf
|
||||
# /usr/share/doc/jarl/ (README, CHANGELOG, docs, contrib)
|
||||
# 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,
|
||||
# restart a running jarld on upgrades, and stop both on removal. No file in /etc belongs to the package,
|
||||
# so an upgrade never asks about configuration files.
|
||||
# /usr/share/doc/jarl/ (README, CHANGELOG, docs)
|
||||
# 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, restart a running jarld on upgrades, and stop both on removal.
|
||||
# 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),
|
||||
# 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}>"
|
||||
fi
|
||||
|
||||
work=dist/deb/jarl_${DEB_VERSION}_${DEB_ARCH}
|
||||
root=$work/root
|
||||
out=dist/jarl_${DEB_VERSION}_${DEB_ARCH}.deb
|
||||
work=dist/deb/${DEB_VERSION}_${DEB_ARCH}
|
||||
stage=$work/stage
|
||||
rm -rf "$work"
|
||||
mkdir -p "$root/DEBIAN"
|
||||
mkdir -p "$stage"
|
||||
|
||||
# Binaries for the package's architecture (bin/ holds the host's, which
|
||||
# also generated the completion scripts).
|
||||
echo "mkdeb: building for linux/$goarch"
|
||||
# go reads GOFLAGS (-trimpath) from the environment.
|
||||
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/jarl" ./cmd/jarl
|
||||
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/bin/jarl" ./cmd/jarl
|
||||
|
||||
make --no-print-directory install-files DESTDIR="$root" PREFIX=/usr \
|
||||
UNITDIR=/usr/lib/systemd/system JARLD_BIN="$work/jarld" JARL_BIN="$work/jarl" >/dev/null
|
||||
# Everything 'make install' would install, then split in two.
|
||||
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.
|
||||
indent() { sed -e 's/^$/./' -e 's/^/ /' "$1"; }
|
||||
{
|
||||
copyright() {
|
||||
echo "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/"
|
||||
echo "Upstream-Name: jarl"
|
||||
echo
|
||||
@@ -68,22 +94,26 @@ indent() { sed -e 's/^$/./' -e 's/^/ /' "$1"; }
|
||||
echo "Copyright: $(sed -n 's/^Copyright (c) //p' LICENSE)"
|
||||
echo "License: Expat"
|
||||
indent LICENSE
|
||||
} > "$root/usr/share/doc/jarl/copyright"
|
||||
}
|
||||
|
||||
for f in postinst prerm postrm; do
|
||||
install -m 0755 "deploy/debian/$f" "$root/DEBIAN/$f"
|
||||
done
|
||||
# Directories 0755 and no group/other write, whatever the build's umask.
|
||||
find "$root" -type d -exec chmod 0755 {} +
|
||||
chmod -R go-w "$root"
|
||||
size=$(du -sk --exclude=DEBIAN "$root" | cut -f1)
|
||||
sed -e "s/@VERSION@/$DEB_VERSION/" -e "s/@ARCH@/$DEB_ARCH/" -e "s/@SIZE@/$size/" \
|
||||
-e "s|@MAINTAINER@|$DEB_MAINTAINER|" deploy/debian/control.in > "$root/DEBIAN/control"
|
||||
(cd "$root" && find . -path ./DEBIAN -prune -o -type f -print | sed 's|^\./||' | sort | xargs md5sum) > "$root/DEBIAN/md5sums"
|
||||
chmod 0644 "$root/DEBIAN/control" "$root/DEBIAN/md5sums"
|
||||
|
||||
# Files belong to root in the package, whoever builds it.
|
||||
dpkg-deb --root-owner-group -Zxz --build "$root" "$out" >/dev/null
|
||||
# build PACKAGE ROOT: writes the control file and md5sums and builds the .deb.
|
||||
build() {
|
||||
pkg=$1 root=$2 out=dist/${1}_${DEB_VERSION}_${DEB_ARCH}.deb
|
||||
copyright > "$root/usr/share/doc/$pkg/copyright"
|
||||
rm -f "$root/usr/share/doc/$pkg/LICENSE"
|
||||
# Directories 0755 and no group/other write, whatever the build's umask.
|
||||
find "$root" -type d -exec chmod 0755 {} +
|
||||
chmod -R go-w "$root"
|
||||
size=$(du -sk --exclude=DEBIAN "$root" | cut -f1)
|
||||
sed -e "s/@VERSION@/$DEB_VERSION/g" -e "s/@ARCH@/$DEB_ARCH/" -e "s/@SIZE@/$size/" \
|
||||
-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"
|
||||
chmod 0644 "$root/DEBIAN/control" "$root/DEBIAN/md5sums"
|
||||
# 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
|
||||
echo "mkdeb: wrote $out"
|
||||
dpkg-deb --info "$out" | sed -n 's/^ \(Package\|Version\|Architecture\|Installed-Size\):/ \1:/p'
|
||||
Reference in new issue
Block a user