- Don't assume. Don't hide confusion. Surface tradeoffs.
- Minimum code that solves the problem. Nothing speculative.
- Touch only what you must. Clean up only your own mess.
- Define success criteria. Loop until verified.
Branch note: This is
dev-branch, the 1.5.x stable/patches line (currently1.5.10). It is a different codebase fromworking-1.6— file naming, layout, and some conventions differ (see below). Do not assume 1.6 patterns or file paths apply here. Fixes often need to be ported between the two branches by hand, not merged.
FOG Project is an open-source network imaging and endpoint management system. It allows IT administrators to deploy OS images to computers over PXE boot, manage client hosts, schedule tasks, push snapins (software packages), manage printers, track users, and run background replication/multicast services.
- Primary language: PHP (the codebase runs on PHP 8;
commons/init.phpuses typed signatures likeInitiator::e(mixed $value): string) - Frontend: jQuery + Bootstrap + AdminLTE (no build step — plain JS served directly)
- Background services: PHP CLI daemons
- ~128 core
*.class.phpfiles underlib/fog/ - Version line: 1.5.10 (the pre-commit hook stamps
dev/stablebranches with thePatcheschannel)
| Thing | dev-branch (1.5.x) | working-1.6 |
|---|---|---|
| Page file names | imagemanagementpage.class.php (one word, .class.php) |
ImageManagement.page.php |
| Page class names | ImageManagementPage (with Page suffix) |
ImageManagement (no suffix) |
| ORM array syntax | older array(...) in many files |
[...] short syntax |
declare(strict_types=1) |
none in lib/fog/ |
present in newer files |
| Node routing | $nodes allowlist array in management/index.php |
node→class mapping |
| Service daemons | no FOGFileDeleter |
adds FOGFileDeleter |
lib/ extra dirs |
has client/, reg-task/, service/ |
reorganized |
When porting a fix from 1.6, expect the target file to have a different name and class name here, and the surrounding code to use array() and other older idioms.
Until working-1.6 is promoted to become the new stable/patches line,
any new feature (as opposed to a bug fix scoped to one branch) should be
developed on working-1.6 first — branch from it, open a PR against it —
and only afterward ported to dev-branch as a separate PR. This is the
reverse of what feels natural given dev-branch is the actively-patched line
today, but working-1.6 is where the project's future lives, and landing
new capability there first keeps it from falling further behind.
See the Secure Boot signing work (fogproject#961, later ported to
working-1.6 in 8226cd9) for what happens when it is done backwards: the
port had to reconcile real divergence after the fact — different page-file
conventions, a page-class shape (_downloadPost($type)) that would have
silently signed the initrd instead of just the kernel without an added gate,
hardcoded paths that broke on non-default install locations — rather than
being designed around any of that from the start.
fogproject/ (dev-branch)
├── bin/ # installfog.sh installer
├── lib/ # Shell library scripts (per-distro functions)
├── packages/
│ ├── service/ # PHP CLI background daemons
│ │ ├── FOGTaskScheduler/
│ │ ├── FOGImageReplicator/
│ │ ├── FOGSnapinReplicator/
│ │ ├── FOGMulticastManager/
│ │ ├── FOGPingHosts/
│ │ ├── FOGImageSize/
│ │ ├── FOGSnapinHash/
│ │ └── lib/service_lib.php
│ └── web/ # The web application
│ ├── api/ # REST API
│ ├── client/ # fog-client related files
│ ├── commons/ # Boot/init files (loaded by ALL entry points)
│ │ ├── base.inc.php # Security headers, starts output buffering
│ │ ├── init.php # Initiator class: autoloader, session, sanitization
│ │ ├── schema.php # DB schema (CREATE TABLE as PHP arrays)
│ │ └── text.php # $foglang[] translation strings
│ ├── lib/
│ │ ├── fog/ # ~128 core *.class.php files (models, managers, utilities)
│ │ ├── pages/ # *page.class.php UI page classes (e.g. hostmanagementpage.class.php)
│ │ ├── hooks/ # *.hook.php hook classes
│ │ ├── events/ # *.event.php event classes
│ │ ├── reports/ # *.report.php report classes
│ │ ├── reg-task/ # registration/task helpers
│ │ ├── client/ # client-side support
│ │ ├── db/ # PDODB, DatabaseManager
│ │ ├── router/ # API routing
│ │ ├── service/ # service support classes
│ │ └── plugins/ # plugin directories
│ └── management/ # Apache/Nginx document root
│ ├── index.php # Main UI entry point ($nodes allowlist lives here)
│ ├── js/fog/ # FOG-specific JS
│ ├── css/ # Stylesheets + LESS source
│ └── languages/ # gettext .po/.mo files
└── src/ # iPXE / binaries source
Every entry point (web UI, API, background services) starts the same way:
commons/base.inc.php
→ commons/init.php (defines Initiator, runs autoloader registration)
→ new LoadGlobals() (bootstraps $DB, $HookManager, $EventManager, $currentUser)
No Composer/PSR-4. Initiator scans BASEPATH with RecursiveDirectoryIterator and registers these extensions (commons/init.php):
spl_autoload_extensions('.class.php,.page.php,.event.php,.hook.php,.report.php');
Class name must match the filename (the page files are lowercase one-word names, e.g. imagemanagementpage.class.php → class ImageManagementPage).
FOGBase (abstract)
├── FOGCore — static utility methods (getSetting, getClass, etc.)
├── FOGController — single-entity ORM base
│ └── Host, Image, Snapin, StorageNode, etc.
├── FOGManagerController — collection/query base
│ └── HostManager, ImageManager, etc.
├── FOGPage — UI page base
│ └── HostManagementPage, ImageManagementPage, etc.
├── Page — HTML shell renderer
├── Hook — base for all hooks
└── LoadGlobals — bootstraps globals on construction
Every entity model declares (note the older array() syntax):
protected $databaseTable = 'hosts';
protected $databaseFields = array(
'id' => 'hostID', // friendly name => DB column name
'name' => 'hostName',
);Access: $host->get('name'), $host->set('name', 'foo'), $host->save(), $host->load(), $host->destroy().
Instantiate with ID to auto-load: new Host(42).
Driven by ?node=host&sub=list&id=42. Allowed node values are whitelisted in management/index.php (the $nodes array); node maps to the matching *page.class.php, and sub maps to a method on that class.
All app config lives in the globalSettings MySQL table:
- Read:
FOGBase::getSetting('FOG_SETTING_NAME') - Write:
FOGBase::setSetting('FOG_SETTING_NAME', $value)
// Register (in hook constructor):
self::$HookManager->register('EVENT_NAME', array($this, 'methodName'));
// Fire:
self::$HookManager->processEvent('EVENT_NAME', array('data' => &$data));Initiator::e($value)— use when echoing user-controlled data into HTML (htmlspecialchars wrapper).- Input reads should use
filter_input(INPUT_GET/POST, 'key', FILTER_*)rather than raw superglobals. - Output runs through an
ob_startsanitizer. - CSRF handled via
csrf.class.phpserver-side + token injection client-side.
- Private methods: single underscore prefix (
_init(),_verCheck()) - PHPDoc: present on classes and methods
- Static globals:
FOGBase::$HookManager,FOGBase::$DB, etc. - Class instantiation: prefer
FOGBase::getClass('ClassName')factory - Translation:
_('string')for inline gettext;$foglang['Key']for pre-defined strings fromtext.php - Do NOT add
declare(strict_types=1)— no file inlib/fog/uses it on this branch - Match the existing
array()style in the file you are editing; don't mass-convert to[]
core.hooksPath is .githooks/, so .githooks/pre-commit runs on every git commit (and pre-merge-commit delegates to it). It auto-modifies and git adds files beyond what you staged — this is expected, not a bug. Do not revert these. It does three things:
updateLanguage()— regeneratesmanagement/languages/messages.potviaxgettext, sorts withmsgcat, thenmsgmerge-updates every.po. Adds the wholelanguages/dir. Skipped if those tools aren't installed.psrfix()— runsphp-cs-fixer fix packages/web --rules=@PSR2andgit add packages/webunconditionally. Two consequences: your code may be auto-reformatted to PSR-2, and any other dirty file underpackages/web/gets swept into your commit regardless of what you staged. Commit files outsidepackages/web/(like thisCLAUDE.md) separately if you need them isolated.- Version bump — derives a version from the branch name + commit count and rewrites
FOG_VERSION/FOG_CHANNELinpackages/web/lib/fog/system.class.php. Ondev/stablebranches the channel isPatches. This step also tends to leave a dangling stagedsystem.class.phpbump after the commit; discard it withgit checkout -- packages/web/lib/fog/system.class.phpif you don't want it in the next commit.
- Do not add
declare(strict_types=1) - Do not assume 1.6 file names/paths — this branch uses
*page.class.phpandarray()idioms - Do not use raw
$_GET/$_POST— usefilter_input() - Do not echo user data without
Initiator::e() - Do not remove hooks, events, or plugin integration points without explicit confirmation
- Do not touch
config.class.php— it's generated and excluded from git - Do not commit to
stableormaster; this branch (dev-branch) is the patches line