#!perl
my $version = '0.15';
use 5.10.1;
use strictures 1;
use File::Path;
use File::Spec;
use Term::ReadKey qw/ReadMode/;
use Bot::Cobalt::Utils qw/ rplprintf mkpasswd /;
use Bot::Cobalt::Frontend::Utils qw/:all/;
use Bot::Cobalt::Frontend::RC qw/rc_read rc_write/;
my $rcfile = $ENV{HOME} ?
File::Spec->catfile( $ENV{HOME}, ".cobalt2rc" )
: ".cobalt2rc";
use Getopt::Long;
GetOptions(
version => sub {
say "$0 $version";
exit 0
},
help => sub {
print(
"$0 $version\n\n",
"Options:\n",
" -c, --rcfile=PATH Path to cobalt2 rcfile\n",
" [default: $rcfile]\n",
);
exit 0
},
'rcfile=s' => \$rcfile,
'config=s' => \$rcfile,
);
my $config_q = {
info => [
{
## For normal questions ask_question and set Var in hash
Question => "Bot's nickname",
Default => "cobalt2",
Var => "CFG_BOT_NICK",
},
{
Question => "Bot's username",
Default => "cobalt",
Var => "CFG_BOT_USERNAME",
},
{
Question => "Bot's 'realname'",
Default => "Bot::Cobalt",
Var => "CFG_BOT_REALNAME",
},
{
Question => "Command character for this bot",
Default => '!',
Var => "CFG_CMD_CHAR",
},
],
server => [
{
Question => "Remote server address",
Default => "irc.cobaltirc.org",
Var => "CFG_SERVER_ADDR",
},
{
## For YesNo => use ask_yesno and default to YesNo value
Question => "Use SSL for this server? (Requires POE::Component::SSLify)",
YesNo => 'n',
Var => "CFG_USE_SSL",
},
{
Question => "Remote IRC port",
Default => 6667,
Var => "CFG_SERVER_PORT",
},
],
channel => [
{
Question => "Channel name",
Default => '#eris',
Var => "CHAN",
},
],
auth => [
{
Question => "Username for your superuser",
Default => "MyUser",
Var => "AUTH_USER",
},
{
Question => "Password",
Hide => 1,
Crypt => 1,
Var => "AUTH_PASS",
},
{
Question => "Hostmask",
Default => '*nobody@example.org',
Var => "AUTH_MASK",
},
],
};
sub ask_from_ref {
my ($hash_r) = @_;
## my ($var, $value) = ask_from_ref($itemref);
unless ($hash_r->{Question} && $hash_r->{Var}) {
die "Missing Question or Var"
}
my $type = $hash_r->{YesNo} ? 'yesno' : 'question' ;
my $var = $hash_r->{Var};
my $q = $hash_r->{Question};
my $value;
if ($type eq 'yesno') {
$value = ask_yesno(
prompt => $q,
default => $hash_r->{YesNo},
);
} else {
ReadMode('noecho') if $hash_r->{Hide};
$value = ask_question(
prompt => $q,
default => $hash_r->{Default},
);
if ($hash_r->{Hide}) {
ReadMode(0);
print "\n";
}
if ($hash_r->{Crypt}) {
$value = mkpasswd($value);
}
}
return($var, $value)
}
sub _deserialize_etc {
## Pulls our /etc from our DATA handle
my $cfs;
{ local $/ ; $cfs = <DATA>; }
my $ref;
{ local $@;
$ref = eval $cfs;
die "Could not reconstitute example confs" if $@;
}
return ref $ref eq 'HASH' ? $ref
: die "Reconstituted example confs not a HASH";
}
sub _slurp {
my ($path) = @_;
## Slurp a file
my $str;
open(my $fh, '<', $path) or die "failed file read: $path: $!" ;
{ local $/ ; $str = <$fh>; }
close($fh) or warn "failed to close $path: $!\n";
return $str
}
## Conf_* funcs
sub Conf_write_confs {
## Conf_write_confs($vars_replacement_hash)
my ($vars_h) = @_;
my ($base, $etc, $var) = rc_read($rcfile);
File::Path::mkpath( $var ."/db" );
File::Path::mkpath( $var ."/tmp" );
my $ref = _deserialize_etc();
STDOUT->autoflush(1);
## use File::Path to create our dir structure
## write our deserialized confs back out
## run rplprintf against each file before writing
for my $confpath (keys %$ref) {
my $output_path = $etc ."/". $confpath ;
(undef, my $dir, my $file) = File::Spec->splitpath($output_path);
File::Path::mkpath($dir) if $dir;
die "tried to create $dir but can't be found?" unless -e $dir;
my $content = $ref->{$confpath};
my $output = rplprintf( $content, $vars_h );
print ">! Writing $output_path .. ";
if (-e $output_path) {
say ">! Exists already: $output_path";
my $yesno = ask_yesno(
prompt => "Overwrite this file?",
default => 'n',
);
if ($yesno) {
say ">! Overwriting $output_path";
} else { say "Skipped $output_path"; next }
}
open(my $fh, '>', $output_path) or die "open failed: $output_path: $!";
print $fh $output;
close($fh) or warn "close failed on $output_path: $!\n";
chmod(0600, $output_path) if $output_path =~ /auth\.conf$/;
print "OK\n";
}
say ">! Finished writing confs.";
}
sub Conf_interactive {
my $cf = {};
print(
"Interactively configuring basic cobalt2 opts.\n",
"This installer only covers the most basic set of options.\n",
"You should review etc/ yourself when finished.\n\n",
"Defaults are shown [like this]\n\n",
);
say ">! Configuring cobalt.conf";
for my $itemref (@{ $config_q->{info} }) {
my ($var, $value) = ask_from_ref( $itemref );
$cf->{$var} = $value;
}
for my $itemref (@{ $config_q->{server} }) {
my ($var, $value) = ask_from_ref($itemref);
$cf->{$var} = $value;
}
print "\n";
say ">! Done configuring cobalt.conf" ;
say ">! There are many more opts; review the file!";
print "\n";
# plugins.conf is probably fine with a suitable example file
say ">! Configuring channels.conf\n";
say "You're going to want to set up an initial channel.";
for my $itemref (@{ $config_q->{channel} }) {
my ($var, $value) = ask_from_ref($itemref);
$cf->{$var} = $value;
}
print "\n";
say "You may want to edit channels.conf and add other channels.\n";
say ">! Configuring auth.conf\n";
say "You'll want at least one SuperUser to control the bot.";
say "Authorized users have a username that may or may not be their IRC nick.";
for my $itemref (@{ $config_q->{auth} }) {
my ($var, $value) = ask_from_ref($itemref);
$cf->{$var} = $value;
}
print "\n";
return $cf
}
## MAIN
print(
"This is the cobalt2 install helper.\n",
"This script will create a cobalt2rc file specifying install paths.\n",
"It will also help you initialize some starter cobalt2 conf files.\n\n",
"> Press ENTER to continue, Ctrl+C to quit.",
);
<STDIN>;
say "> Default rcfile: $rcfile";
## prompt user for a rcfile path
my $enter_diff_yn = ask_yesno(
prompt => "Enter a different rcfile location?",
default => 'n',
);
if ( $enter_diff_yn ) {
$rcfile = ask_question(
prompt => "Path to rcfile",
);
}
say ">! using rcfile: $rcfile";
## prompt user for a relative basedir
say "> Cobalt needs a place for etc/ and var/ to live.";
say "> Normally this would be somewhere in your HOME directory.";
say "> Specify a directory relative to your HOME for cobalt to live:";
my $relative_basedir = ask_question(
prompt => "Base directory",
default => "cobalt2",
);
my $real_basedir; ## Reported by rc_write
if (-e $rcfile) {
say ">! $rcfile seems to already exist.";
my $overwrite_yn = ask_yesno(
prompt => "Overwrite existing rcfile?",
default => 'n',
);
if ( $overwrite_yn ) {
$real_basedir = rc_write($rcfile, $relative_basedir)
or die "Failed rc_write";
} else {
say ">! Skipping rcfile write; attempting to use existing.";
}
} else {
$real_basedir = rc_write($rcfile, $relative_basedir)
or die "Failed rc_write";
}
say ">! Wrote rcfile; base path is $real_basedir";
say ">! To change, edit $rcfile\n";
my $rplvars = Conf_interactive();
Conf_write_confs($rplvars);
say ">! Finished.";
=pod
=head1 NAME
cobalt2-installer - install a cobalt2 instance
=head1 SYNOPSIS
B<cobalt2-installer> [ --rcfile=PATH ]
=head1 DESCRIPTION
A simple walk-through installer for fresh L<Bot::Cobalt> instances.
Asks a few questions & copies initial confs and langsets to a specified
directory.
Typically you would run this installer before starting Cobalt:
## Initiate a new cobalt2 instance
## Each instance has its own rcfile
$ cobalt2-installer
$ cobalt2
You can run multiple instances of Cobalt, but they each need their own
I<etc> and I<var> directories, specified in their own rcfile:
## Initiate another cobalt2 instance
$ cobalt2-installer --rcfile=${HOME}/cobalts/MyCobalt.rc
$ cobalt2 --rcfile=${HOME}/cobalts/MyCobalt.rc
=head1 AUTHOR
Jon Portnoy <avenj@cobaltirc.org>
L<http://www.cobaltirc.org>
=cut
__DATA__