CLAUDE.md 14 KB

CLAUDE.md

Guidance for Claude Code (and human contributors) working in this repository.

What ArozOS is

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).

What AGI is

In this codebase AGI stands for ArOZ Online JavaScript Gateway Interfacenot "Artificial General Intelligence". It is the server-side JavaScript runtime that powers ArozOS web apps: module scripts with a .agi (or .js) extension are executed inside a sandboxed Otto JavaScript VM, one fresh VM per request/script, with permission-checked access to ArozOS functions (file system, database, sharing, IoT, image/zip/ ffmpeg helpers, SQLite, WebSockets, an LLM/aimodel chat library, and more).

  • Where it lives: src/mod/agi/ (agi*.go); the runtime version is the AgiVersion constant in src/mod/agi/agi.go. The gateway is constructed in src/agi.go (AGIInit, run from src/startup.go) via agi.NewGateway(agi.AgiSysInfo{…}).
  • Built-in globals & functions: core globals (USERNAME, USERICON, HTTP_RESP, LOADED_MODULES, …) and functions (sendResp, sendJSONResp, registerModule, requirelib, includes, execd, the …DBItem DB helpers, …) are injected per VM by injectStandardLibs / injectUserFunctions (src/mod/agi/agi.user.go).
  • Loadable libraries — pulled in on demand with requirelib("name"); registered in LoadAllFunctionalModules (src/mod/agi/moduleManager.go): filelib, imagelib, http, share, iot, appdata, sysinfo, ziplib (incl. 7z), sqlite, aimodel, and ffmpeg (only when ffmpeg is on the host), plus websocket and scheduler which are injected only in an HTTP request context.
  • Execution entry points:
    • init.agi — a web app's startup/registration script, scanned at boot from ./web/*/init.agi (InitiateAllWebAppModules) and run with system scope only (no user functions); used to registerModule(…).
    • Front-end calls/system/ajgi/interface (logged-in users) and /api/ajgi/interface (token auth; this is the -rpt callback subservices receive). Both run scripts scoped to the invoking user's permissions.
    • Serverless / external endpoints, plus nightly tasks and user-approved scheduled (cron) tasks.
  • Full API reference: src/mod/agi/README.md. When you change AGI functions or signatures, also update the in-app help data file src/web/Terminal/docs/api.json to match (one object per library section; the README's maintainer note explains how).

What a WebApp is

A WebApp (a.k.a. a module) is a user-facing application that shows up on the ArozOS desktop. Each one is a folder under src/web/ — e.g. Photo/, Music/, NotepadA/, Calendar/, Code Studio/ — holding the front-end assets (HTML/JS/CSS) plus an optional init.agi and any backend .agi scripts it calls.

  • Registration: a web app announces itself from its init.agi by calling registerModule(JSON.stringify(moduleLaunchInfo)). The launch-info object maps field-for-field to the ModuleInfo struct in src/mod/modules/module.go: Name, Desc, Group, IconPath, Version, StartDir, SupportFW/LaunchFWDir, SupportEmb/LaunchEmb, InitFWSize, InitEmbSize, SupportedExt. See src/web/Photo/init.agi for a minimal example.
  • Launch modes: full page (StartDir), floatWindow (SupportFW + LaunchFWDir, sized by InitFWSize), and embedded (SupportEmb + LaunchEmb, sized by InitEmbSize) — embedded is what loads when a file is opened with the module. SupportedExt registers the file-type associations that let the module become a default opener.
  • Go-side handling: ModuleHandler (src/mod/modules/module.go) holds the loaded-module list; RegisterModuleFromAGI is the hook init.agi drives, and module visibility is filtered per user by GetModuleListJSONForUser (users only see modules they have permission for).
  • Scope reminder: init.agi runs with system scope (registration / system functions only — don't call user or file functions there); backend .agi scripts invoked from the front end run with the invoking user's scope. Subservices (below) also register through ModuleInfo, so they appear on the desktop just like WebApps.

What a SubService is

A SubService lets ArozOS launch an independent binary web server (written in any language) as a child process and reverse-proxy it under the main server, so it appears as a normal ArozOS module. Reach for it when a feature needs a real native binary rather than a sandboxed AGI script — heavy compute, an existing Go/Rust/… server, or third-party software such as Syncthing.

  • Where it lives: package src/mod/subservice/ (SubService, SubServiceRouter); wiring + boot-time scan in src/subservice.go (SubserviceInit, run from src/startup.go). Disable it all with the -disable_subservice flag; reverse-proxy ports start at subserviceBasePort (12810, src/flags.go).
  • Folder convention: drop the binary in ./subservice/<name>/, named for its platform — <name>.exe (Windows) or <name>_<GOOS>_<GOARCH> (e.g. demo_linux_amd64); on Linux an apt-installed binary on PATH is preferred.
  • Lifecycle: ArozOS probes the binary with <bin> -info (it must print its ModuleInfo as JSON — or ship a moduleInfo.json instead), then launches it as <bin> -port <port> -rpt http://localhost:<parentPort>/api/ajgi/interface (the -rpt URL is the AGI callback so the service can call ArozOS APIs back). The reverse-proxy URL prefix is the directory part of StartDir and must not collide with a reserved path (web, system, ws, …, src/subservice.go). A failed proxy is auto-restarted.
  • Auth: proxied requests are permission-checked (per-module access) and get aouser, aotoken and X-Forwarded-Host headers injected; routing happens in the authenticated branch of src/main.router.go.
  • Marker files (next to the binary): .disabled (skip at boot; toggle in the admin UI), .noproxy (run but don't proxy — compatibility mode), .startscript (launch start.sh/start.bat instead of the binary), .intport (pass the port without a leading :), moduleInfo.json (static info, skips the -info probe).
  • Admin endpoints: /system/subservice/{list,kill,start} (admin only). Full guide: the "Subservice Logics and Configuration" section of src/README.md.

Build, run and test

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)

Mandatory contribution rules

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.

1. Use the managed logger, never the standard log package

New 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.

2. New functions ship with tests

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.

3. Dependencies must be MIT / commercial-use-OK

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.

4. New endpoints get the right security control

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.

5. Stay portable — no system dependencies, cross-platform safe

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:

  • No hardcoded OS paths. Build paths with filepath.Join, and resolve locations via os.TempDir(), os.UserHomeDir(), or paths relative to the binary — never literal "/usr/...", "/etc/..." or "C:\\...".
  • No shelling out to platform tools in shared code. Avoid making features depend on external binaries. When a platform-specific call (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.
  • Cross-compile to sanity-check: 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.

How enforcement works

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.

Repository layout cheatsheet

  • 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/mod/agi/ — the AGI JavaScript gateway runtime (see "What AGI is"); API reference in src/mod/agi/README.md.
  • src/mod/modules/ — module registry and the ModuleInfo struct shared by WebApps and SubServices (see "What a WebApp is").
  • src/mod/subservice/ — reverse-proxied binary subservices (see "What a SubService is"); wired up in src/subservice.go.
  • src/web/ — front-end assets and WebApps (one folder per module; see "What a WebApp is").
  • src/system/ — runtime data and config (not shipped in release).