Enabling and Disabling Startup Services with systemd

I'm still getting used to the systemd paradigm on RHEL/CentOS 7 and instinctively reached for chkconfig to add xendomains to the runlevels:
chkconfig --level 2345 xendomains on
This works with older versions of Xen that come with SysV init scripts; later versions are shipping systemd init scripts and must be enabled the systemd way:
systemctl enable xendomains.service
To get a list of systemd scripts:
systemctl list-unit-files

Hello (Again) World: furry.media and How Karma got his Groove Back

Out with the old...
...in with the new.

I made my first furry site in 2002 as an exercise in developing my coding abilities. This quickly expanded into a portfolio of several sites which helped me cultivate skills that would come to greatly benefit me professionally. Unfortunately, maintaining and moderating online communities takes a substantial amount of time and the popularity of those communities demanded a level of hosting that at the time was very expensive. At one point the hosting bill was running in excess of $550CAD/month.

Although I accepted donations and ran ads on Ychan, donations were irregular and never covered more than a fraction of expenses. By 2014 the online advertising sector was at the bottom of a years-long decline and I was more busy than ever, having recently started my own company. When a direct-advertising partner for Ychan - our largest outside source of funding - pulled out of our arrangement I started thinking about winding down operations. As much as I wished the sites could have stayed online the enormous personal expense in keeping them funded precluded handing them over to someone else to manage. Additionally, the last major rewrite of the sites was in 2007 and by this time the codebase was an ageing patchwork that had potential security concerns for the users if a massive overhaul wasn't undertaken. After 11 continuous years of service I made the painful decision to shutter the sites so I could focus on my career and do the things necessary to ensure the security of my future: make investments, buy a house, cars etc.

About a year ago I found myself in California. I had accomplished everything I set out to do when I shut the sites down and, taken with the energy of Silicon Valley I suppose, started thinking about my next steps. Although I had been very fortunate professionally in the preceding years I had thoroughly burned myself out; typically putting in 12 hour days over 7 day weeks and sometimes working over 24 consecutive hours to meet critical deadlines. Almost all of this time was spent working to make other peoples' products and realize other peoples' dreams at the expense of my health and happiness. When you're doing all of your coding for the money and by the seat of your pants quality suffers, skills stagnate and frankly a part of your spirit dies. I missed the freedom that personal projects like my old furry sites afforded me to learn, grow and do the things that I wanted to do - without having to worry about budgets or deadlines or the silly whims and wishes of people who sign cheques.

Behold: kore

I came to the conclusion that the way I go about software development requires a sea change. I've learned a lot of neat tricks over the years that can only be properly implemented by starting a project from the beginning. At the same time I want to update my skill-set; I decided the best way to do that is to reignite the projects that gave me those skills in the first place. The cost of hosting is no longer an issue; it's now possible to do with one dedicated server what used to take us four. The problem of time remains, however: if I didn't have time to overhaul the furry sites in 2014 I certainly have less time now. If this was going to work I'd have to figure out a way to have my cake and eat it too.

To that end, I've set out to create a new software platform that will allow me to put all of my future efforts into a unified codebase that delivers every site and app from a single conceptual source. So much code from site to site and product to product is repeated with only minor variations that it is possible to reduce all of them to a set of core libraries plus a collection of simplified overlays. While I'm working on my furry sites which carry the freedom to do whatever I want, additions and improvements to the core codebase immediately become available to my professional projects and vice versa. Things like general maintenance and security enhancements can be done once without having to waste time repeating them at dozens of independent instances with varying conventions.

I started this blog in 2010 to organize coding and administration notes and so I would have a personal WordPress installation to maintain. This would force me to learn how to make templates, stay on top of security updates and general maintenance etc. for those clients that insisted on using it. Being common, complicated, off-the-shelf software, WordPress is naturally one of the most popular targets for exploitation and automated attacks. It wasn't long before I decided to stop supporting WordPress and the customers that use it altogether because the upkeep is simply not worth my time. I did find foxpa.ws itself to be worthwhile however and rewriting it with the new kore platform has given me the opportunity to create a powerful general-purpose content management system that can now be re-used in future kore-based projects while simultaneously giving me a platform to document and blog about its ongoing development.

The new foxpa.ws blog is written entirely from scratch, with the exception of the highlight.js library for code highlighting. The layout is - unlike its fixed-width predecessor - responsive and optimized for mobile viewing.


The plan now is to continue re-writing and re-launching the old sites with the kore platform under the new furry.media banner. Please stay tuned to this blog and the furry.media twitter feed if you would like to watch things take shape.

