v0.27.1 packaging update, install and initial config

This commit is contained in:
lixen committed 2026-10-06 00:14:05 -04:00
1 parent 60abc97eb0
commit 4513a9e62b
15 files changed
+433 -112

No files matched your search

+51 -14
View File
@@ -7,7 +7,7 @@ on:
- Dockerfile - Dockerfile
- go.mod - go.mod
- go.sum - go.sum
- config/logwisp.toml - config/**
- doc/lw.1 - doc/lw.1
- deploy/package/** - deploy/package/**
- .github/workflows/package.yml - .github/workflows/package.yml
@@ -17,9 +17,9 @@ on:
permissions: permissions:
contents: read contents: read
jobs: jobs:
# The AUR package from this commit: makepkg finds the tarball the PKGBUILD # make arch builds the AUR package from this checkout; the package installs
# names next to it and skips the download, then builds, tests and packages. # over an edited configuration make install left, reinstalls keeping it, and
# checkout's safe.directory lasts only its own step; root owns .git here. # goes. makepkg refuses root, so a user that owns the checkout runs it.
arch: arch:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 20 timeout-minutes: 20
@@ -28,24 +28,52 @@ jobs:
- run: pacman -Syu --noconfirm --needed git go - run: pacman -Syu --noconfirm --needed git go
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- run: | - run: |
ver=$(sed -n 's/^pkgver=//p' deploy/package/arch/PKGBUILD)
dir=$(mktemp -d)
git -c safe.directory="$PWD" archive --prefix="logwisp-$ver/" -o "$dir/logwisp-$ver.tar.gz" HEAD
cp deploy/package/arch/PKGBUILD "$dir"
useradd -m build useradd -m build
chown -R build "$dir" chown -R build .
su build -c "cd '$dir' && makepkg --noconfirm" su build -c 'make arch'
pacman -U --noconfirm "$dir"/logwisp-*.pkg.tar.zst
- run: | - run: |
for f in bin/arch/logwisp-*.pkg.tar.zst; do case $f in *-debug-*) ;; *) pkg=$f ;; esac; done
install -D -m 0644 config/logwisp.toml /etc/logwisp/logwisp.toml
echo '# edited' >> /etc/logwisp/logwisp.toml
pacman -U --noconfirm "$pkg"
test -f /etc/logwisp/logwisp.toml.pacnew
lw --version lw --version
test -f /usr/share/bash-completion/completions/lw test -f /usr/share/bash-completion/completions/lw
test -f /usr/share/zsh/site-functions/_lw test -f /usr/share/zsh/site-functions/_lw
test -f /usr/share/fish/vendor_completions.d/lw.fish test -f /usr/share/fish/vendor_completions.d/lw.fish
pacman -U --noconfirm "$pkg"
grep -q '^# edited' /etc/logwisp/logwisp.toml
lw --check -c /etc/logwisp/logwisp.toml
pacman -R --noconfirm logwisp pacman -R --noconfirm logwisp
test ! -e /usr/bin/lw test ! -e /usr/bin/lw
test -f /etc/logwisp/logwisp.toml.pacsave
# make install SERVICE=no into a home, twice, then lw config init places the
# configuration lw reads without -c, and never replaces it
user:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- run: make release
- run: |
make install SERVICE=no PREFIX="$HOME/.local"
make install SERVICE=no PREFIX="$HOME/.local"
export PATH="$HOME/.local/bin:$PATH"
test ! -e "$HOME/.local/lib/systemd/system/logwisp.service"
test ! -e /etc/logwisp/logwisp.toml
lw config init
lw --check
lw --dump | grep -q 'id = "default_source"'
if lw config init; then exit 1; fi
make uninstall PREFIX="$HOME/.local"
test ! -e "$HOME/.local/bin/lw"
test -f "$HOME/.config/logwisp/logwisp.toml"
# make deb on a host with systemd: the service runs from the package's # make deb on a host with systemd: the service runs from the package's
# configuration; remove stops and disables it and leaves the conffile, purge # configuration and survives a reinstall that keeps an edited one; remove
# removes that too # stops and disables it and leaves the conffile, purge removes that too
deb: deb:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
@@ -62,6 +90,11 @@ jobs:
sudo systemctl enable --now logwisp sudo systemctl enable --now logwisp
sleep 3 sleep 3
systemctl is-active logwisp || { sudo journalctl -u logwisp --no-pager; exit 1; } systemctl is-active logwisp || { sudo journalctl -u logwisp --no-pager; exit 1; }
echo '# edited' | sudo tee -a /etc/logwisp/logwisp.toml
sudo apt-get install --reinstall -y ./bin/logwisp_*.deb
grep -q '^# edited' /etc/logwisp/logwisp.toml
sleep 3
systemctl is-active logwisp || { sudo journalctl -u logwisp --no-pager; exit 1; }
sudo apt-get remove -y logwisp sudo apt-get remove -y logwisp
test ! -e /usr/bin/lw test ! -e /usr/bin/lw
test ! -e /usr/lib/systemd/system/logwisp.service test ! -e /usr/lib/systemd/system/logwisp.service
@@ -70,7 +103,8 @@ jobs:
test -f /etc/logwisp/logwisp.toml test -f /etc/logwisp/logwisp.toml
sudo apt-get purge -y logwisp sudo apt-get purge -y logwisp
test ! -e /etc/logwisp/logwisp.toml test ! -e /etc/logwisp/logwisp.toml
# BSD make on FreeBSD: build, install, run the rc.d service, uninstall # BSD make on FreeBSD: build, install twice keeping an edited configuration,
# run the rc.d service, uninstall
freebsd: freebsd:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 20 timeout-minutes: 20
@@ -85,6 +119,9 @@ jobs:
set -e set -e
make release make release
make install make install
echo '# edited' >> /usr/local/etc/logwisp/logwisp.toml
make install
grep -q '^# edited' /usr/local/etc/logwisp/logwisp.toml
lw --version lw --version
pw groupadd logwisp pw groupadd logwisp
pw useradd logwisp -g logwisp -d /nonexistent -s /usr/sbin/nologin pw useradd logwisp -g logwisp -d /nonexistent -s /usr/sbin/nologin
+3 -3
View File
@@ -77,9 +77,9 @@ change touches.
keys must fail at any depth. Set defaults on the options struct before the keys must fail at any depth. Set defaults on the options struct before the
call; `TestEveryPluginRejectsUnknownKeys` covers every registered plugin. call; `TestEveryPluginRejectsUnknownKeys` covers every registered plugin.
- `cmd/lw/cli.go` owns the command line: lw's own flags, and the commands - `cmd/lw/cli.go` owns the command line: lw's own flags, and the commands
table whose groups (`auth`, `tls`, `preset`) share one flag mechanism; table whose groups (`auth`, `tls`, `preset`, `config`) share one flag
`config.Load` takes the parsed `config.Args`. Presets are rows of one table mechanism; `config.Load` takes the parsed `config.Args`. Presets are rows of
in `internal/config/preset.go`. one table in `internal/config/preset.go`.
- Network security has three seams: `tlsx` (TLS configs, certificates made at - Network security has three seams: `tlsx` (TLS configs, certificates made at
startup or by `lw tls`, pins, startup warnings), `authz`, and `netacl` startup or by `lw tls`, pins, startup warnings), `authz`, and `netacl`
(address rules and per-client limits: a listener wraps the socket (address rules and per-client limits: a listener wraps the socket
+36 -16
View File
@@ -45,6 +45,9 @@ IMAGE_RUN = $(CONTAINER_ENGINE) run --read-only --user 65532:65532 \
--network none --cap-drop ALL --security-opt no-new-privileges --network none --cap-drop ALL --security-opt no-new-privileges
DESTDIR ?= DESTDIR ?=
# SERVICE=no installs lw alone: no service files and no system configuration,
# which lw config init then places where the user wants it.
SERVICE ?= yes
INSTALL_OS_DEFAULT != uname -s INSTALL_OS_DEFAULT != uname -s
INSTALL_OS ?= $(INSTALL_OS_DEFAULT) INSTALL_OS ?= $(INSTALL_OS_DEFAULT)
# FreeBSD keeps everything outside the base system under /usr/local. # FreeBSD keeps everything outside the base system under /usr/local.
@@ -66,7 +69,7 @@ DEB_MAINTAINER ?= Maintainer <maintainer@example.org>
.DEFAULT_GOAL := help .DEFAULT_GOAL := help
.PHONY: help check-go build release dev test verify e2e image image-check install uninstall deb completion clean version .PHONY: help check-go build release dev test verify e2e image image-check install uninstall deb arch completion clean version
help: help:
@echo "Usage: make <target> [VARIABLE=value ...]" @echo "Usage: make <target> [VARIABLE=value ...]"
@@ -87,12 +90,13 @@ help:
@echo " image-check Run it read-only, offline, without capabilities: --version, then" @echo " image-check Run it read-only, offline, without capabilities: --version, then"
@echo " --check on $(IMAGE_CHECK_CONFIG)" @echo " --check on $(IMAGE_CHECK_CONFIG)"
@echo "Install (build or release first):" @echo "Install (build or release first):"
@echo " install Binary, manual, sample config, service files, shell completion," @echo " install Binary, manual, shell completion, licence and docs; unless SERVICE=no,"
@echo " licence and docs" @echo " the service files and $(SYSCONFDIR)/logwisp/logwisp.toml (kept if present)"
@echo " uninstall Remove them, keeping $(SYSCONFDIR)/logwisp" @echo " uninstall Remove them, keeping $(SYSCONFDIR)/logwisp"
@echo " deb Package a release build as $(BIN_DIR)/logwisp_VERSION_ARCH.deb (dpkg-deb)" @echo " deb Package a release build as $(BIN_DIR)/logwisp_VERSION_ARCH.deb (dpkg-deb)"
@echo " arch Build the Arch package from this working tree in $(BIN_DIR)/arch (makepkg)"
@echo "" @echo ""
@echo "Variables: GO=$(GO) PREFIX=$(PREFIX) SYSCONFDIR=$(SYSCONFDIR) DESTDIR=$(DESTDIR)" @echo "Variables: GO=$(GO) PREFIX=$(PREFIX) SYSCONFDIR=$(SYSCONFDIR) DESTDIR=$(DESTDIR) SERVICE=$(SERVICE)"
@echo " INSTALL_OS=$(INSTALL_OS) (the layout install uses: FreeBSD or any other)" @echo " INSTALL_OS=$(INSTALL_OS) (the layout install uses: FreeBSD or any other)"
@echo " GO_BUILDFLAGS, GO_LDFLAGS (extra go build and linker flags), E2E (scripts)," @echo " GO_BUILDFLAGS, GO_LDFLAGS (extra go build and linker flags), E2E (scripts),"
@echo " CONTAINER_ENGINE, IMAGE, IMAGE_TAG, BUILD_CA, IMAGE_BUILD_FLAGS, IMAGE_CHECK_CONFIG," @echo " CONTAINER_ENGINE, IMAGE, IMAGE_TAG, BUILD_CA, IMAGE_BUILD_FLAGS, IMAGE_CHECK_CONFIG,"
@@ -131,10 +135,10 @@ verify: test
$(GO) vet ./... $(GO) vet ./...
@base='$(GOFMT_BASE)'; \ @base='$(GOFMT_BASE)'; \
if [ -n "$$base" ] && fork=$$(git merge-base "$$base" HEAD 2>/dev/null); then \ if [ -n "$$base" ] && fork=$$(git merge-base "$$base" HEAD 2>/dev/null); then \
files=$$( { git diff --name-only --find-renames=100% --diff-filter=dr "$$fork" -- cmd internal; \ files=$$( { git diff --name-only --find-renames=100% --diff-filter=dr "$$fork" -- cmd config internal; \
git ls-files --others --exclude-standard -- cmd internal; } | grep '\.go$$' || true); \ git ls-files --others --exclude-standard -- cmd config internal; } | grep '\.go$$' || true); \
else \ else \
files=$$(find cmd internal -name '*.go'); \ files=$$(find cmd config internal -name '*.go'); \
fi; \ fi; \
bad=$$( [ -z "$$files" ] || "$$($(GO) env GOROOT)/bin/gofmt" -l $$files ); \ bad=$$( [ -z "$$files" ] || "$$($(GO) env GOROOT)/bin/gofmt" -l $$files ); \
if [ -n "$$bad" ]; then echo "gofmt -l reports:"; echo "$$bad"; exit 1; fi if [ -n "$$bad" ]; then echo "gofmt -l reports:"; echo "$$bad"; exit 1; fi
@@ -192,10 +196,11 @@ image-check:
# install stages what a package ships and compiles nothing, so a packager owns # install stages what a package ships and compiles nothing, so a packager owns
# the build flags. The service files get the installed paths substituted; a # the build flags. The service files get the installed paths substituted; a
# configuration already in place is kept. # configuration already in place is kept, on every system alike.
install: install:
@set -eu; \ @set -eu; \
[ -x $(BIN_DIR)/$(BINARY) ] || { echo "no $(BIN_DIR)/$(BINARY): run make build or make release first" >&2; exit 1; }; \ [ -x $(BIN_DIR)/$(BINARY) ] || { echo "no $(BIN_DIR)/$(BINARY): run make build or make release first" >&2; exit 1; }; \
case '$(SERVICE)' in yes|no) ;; *) echo "SERVICE is yes or no, not '$(SERVICE)'" >&2; exit 2 ;; esac; \
put() { install -d -m 0755 "$${3%/*}"; install -m "$$1" "$$2" "$$3"; echo "install $$3"; }; \ put() { install -d -m 0755 "$${3%/*}"; install -m "$$1" "$$2" "$$3"; echo "install $$3"; }; \
sub() { install -d -m 0755 "$${3%/*}"; \ sub() { install -d -m 0755 "$${3%/*}"; \
sed -e 's|@BINDIR@|$(BINDIR)|g' -e 's|@SYSCONFDIR@|$(SYSCONFDIR)|g' -e 's|%%BINDIR%%|$(BINDIR)|g' \ sed -e 's|@BINDIR@|$(BINDIR)|g' -e 's|@SYSCONFDIR@|$(SYSCONFDIR)|g' -e 's|%%BINDIR%%|$(BINDIR)|g' \
@@ -209,14 +214,13 @@ install:
put 0644 $(PKG_DIR)/completion/lw.fish "$$root$(FISHCOMPDIR)/lw.fish"; \ put 0644 $(PKG_DIR)/completion/lw.fish "$$root$(FISHCOMPDIR)/lw.fish"; \
put 0644 LICENSE "$$root$(LICENSEDIR)/LICENSE"; \ put 0644 LICENSE "$$root$(LICENSEDIR)/LICENSE"; \
for src in README.md doc/*.md; do put 0644 "$$src" "$$root$(DOCDIR)/$$src"; done; \ for src in README.md doc/*.md; do put 0644 "$$src" "$$root$(DOCDIR)/$$src"; done; \
case '$(INSTALL_OS)' in \ if [ '$(SERVICE)' = no ]; then echo "skip the service and $$etc (SERVICE=no): lw config init writes a configuration"; exit 0; fi; \
FreeBSD) \
put 0644 config/logwisp.toml "$$etc/logwisp.toml.sample"; \
if [ -z "$$root" ] && [ ! -e "$$etc/logwisp.toml" ]; then put 0644 config/logwisp.toml "$$etc/logwisp.toml"; fi; \
sub 0755 $(PKG_DIR)/logwisp.rc "$$root$(PREFIX)/etc/rc.d/logwisp" ;; \
*) \
if [ -e "$$etc/logwisp.toml" ]; then echo "keep $$etc/logwisp.toml"; \ if [ -e "$$etc/logwisp.toml" ]; then echo "keep $$etc/logwisp.toml"; \
else put 0644 config/logwisp.toml "$$etc/logwisp.toml"; fi; \ else put 0644 config/logwisp.toml "$$etc/logwisp.toml"; fi; \
case '$(INSTALL_OS)' in \
FreeBSD) \
sub 0755 $(PKG_DIR)/logwisp.rc "$$root$(PREFIX)/etc/rc.d/logwisp" ;; \
*) \
sub 0644 $(PKG_DIR)/logwisp.service "$$root$(PREFIX)/lib/systemd/system/logwisp.service"; \ sub 0644 $(PKG_DIR)/logwisp.service "$$root$(PREFIX)/lib/systemd/system/logwisp.service"; \
sub 0644 $(PKG_DIR)/logwisp.sysusers "$$root$(PREFIX)/lib/sysusers.d/logwisp.conf"; \ sub 0644 $(PKG_DIR)/logwisp.sysusers "$$root$(PREFIX)/lib/sysusers.d/logwisp.conf"; \
sub 0644 $(PKG_DIR)/logwisp.tmpfiles "$$root$(PREFIX)/lib/tmpfiles.d/logwisp.conf" ;; \ sub 0644 $(PKG_DIR)/logwisp.tmpfiles "$$root$(PREFIX)/lib/tmpfiles.d/logwisp.conf" ;; \
@@ -228,14 +232,14 @@ uninstall:
"$$root$(BASHCOMPDIR)/lw" "$$root$(ZSHCOMPDIR)/_lw" "$$root$(FISHCOMPDIR)/lw.fish" \ "$$root$(BASHCOMPDIR)/lw" "$$root$(ZSHCOMPDIR)/_lw" "$$root$(FISHCOMPDIR)/lw.fish" \
"$$root$(PREFIX)/lib/systemd/system/logwisp.service" \ "$$root$(PREFIX)/lib/systemd/system/logwisp.service" \
"$$root$(PREFIX)/lib/sysusers.d/logwisp.conf" "$$root$(PREFIX)/lib/tmpfiles.d/logwisp.conf" \ "$$root$(PREFIX)/lib/sysusers.d/logwisp.conf" "$$root$(PREFIX)/lib/tmpfiles.d/logwisp.conf" \
"$$root$(PREFIX)/etc/rc.d/logwisp" "$$root$(SYSCONFDIR)/logwisp/logwisp.toml.sample"; do \ "$$root$(PREFIX)/etc/rc.d/logwisp"; do \
if [ -e "$$f" ]; then rm -f "$$f"; echo "remove $$f"; fi; \ if [ -e "$$f" ]; then rm -f "$$f"; echo "remove $$f"; fi; \
done; \ done; \
for d in "$$root$(DOCDIR)" "$$root$(LICENSEDIR)"; do \ for d in "$$root$(DOCDIR)" "$$root$(LICENSEDIR)"; do \
case "$$d" in */logwisp) ;; *) echo "keep $$d (not a logwisp directory)"; continue ;; esac; \ case "$$d" in */logwisp) ;; *) echo "keep $$d (not a logwisp directory)"; continue ;; esac; \
if [ -d "$$d" ]; then rm -rf "$$d"; echo "remove $$d"; fi; \ if [ -d "$$d" ]; then rm -rf "$$d"; echo "remove $$d"; fi; \
done; \ done; \
echo "keep $$root$(SYSCONFDIR)/logwisp (configuration and credentials)" if [ -d "$$root$(SYSCONFDIR)/logwisp" ]; then echo "keep $$root$(SYSCONFDIR)/logwisp (configuration and credentials)"; fi
# deb stages install in Debian's layout and takes the architecture from the # deb stages install in Debian's layout and takes the architecture from the
# binary. An untagged or modified tree's version sorts below every release and # binary. An untagged or modified tree's version sorts below every release and
@@ -268,7 +272,23 @@ deb: release
sed -e 's/^$$/./' -e 's/^/ /' $(PKG_DIR)/freebsd/pkg-descr; } > "$$stage/DEBIAN/control"; \ sed -e 's/^$$/./' -e 's/^/ /' $(PKG_DIR)/freebsd/pkg-descr; } > "$$stage/DEBIAN/control"; \
dpkg-deb --root-owner-group --build "$$stage" "$(BIN_DIR)/logwisp_$${version}_$$arch.deb" dpkg-deb --root-owner-group --build "$$stage" "$(BIN_DIR)/logwisp_$${version}_$$arch.deb"
# arch builds the PKGBUILD from this working tree, tracked and new files, as
# the release tarball it names; no tag or download is needed. Untagged, the
# version is 0.rCOUNT.gHASH, below every release and rising with each commit.
# The package and build tree stay in bin/arch whatever makepkg.conf says.
arch:
@set -eu; dir=$(BIN_DIR)/arch; [ ! -d "$$dir" ] || chmod -R u+w "$$dir"; rm -rf "$$dir"; mkdir -p "$$dir"; \
ver=$$(echo '$(VERSION)' | sed -e 's/^v//' -e 's/-\([0-9][0-9]*\)-g/.r\1.g/' -e 's/-/./g'); \
case '$(VERSION)' in v[0-9]*) ;; *) ver="0.r$$(git rev-list --count HEAD).g$$ver" ;; esac; \
git ls-files -z --cached --others --exclude-standard | tar --null -T - --ignore-failed-read \
--transform "s,^,logwisp-$$ver/,S" -czf "$$dir/logwisp-$$ver.tar.gz"; \
sed "s/^pkgver=.*/pkgver=$$ver/" $(PKG_DIR)/arch/PKGBUILD > "$$dir/PKGBUILD"; \
cd "$$dir"; export PKGDEST="$$PWD" BUILDDIR="$$PWD"; makepkg; \
echo "install or reinstall: sudo pacman -U $$(makepkg --packagelist | grep -v -- -debug-)"
# A toolchain go downloaded into make arch's GOPATH is read-only.
clean: clean:
[ ! -d $(BIN_DIR) ] || chmod -R u+w $(BIN_DIR)
rm -rf $(BIN_DIR) rm -rf $(BIN_DIR)
version: version:
+5 -2
View File
@@ -186,8 +186,11 @@ docker run --rm --read-only --cap-drop ALL --security-opt no-new-privileges \
``` ```
`sudo make install` installs the binary, the manual, shell completion, a `sudo make install` installs the binary, the manual, shell completion, a
sample configuration and a systemd unit or FreeBSD rc.d script; `make deb` systemd unit or FreeBSD rc.d script and its configuration; `make deb` and
packages them for Debian and Ubuntu. See [Installation](doc/installation.md). `make arch` package them for Debian, Ubuntu and Arch. Without a service,
`make install SERVICE=no PREFIX="$HOME/.local"` installs `lw` alone and
`lw config init` writes the annotated configuration it reads. See
[Installation](doc/installation.md).
## System Requirements ## System Requirements
+44
View File
@@ -6,11 +6,15 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/fs"
"os"
"path/filepath"
"regexp" "regexp"
"slices" "slices"
"strings" "strings"
"unicode" "unicode"
shipped "github.com/lixenwraith/logwisp/config"
"github.com/lixenwraith/logwisp/internal/config" "github.com/lixenwraith/logwisp/internal/config"
"github.com/lixenwraith/toml" "github.com/lixenwraith/toml"
@@ -37,6 +41,8 @@ var commands = []command{
"Files are never overwritten; keys are written mode 0600.", tlsCommands}, "Files are never overwritten; keys are written mode 0600.", tlsCommands},
{"preset", "Show the pipeline a preset makes, as TOML", {"preset", "Show the pipeline a preset makes, as TOML",
"lw --preset NAME,key=value runs one; flags here are its keys.", presetCommands()}, "lw --preset NAME,key=value runs one; flags here are its keys.", presetCommands()},
{"config", "Place the annotated default configuration",
"Files are never overwritten; lw --dump prints the effective configuration.", configCommands},
} }
// shorts are lw's one-letter flags, each its long flag in every command that // shorts are lw's one-letter flags, each its long flag in every command that
@@ -287,3 +293,41 @@ func presetCommands() []subcommand {
} }
return subs return subs
} }
var configCommands = []subcommand{
{"init", "[--out FILE]",
"Write the annotated default configuration, to edit and run",
nil, defineConfigInit},
}
func defineConfigInit(flags *flag.FlagSet) func(stdout, stderr io.Writer) error {
out := flags.String("out", "", "`file` to write (default: ~/.config/logwisp/logwisp.toml)")
return func(_, stderr io.Writer) error {
user, err := config.UserFile()
path := cmp.Or(*out, user)
if path == "" {
return fmt.Errorf("no home directory for the default file (%v): name one with --out", err)
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
if err := writeNew(path, shipped.Sample, 0o644); errors.Is(err, fs.ErrExist) {
if fi, err := os.Stat(path); err == nil && fi.IsDir() {
return fmt.Errorf("%s is a directory: name the file, as in %s", path, filepath.Join(path, "logwisp.toml"))
}
return fmt.Errorf("%s exists; remove it to replace it", path)
} else if err != nil {
return err
}
// -c refuses a path that climbs out with ..; an absolute one loads anywhere
if abs, err := filepath.Abs(path); err == nil {
path = abs
}
if found, err := filepath.Abs(config.DefaultPath()); err == nil && found == path {
fmt.Fprintf(stderr, "wrote %s; lw reads it without -c\n", path)
} else {
fmt.Fprintf(stderr, "wrote %s; run it with lw -c %s\n", path, path)
}
return nil
}
}
+30
View File
@@ -11,6 +11,7 @@ import (
"strings" "strings"
"testing" "testing"
shipped "github.com/lixenwraith/logwisp/config"
"github.com/lixenwraith/logwisp/internal/config" "github.com/lixenwraith/logwisp/internal/config"
"github.com/lixenwraith/logwisp/internal/testutil" "github.com/lixenwraith/logwisp/internal/testutil"
"github.com/lixenwraith/logwisp/internal/tlsx" "github.com/lixenwraith/logwisp/internal/tlsx"
@@ -261,6 +262,35 @@ func TestTLSCommandsIssueAVerifiableChain(t *testing.T) {
} }
} }
// lw config init writes the shipped configuration to the file lw reads without
// -c, or to --out, says how lw finds it, and never replaces a file
func TestConfigInitWritesTheShippedFileOnce(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("LOGWISP_CONFIG_FILE", "")
t.Setenv("LOGWISP_CONFIG_DIR", "")
other := filepath.Join(t.TempDir(), "etc", "logwisp.toml")
for _, c := range []struct {
args []string
hint string
}{
{[]string{"init"}, "lw reads it without -c"},
{[]string{"init", "--out", other}, "lw -c " + other},
} {
if code, _, stderr := runCommand(t, "config", c.args...); code != 0 || !strings.Contains(stderr, c.hint) {
t.Fatalf("lw config %v: exit %d: %s", c.args, code, stderr)
}
if code, _, stderr := runCommand(t, "config", c.args...); code != 1 || !strings.Contains(stderr, "exists") {
t.Fatalf("lw config %v again: exit %d: %s", c.args, code, stderr)
}
}
for _, path := range []string{filepath.Join(home, ".config", "logwisp", "logwisp.toml"), other} {
if data, err := os.ReadFile(path); err != nil || !bytes.Equal(data, shipped.Sample) {
t.Errorf("%s is not the shipped configuration: %v", path, err)
}
}
}
var update = flag.Bool("update", false, "rewrite the completion scripts: make completion") var update = flag.Bool("update", false, "rewrite the completion scripts: make completion")
// The installed completion scripts are what lw's tables generate // The installed completion scripts are what lw's tables generate
+8
View File
@@ -0,0 +1,8 @@
// Package config ships logwisp.toml, the annotated default configuration that
// make install and the packages place and lw config init writes.
package config
import _ "embed"
//go:embed logwisp.toml
var Sample []byte
+17
View File
@@ -150,12 +150,29 @@ preset:*)
'(-h --help)'{-h,--help}'[print the flags]' ;; '(-h --help)'{-h,--help}'[print the flags]' ;;
esac esac
return ;; return ;;
config:2) ;;
config:3)
described=(
'init:Write the annotated default configuration, to edit and run'
)
_describe -t commands 'lw config command' described
return ;;
config:*)
shift 2 words
(( CURRENT -= 2 ))
case ${words[1]} in
init) _arguments -S \
'--out=[file to write (default: ~/.config/logwisp/logwisp.toml)]:value:_default' \
'(-h --help)'{-h,--help}'[print the flags]' ;;
esac
return ;;
esac esac
if (( CURRENT == 2 )) && [[ $PREFIX != -* ]]; then if (( CURRENT == 2 )) && [[ $PREFIX != -* ]]; then
described=( described=(
'auth:SCRAM users, bearer tokens, tcp sink viewing' 'auth:SCRAM users, bearer tokens, tcp sink viewing'
'tls:A CA and certificates for TLS and mTLS' 'tls:A CA and certificates for TLS and mTLS'
'preset:Show the pipeline a preset makes, as TOML' 'preset:Show the pipeline a preset makes, as TOML'
'config:Place the annotated default configuration'
'help:Print the help and exit' 'help:Print the help and exit'
) )
_describe -t commands 'lw command' described _describe -t commands 'lw command' described
+10 -1
View File
@@ -10,7 +10,7 @@ _lw() {
elif [[ $prev == = ]]; then prev=${COMP_WORDS[i-2]} elif [[ $prev == = ]]; then prev=${COMP_WORDS[i-2]}
fi fi
case ${COMP_WORDS[1]}:$i in case ${COMP_WORDS[1]}:$i in
*:1) words='--auto_reload --check -t --color --config -c --dump --filter --format --heartbeat --help -h --logging.console.target --logging.file.directory --logging.file.max_size_mb --logging.file.max_total_size_mb --logging.file.name --logging.file.retention_hours --logging.format --logging.level --logging.output --logging.sanitization --pipeline --preset -p --quiet -q --rate-limit --sink --source --status_reporter --version -V auth tls preset help' ;; *:1) words='--auto_reload --check -t --color --config -c --dump --filter --format --heartbeat --help -h --logging.console.target --logging.file.directory --logging.file.max_size_mb --logging.file.max_total_size_mb --logging.file.name --logging.file.retention_hours --logging.format --logging.level --logging.output --logging.sanitization --pipeline --preset -p --quiet -q --rate-limit --sink --source --status_reporter --version -V auth tls preset config help' ;;
auth:2) words='add-user remove-user token stream' ;; auth:2) words='add-user remove-user token stream' ;;
auth:*) auth:*)
case ${COMP_WORDS[2]} in case ${COMP_WORDS[2]} in
@@ -80,6 +80,15 @@ _lw() {
esac esac
words='--allow --cert --client_ca --deny --format --hosts --issuer_cert --issuer_key --key --listen --out --tls --transport --users --help -h' ;; words='--allow --cert --client_ca --deny --format --hosts --issuer_cert --issuer_key --key --listen --out --tls --transport --users --help -h' ;;
esac ;; esac ;;
config:2) words='init' ;;
config:*)
case ${COMP_WORDS[2]} in
init)
case $prev in
--out|-out) COMPREPLY=(); return ;;
esac
words='--out --help -h' ;;
esac ;;
*) *)
case $prev in case $prev in
--logging.file.directory) _lw_reply -d; return ;; --logging.file.directory) _lw_reply -d; return ;;
+32 -27
View File
@@ -12,34 +12,35 @@ set -l __lw_first 'test (count (commandline -opc)) -eq 1'
complete -c lw -n $__lw_first -a auth -d 'SCRAM users, bearer tokens, tcp sink viewing' complete -c lw -n $__lw_first -a auth -d 'SCRAM users, bearer tokens, tcp sink viewing'
complete -c lw -n $__lw_first -a tls -d 'A CA and certificates for TLS and mTLS' complete -c lw -n $__lw_first -a tls -d 'A CA and certificates for TLS and mTLS'
complete -c lw -n $__lw_first -a preset -d 'Show the pipeline a preset makes, as TOML' complete -c lw -n $__lw_first -a preset -d 'Show the pipeline a preset makes, as TOML'
complete -c lw -n $__lw_first -a config -d 'Place the annotated default configuration'
complete -c lw -n $__lw_first -a help -d 'Print the help and exit' complete -c lw -n $__lw_first -a help -d 'Print the help and exit'
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l auto_reload complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l auto_reload
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l check -o t complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l check -o t
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l color complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l color
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l config -o c -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l config -o c -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l dump complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l dump
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l filter -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l filter -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l format -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l format -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l heartbeat -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l heartbeat -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l help -o h complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l help -o h
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l logging.console.target -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l logging.console.target -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l logging.file.directory -x -a '(__fish_complete_directories)' complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l logging.file.directory -x -a '(__fish_complete_directories)'
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l logging.file.max_size_mb -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l logging.file.max_size_mb -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l logging.file.max_total_size_mb -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l logging.file.max_total_size_mb -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l logging.file.name -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l logging.file.name -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l logging.file.retention_hours -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l logging.file.retention_hours -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l logging.format -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l logging.format -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l logging.level -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l logging.level -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l logging.output -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l logging.output -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l logging.sanitization -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l logging.sanitization -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l pipeline -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l pipeline -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l preset -o p -x -a 'pipe tail serve edge aggregator' complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l preset -o p -x -a 'pipe tail serve edge aggregator'
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l quiet -o q complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l quiet -o q
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l rate-limit -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l rate-limit -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l sink -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l sink -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l source -r -F complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l source -r -F
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l status_reporter complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l status_reporter
complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset' -l version -o V complete -c lw -n 'not __lw_in auth; and not __lw_in tls; and not __lw_in preset; and not __lw_in config' -l version -o V
complete -c lw -n '__lw_in auth; and test (count (commandline -opc)) -eq 2' -a add-user -d 'Add a user to a credentials file, or replace its password' complete -c lw -n '__lw_in auth; and test (count (commandline -opc)) -eq 2' -a add-user -d 'Add a user to a credentials file, or replace its password'
complete -c lw -n '__lw_in auth; and test (count (commandline -opc)) -eq 2' -a remove-user -d 'Remove a user from a credentials file' complete -c lw -n '__lw_in auth; and test (count (commandline -opc)) -eq 2' -a remove-user -d 'Remove a user from a credentials file'
@@ -143,3 +144,7 @@ complete -c lw -n '__lw_in preset aggregator' -l tls -r -F -d 'self|issuer|files
complete -c lw -n '__lw_in preset aggregator' -l transport -r -F -d 'tcp|http: tcp_chain or http_chain' complete -c lw -n '__lw_in preset aggregator' -l transport -r -F -d 'tcp|http: tcp_chain or http_chain'
complete -c lw -n '__lw_in preset aggregator' -l users -r -F -d 'credentials file (lw auth add-user): edges log in with SCRAM' complete -c lw -n '__lw_in preset aggregator' -l users -r -F -d 'credentials file (lw auth add-user): edges log in with SCRAM'
complete -c lw -n '__lw_in preset aggregator' -l help -o h -d 'print the flags' complete -c lw -n '__lw_in preset aggregator' -l help -o h -d 'print the flags'
complete -c lw -n '__lw_in config; and test (count (commandline -opc)) -eq 2' -a init -d 'Write the annotated default configuration, to edit and run'
complete -c lw -n '__lw_in config init' -l out -r -F -d 'file to write (default: ~/.config/logwisp/logwisp.toml)'
complete -c lw -n '__lw_in config init' -l help -o h -d 'print the flags'
+25 -3
View File
@@ -9,6 +9,7 @@ lw --dump [options]
lw auth COMMAND [flags] lw auth COMMAND [flags]
lw tls COMMAND [flags] lw tls COMMAND [flags]
lw preset NAME [flags] lw preset NAME [flags]
lw config init [--out FILE]
lw help | -h | --help lw help | -h | --help
lw -V | --version lw -V | --version
``` ```
@@ -21,7 +22,9 @@ service.
A [preset](#presets) builds a common pipeline from a few keys. `lw auth` A [preset](#presets) builds a common pipeline from a few keys. `lw auth`
manages SCRAM credentials and logs in to `scram` listeners manages SCRAM credentials and logs in to `scram` listeners
([below](#lw-auth)); `lw tls` makes a certificate authority and certificates ([below](#lw-auth)); `lw tls` makes a certificate authority and certificates
([below](#lw-tls)). A command is recognized only as the first argument. ([below](#lw-tls)); `lw config init` writes the annotated default
configuration ([below](#lw-config)). A command is recognized only as the
first argument.
## Options ## Options
@@ -43,8 +46,7 @@ manages SCRAM credentials and logs in to `scram` listeners
`--password-file`); configuration keys, spec keys and preset keys keep `--password-file`); configuration keys, spec keys and preset keys keep
TOML's `_` and `.` (`--status_reporter`, `--sink http,tls.cert_file=F`, TOML's `_` and `.` (`--status_reporter`, `--sink http,tls.cert_file=F`,
`lw preset edge --password_file F`). `lw preset edge --password_file F`).
- `lw auth`, `lw tls` and `lw preset` also take a long option after one dash - The commands also take a long option after one dash (`-user NAME`).
(`-user NAME`), as earlier releases documented.
Any scalar configuration key is settable as a flag using its TOML path; Any scalar configuration key is settable as a flag using its TOML path;
pipelines have [their own flags](#pipelines): pipelines have [their own flags](#pipelines):
@@ -540,3 +542,23 @@ lw tls ca --dir /etc/logwisp/pki
lw tls cert --ca-dir /etc/logwisp/pki --name agg.example.org --server lw tls cert --ca-dir /etc/logwisp/pki --name agg.example.org --server
lw tls cert --ca-dir /etc/logwisp/pki --name edge-01 --client lw tls cert --ca-dir /etc/logwisp/pki --name edge-01 --client
``` ```
## `lw config`
```
lw config init [--out FILE]
```
`init` writes the annotated configuration that ships as
`config/logwisp.toml`, every option with its default, to edit and run. It
never replaces a file (remove one to replace it). Without `--out` it writes
`~/.config/logwisp/logwisp.toml`, the file lw reads without `-c` unless a
`LOGWISP_CONFIG_*` variable names another; with that file in place, lw
without pipeline options runs its pipelines rather than filtering stdin.
Any other path is run with `-c FILE`; lw prints which applies. `lw --dump` prints the
effective configuration instead, flags and environment included.
```bash
lw config init
sudo lw config init --out /usr/local/etc/logwisp/logwisp.toml
```
+111 -23
View File
@@ -42,7 +42,8 @@ The Makefile works with GNU make and BSD make alike. Targets:
- Container - Container
- `make image`, `make image-check`: see [Container Image](#container-image) - `make image`, `make image-check`: see [Container Image](#container-image)
- Install - Install
- `make install`, `make uninstall`, `make deb`: see [Installing](#installing) - `make install`, `make uninstall`, `make deb`, `make arch`: see
[Installing](#installing)
Variables: Variables:
@@ -64,8 +65,9 @@ checkout.
## Installing ## Installing
`make install` copies what a package ships and compiles nothing, so build `make install` copies what a package ships and compiles nothing, so build
first. It honours `DESTDIR`, `PREFIX`, `SYSCONFDIR` and `BINDIR` (default first. It honours `DESTDIR`, `PREFIX`, `SYSCONFDIR`, `BINDIR` (default
`$PREFIX/bin`, the path the systemd unit and the rc.d script run `lw` from): `$PREFIX/bin`, the path the systemd unit and the rc.d script run `lw` from)
and `SERVICE`:
- Linux defaults: `PREFIX=/usr`, `SYSCONFDIR=/etc`. Outside a package - Linux defaults: `PREFIX=/usr`, `SYSCONFDIR=/etc`. Outside a package
manager, prefer `PREFIX=/usr/local`; systemd reads units, sysusers and manager, prefer `PREFIX=/usr/local`; systemd reads units, sysusers and
@@ -73,6 +75,8 @@ first. It honours `DESTDIR`, `PREFIX`, `SYSCONFDIR` and `BINDIR` (default
- FreeBSD defaults: `PREFIX=/usr/local`, `SYSCONFDIR=/usr/local/etc`. - FreeBSD defaults: `PREFIX=/usr/local`, `SYSCONFDIR=/usr/local/etc`.
- `INSTALL_OS` (default: `uname -s`) picks the layout, so - `INSTALL_OS` (default: `uname -s`) picks the layout, so
`INSTALL_OS=FreeBSD DESTDIR=...` stages the FreeBSD one on another system. `INSTALL_OS=FreeBSD DESTDIR=...` stages the FreeBSD one on another system.
- `SERVICE`: `yes` (default) adds the service and its configuration; `no`
installs `lw` alone ([Without a Service](#without-a-service)).
- `BASHCOMPDIR`, `ZSHCOMPDIR`, `FISHCOMPDIR`: where the shell completion - `BASHCOMPDIR`, `ZSHCOMPDIR`, `FISHCOMPDIR`: where the shell completion
goes, by default `$PREFIX/share/bash-completion/completions`, goes, by default `$PREFIX/share/bash-completion/completions`,
`$PREFIX/share/zsh/site-functions` (`/usr/share/zsh/vendor-completions` `$PREFIX/share/zsh/site-functions` (`/usr/share/zsh/vendor-completions`
@@ -84,7 +88,7 @@ make release
sudo make install PREFIX=/usr/local sudo make install PREFIX=/usr/local
``` ```
Every system gets: Every install gets:
- `$BINDIR/lw` and the manual `$PREFIX/share/man/man1/lw.1` - `$BINDIR/lw` and the manual `$PREFIX/share/man/man1/lw.1`
- completion for `lw`, its commands, flags, files and preset names: - completion for `lw`, its commands, flags, files and preset names:
@@ -92,10 +96,13 @@ Every system gets:
- `$PREFIX/share/doc/logwisp/` (this documentation) and - `$PREFIX/share/doc/logwisp/` (this documentation) and
`$PREFIX/share/licenses/logwisp/LICENSE` `$PREFIX/share/licenses/logwisp/LICENSE`
A service install also gets the service files below and, on Linux and
FreeBSD alike, `$SYSCONFDIR/logwisp/logwisp.toml`: the annotated sample
configuration, placed when none is there and otherwise kept. Edit it before
starting the service.
### Linux (systemd) ### Linux (systemd)
- `$SYSCONFDIR/logwisp/logwisp.toml`: the annotated sample configuration,
kept when one is already there. Edit it before starting the service.
- `$PREFIX/lib/systemd/system/logwisp.service`: runs - `$PREFIX/lib/systemd/system/logwisp.service`: runs
`lw -c $SYSCONFDIR/logwisp/logwisp.toml` as the `logwisp` user, from `lw -c $SYSCONFDIR/logwisp/logwisp.toml` as the `logwisp` user, from
`/var/lib/logwisp`; `systemctl reload` sends `SIGHUP`. `/var/lib/logwisp`; `systemctl reload` sends `SIGHUP`.
@@ -133,11 +140,11 @@ sudo systemctl reload logwisp
### FreeBSD (rc.d) ### FreeBSD (rc.d)
- `$SYSCONFDIR/logwisp/logwisp.toml.sample`, copied to `logwisp.toml` when `$PREFIX/etc/rc.d/logwisp` runs `lw` through `daemon(8)` as `logwisp_user`,
none exists and `DESTDIR` is empty with its output in syslog under the tag `logwisp`; `service logwisp reload`
- `$PREFIX/etc/rc.d/logwisp`: runs `lw` through `daemon(8)` as sends `SIGHUP`. The port in `deploy/package/freebsd/` places the
`logwisp_user`, with its output in syslog under the tag `logwisp`; configuration the ports way, to the same effect: `@sample` installs
`service logwisp reload` sends `SIGHUP` `logwisp.toml.sample` and copies it to `logwisp.toml` when none exists.
```bash ```bash
sudo pw useradd logwisp -d /nonexistent -s /usr/sbin/nologin -c "LogWisp log transport" sudo pw useradd logwisp -d /nonexistent -s /usr/sbin/nologin -c "LogWisp log transport"
@@ -172,18 +179,93 @@ sudo systemctl enable --now logwisp
- Installing creates the `logwisp` account and its directories where - Installing creates the `logwisp` account and its directories where
`systemd-sysusers` and `systemd-tmpfiles` exist; the service waits to be `systemd-sysusers` and `systemd-tmpfiles` exist; the service waits to be
enabled, and an upgrade restarts it if it runs. enabled, and an upgrade or reinstall restarts it if it runs.
- `/etc/logwisp/logwisp.toml` is a conffile: an upgrade keeps your edits, - `/etc/logwisp/logwisp.toml` is a conffile: an upgrade or reinstall keeps
and only a purge removes it. your edits, and only a purge removes it.
- The same version again: `sudo apt install --reinstall ./bin/logwisp_*.deb`.
- Removing stops and disables the service. The account, its directories - Removing stops and disables the service. The account, its directories
and any credentials in `/etc/logwisp` stay, as with `make uninstall`. and any credentials in `/etc/logwisp` stay, as with `make uninstall`.
### Arch Linux (makepkg)
`make arch` builds the PKGBUILD in `deploy/package/arch/` from the working
tree, committed or not, into `bin/arch/`, then prints the `pacman -U` line
that installs the package. It needs `base-devel` and `go`, and runs as a
user: `makepkg` refuses root.
```bash
make arch
sudo pacman -U bin/arch/logwisp-<version>-1-x86_64.pkg.tar.zst
sudo systemctl enable --now logwisp
```
- pacman's hooks create the `logwisp` account and its directories; the
service waits to be enabled.
- `/etc/logwisp/logwisp.toml` is a `backup` file: an upgrade or reinstall
keeps your edits, and a shipped version that changed lands beside it as
`logwisp.toml.pacnew`. `pacman -R` keeps an edited one as
`logwisp.toml.pacsave`.
- Reinstall: `make arch` and the same `pacman -U`. pacman does not restart
the service: `sudo systemctl restart logwisp`.
- An earlier `sudo make install` conflicts with the package
(`PREFIX=/usr`) or overrides it (`PREFIX=/usr/local`, whose unit and `lw`
come first): `sudo make uninstall` with that `PREFIX` first. The
configuration it keeps is no conflict: pacman keeps it, and when it was
edited, the shipped one lands beside it as `logwisp.toml.pacnew`.
### Reinstalling
Every method runs again over its own installation, for an upgrade or the
same version, and keeps an edited configuration: `make install` leaves an
existing `$SYSCONFDIR/logwisp/logwisp.toml` alone, and the packages treat it
as above. Only the `.deb` restarts a running service; otherwise restart it
(`sudo systemctl restart logwisp`, `sudo service logwisp restart`). To see
what a release changed in the shipped configuration:
```bash
d=$(mktemp -d); lw config init --out "$d/logwisp.toml"; diff "$d/logwisp.toml" /etc/logwisp/logwisp.toml
```
### Without a Service
`SERVICE=no` installs `lw`, its manual, completion and documentation, and
nothing else: no service files, account or configuration. For one user,
without root:
```bash
make release
make install SERVICE=no PREFIX="$HOME/.local"
lw config init
```
- `~/.local/bin` goes on `PATH`. `man lw` then finds the manual, and bash
and fish the completion; zsh needs
`fpath=(~/.local/share/zsh/site-functions $fpath)` before `compinit`.
- `make uninstall PREFIX="$HOME/.local"` removes it.
For every user: `sudo make install SERVICE=no PREFIX=/usr/local`.
`lw config init` writes the shipped, annotated configuration, never over an
existing file, where you choose:
- `lw config init`: `~/.config/logwisp/logwisp.toml`, which `lw` reads
without `-c` unless a `LOGWISP_CONFIG_*` variable names another file. With
it in place, `lw` without pipeline options runs its
pipelines instead of copying stdin to stdout; pipeline options and presets
still replace them.
- `sudo lw config init --out /usr/local/etc/logwisp/logwisp.toml`: a file
for every user, named with `-c`, or without it by
`LOGWISP_CONFIG_DIR=/usr/local/etc/logwisp`
([File Location](configuration.md#file-location)).
`lw --check` (with the same `-c`) then validates it.
### Uninstall ### Uninstall
`sudo make uninstall` (with the `PREFIX` used to install) removes everything `sudo make uninstall` (with the `PREFIX` used to install) removes everything
`make install` wrote except `$SYSCONFDIR/logwisp`, which holds configuration `make install` wrote except `$SYSCONFDIR/logwisp`, which holds configuration
and credentials. Remove that, the working directories and the account by and credentials, and any file `lw config init` wrote. Remove those, the
hand. working directories and the account by hand.
## Container Image ## Container Image
@@ -269,15 +351,21 @@ docker run -d --name logwisp \
In place: In place:
- `make install` with `DESTDIR`, `PREFIX` and `SYSCONFDIR`, the manual, - `make install` with `DESTDIR`, `PREFIX`, `SYSCONFDIR` and `SERVICE`, the
shell completion and the service files in `deploy/package/` manual, shell completion and the service files in `deploy/package/`
- `make deb` - `make deb`, and `make arch` building the PKGBUILD from the working tree
- skeletons: `deploy/package/arch/PKGBUILD` and a `sysutils/logwisp` port in - skeletons: `deploy/package/arch/PKGBUILD` and a `sysutils/logwisp` port in
`deploy/package/freebsd/` `deploy/package/freebsd/`
- `.github/workflows/package.yml`, on changes to them: `makepkg` on the - `.github/workflows/package.yml`, on changes to them:
PKGBUILD in an Arch container, the `.deb` installed, started, removed and - Arch container: `make arch`, installed over an edited configuration,
purged on Ubuntu, BSD make building, installing and running the rc.d reinstalled keeping it, removed
service on FreeBSD, and `make image image-check` - Ubuntu: the `.deb` installed, started, reinstalled keeping an edited
configuration, removed and purged
- Ubuntu, without a service: `make install SERVICE=no` into a home twice,
then `lw config init` writing the file `lw` reads, once
- FreeBSD: BSD make building, installing twice keeping an edited
configuration, and running the rc.d service
- `make image image-check`
Still missing: release tags, which both skeletons download; submitting the Still missing: release tags, which both skeletons download; submitting the
AUR package and the port; a Debian source package for a PPA or the archive. AUR package and the port; a Debian source package for a PPA or the archive.
+34 -13
View File
@@ -20,7 +20,7 @@
.Op Fl \-rate\-limit Ar spec .Op Fl \-rate\-limit Ar spec
.Op Fl \-heartbeat Ar spec .Op Fl \-heartbeat Ar spec
.Nm .Nm
.Cm auth | tls | preset .Cm auth | tls | preset | config
.Ar command .Ar command
.Op Ar flags .Op Ar flags
.Nm .Nm
@@ -124,12 +124,7 @@ The options of
join words with join words with
.Ql - ; .Ql - ;
a configuration or preset key keeps its TOML spelling. a configuration or preset key keeps its TOML spelling.
The The commands also take a long option after a single dash.
.Cm auth ,
.Cm tls
and
.Cm preset
commands also take a long option after a single dash.
.Pp .Pp
The options are as follows: The options are as follows:
.Bl -tag -width Ds .Bl -tag -width Ds
@@ -549,6 +544,19 @@ An IPv6
.Ar host .Ar host
is written in brackets, as in is written in brackets, as in
.Li [2001:db8::1]:8443 . .Li [2001:db8::1]:8443 .
.Ss Configuration command
.Bl -tag -width Ds
.It Cm config init Op Fl \-out Ar file
Write the annotated configuration
.Nm
ships, every option with its default, to
.Ar file ,
by default
.Pa ~/.config/logwisp/logwisp.toml ,
the first file in
.Sx FILES .
An existing file is never replaced.
.El
.Sh ENVIRONMENT .Sh ENVIRONMENT
.Bl -tag -width Ds .Bl -tag -width Ds
.It Ev LOGWISP_ Ns Ar KEY .It Ev LOGWISP_ Ns Ar KEY
@@ -611,7 +619,11 @@ reads the first of these that exists:
.Pa ./logwisp.toml .Pa ./logwisp.toml
.El .El
.Pp .Pp
Packages install a sample configuration and service files that pass it with .Nm
.Cm config init
writes the first.
A service installation places the annotated configuration, keeping one
already there, and service files that pass it with
.Fl c : .Fl c :
.Bl -tag -width Ds .Bl -tag -width Ds
.It Pa /etc/logwisp/logwisp.toml .It Pa /etc/logwisp/logwisp.toml
@@ -620,9 +632,7 @@ The configuration on Linux, used by
.It Pa /usr/local/etc/logwisp/logwisp.toml .It Pa /usr/local/etc/logwisp/logwisp.toml
The configuration on The configuration on
.Fx , .Fx ,
installed as used by
.Pa logwisp.toml.sample
and used by
.Pa /usr/local/etc/rc.d/logwisp . .Pa /usr/local/etc/rc.d/logwisp .
.It Pa /var/lib/logwisp , /var/db/logwisp .It Pa /var/lib/logwisp , /var/db/logwisp
The service's working directory on Linux and on The service's working directory on Linux and on
@@ -668,9 +678,10 @@ the shell reports status 141.
.Pp .Pp
.Nm .Nm
.Cm auth , .Cm auth ,
.Cm tls .Cm tls ,
and
.Cm preset .Cm preset
and
.Cm config
do the same: 0 on success or help, 1 on failure, and 2 on a usage error. do the same: 0 on success or help, 1 on failure, and 2 on a usage error.
.Sh SIGNALS .Sh SIGNALS
.Bl -tag -width "SIGINT, SIGTERM" .Bl -tag -width "SIGINT, SIGTERM"
@@ -749,6 +760,16 @@ edge# lw \-\-preset edge,path=/var/log/app,to=agg.example.org:9000,\e
ca=/etc/logwisp/pki/ca.crt,user=edge-01,password_file=/root/edge-01.pass ca=/etc/logwisp/pki/ca.crt,user=edge-01,password_file=/root/edge-01.pass
.Ed .Ed
.Pp .Pp
Write the annotated configuration to the file
.Nm
reads without
.Fl c ,
edit it, then check it:
.Bd -literal -offset indent
$ lw config init
$ lw \-\-check
.Ed
.Pp
Check a configuration without binding a port or touching a log file: Check a configuration without binding a port or touching a log file:
.Bd -literal -offset indent .Bd -literal -offset indent
$ lw \-\-check \-c /etc/logwisp/logwisp.toml $ lw \-\-check \-c /etc/logwisp/logwisp.toml
+11 -8
View File
@@ -11,15 +11,18 @@ The foundation exists:
24.04 and 26.04, and the FreeBSD 15.1 ports tree. 24.04 and 26.04, and the FreeBSD 15.1 ports tree.
- The canonical module path `github.com/lixenwraith/logwisp`, which - The canonical module path `github.com/lixenwraith/logwisp`, which
`go install`, FreeBSD's `USES=go:modules` and Debian's dh-golang expect. `go install`, FreeBSD's `USES=go:modules` and Debian's dh-golang expect.
- `make install` with `DESTDIR`, `PREFIX` and `SYSCONFDIR`: the binary, the - `make install` with `DESTDIR`, `PREFIX`, `SYSCONFDIR` and `SERVICE`: the
`doc/lw.1` manual, bash, zsh and fish completion generated from `cmd/lw`'s binary, the `doc/lw.1` manual, bash, zsh and fish completion generated from
`shorts` and `commands` tables, and the service files in `deploy/package/` `cmd/lw`'s `shorts` and `commands` tables, and the service files in
(systemd unit, sysusers, tmpfiles, FreeBSD rc.d script). `deploy/package/` (systemd unit, sysusers, tmpfiles, FreeBSD rc.d script)
- `make deb`, a binary package built with `dpkg-deb`. with the configuration, kept on reinstall.
- `make deb`, a binary package built with `dpkg-deb`, and `make arch`, the
PKGBUILD built from the working tree.
- Skeletons in `deploy/package/arch/` and `deploy/package/freebsd/`. - Skeletons in `deploy/package/arch/` and `deploy/package/freebsd/`.
- `.github/workflows/package.yml`: `makepkg` on the PKGBUILD, the `.deb` - `.github/workflows/package.yml`: `make arch`, the `.deb` and BSD make on
installed, started and purged, BSD make and the rc.d service on FreeBSD FreeBSD 15.1, each reinstalled over an edited configuration; the systemd
15.1, and `make image image-check`. and rc.d services run; a home install with `SERVICE=no` and
`lw config init`; and `make image image-check`.
What remains is the maintainer's: it needs push rights, an identity or a What remains is the maintainer's: it needs push rights, an identity or a
release tag, and both skeletons download the tagged source. release tag, and both skeletons download the tagged source.
+16 -2
View File
@@ -219,11 +219,25 @@ func resolveConfigPath(file string) (path string, isExplicit bool) {
if configDir := os.Getenv("LOGWISP_CONFIG_DIR"); configDir != "" { if configDir := os.Getenv("LOGWISP_CONFIG_DIR"); configDir != "" {
return filepath.Join(configDir, "logwisp.toml"), true return filepath.Join(configDir, "logwisp.toml"), true
} }
if homeDir, err := os.UserHomeDir(); err == nil { if path, err := UserFile(); err == nil {
path = filepath.Join(homeDir, ".config", "logwisp", "logwisp.toml")
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {
return path, false return path, false
} }
} }
return "logwisp.toml", false return "logwisp.toml", false
} }
// DefaultPath is the file lw reads without -c
func DefaultPath() string {
path, _ := resolveConfigPath("")
return path
}
// UserFile is ~/.config/logwisp/logwisp.toml, the file lw reads first without -c
func UserFile() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".config", "logwisp", "logwisp.toml"), nil
}