Guidance for Claude Code (and human contributors) working in this repository.
ArozOS is a self-hosted, web-based cloud desktop / NAS operating system written
in Go. It runs as a single binary on everything from a Raspberry Pi to a desktop
server. The Go module lives in src/ (module path imuslab.com/arozos);
the repository root holds docs, the installer and release tooling.
License: GPLv3 (see LICENSE).
All Go commands run from src/:
cd src
go mod tidy
go build # produces ./arozos
./arozos -port 8080 # run (sudo only needed for hardware/WiFi features)
go test ./... # run the test suite
go vet ./... # static checks
gofmt -l . # list unformatted files (should be empty)
make binary # cross-compile every supported OS/arch (see Makefile)
These five rules are enforced on new and changed code by a Claude Code
PostToolUse hook (during editing) and by CI (on every pull request). Both call
scripts/check-conventions.sh. Existing legacy
code is grandfathered — CI only inspects the lines a change adds — but do not add
new violations, and prefer fixing nearby ones when you touch them.
log packageNew code must send log output through the system logger so it lands in the managed, rotated system log instead of bare stdout.
import "imuslab.com/arozos/mod/info/logger"
// Good — title, message, and the originating error (nil if none):
logger.PrintAndLog("ModuleName", "could not open config", err)
// Bad — bypasses the system log:
log.Println("could not open config", err) // and log.Printf/Fatal/Panic
The package-level logger.PrintAndLog delegates to the system-wide logger wired
up in src/main.go; you do not need your own *logger.Logger
instance. The only file allowed to wrap the standard log package is the logger
implementation itself (src/mod/info/logger/).
Enforced as an ERROR (blocks CI) on added log.Print* / log.Fatal* /
log.Panic* calls.
Every package under src/mod/ is expected to carry a *_test.go file, and new
functions must come with table-driven Go tests (see
src/mod/info/logger/logger_test.go for
the house style: t.TempDir(), t.Fatalf/t.Errorf, one Test… per behaviour).
cd src && go test ./mod/yourpackage/ # must pass before you push
Enforced by the CI go test ./... gate; the convention checker additionally
warns when a touched mod/ package has no test file at all.
Any module added to src/go.mod must be licensed MIT, BSD-2/3,
Apache-2.0, MPL-2.0, or ISC — permissive, GPL-compatible, and fine for
commercial redistribution. Do not add GPL/AGPL/LGPL, source-available
(BSL/SSPL), or unknown-licensed modules. When unsure, state the dependency's
license in your summary so it can be reviewed before merge.
go install github.com/google/go-licenses@latest # optional audit helper
go-licenses report imuslab.com/arozos
Enforced by a CI reminder whenever go.mod/go.sum changes — verify the
license of each new dependency before merging.
Register authenticated endpoints through the permission router, not raw
http.HandleFunc, so they inherit login, per-module permission and (optionally)
admin/LAN/CSRF checks:
import prout "imuslab.com/arozos/mod/prouter"
router := prout.NewModuleRouter(prout.RouterOption{
ModuleName: "System Setting",
AdminOnly: true, // gate admin-only actions
UserHandler: userHandler,
DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
utils.SendErrorResponse(w, "Permission Denied")
},
})
router.HandleFunc("/system/yourmodule/action", yourHandler)
Use raw http.HandleFunc only for deliberately public endpoints (e.g. the
/public/... registration pages) and treat all request input as untrusted —
validate parameters with mod/utils helpers and never interpolate user input
into shell commands or file paths. See
src/main.router.go and
src/register.go for the patterns.
Enforced by a CI/hook warning on every added raw http.HandleFunc, prompting
a deliberate "authenticated vs. intentionally public" decision.
ArozOS ships as one self-contained binary that must build and run across the
targets in the Makefile (Linux amd64/386/arm/arm64/mipsle/
riscv64, macOS, Windows). Therefore:
filepath.Join, and resolve
locations via os.TempDir(), os.UserHomeDir(), or paths relative to the
binary — never literal "/usr/...", "/etc/..." or "C:\\...".exec.Command,
syscall) is unavoidable, isolate it in a build-tagged file — foo_linux.go,
foo_windows.go, foo_darwin.go, or behind a //go:build constraint — and
provide a fallback for other platforms. See
src/mod/network/wifi/ for the pattern.cd src && GOOS=windows GOARCH=amd64 go build ./....Enforced as an ERROR on added hardcoded OS path literals, and as a warning
when exec.Command/syscall appears in a non-build-tagged file.
| Mechanism | When it runs | What it does |
|---|---|---|
PostToolUse hook (.claude/settings.json) |
After Claude edits a Go file | Runs the checker on that file and feeds any finding back so Claude self-corrects |
GitHub Actions (.github/workflows/ci.yml) |
On every push / PR | gofmt, go build, go test ./..., and the diff-scoped convention checker (blocking); plus module-wide go vet (advisory — never fails CI on grandfathered legacy code) |
scripts/check-conventions.sh |
Manually or from the above | Single source of truth for the rules above |
Run it yourself before pushing:
sh scripts/check-conventions.sh src/path/to/file.go # check specific files
sh scripts/check-conventions.sh --diff origin/master # check everything you changed
Escape hatch: in the rare, justified case where a line must keep a raw
log/path literal, append the marker arozos-lint-ignore to that line with a
short comment explaining why. Use it sparingly — it is reviewed.
src/ — Go module root; main*.go boot the server, *.go are feature handlers.src/mod/ — self-contained library packages (each with its own tests).src/mod/info/logger/ — the system logger (rule 1).src/mod/prouter/ — permission/auth router (rule 4).src/web/ — front-end assets and web apps.src/system/ — runtime data and config (not shipped in release).