Manually Parse Multipart Form Data to Populate PHP Global $_POST/$_PUT Variables with a Simulated Request Body from stdin or File

For testing purposes I needed to simulate a multipart POST request from the command line. The body of a multipart request is huge and full of newlines so it's practical to dump it into a text file and redirect it to stdin. Multipart can't be parsed simply with parse_str() so it's necessary to manually parse the input. It seems most people running into this problem are trying to implement an ideologically-correct RESTful PUT interface but I found a tidy, global-agnostic class by misiek08 at https://gist.github.com/misiek08/7988b3b9a9911e35d0b3 and made a minor correction.

I've converted it into a static class and added a couple of functions to suit my use case:
class HttpMultipartParser { public static function populate_post_stdin() { $parsed = self::parse_stdin(); $_POST = $parsed['variables']; $_FILES = $parsed['files']; } public static function populate_put_stdin() { $parsed = self::parse_stdin(); $_PUT = $parsed['variables']; $_FILES = $parsed['files']; } public static function parse_stdin() { $stream = fopen('php://stdin', 'r'); return self::parse_multipart($stream); } public static function parse_multipart($stream, $boundary = null) { $return = array('variables' => array(), 'files' => array()); $partInfo = null; while(($lineN = fgets($stream)) !== false) { if(strpos($lineN, '--') === 0) { if(!isSet($boundary) || $boundary == null) { $boundary = rtrim($lineN); } continue; } $line = rtrim($lineN); if($line == '') { if(!empty($partInfo['Content-Disposition']['filename'])) { self::parse_file($stream, $boundary, $partInfo, $return['files']); } elseif($partInfo != null) { self::parse_variable($stream, $boundary, $partInfo['Content-Disposition']['name'], $return['variables']); } $partInfo = null; continue; } $delim = strpos($line, ':'); $headerKey = substr($line, 0, $delim); $headerVal = ltrim($line, $delim + 1); $partInfo[$headerKey] = self::parse_header_value($headerVal, $headerKey); } fclose($stream); return $return; } public static function parse_header_value($line, $header = '') { $retval = array(); $regex = '/(^|;)\s*(?P<name>[^=:,;\s"]*):?(=("(?P<quotedValue>[^"]*(\\.[^"]*)*)")|(\s*(?P<value>[^=,;\s"]*)))?/mx'; $matches = null; preg_match_all($regex, $line, $matches, PREG_SET_ORDER); for($i = 0; $i < count($matches); $i++) { $match = $matches[$i]; $name = $match['name']; $quotedValue = $match['quotedValue']; if(empty($quotedValue)) { $value = $match['value']; } else { $value = stripcslashes($quotedValue); } if($name == $header && $i == 0) { $name = 'value'; } $retval[$name] = $value; } return $retval; } public static function parse_variable($stream, $boundary, $name, &$array) { $fullValue = ''; $lastLine = null; while(($lineN = fgets($stream)) !== false && strpos($lineN, $boundary) !== 0) { if($lastLine != null) { $fullValue .= $lastLine; } $lastLine = $lineN; } if($lastLine != null) { $fullValue .= rtrim($lastLine, '\r\n'); } $array[$name] = $fullValue; } public static function parse_file($stream, $boundary, $info, &$array) { $tempdir = sys_get_temp_dir(); $name = $info['Content-Disposition']['name']; $fileStruct['name'] = $info['Content-Disposition']['filename']; $fileStruct['type'] = $info['Content-Type']['value']; $array[$name] = &$fileStruct; if(empty($tempdir)) { $fileStruct['error'] = UPLOAD_ERR_NO_TMP_DIR; return; } $tempname = tempnam($tempdir, 'php_upl'); $outFP = fopen($tempname, 'wb'); if($outFP === false) { $fileStruct['error'] = UPLOAD_ERR_CANT_WRITE; return; } $lastLine = null; while(($lineN = fgets($stream, 4096)) !== false) { if($lastLine != null) { if(strpos($lineN, $boundary) === 0) break; if(fwrite($outFP, $lastLine) === false) { $fileStruct = UPLOAD_ERR_CANT_WRITE; return; } } $lastLine = $lineN; } if($lastLine != null) { if(fwrite($outFP, rtrim($lastLine, '\r\n')) === false) { $fileStruct['error'] = UPLOAD_ERR_CANT_WRITE; return; } } $fileStruct['error'] = UPLOAD_ERR_OK; $fileStruct['size'] = filesize($tempname); $fileStruct['tmp_name'] = $tempname; } }