Bump version, update CHANGELOG, and retake README screenshots (Jobs, History, Settings) to match the current GUI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
12 KiB
GoSentry — Development
Toolchain, dependency, build, and release information for contributors.
Contents
- Technology Stack and Tools
- External Libraries
- Run From Source
- Building the Executable
- Building a Release
- CI
1. Technology Stack and Tools
GoSentry is a single desktop process written in Go with a Fyne GUI. There is no server component and no external runtime: the release artifact is one native executable per platform.
| Layer | Choice |
|---|---|
| Language | Go 1.22 or newer |
| GUI toolkit | Fyne v2 (OpenGL desktop backend) |
| Scheduling | robfig/cron/v3 expression parser |
| Persistence | Plain JSON files (gosentry.json, jobs.json) |
| Build | go build driven by the scripts in scripts/ |
| Reproducible builds | Docker (golang:1.22-bookworm based Dockerfile) |
| CI | GitHub Actions and Forgejo Actions (Codeberg) |
CGO is mandatory. The Fyne desktop backend links against native OpenGL and
window-system libraries, so a C compiler must be present for every build,
including go run and go test.
Toolchain — Windows
- Go 1.22 or newer.
- MSYS2 with UCRT64 GCC in
C:\msys64\ucrt64\bin(pluswindresfor the icon resource).
# 1. Install Go 1.22 or newer from https://go.dev/dl/.
# The default installer path is C:\Program Files\Go.
go version
# 2. Install MSYS2 from https://www.msys2.org/.
# Use the default installation path so UCRT64 tools are placed under
# C:\msys64\ucrt64\bin.
# 3. Open "MSYS2 UCRT64" from the Start menu and install GCC plus windres.
pacman -Syu
pacman -S --needed mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-binutils
# 4. In PowerShell, check that the compiler is available where the build script
# expects it. build-windows.bat prepends this directory automatically.
Test-Path C:\msys64\ucrt64\bin\gcc.exe
Test-Path C:\msys64\ucrt64\bin\windres.exe
Toolchain — Linux
- Go 1.22 or newer.
- A C compiler.
- Fyne native build dependencies, including OpenGL/X11 development packages.
# Go builds the application, gcc is required by CGO/Fyne, and the OpenGL/X11
# development packages provide the native desktop headers used by Fyne.
sudo apt install golang gcc libgl1-mesa-dev xorg-dev
Repository scripts
| Script | Purpose |
|---|---|
scripts/test.bat, scripts/test.sh |
go vet ./... then go test -race ./... |
scripts/build-windows.bat |
Windows amd64 executable |
scripts/build-linux.sh |
Linux amd64 executable |
scripts/build-linux-docker.sh |
Linux amd64 executable, built in Docker |
scripts/build-release-linux.sh |
Multi-target release artifacts from one Linux/Docker workflow |
scripts/package-windows.bat, scripts/package-linux.sh |
Wrap a built binary into a distributable archive |
scripts/ci-build-release.sh |
Entry point used by both CI workflows |
Build outputs are written to dist/. The package layout is documented in
ARCHITECTURE.md.
2. External Libraries
GoSentry keeps the direct dependency list intentionally small. GoSentry itself is distributed under the MIT License.
| Dependency | Version | Repository | License |
|---|---|---|---|
| Go toolchain | 1.22+ | https://go.googlesource.com/go | BSD 3-Clause |
fyne.io/fyne/v2 |
v2.7.4 | https://github.com/fyne-io/fyne | BSD 3-Clause |
github.com/robfig/cron/v3 |
v3.0.1 | https://github.com/robfig/cron | MIT |
The remaining entries in go.mod are indirect dependencies pulled in by Fyne
and the Go module resolver. To list every direct and indirect module used by the
current checkout:
go list -m all
3. Run From Source
Windows:
# Fyne requires CGO on Windows. MSYS2 UCRT64 provides the C compiler and native
# libraries used by the desktop backend.
$env:Path = 'C:\msys64\ucrt64\bin;' + $env:Path
$env:CGO_ENABLED = '1'
# go run starts the app from source. Use scripts\build-windows.bat when you need
# a standalone .exe without a console window.
& 'C:\Program Files\Go\bin\go.exe' run ./cmd/gosentry
Linux:
# CGO must stay enabled because the Fyne GUI links against native Linux desktop
# libraries.
CGO_ENABLED=1 go run ./cmd/gosentry
The same environment is required for the test suite — see TESTS.md:
scripts\test.bat
4. Building the Executable
Windows
# Builds dist\windows\gosentry-<version>-windows-amd64.exe. The script changes
# to the repository root first, so double-clicking it from Explorer works. It
# also adds MSYS2 UCRT64 to PATH for this process only, embeds the Windows icon
# when windres is available, and uses the Windows GUI subsystem so no console
# window opens at startup.
.\scripts\build-windows.bat
The Windows build is created as a GUI application, so it does not open a terminal window. The binary is written to:
dist\windows\gosentry-<version>-windows-amd64.exe
Linux
# Make the helper executable once, then build a linux/amd64 Fyne binary.
chmod +x ./scripts/build-linux.sh
./scripts/build-linux.sh
The binary is written to:
dist/linux/gosentry-<version>-linux-amd64
Linux using Docker
# Builds the Linux binary inside Docker using the versioned image tag
# gitea.mixdep.ru/mix/gosentry-builder:<version>. Useful from hosts or CI jobs
# where the native Linux/Fyne packages are not installed locally.
chmod +x ./scripts/build-linux-docker.sh
./scripts/build-linux-docker.sh
The binary is copied to:
dist/linux/gosentry-<version>-linux-amd64
5. Building a Release
All targets from Linux
# Interactively choose Linux amd64, Linux arm64, Windows amd64, or all artifacts
# from one Linux/Docker workflow. The Dockerfile contains the builder
# environment; the build commands live in this script. Docker runs the build
# with the current user's UID/GID so dist/ files are not owned by root.
chmod +x ./scripts/build-release-linux.sh
./scripts/build-release-linux.sh
Non-interactive release builds can pass target names:
# Build only Linux arm64 and Windows amd64 artifacts.
./scripts/build-release-linux.sh linux-arm64 windows-amd64
The binaries are copied to:
dist/linux/gosentry-<version>-linux-amd64
dist/linux/gosentry-<version>-linux-arm64
dist/windows/gosentry-<version>-windows-amd64.exe
Packaging
The package-* scripts build the binary for their platform and wrap it in a
distributable archive together with README.md and CHANGELOG.md:
Windows:
scripts\package-windows.bat
dist\windows\gosentry-<version>-windows-amd64.zip
Linux:
./scripts/package-linux.sh
dist/linux/gosentry-<version>-linux-amd64.tar.gz
dist/linux/gosentry-<version>-linux-arm64.tar.gz
The arm64 archive is produced only when the aarch64-linux-gnu-gcc cross
compiler is available; otherwise that target is skipped with a message.
The version stamped into the file names and into the binary comes from
src/app/version.go.
6. CI
Tagged releases are built automatically on both GitHub and Codeberg:
.github/workflows/release.yml— GitHub Actions..forgejo/workflows/release.yml— Forgejo Actions (Codeberg).
Both run inside golang:1.22-bookworm (the same base image as the
Dockerfile), install the cross toolchain, and call
scripts/ci-build-release.sh, which builds and packages all three artifacts:
dist/linux/gosentry-<version>-linux-amd64.tar.gz
dist/linux/gosentry-<version>-linux-arm64.tar.gz
dist/windows/gosentry-<version>-windows-amd64.zip
The Windows binary is cross-compiled with MinGW-w64 from the Linux job, so no
Windows runner is required. Each archive contains the executable plus README.md
and CHANGELOG.md, matching the local package-* scripts.
Cutting a release
Before tagging:
- Bump
src/app/version.go. The tag must match it exactly. - Add the version's CHANGELOG.md section.
- Retake the README screenshots (
docs/screenshots/screenshot_jobs.PNG,docs/screenshots/screenshot_settings.PNG,docs/screenshots/screenshot_history.PNG) if the GUI changed its appearance. This is easy to forget because nothing fails without it:README.mdis packaged inside every release archive and is what the forge shows on the project page, so a stale shot advertises an application that no longer exists. Take them from a real build, not from a development run with test data. - Run
scripts/test.bat(orgo vet ./... && go test -race ./...) and pushmain, so the tag lands on a commit the forge actually has.
Then create and publish a release with a matching v tag on the forge (GitHub
Releases / Codeberg releases). origin is the Gitea repository, and GitHub is a
push mirror of it, so the tag is pushed to Gitea and reaches GitHub through the
mirror — never created on GitHub directly (see
Releasing through the GitHub push mirror):
git tag v0.11.5
git push origin v0.11.5 # Gitea; and to the Codeberg remote
# wait for the mirror, then confirm GitHub actually has the tag
git ls-remote --tags https://github.com/mixeme/gosentry.git v0.11.5
gh release create v0.11.5 --verify-tag --generate-notes # GitHub; publishes the release
Publishing the release triggers the workflow: it strips the leading v from
the tag and injects it as the version (so the tag must match version.go),
builds the archives, and attaches them to that release. workflow_dispatch
also allows a manual, upload-free build to smoke-test the pipeline.
Codeberg publishing needs a repository secret named RELEASE_TOKEN (a Codeberg
access token with the write:repository scope) under
Settings → Actions → Secrets. Without it the build and packaging steps still
succeed, but the upload step fails on authentication and takes the job down with
it, leaving a published release with no attached assets. GitHub needs no such
setup: softprops/action-gh-release falls back to the built-in GITHUB_TOKEN,
and the workflow already grants it contents: write.
Releasing through the GitHub push mirror
The GitHub repository mixeme/gosentry is not a separate remote you push to; it
is a push mirror driven by Gitea. Gitea mirrors with pruning, so every ref that
exists on GitHub but not in Gitea is deleted on the next synchronisation.
This is what breaks the obvious way of cutting a GitHub release. gh release create v1.0.0 creates the tag on GitHub when it is missing — a tag Gitea has
never heard of. The next mirror run prunes it, GitHub orphans the release whose
tag disappeared and turns it into a draft, and the release looks deleted on the
Releases page. The archives go with it. Nothing reports an error: the workflow
ran, the assets uploaded, and the release evaporated afterwards.
The order that works is therefore:
git push origin <tag>— the tag enters Gitea, which owns it.- Wait for the mirror, or force it with Settings → Repository → Mirror Settings → Synchronize Now in Gitea.
git ls-remote --tags https://github.com/mixeme/gosentry.git <tag>— confirm GitHub has it.gh release create <tag> --verify-tag …—--verify-tagis the guard, not a nicety: without itghsilently creates the doomed tag when the mirror has not caught up yet.
Release notes and assets are GitHub-side metadata; a mirror push cannot touch them, so once the release sits on a mirrored tag, later synchronisations leave it alone. Two consequences follow. Moving a published tag in Gitea force-pushes it on GitHub and leaves the release pointing at a different commit, and deleting a published tag in Gitea destroys the GitHub release along with its uploaded archives — neither is recoverable from the mirror side. Codeberg is unaffected: its releases live in the same forge as its tags.