The FOG client: python 2, cron every five minutes and a fleet that rebooted itself into place
Before this blog’s other archaeology pieces, there was this one. Years before the apthogen era
, under the gobel1 identity, I ran workstation fleets cloned with FOG
(Free Opensource Ghost). FOG’s client story was Windows-first, so the Linux side needed a small client of its own: enter FOG.py, version 0.2, python 2, about 150 lines plus a handful of shell hooks. It predates 2013; the git repo was only re-inited in May 2013, one commit in French, six commits total. The archaeology is genuine.
What it did
Three duties, all cron-or-hook driven:
- Job polling:
etc/crontabruns/usr/bin/fogtasksevery five minutes as root. FOG.py asksjobs.php?mac=<mac>for pending imaging tasks; if there is one andwhoshows no user ontty7, the machine reboots itself into imaging. An unused classroom computer is a computer waiting to be re-imaged. - Hostname by MAC: at boot,
init.d/hostnamecallsfogsethostname, which asks the FOG serverhostlookupbymac.phpand rewrites/etc/hostnameand/etc/hosts, then/bin/hostname -F. The fleet’s identity lives in the FOG database, not on the disks. - User tracking:
etc/gdm/PostLogin/DefaultandPostSession/Defaultfirefogtracker LOGIN/fogtracker LOGOUTas root, sending base64-encoded MAC, action and username back tousertracking.report.php. Who used which machine, for the audit trail of the era.
The protocol: #! codes
The FOG service protocol is a study in 2012 minimalism: the server answers with two-character status codes, one per line: #!ok, #!db (database error), #!im (invalid MAC), #!nj (no jobs), #!ih (invalid hostname)… The client maps them to messages and exits. No JSON, no XML, no versioning. It is a TFTP-era contract and honestly, for a fleet of machines that only ask “any jobs for me?”, it is enough.
The sins, kindly dissected
The client predates every security conversation I ever had:
self.host="http://172.16.106.90/"
A private IP hardcoded in source, HTTP, no TLS and none available. The user-tracking call sends ?mac=<b64>&action=<b64>&user=<b64>&date=<b64> with date='', a field that is never filled (the parameter survived two refactors without anyone noticing). MAC discovery is a regex over /sbin/ifconfig output looking for HWaddr: the interface that would be renamed, then deprecated, then removed from every Linux distro within a decade. And the good ones, my favorites:
except:
Self._exit('No MAC Found')
Self._exit with a capital S: the typo inside the except block that would itself raise NameError the day it was needed. And c.strip(' ') computed then thrown away because the assignment was forgotten. Python 2 without linters was a festival.
What it taught, in the order I learned it
- Config in code compiles into every install. The fleet-wide
172.16.106.90took a re-image to change. A decade later, the original firmware, inherited at a former employer and not mine, hardcoded its server address too: on that kind of board, config files are complicated and addressing stayed on the DHCP of each client’s infrastructure. Same reflex, different contexts. - Parse no OS output you do not own. The
ifconfigregex worked until the dayeth0becameenp3s0and MAC discovery died with it. - GET params for state changes are a lie told by convenience. The tracking endpoints wrote to the server; every reverse proxy log became a user database.
- And yet: it ran. Five-minute cron, shell hooks, python2 stdlib plus httplib2: a fleet of machines kept itself imaged and audited for years with zero maintenance. The sin list is long; the uptime list is longer.
The FOG project itself went on to grow a proper Linux client. This one retired with its fleet and its repo (bash, irssi , mutt, vim dotfiles alongside) stayed as a 2013 time capsule under the gobel1 identity, a sibling era of the vhalholl years .