Skip to content

SEPE-1165: Add CI, document the fork, and fix three agent bugs - #9

Merged
eschoeller merged 5 commits into
mainfrom
SEPE-1165_ci_and_agent_fixes
Jul 28, 2026
Merged

SEPE-1165: Add CI, document the fork, and fix three agent bugs#9
eschoeller merged 5 commits into
mainfrom
SEPE-1165_ci_and_agent_fixes

Conversation

@eschoeller

Copy link
Copy Markdown
Contributor

Recovery PR. Combines the work from #7 and #8, which were auto-closed without merging.

What happened

Merging the five-deep stack bottom-up with squash merges went wrong. gh pr merge --delete-branch removed each base branch as it merged, and GitHub auto-closes any PR whose base branch is deleted. #5's content survived because it had been rebased into #6's branch and rode along; #7's and #8's did not.

Nothing was lost — both branches were intact. The five genuinely-missing commits are cherry-picked here onto current main, unchanged and in original order:

92e3850  Add CI and document the fork                    (was #7)
69bd6c1  Pin govulncheck and harden the package check    (was #7)
da16620  Stop the logger init panicking on an unusable log path  (was #8)
c4febec  Let systemd own the runtime directory                   (was #8)
a3576aa  Do not abort postinstall when the config already exists (was #8)

All five applied without conflict. Lesson for next time: with a stack this deep, either merge without --delete-branch or retarget each child before merging its parent.

Contents

CIbuild.yml following the oit-sepe-naemon-build pipeline shape: runs on every push, publish job gated on tags.

test ── build-packages ── verify-package ── publish (tags only)

govulncheck is a deliberate gate — clearing 25 reachable CVEs is why this fork moved off the original dependency set. verify-package asserts the packaged binary path matches what the systemd unit invokes, verified to exit non-zero against a simulated regression rather than merely passing today.

Three agent bugs, all found by installing the CI-built RPM on a real RHEL 8 cluster and driving it the way Naemon does:

Bug Effect Fix
init() discarded zap's error then called Sugar() segfault when run as any non-root user — i.e. the primary use case fall back to stderr, then no-op
/var/run/pdagent shipped in the package /run is tmpfs; recreated root-owned, daemon can't unlink its own pidfile, every restart fails RuntimeDirectory=pdagent
pdagent init exits non-zero when config exists set -e aborts postinstall before the service starts — every upgrade leaves the daemon down only initialise when absent

Verification

Build, vet and the full Go suite pass. Verified on both RHEL 8 peers:

Check Before After
pdagent health as naemon, production segfault OK
/run/pdagent ownership root:root pdagent:pdagent
3 consecutive restarts failed active ×3
upgrade over existing install daemon down active

Content re-verified after cherry-pick: CI workflow, pinned govulncheck, README fork docs, signs removed, logger fix, RuntimeDirectory, /var/run unpackaged, postinstall guard — all present.

The repo had no working CI. Upstream's .circleci config does nothing on
GitHub, so nothing verified build, vet, tests, vulnerabilities or the
resulting packages on a pull request.

Add build.yml, following the pipeline shape used in oit-sepe-naemon-build:
runs on every push, with a publish job gated on tag pushes that attaches
artifacts to a GitHub Release.

  test ── build-packages ── verify-package ── publish (tags only)

The govulncheck gate is deliberate. Clearing 25 reachable CVEs is the
reason this fork moved off the original dependency set, so a regression
there should fail the build rather than be discovered later.

verify-package asserts the packaged binary path matches the path the
systemd unit and wrapper scripts invoke. That mismatch is not
hypothetical -- porting to goreleaser v2 silently moved the binary to
/usr/bin while everything referencing it expects /usr/local/bin, which
installs cleanly and then fails to start. The check was confirmed to exit
non-zero against a simulated regression, not just to pass today. It is
driven from the runner rather than via `container:` so the actions run
against the runner's Node rather than RHEL 8's older glibc.

Drop the signs block. It expected a GPG key that does not exist, and
oit-sepe-naemon-build does not sign its packages either.

Document the fork in the README, including the changes made relative to
upstream as Apache 2.0 section 4(b) asks for, the pipeline, and the
release procedure.

(cherry picked from commit 60f76f2)
Pin govulncheck to v1.6.0 via an env var so a tool release cannot change
the gate's behaviour without a visible commit. This does not weaken
coverage: the vulnerability database is fetched from vuln.go.dev at run
time, so a pinned binary still picks up newly published advisories.

Replace `rpm=$(ls /dist/*x86_64.rpm)` in the layout check. The glob
silently produced an empty value when nothing matched, which would have
turned a missing package into a confusing rpm error rather than a clear
failure. Now every built RPM is checked rather than one representative
arch, and an empty dist/ fails loudly.

Verified: the check reports OK across all three RPMs, and exits non-zero
against an empty dist/.

(cherry picked from commit cff8809)
init() discarded the error from zap's Build() and then called Sugar() on
the result, so any failure to construct the logger surfaced as a nil
pointer dereference during package initialisation -- before main() runs,
producing a stack trace rather than a diagnostic.

This is reachable in ordinary use. The daemon runs as pdagent and owns
/var/log/pdagent, but the CLI runs as whoever invoked it. A monitoring
system calling `pdagent nagios enqueue` does so as its own user, which
has no reason to be able to write the daemon's log, so opening it fails
and the command crashes:

  panic: runtime error: invalid memory address or nil pointer dereference
  go.uber.org/zap.(*Logger).Sugar(...)
  github.com/PagerDuty/go-pdagent/pkg/common.init.2()
          pkg/common/logging.go:22

Fall back to stderr when the log file cannot be opened, and to a no-op
logger if even that fails, so Sugar() always has a receiver. Being unable
to write a log file should not stop an event being enqueued.

Extracted into newBaseLogger() with the path as a variable so the failure
can be tested. Verified on RHEL 8: `pdagent health` as the naemon user
with APP_ENV=production returned a segfault before and returns OK now.

(cherry picked from commit c908e52)
/var/run/pdagent was shipped in the package. /run is tmpfs, so it does
not survive a reboot, and a package operation recreates it owned by root
while the daemon runs as User=pdagent. Unlinking a file needs write
permission on the containing directory, so the daemon could not remove
its own pidfile on shutdown:

  pdagent[10769]: remove /var/run/pdagent/pidfile: permission denied
  pdagent[10865]: pidfile already exists

Every subsequent start then failed, leaving the service dead after any
restart.

Drop the directory from the package and declare RuntimeDirectory= in the
unit instead. systemd creates /run/pdagent with User/Group ownership
before ExecStart and removes it on stop, which takes the pidfile with it
and makes a stale pidfile impossible.

Verified on RHEL 8: three consecutive restarts, all active, with
/run/pdagent owned pdagent:pdagent.

(cherry picked from commit 70afed8)
`pdagent init` refuses to overwrite an existing config and exits
non-zero. Package managers preserve /etc/pdagent/config.yaml across
removal and upgrade, so on every upgrade the scriptlet aborted under
`set -e`:

  Error writing config: Config File "/etc/pdagent/config.yaml" Already Exists
  Error in POSTIN scriptlet in rpm package oit-sepe-go-pdagent

That happens before the service is enabled and started, so upgrading the
package left the daemon down.

Only initialise when no config is present. Verified on RHEL 8: upgrading
over an existing install now leaves the service active.

(cherry picked from commit ee65bcb)
@eschoeller
eschoeller merged commit fc9ae69 into main Jul 28, 2026
4 checks passed
@eschoeller
eschoeller deleted the SEPE-1165_ci_and_agent_fixes branch July 28, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant