guest@6admin.io:~$

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/crontab runs /usr/bin/fogtasks every five minutes as root. FOG.py asks jobs.php?mac=<mac> for pending imaging tasks; if there is one and who shows no user on tty7, 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/hostname calls fogsethostname, which asks the FOG server hostlookupbymac.php and rewrites /etc/hostname and /etc/hosts, then /bin/hostname -F. The fleet’s identity lives in the FOG database, not on the disks.
  • User tracking: etc/gdm/PostLogin/Default and PostSession/Default fire fogtracker LOGIN / fogtracker LOGOUT as root, sending base64-encoded MAC, action and username back to usertracking.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.90 took 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 ifconfig regex worked until the day eth0 became enp3s0 and 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 .