When ikiwiki-hosting is used as a business, information about customers needs to be stored. Rather than use a database, we use the customersite, accessed by the IkiWiki::Customer module.

This is a regular site managed by ikiwiki-hosting like any other. Its configuration is somewhat special though. When setting it up:

  • Be sure to configure ikiwiki to exclude: customer/*, and turn off branchability, to avoid customer data leaking out! Also, disable recentchanges, that can leak data too.
  • Configure it to accept a ssh key owned by the controlsite, so that site can push into it.
  • Configure its git_wrapper_background_command to update the controlsite's checkout of the customersite, by having it run ". /etc/ikiwiki-hosting/ikiwiki-hosting.conf; ikisite-wrapper updatecustomersite $controlsite"

Inside the customersite, there is a customer directory, which holds information about all the customers. Each customer has a subdirectory there, which holds files for each piece of data tracked about a customer.

The name of a customer's subdirectory is obtained by taking the sha1sum of their username. If a customer has multiple openids or emails used for login, they can have multiple subdirectories, symlinked together. (ikiwiki will not render the symlinks, but that's ok; it's not rendering customer data at all if configured right.)

The individual files hold one single piece of data, or a unordered list (ie, a list of email addresses). Files holding a list have "_list" in their filename.

This design is intended to allow changes to be made to multiple branches of the customersite, and merged together with little chance of conflicts. Git's union merge driver can be used to merge the list files. Other fields may have custom merge drivers.

locking

A file named lock in each customer's directory is used as a lock file by anything that reads or writes a customer's data.

A file named lock in the top of the customersite is used as a lock file for anything that needs to change arbitrary files anywhere in the customersite. (For example, a git pull.) This lock should be taken first, as a shared lock by things that do customer-level locking.

fields

  • notes.mdwn freeform notes about customer
  • currentplan holds the name of the price plan the user has agreed to
  • email holds the current contact email of the user
  • email_list holds all known emails of the user
  • name holds the user's name (preferably full name, but may be whatever is available from openid, or possibly derived from email address).
  • openid_list holds the openid(s) of the user
  • startdate when this customer's account was first created (represented as time from unix epoch)
  • balance amount of money owed (in cents) (a negative balance indicates the customer has a credit)
  • previousbalance what the balance was before the last billing or payment event (be sure to update this when changing the balance)
  • lastbilldate when user was last billed (represented as time from unix epoch)
  • disable_billing if set to a true value, bills will not be sent (used for test accounts)
  • lastreminderdate when last billing reminder was sent
  • bill_startdate start of billing period for last bill
  • bill_enddate end of billing period for last bill
  • lastinvoiceid id of the last invoice sent
  • lastplan name of the price plan used in the last invoice sent

non-customer data

In a charming example of scope creep, we're storing a bit of non-customer-specific data in the customersite already:

  • invoice/last - stores the last used invoice id.
  • invoice/YYY-MM/ - stores copies of invoices sent to customers