- Purpose: Semaphore self-hosted agent CLI written in Go 1.23; registers to the Semaphore SaaS, pulls jobs, runs them via pluggable executors, and reports status/logs.
- Binaries:
main.gobuilds a singleagentCLI (seemake build,make build.windows). Optionalservecommand spins up a lightweight local HTTP API (pkg/server) mainly for integration tests and hooks. - Configuration: Defaults come from
pkg/config/config.go; runtime values flow from CLI flags (pflag), env vars (SEMAPHORE_AGENT_*), or a YAML file (config.example.yamlas template).
main.gobootstraps logging, loads config, then eitherRunListener(default) or HTTP server handlers depending on CLI args.pkg/listener/listener.goorchestrates lifecycle: registers the agent (pkg/api), long-polls for jobs, spawnsJobProcessor.pkg/listener/job_processor.goprepares workspace, applies hooks (pre/post job), initializes event logging, selects executor, streams updates back.- Graceful shutdown logic (signals, idle timeout) is centralized through
pkg/listenerandshutdown_reason.go.
pkg/api: HTTP client models for Semaphore endpoints (register agent, fetch job requests). Requires endpoint/token from config.pkg/jobs: Domain model for jobs (commands, files, secrets) with helper logic around panic recovery and resource locks.pkg/executors: Strategy interface plus implementations (shell_executor,docker_compose_executor,kubernetes_executor). Handles workspace setup, command execution, log streaming.pkg/eventlogger: Multiplexed logging backends (in-memory, file, HTTP). Default pipeline: formatter →httpbackend(streams to Semaphore) with file or stdout mirrors.pkg/httputils,pkg/osinfo,pkg/random,pkg/retry: shared utilities to keep domain packages focused.pkg/kubernetes,pkg/docker,pkg/aws: helper modules invoked by executors for cluster API interactions, Docker Compose templating, and AWS metadata respectively.pkg/server: Local HTTP server used for self-hosted coordination (/jobs,/statusetc.), typically driven throughmake serveor tests.
- Listener authenticates with
POST /agents/registerusing token. - Long polling obtains
JobRequest(seepkg/api/job_request.go). - Processor sets up workspace: downloads files (
pkg/listener.ParseFiles), exports env vars, runs pre-job hook if configured. - Executor runs commands (selected from job payload). Shell executor runs directly on host; Docker Compose builds ephemeral services; Kubernetes executor schedules pods using supplied pod spec and allowed image list.
- Event logs are appended via
pkg/eventlogger.Logger, forwarded to Semaphore and optionally flushed to disk. - Post-job hook runs, artifacts/log uploads occur if enabled (
config.UploadJobLogs*). - Processor reports status, acknowledges completion; listener loops for next job or exits based on disconnect flags.
- CLI flags defined in
RunListener; env vars map 1:1 viaSEMAPHORE_AGENT_<FLAG>(dashes become underscores). - File injections support
--files path:destpairs; validation handled inpkg/listener/files.go. - Hooks (
--shutdown-hook-path,--pre-job-hook-path,--post-job-hook-path) execute via shell; when--source-pre-job-hookis set, the script runs within the current shell. - Sensitive data (tokens, certs) must never be committed—use local overrides. Example config lives in repository solely for documentation.
- Logging uses
logruswith levels derived fromSEMAPHORE_AGENT_LOG_LEVEL; defaults to info, writes to rotating file under$TMPDIR/agent_logunless overridden. - Event logs leverage chunked HTTP uploads, with optional plain-text file backup (
pkg/eventlogger/filebackend). - StatsD support is provided via
gopkg.in/alexcesaro/statsd.v2(seepkg/listener/selfhostedapifor references).
- Unit tests co-located with code (
*_test.go).testtarget runs viagotestsumcapturing JUnit reports. - Integration/end-to-end tests live in
test/e2e(Ruby scripts) with Docker Compose helpers undertest/e2e_support. Trigger usingmake e2e TEST=<script>. - Static analysis:
make lint(revive). Security/regression suites viamake check.static,check.deps,check.dockerrequiringrenderedtext/security-toolbox. - Benchmarks example:
make test.bench, mostly targeting performance hotspots like event logging.
Makefilecontains canonical targets for building cross-platform binaries, docker images (docker.build,docker.push), and release tagging (release.minor/major/patch).- Docker images use
Dockerfile.self_hostedfor production,Dockerfile.devfor local dev,Dockerfile.ecrfor AWS integration tests, andDockerfile.empty_ubuntufor sandbox experiments. - Release automation integrates with Semaphore pipelines; tagging triggers builds that publish to GitHub and Homebrew via scripts referenced in
README.md.
docs/: supplemental guides (currently Kubernetes executor details).examples/: sample configurations/scripts for self-hosted deployments.scripts/: utility scripts for CI/CD tasks, log collection, or environment bootstrapping.config.example.yaml: baseline YAML demonstrating all config fields.lint.toml: revive configuration (style rules, ignores).large_log.txt: fixture for event logger performance tests.run*/: captured logs/traces from local executions—safe to ignore unless debugging historical failures.
- Many commands assume Docker/Compose availability; ensure local environment mirrors production (see
empty.ubuntu.machinetarget). - Kubernetes executor requires
SEMAPHORE_KUBECONFIGor in-cluster config plus allowed image regexes; missing configs lead to pod creation failures early in the job run. - When modifying job processing, keep concurrency limits and interrupt handling in mind (
InterruptionGracePeriod,DisconnectAfterIdleTimeout). - Event logger writes large buffers; prefer streaming to avoid memory bloat. See recent commit
fix: improve eventlogger.GeneratePlainTextFile()for context on large files. - Before touching release scripts, review
CHANGELOG.mdand follow semantic versioning viamake tag.*targets.
- Start debugging by running
make serve+make run JOB=test/job.jsonusing fixtures inexamples/to reproduce flows. - Trace a job end-to-end via
pkg/listener/listener.go:Run()(entry point),pkg/listener/job_processor.go:Process(), and chosen executor implementation. - For API schema updates, adjust
pkg/apimodels first; downstream packages (pkg/jobs,pkg/executors) typically only consume typed structs. - Use
pkg/eventlogger/test_helpers.goto stub logging when writing tests around job execution. - Architecture diagram suggestion: Listener ↔ Semaphore API ↔ Executor ↔ Workspace; keep this mental model handy.