masq
Version:
A simple local dns server extracted from Pow
126 lines (108 loc) • 4.68 kB
text/coffeescript
# The `Installer` class, in conjunction with the private
# `InstallerFile` class, creates and installs local and system
# configuration files if they're missing or out of date. It's used by
# the Pow install script to set up the system for local development.
async = require "async"
fs = require "fs"
path = require "path"
mkdirp = require "mkdirp"
{chown} = require "./utils"
util = require "util"
# Import the Eco templates for the `/etc/resolver` and `launchd`
# configuration files.
resolverSource = require "./templates/resolver"
daemonSource = require "./templates/cx.masq.masqd.plist"
# `InstallerFile` represents a single file candidate for installation:
# a pathname, a string of the file's source, and optional flags
# indicating whether the file needs to be installed as root and what
# permission bits it should have.
class InstallerFile
constructor: (@path, source, @root = false, @mode = 0o644) ->
@source = source.trim()
# Check to see whether the file actually needs to be installed. If
# the file exists on the filesystem with the specified path and
# contents, `callback` is invoked with false. Otherwise, `callback`
# is invoked with true.
isStale: (callback) ->
fs.exists @path, (exists) =>
if exists
fs.readFile @path, "utf8", (err, contents) =>
if err
callback true
else
callback @source isnt contents.trim()
else
callback true
# Create all the parent directories of the file's path, if
# necessary, and then invoke `callback`.
vivifyPath: (callback) ->
mkdirp path.dirname(@path), 0o755, callback
# Write the file's source to disk and invoke `callback`.
writeFile: (callback) ->
fs.writeFile @path, @source, "utf8", callback
# If the root flag is set for this file, change its ownership to the
# `root` user and `wheel` group. Then invoke `callback`.
setOwnership: (callback) ->
if @root
chown @path, "root:wheel", callback
else
callback false
# Set permissions on the installed file with `chmod`.
setPermissions: (callback) ->
fs.chmod @path, @mode, callback
# Install a file asynchronously, first by making its parent
# directory, then writing it to disk, and finally setting its
# ownership and permission bits.
install: (callback) ->
async.series [
@vivifyPath.bind(@),
@writeFile.bind(@),
@setOwnership.bind(@),
@setPermissions.bind(@)
], callback
# The `Installer` class operates on a set of `InstallerFile` instances.
# It can check to see if any files are stale and whether or not root
# access is necessary for installation. It can also install any stale
# files asynchronously.
module.exports = class Installer
# Factory method that takes a `Configuration` instance and returns
# an `Installer` for DNS configuration files.
@getSystemInstaller: (configuration) ->
files = []
for domain in configuration.domains
files.push new InstallerFile "/etc/resolver/#{domain}",
resolverSource(configuration),
true
new Installer files
# Factory method that takes a `Configuration` instance and returns
# an `Installer` for the Pow `launchctl` daemon configuration file.
@getLocalInstaller: (configuration) ->
new Installer [
new InstallerFile "#{process.env.HOME}/Library/LaunchAgents/cx.masq.masqd.plist",
daemonSource(configuration)
]
# Create an installer for a set of files.
constructor: (@files = []) ->
# Invoke `callback` with an array of any files that need to be
# installed.
getStaleFiles: (callback) ->
async.select @files, (file, proceed) ->
file.isStale proceed
, callback @files
# Invoke `callback` with a boolean argument indicating whether or
# not any files need to be installed as root.
needsRootPrivileges: (callback) ->
@getStaleFiles (files) ->
async.detect files, (file, proceed) ->
proceed file.root
, (result) ->
callback result?
# Installs any stale files asynchronously and then invokes
# `callback`.
install: (callback) ->
@getStaleFiles (files) ->
async.forEach files, (file, proceed) ->
file.install (err) ->
console.log file.path unless err
proceed err
, callback