- now that we have to configure usher's configuration file anyway,

we can skip the explicit configuration of its host and admin
  password, as we can directly read that from the configuration file
  itself
- expand the SyncMonotone plugin a bit to where this actually becomes
  useful, i.e.  create an accompanying key for each created database
  and also add some initial database-specific configuration
- update the config docs in idf.php-dist to reflect the changes and
  add more details about the inner workings of the SyncMonotone plugin
This commit is contained in:
Thomas Keller 2010-08-29 23:01:25 +00:00
parent 194dcad0e3
commit b648e6f7a7
5 changed files with 170 additions and 63 deletions

View File

@ -111,7 +111,7 @@ function IDF_Middleware_ContextPreProcessor($request)
$request->project); $request->project);
$c = array_merge($c, $request->rights); $c = array_merge($c, $request->rights);
} }
$c['usherConfigured'] = Pluf::f("mtn_usher", null) !== null; $c['usherConfigured'] = Pluf::f("mtn_usher_conf", null) !== null;
return $c; return $c;
} }

View File

@ -41,8 +41,15 @@ class IDF_Plugin_SyncMonotone
} }
/** /**
* Run mtn init command to create the corresponding monotone * Four steps to setup a new monotone project:
* repository and add the database to the configured usher instance *
* 1) run mtn db init to initialize a new database underknees
* 'mtn_repositories'
* 2) create a new server key in the same directory
* 3) write monotonerc for access control
* 4) add the database as new local server in the usher configuration
* 5) reload the running usher instance so it acknowledges the new
* server
* *
* @param IDF_Project * @param IDF_Project
*/ */
@ -52,45 +59,113 @@ class IDF_Plugin_SyncMonotone
return; return;
} }
$repotempl = Pluf::f('mtn_repositories', false); $projecttempl = Pluf::f('mtn_repositories', false);
if ($repotempl === false) { if ($projecttempl === false) {
throw new IDF_Scm_Exception( throw new IDF_Scm_Exception(
'"mtn_repositories" must be defined in your configuration file.' '"mtn_repositories" must be defined in your configuration file.'
); );
} }
$usher_config = Pluf::f('mtn_usher', array()); $usher_config = Pluf::f('mtn_usher_conf', false);
if (!array_key_exists('rcfile', $usher_config) || if (!$usher_config || !is_writable($usher_config)) {
!is_writable($usher_config['rcfile'])) {
throw new IDF_Scm_Exception( throw new IDF_Scm_Exception(
'"rcfile" in "mtn_usher" does not exist or is not writable.' '"mtn_usher_conf" does not exist or is not writable.'
); );
} }
$shortname = $project->shortname; $shortname = $project->shortname;
$dbfile = sprintf($repotempl, $shortname); $projectpath = sprintf($projecttempl, $shortname);
if (file_exists($dbfile)) { if (file_exists($projectpath)) {
throw new IDF_Scm_Exception(sprintf( throw new IDF_Scm_Exception(sprintf(
__('The repository %s already exists.'), $dbfile __('The project path %s already exists.'), $projectpath
)); ));
} }
$return = 0;
$output = array(); if (!mkdir($projectpath)) {
throw new IDF_Scm_Exception(sprintf(
__('The project path %s could not be created.'), $projectpath
));
}
//
// step 1) create a new database
//
$dbfile = $projectpath.'/database.mtn';
$cmd = sprintf( $cmd = sprintf(
Pluf::f('mtn_path', 'mtn').' db init -d %s', Pluf::f('mtn_path', 'mtn').' db init -d %s',
escapeshellarg($dbfile) escapeshellarg($dbfile)
); );
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$ll = exec($cmd, $output, $return); $ll = exec($cmd, $output = array(), $return = 0);
if ($return != 0) { if ($return != 0) {
throw new IDF_Scm_Exception(sprintf( throw new IDF_Scm_Exception(sprintf(
__('Could not create repository %s - please check '. __('The database file %s could not be created.'), $dbfile
'your error log for details.'),
$dbfile
)); ));
} }
$usher_rc = file_get_contents($usher_config['rcfile']); //
// step 2) create a server key
//
// try to parse the key's domain part from the remote_url's host
// name, otherwise fall back to the configured Apache server name
$server = $_SERVER['SERVER_NAME'];
$remote_url = Pluf::f('mtn_remote_url');
if (($parsed = parse_url($remote_url)) !== false &&
!empty($parsed['host'])) {
$server = $parsed['host'];
}
$keyname = $shortname.'-server@'.$server;
$cmd = sprintf(
Pluf::f('mtn_path', 'mtn').' au genkey --confdir=%s %s ""',
escapeshellarg($projectpath),
escapeshellarg($keyname)
);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
$ll = exec($cmd, $output = array(), $return = 0);
if ($return != 0) {
throw new IDF_Scm_Exception(sprintf(
__('The server key %s could not be created.'), $keyname
));
}
//
// step 3) write monotonerc for access control
// FIXME: netsync access control is still missing!
//
$monotonerc =<<<END
function get_remote_automate_permitted(key_identity, command, options)
local read_only_commands = {
"get_corresponding_path", "get_content_changed", "tags", "branches",
"common_ancestors", "packet_for_fdelta", "packet_for_fdata",
"packets_for_certs", "packet_for_rdata", "get_manifest_of",
"get_revision", "select", "graph", "children", "parents", "roots",
"leaves", "ancestry_difference", "toposort", "erase_ancestors",
"descendents", "ancestors", "heads", "get_file_of", "get_file",
"interface_version", "get_attributes", "content_diff",
"file_merge", "show_conflicts", "certs", "keys"
}
for _,v in ipairs(read_only_commands) do
if (v == command[1]) then
return true
end
end
return false
end
END;
$rcfile = $projectpath.'/monotonerc';
// FIXME: sanity
$fp = fopen($rcfile, 'w');
fwrite($fp, $monotonerc);
fclose($fp);
//
// step 4) read in and append the usher config with the new server
//
$usher_rc = file_get_contents($usher_config);
$parsed_config = array(); $parsed_config = array();
try { try {
$parsed_config = IDF_Scm_Monotone_BasicIO::parse($usher_rc); $parsed_config = IDF_Scm_Monotone_BasicIO::parse($usher_rc);
@ -98,7 +173,7 @@ class IDF_Plugin_SyncMonotone
catch (Exception $e) { catch (Exception $e) {
throw new IDF_Scm_Exception(sprintf( throw new IDF_Scm_Exception(sprintf(
__('Could not parse usher configuration in "%s": %s'), __('Could not parse usher configuration in "%s": %s'),
$usher_config['rcfile'], $e->getMessage() $usher_config, $e->getMessage()
)); ));
} }
@ -121,17 +196,23 @@ class IDF_Plugin_SyncMonotone
$new_server = array( $new_server = array(
array('key' => 'server', 'values' => array($shortname)), array('key' => 'server', 'values' => array($shortname)),
array('key' => 'local', 'values' => array('-d', $dbfile)), array('key' => 'local', 'values' => array(
'--confdir', $projectpath,
'-d', $dbfile
)),
); );
$parsed_config[] = $new_server; $parsed_config[] = $new_server;
$usher_rc = IDF_Scm_Monotone_BasicIO::compile($parsed_config); $usher_rc = IDF_Scm_Monotone_BasicIO::compile($parsed_config);
// FIXME: more sanity - what happens on failing writes? // FIXME: more sanity - what happens on failing writes?
$fp = fopen($usher_config['rcfile'], 'w'); $fp = fopen($usher_config, 'w');
fwrite($fp, $usher_rc); fwrite($fp, $usher_rc);
fclose($fp); fclose($fp);
//
// step 5) reload usher to pick up the new configuration
//
IDF_Scm_Monotone_Usher::reload(); IDF_Scm_Monotone_Usher::reload();
} }
} }

