#!/usr/bin/perl use strict; use lib qw(/usr/lib/libDrakX); use standalone; use common; use mouse; use detect_devices; use ugtk2 qw(:create :dialogs :helpers :wrappers); use interactive; use Libconf qw(:functions); use Libconf::Glueconf::NUT::Ups_conf; # config files: my %files = (devices => "/etc/ups/ups.conf", access => "/etc/ups/upsd.conf", users => "/etc/ups/upsd.users", ); my ($struct, $users); # NUT configuration my ($w, $in); # GUI my %indexes; sub writeconf() { info_dialog(N("Warning"), "Write support for users is incomplete\n\nIt lacks some support for some extra fields that would be lost else"); log::explanations("Updating NUT configuration accordingly"); $struct->writeConf($files{devices}); if ($users) { log::explanations("Updating NUT users configuration accordingly"); $users->writeConf($files{users}); } require services; services::restart("upsd"); } sub read_nut_config() { $struct = Libconf::Glueconf::NUT::Ups_conf->new({ filename => $files{devices} }); } sub readDriversList() { my (%ups, @ups); local $_; foreach (cat_(first(glob("/usr/share/doc/nut-*/docs/driver.list")))) { /^#/ and next; if (my ($vendor, $model, $extra, $driver) = /^"(.*)"\s+"(.*)"\s+"(.*)"\s+"(.*)"/) { $ups{$vendor}{$model . $extra} = { driver => $driver, extra => $extra, }; push @ups, "$vendor|$model ($extra)"; } } \%ups, \@ups; } my %models; sub add_device_wizard { my ($in, $config) = @_; my ($ups_models, $model_list) = readDriversList(); use wizards; my ($ups, $vendor, $model, $extra, $name, $driver, $port, @new_devices, $opts); my $w = wizards->new; my $wiz; my %methods = ( # network => N("Connected through the network"), # need SNMP probe # serial => N("Connected through a serial port"), # usb => N("Connected through an usb cable"), auto => N("Connected through a serial port or an usb cable"), manual => N("Manual configuration"), ); my $method = $methods{auto}; $wiz = { #defaultimage => "logdrake.png", # FIXME name => N("Add an UPS device"), pages => { welcome => { name => N("Welcome to the UPS configuration utility. Here, you'll add a new UPS to your system.\n"), no_back => 1, next => 'method' }, method => { name => N("We're going to add an UPS device. Do you want to autodetect UPS devices connected to this machine or to manually select them?"), data => [ { label => N("Autodetection"), val => \$method, type => "list", list => [ values %methods ] } ], post => sub { +{ reverse %methods }->{$method} }, }, auto => { end => 1, pre => sub { local $::isWizard; my $_wait = $in->wait_message(N("Please wait"), N("Detection in progress")); # UPS autoconfig: detect_devices::probeSerialDevices() if !$::testing; @new_devices = (); foreach my $ups_device (detect_devices::getUPS()) { my $str = $ups_device->{name} || $ups_device->{DESCRIPTION}; $str =~ s/ /_/g; $name = $str; if (!exists $struct->{$str}) { $port = $struct->{$str}{port} = $ups_device->{port} || $ups_device->{DEVICE}; $driver = $struct->{$str}{driver} = $ups_device->{driver}; push @new_devices, $str; } } }, name => sub { if (@new_devices) { N("Congratulations") . "\n\n" . N("The wizard successfully added the following UPS devices:") . join("\n\n-", @new_devices); } else { N("No new UPS devices was found"); } }, }, manual => { name => N("UPS driver configuration") . "\n\n" . N("Please select your UPS model."), data => [ { label => N("Manufacturer / Model:"), val => \$ups, list => $model_list, type => 'combo', sort => 1, separator => '|' }, ], post => sub { ($vendor, $model, $extra) = ($1, $2, $3) if $ups =~ /(.*)\|(.*) \((.*)\)$/; ($name, $driver, $port) = ("myups", $ups_models->{$vendor}{$model . $extra}{driver}, ""); ($driver, $opts) = split(/\s+/, $driver); "driver"; }, }, driver => { name => sub { N("UPS driver configuration") . "\n\n" . N("We are configuring the \"%s\" UPS from \"%s\". Please fill in its name, its driver and its port.", $model, $vendor); }, data => sub { [ { label => N("Name:"), val => \$name, help => N("The name of your ups") }, { label => N("Driver:"), val => \$driver, help => N("The driver that manages your ups") }, { label => N("Port:"), val => \$port, format => \&mouse::serial_port2text, type => "combo", list => [ &mouse::serial_ports() ], not_edit => 0, help => N("The port on which is connected your ups") }, ]; }, post => sub { $port = '/dev/' . $port if $port !~ m!/dev/!; return 'end'; }, }, end => { name => sub { N("Congratulations") . "\n\n" . N("The wizard successfully configured the new \"%s\" UPS device.", $model . "|" . $vendor); }, end => 1, no_back => 1, next => 0 }, }, }; $w->process($wiz, $in); $config->{$name}{driver} = $driver; $config->{$name}{port} = $port; $config->{$name}{$1} = $2 if $opts =~ /\b(.*)=(.*)\b/; # refresh the GUI when needed: $models{ups}->append_set(1 => $name, 2 => $driver, 3 => $port) if $models{ups}; log::explanations(qq(Configuring "$name" UPS)); } my (@acls, @rules); sub load_access_conf() { foreach (cat_($files{access})) { s/#.*//; if (/^\s*ACL\s*(\S*)\s*(\S*)/) { my ($ip, $mask) = split('/', $2); push @acls, [ $1, $ip, $mask ]; } elsif (/^\s*ACCESS\s*(\S*)\s*(\S*)\s*(\S*)/) { push @rules, [ $1, $2, $3 ]; } } } #------------------------------------------------------------------ # misc gui data sub edit_row { my ($model, $o_iter) = @_; # create new item if needed (that is when adding a new one) at end of list my $iter = $o_iter || $model->append; my $dialog = Gtk2::Dialog->new; $dialog->set_transient_for($w->{real_window}); $dialog->set_modal(1); gtkpack_($dialog->vbox, #map { #} ); gtkadd($dialog->action_area, gtksignal_connect(Gtk2::Button->new(N("Ok")), clicked => sub { # create new item if needed (that is when adding a new one) at end of list $iter ||= $model->append; # $model->set($iter, 1 => $file->get_text); # FILL ME $dialog->destroy; # $modified++; }), gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { $dialog->destroy }), ); $dialog->show_all; } sub add_callback { my ($model, $_list, $_getindex) = @_; edit_row($model); } sub edit_callback { my ($model, $list) = @_; my ($iter) = $list->get_selection->get_selected; return unless $iter; edit_row($model, $iter); } sub del_callback { my ($model, $list) = @_; my (undef, $iter) = $list->get_selection->get_selected; my $removed_idx = $list->get($iter, 0); # 1st column is index #@rules = grep { $_->{index} ne $removed_idx } @rules; #$tree->remove($iter); #sensitive_buttons(0); #$modified++; } my @pages = ( { name => N("UPS devices"), columns => [ N("Name"), N("Driver"), N("Port") ], # N("Manufacturer"), N("Model"), callbacks => { add => sub { eval { add_device_wizard($in, $struct) }; my $err = $@; die $err if $err && $err !~ /wizcancel/; $::WizardWindow->destroy if defined $::WizardWindow; undef $::WizardWindow; }, edit => sub {}, remove => sub {}, }, load => sub { read_nut_config(); map { [ $_, @{$struct->{$_}}{qw(driver port)} ] } keys %$struct; }, id => "ups", }, { name => N("UPS users"), columns => [ N("Name") ], callbacks => { add => sub { my ($name) = @_; $users->{$name} = {}; }, edit => sub {}, remove => sub {}, }, load => sub { $users = Libconf::Glueconf::NUT::Ups_conf->new({ filename => $files{users} }); map { [ $_ ] } keys %$users; }, id => "users", }, { name => N("Access Control Lists"), columns => [ N("Name"), N("IP address"), N("IP mask") ], callbacks => { add => sub {}, edit => sub {}, remove => sub {}, }, load => sub { load_access_conf(); @acls; }, id => "acls", }, { name => N("Rules"), columns => [ N("Action"), N("Level"), N("ACL name"), N("Password") ], callbacks => { N("Add") => sub {}, N("Edit") => sub {}, N("Remove") => sub {}, }, load => sub { @rules }, # already loaded when we loaded acls id => "rules", }, ); #------------------------------------------------------------------ # initialize: #$in = 'interactive'->vnew('su'); # require_root_capability(); $in = 'interactive'->vnew; $ugtk2::wm_icon = "drakups"; $in->do_pkgs->ensure_is_installed('nut-server', '/etc/rc.d/init.d/upsd') if !$::testing; if (member('--wizard', @ARGV)) { read_nut_config(); add_device_wizard($in, $struct); writeconf(); $in->exit($@ ? 1 : 0); } $w = ugtk2->new(N("DrakUPS")); if (!$::isEmbedded) { $::main_window = $w->{real_window}; $w->{window}->set_size_request(500, 550); $w->{rwindow}->set_title(N("DrakUPS")); } #------------------------------------------------------------------ # main window: my $_msg = N("Welcome to the UPS configuration tools"); $w->{window}->add(gtkpack_(Gtk2::VBox->new, if_(!$::isEmbedded, 0, Gtk2::Banner->new('drakups', N("DrakUPS"))), 1, my $nb = Gtk2::Notebook->new, 0, create_okcancel(my $oc = { ok_clicked => sub { #$_->{save}->() foreach @pages; writeconf(); $w->exit; }, cancel_clicked => sub { $w->exit }, }, ), ), ); #------------------------------------------------------------------ # build the notebook my %labels = ( add => N("Add"), edit => N("Edit"), remove => N("Remove"), ); foreach my $i (@pages) { my $model = $models{$i->{id}} = Gtk2::ListStore->new("Glib::Int", ("Glib::String") x listlength(@{$i->{columns}})); my (%buttons, $list); $indexes{$i->{name}} = 0; my $idx = \$indexes{$i->{name}}; my $getindex = sub { $$idx++ }; $nb->append_page(gtkpack_(Gtk2::VBox->new, 1, create_scrolled_window($list = Gtk2::TreeView->new_with_model($model), [ 'automatic', 'automatic' ]), 0, gtkpack(Gtk2::HButtonBox->new, (map { my ($id, $label, $sub) = @$_; gtksignal_connect($buttons{$id} = Gtk2::Button->new($label), clicked => sub { $sub->($model, $list, $getindex); }); } ([ 'add', N("Add"), $i->{callbacks}{add} || \&add_callback ], [ 'edit', N("Edit"), \&edit_callback ], [ 'remove', N("Remove"), \&del_callback ], ) ) #(map { # gtksignal_connect(Gtk2::Button->new($_), clicked => $i->{callbacks}{$_}), #} keys %{$i->{callbacks}}) ), ), Gtk2::Label->new($i->{name}), ); #$i->{list} = $list; each_index { $list->append_column(Gtk2::TreeViewColumn->new_with_attributes($_, Gtk2::CellRendererText->new, 'text' => $::i + 1)); } @{$i->{columns}}; my @u = $i->{load}->(); foreach my $line (@u) { $model->append_set(0 => $getindex->(), map_index { $::i + 1 => $_ } @$line); } my $set_sensitive = sub { my ($bool) = @_; $buttons{$_}->set_sensitive($bool) foreach qw(remove edit); }; $set_sensitive->(0); $list->get_selection->signal_connect('changed' => sub { my ($select) = @_; my (undef, $iter) = $select->get_selected; $set_sensitive->(defined $iter); }); } #------------------------------------------------------------------ # let's start the show: $w->{rwindow}->show_all; $w->main; pan class="hl str">&& find [A-Z]* -noleaf -type f |"); my @l = difference2([ chomp_(<$F>) ], [ 'ROC', 'PRC' ]); close $F or die "cannot list the available zoneinfos"; sort @l; } sub read() { my %t = getVarsFromSh("$::prefix/etc/sysconfig/clock") or return {}; { timezone => $t{ZONE}, UTC => text2bool($t{UTC}) }; } my $ntp_conf_file = "/etc/ntp.conf"; sub ntp_server() { find { $_ ne '127.127.1.0' } map { if_(/^\s*server\s+(\S*)/, $1) } cat_($::prefix . $ntp_conf_file); } sub set_ntp_server { my ($server) = @_; my $f = $::prefix . $ntp_conf_file; -f $f or return; my $pool_match = qr/\.pool\.ntp\.org$/; my @servers = $server =~ $pool_match ? (map { "$_.$server" } 0 .. 2) : $server; my $added = 0; substInFile { if (/^#?\s*server\s+(\S*)/ && $1 ne '127.127.1.0') { $_ = $added ? $_ =~ $pool_match ? undef : "#server $1\n" : join('', map { "server $_\n" } @servers); $added = 1; } } $f; output_p("$::prefix/etc/ntp/step-tickers", join('', map { "$_\n" } @servers)); require services; services::set_status('ntpd', to_bool($server), $::isInstall); } sub write { my ($t) = @_; set_ntp_server($t->{ntp}); my $tz_prefix = get_timezone_prefix(); eval { cp_af($tz_prefix . '/' . $t->{timezone}, "$::prefix/etc/localtime") }; $@ and log::l("installing /etc/localtime failed"); setVarsInSh("$::prefix/etc/sysconfig/clock", { ZONE => $t->{timezone}, UTC => bool2text($t->{UTC}), ARC => "false", }); my $adjtime_file = $::prefix . '/etc/adjtime'; my @adjtime = cat_($adjtime_file); @adjtime or @adjtime = ("0.0 0 0.0\n", "0\n"); $adjtime[2] = $t->{UTC} ? "UTC\n" : "LOCAL\n"; output_p($adjtime_file, @adjtime); } sub reload_sys_clock { my ($t) = @_; require run_program; any::disable_x_screensaver(); run_program::run('hwclock', '--hctosys', ($t->{UTC} ? '--utc' : '--localtime')); any::enable_x_screensaver(); } #- best guesses for a given country my %c2t = ( 'AM' => 'Asia/Yerevan', 'AR' => 'America/Buenos_Aires', 'AT' => 'Europe/Vienna', 'AU' => 'Australia/Sydney', 'BA' => 'Europe/Sarajevo', 'BE' => 'Europe/Brussels', 'BG' => 'Europe/Sofia', 'BR' => 'America/Sao_Paulo', #- most brazilians live on this time zone 'BY' => 'Europe/Minsk', 'CA' => 'Canada/Eastern', 'CH' => 'Europe/Zurich', 'CN' => 'Asia/Beijing', 'CZ' => 'Europe/Prague', 'DE' => 'Europe/Berlin', 'DK' => 'Europe/Copenhagen', 'EE' => 'Europe/Tallinn', 'ES' => 'Europe/Madrid', 'FI' => 'Europe/Helsinki', 'FR' => 'Europe/Paris', 'GB' => 'Europe/London', 'GE' => 'Asia/Yerevan', 'GL' => 'Arctic/Longyearbyen', 'GR' => 'Europe/Athens', 'HR' => 'Europe/Zagreb', 'HU' => 'Europe/Budapest', 'ID' => 'Asia/Jakarta', 'IE' => 'Europe/Dublin', 'IL' => 'Asia/Tel_Aviv', 'IN' => 'Asia/Kolkata', 'IR' => 'Asia/Tehran', 'IS' => 'Atlantic/Reykjavik', 'IT' => 'Europe/Rome', 'JP' => 'Asia/Tokyo', 'KR' => 'Asia/Seoul', 'LT' => 'Europe/Vilnius', 'LV' => 'Europe/Riga', 'MK' => 'Europe/Skopje', 'MT' => 'Europe/Malta', 'MX' => 'America/Mexico_City', 'MY' => 'Asia/Kuala_Lumpur', 'NL' => 'Europe/Amsterdam', 'NO' => 'Europe/Oslo', 'NZ' => 'Pacific/Auckland', 'PL' => 'Europe/Warsaw', 'PT' => 'Europe/Lisbon', 'RO' => 'Europe/Bucharest', 'RU' => 'Europe/Moscow', 'SE' => 'Europe/Stockholm', 'SI' => 'Europe/Ljubljana', 'SK' => 'Europe/Bratislava', 'TH' => 'Asia/Bangkok', 'TJ' => 'Asia/Dushanbe', 'TR' => 'Europe/Istanbul', 'TW' => 'Asia/Taipei', 'UA' => 'Europe/Kiev', 'US' => 'America/New_York', 'UZ' => 'Asia/Tashkent', 'VN' => 'Asia/Saigon', 'YU' => 'Europe/Belgrade', 'ZA' => 'Africa/Johannesburg', ); sub fuzzyChoice { my ($b, $count) = bestMatchSentence($_[0], keys %c2t); $count ? $b : ''; } sub bestTimezone { $c2t{fuzzyChoice($_[0])} || 'GMT' } our %ntp_servers; sub get_ntp_server_tree { my ($zone) = @_; map { $ntp_servers{$zone}{$_} => ( exists $ntp_servers{$_} ? $zone ? translate($_) . "|" . N("All servers") : N("All servers") : translate($zone) . "|" . translate($_) ), get_ntp_server_tree($_); } keys %{$ntp_servers{$zone}}; } sub ntp_servers() { # FIXME: missing parameter: +{ get_ntp_server_tree() }; } sub dump_ntp_zone { my ($zone) = @_; map { if_(/\[\d+\](.+) -- (.+\.ntp\.org)/, $1 => $2) } `lynx -dump http://www.pool.ntp.org/zone/$zone`; } sub print_ntp_zone { my ($zone, $name) = @_; # FIXME: missing parameter: my %servers = dump_ntp_zone($zone); print qq(\$ntp_servers{"$name"} = {\n); print join('', map { qq( N_("$_") => "$servers{$_}",\n) } sort(keys %servers)); print "};\n"; \%servers; } sub print_ntp_servers() { print_ntp_zone(); my $servers = print_ntp_zone('@', "Global"); foreach my $name (sort(keys %$servers)) { my ($zone) = $servers->{$name} =~ /^(.*?)\./; print_ntp_zone($zone, $name); } } # perl -Mtimezone -e 'timezone::print_ntp_servers()' $ntp_servers{""} = { N_("Global") => "pool.ntp.org", }; $ntp_servers{Global} = { N_("Africa") => "africa.pool.ntp.org", N_("Asia") => "asia.pool.ntp.org", N_("Europe") => "europe.pool.ntp.org", N_("North America") => "north-america.pool.ntp.org", N_("Oceania") => "oceania.pool.ntp.org", N_("South America") => "south-america.pool.ntp.org", }; $ntp_servers{Africa} = {