View File

@ -21,6 +21,8 @@
# #
# ***** END LICENSE BLOCK ***** */ # ***** END LICENSE BLOCK ***** */
require_once(dirname(__FILE__) . "/BasicIO.php");
/** /**
* Connects with the admininistrative interface of usher, * Connects with the admininistrative interface of usher,
* the monotone proxy. This class contains only static methods because * the monotone proxy. This class contains only static methods because
@ -190,36 +192,56 @@ class IDF_Scm_Monotone_Usher
private static function _triggerCommand($cmd) private static function _triggerCommand($cmd)
{ {
$uc = Pluf::f('mtn_usher'); $uc = Pluf::f('mtn_usher_conf', false);
if (empty($uc['host'])) { if (!$uc || !is_readable($uc)) {
throw new IDF_Scm_Exception(
'"mtn_usher_conf" is not configured or not readable'
);
}
$parsed_config =
IDF_Scm_Monotone_BasicIO::parse(file_get_contents($uc));
$host = $port = $user = $pass = null;
foreach ($parsed_config as $stanza) {
foreach ($stanza as $line) {
if ($line['key'] == 'adminaddr') {
list($host, $port) = explode(":", @$line['values'][0]);
break;
}
if ($line['key'] == 'userpass') {
$user = @$line['values'][0];
$pass = @$line['values'][1];
}
}
}
if (empty($host)) {
throw new IDF_Scm_Exception('usher host is empty'); throw new IDF_Scm_Exception('usher host is empty');
} }
if (!preg_match('/^\d+$/', $uc['port']) || if (!preg_match('/^\d+$/', $port))
$uc['port'] == 0)
{ {
throw new IDF_Scm_Exception('usher port is invalid'); throw new IDF_Scm_Exception('usher port is invalid');
} }
if (empty($uc['user'])) { if (empty($user)) {
throw new IDF_Scm_Exception('usher user is empty'); throw new IDF_Scm_Exception('usher user is empty');
} }
if (empty($uc['pass'])) { if (empty($pass)) {
throw new IDF_Scm_Exception('usher pass is empty'); throw new IDF_Scm_Exception('usher pass is empty');
} }
$sock = @fsockopen($uc['host'], $uc['port'], $errno, $errstr); $sock = @fsockopen($host, $port, $errno, $errstr);
if (!$sock) { if (!$sock) {
throw new IDF_Scm_Exception( throw new IDF_Scm_Exception(
"could not connect to usher: $errstr ($errno)" "could not connect to usher: $errstr ($errno)"
); );
} }
fwrite($sock, 'USERPASS '.$uc['user'].' '.$uc['pass']."\n"); fwrite($sock, 'USERPASS '.$user.' '.$pass."\n");
if (feof($sock)) { if (feof($sock)) {
throw new IDF_Scm_Exception( throw new IDF_Scm_Exception(
'usher closed the connection - probably wrong admin '. 'usher closed the connection - this should not happen'
'username or password'
); );
} }
@ -232,7 +254,7 @@ class IDF_Scm_Monotone_Usher
$out = rtrim($out); $out = rtrim($out);
if ($out == 'unknown command') { if ($out == 'unknown command') {
throw new IDF_Scm_Exception("unknown command: $cmd"); throw new IDF_Scm_Exception('unknown command: '.$cmd);
} }
return $out; return $out;

View File

@ -78,9 +78,11 @@ $cfg['mtn_path'] = 'mtn';
# Additional options for the started monotone process # Additional options for the started monotone process
$cfg['mtn_opts'] = array('--no-workspace', '--norc'); $cfg['mtn_opts'] = array('--no-workspace', '--norc');
# #
# You can setup monotone for use with indefero in two ways: # You can setup monotone for use with indefero in several ways. The
# two most-used should be:
# #
# 1) One database for everything: # 1) One database for everything:
#
# Set 'mtn_repositories' below to a fixed database path, such as # Set 'mtn_repositories' below to a fixed database path, such as
# '/home/mtn/repositories/all_projects.mtn' # '/home/mtn/repositories/all_projects.mtn'
# #
@ -94,26 +96,39 @@ $cfg['mtn_opts'] = array('--no-workspace', '--norc');
# the database # the database
# #
# 2) One database for every project with 'usher': # 2) One database for every project with 'usher':
# Set 'mtn_remote_url' below to a string which matches your setup. #
# Download and configure 'usher'
# (mtn clone mtn://monotone.ca?net.venge.monotone.contrib.usher)
# which acts as proxy in front of all single project databases.
# Create a basic configuration file for it and add a secret admin
# username and password. Finally, point the below variable
# 'mtn_usher_conf' to this configuration file.
#
# Then set 'mtn_remote_url' below to a string which matches your setup.
# Again, the '%s' placeholder will be expanded to the project's # Again, the '%s' placeholder will be expanded to the project's
# short name. Note that 'mtn_remote_url' is used as internal # short name. Note that 'mtn_remote_url' is used as internal
# URI (to access the data for indefero) as well as external URI # URI (to access the data for indefero) as well as external URI
# (for end users) at the same time. # (for end users) at the same time. 'mtn_repositories' should then
# point to a directory where all project-related files (databases,
# keys, configurations) are kept, as these are automatically created
# on project creation by IDF.
# #
# Then download and configure 'usher' # Example: 'mtn_repositories' is configured to be '/var/monotone/%s'
# (mtn clone mtn://monotone.ca?net.venge.monotone.contrib.usher)
# which acts as proxy in front of all single project databases.
# Usher's server names should be mapped to the project's short names,
# so you end up with something like this for every project:
# #
# server "project" # - IDF tries to create /var/monotone/<projectname> as root directory
# local "-d" "/home/mtn/repositories/project.mtn" "*" # - The database is placed in as /var/monotone/<projectname>/database.mtn
# - The server key is put into /var/monotone/<projectname>/keys and
# is named "<projectname>-server@<host>", where host is the host part
# of 'mtn_remote_url'
# #
# Alternatively if you assign every project a unique DNS such as # therefor /var/monotone MUST be read/writable for the www user and all
# 'project.my-hosting.biz', you can also configure it like this: # files which are created underknees MUST be read/writable by the user
# who is executing the usher instance! The best way to achieve this is with
# default (POSIX) ACLs on /var/monotone.
# #
# host "project.my-hosting.biz" #
# local "-d" "/home/mtn/repositories/project.mtn" "*" # You could also choose to setup usher by hand, i.e. with individual
# databases, in this case leave 'mtn_usher_conf' below commented out.
# #
# Pro: - read and write access can be granted per project # Pro: - read and write access can be granted per project
# - no database locking issues # - no database locking issues
@ -143,22 +158,11 @@ $cfg['mtn_remote_url'] = 'mtn://my-host.biz/%s';
$cfg['mtn_db_access'] = 'remote'; $cfg['mtn_db_access'] = 'remote';
# #
# If configured, this allows basic control of a running usher process # If configured, this allows basic control of a running usher process
# via the forge administration # via the forge administration. The variable must point to the full (writable)
# # path of the usher configuration file which gets updated when new projects
# 'host' and 'port' must be set to the specific bits from usher's # are added
# configured 'adminaddr', 'user' and 'pass' must match the values set for
# the configured 'userpass' combination. The 'rcfile' variable must point
# to the full (writable) path of the usher configuration file which gets
# updated when new projects are added
#
#$cfg['mtn_usher'] = array(
# 'host' => 'localhost',
# 'port' => 12345,
# 'user' => 'admin',
# 'pass' => 'admin',
# 'rcfile' => '/path/to/usher.conf',
#);
# #
#$cfg['mtn_usher_conf'] = '/path/to/usher.conf';
# Mercurial repositories path # Mercurial repositories path
#$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s'; #$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s';

View File

@ -386,7 +386,7 @@ $ctl[] = array('regex' => '#^/admin/users/(\d+)/$#',
'model' => 'IDF_Views_Admin', 'model' => 'IDF_Views_Admin',
'method' => 'userUpdate'); 'method' => 'userUpdate');
if (Pluf::f("mtn_usher", null) !== null) if (Pluf::f("mtn_usher_conf", null) !== null)
{ {
$ctl[] = array('regex' => '#^/admin/usher/$#', $ctl[] = array('regex' => '#^/admin/usher/$#',
'base' => $base, 'base' => $base,