freeswitch provisioning: one file per domain, RT#18087
[freeside.git] / FS / FS / part_export / freeswitch.pm
1 package FS::part_export::freeswitch;
2 use base qw( FS::part_export );
3
4 use vars qw( %info ); # $DEBUG );
5 #use Data::Dumper;
6 use Tie::IxHash;
7 use Text::Template;
8 use FS::Record qw( qsearch ); #qsearchs );
9 use FS::svc_phone;
10 #use FS::Schema qw( dbdef );
11
12 #$DEBUG = 1;
13
14 tie my %options, 'Tie::IxHash',
15   'user'  => { label => 'SSH username', default=>'root', },
16   'directory' => { label   => 'Directory to store FreeSWITCH account XML files',
17                    default => '/usr/local/freeswitch/conf/directory/',
18                  },
19   #'domain'    => { label => 'Optional fixed SIP domain to use, overrides svc_phone domain', },
20   'reload'    => { label   => 'Reload command',
21                    default => '/usr/local/freeswitch/bin/fs_cli -x reloadxml',
22                  },
23   'user_template' => { label   => 'User XML configuration template',
24                        type    => 'textarea',
25                        default => <<'END',
26 <domain name="<% $domain %>">
27   <user id="<% $phonenum %>">
28     <params>
29       <param name="password" value="<% $sip_password %>"/>
30     </params>
31   </user>
32 </domain>
33 END
34                      },
35 ;
36
37 %info = (
38   'svc'     => 'svc_phone',
39   'desc'    => 'Provision phone services to FreeSWITCH XML configuration files',
40   'options' => \%options,
41   'notes'   => <<'END',
42 Export XML account configuration files to FreeSWITCH, one per domain.
43 <br><br>
44 You will need to enable the svc_phone-domain configuration setting and
45 <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">setup SSH for unattended operation</a>.
46 END
47 );
48
49 sub rebless { shift; }
50
51 sub _export_insert {
52   my( $self, $svc_phone ) = ( shift, shift );
53
54   $self->_export_rebuild_domain($svc_phone);
55
56 }
57
58 sub _export_replace {
59   my( $self, $new, $old ) = ( shift, shift, shift );
60
61   my $error = $self->_export_rebuild_domain($new);
62   return $error if $error;
63
64   if ( $new->domsvc ne $old->domsvc && $old->domsvc ) {
65     $error = $self->_export_rebuild_domain($old);
66     return $error if $error;
67   }
68
69   '';
70 }
71
72 sub _export_delete {
73   my( $self, $svc_phone ) = ( shift, shift );
74
75   $self->_export_rebuild_domain($svc_phone);
76 }
77
78 sub _export_rebuild_domain {
79   my($self, $svc_phone) = ( shift, shift );
80
81   eval "use Net::SCP;";
82   die $@ if $@;
83
84   #create and copy over file
85
86   my $tempdir = '%%%FREESIDE_CONF%%%/cache.'. $FS::UID::datasrc;
87
88   my $domain = $svc_phone->domain or return "domain required";
89
90   my $fh = new File::Temp(
91     TEMPLATE => "$tempdir/freeswitch.$domain.XXXXXXXX",
92     DIR      => $dir,
93     #UNLINK   => 0,
94   );
95
96   print $fh qq(<domain name="$domain">\n);
97
98   my @dom_svc_phone = qsearch( 'svc_phone', { 'domsvc'=>$svc_phone->domsvc } );
99
100   foreach my $dom_svc_phone (@dom_svc_phone) {
101
102     print $fh $self->freeswitch_template_fillin( $dom_svc_phone, 'user' )
103       or die "print to freeswitch template failed: $!";
104
105   }
106
107   print $fh qq(</domain>\n);
108   $fh->flush;
109
110   my $scp = new Net::SCP;
111   my $user = $self->option('user')||'root';
112   my $host = $self->machine;
113   my $dir = $self->option('directory');
114
115   $scp->scp( $fh->filename, "$user\@$host:$dir/$domain.xml" )
116     or return $scp->{errstr};
117
118   #signal freeswitch to reload config
119   $self->freeswitch_ssh( command => $self->option('reload') );
120
121   '';
122
123 }
124
125 sub freeswitch_template_fillin {
126   my( $self, $svc_phone, $template ) = (shift, shift, shift);
127
128   $template ||= 'user'; #?
129
130   #cache a %tt hash?
131   my $tt = new Text::Template (
132     TYPE       => 'STRING',
133     SOURCE     => $self->option($template.'_template'),
134     DELIMITERS => [ '<%', '%>' ],
135   );
136
137   #false lazinessish w/phone_shellcommands::_export_command
138   my %hash = (
139     map { $_ => $svc_phone->getfield($_) } $svc_phone->fields
140   );
141
142   #might as well do em all, they're all going in an XML file as attribs
143   foreach ( keys %hash ) {
144     $hash{$_} =~ s/'/&apos;/g;
145     $hash{$_} =~ s/"/&quot;/g;
146   }
147
148   $tt->fill_in(
149     HASH => \%hash,
150   );
151 }
152
153 ##a good idea to queue anything that could fail or take any time
154 #sub shellcommands_queue {
155 #  my( $self, $svcnum ) = (shift, shift);
156 #  my $queue = new FS::queue {
157 #    'svcnum' => $svcnum,
158 #    'job'    => "FS::part_export::freeswitch::ssh_cmd",
159 #  };
160 #  $queue->insert( @_ );
161 #}
162
163 sub freeswitch_ssh { #method
164   my $self = shift;
165   ssh_cmd( user    => $self->option('user')||'root',
166            host    => $self->machine,
167            @_,
168          );
169 }
170
171 sub ssh_cmd { #subroutine, not method
172   use Net::OpenSSH;
173   my $opt = { @_ };
174   open my $def_in, '<', '/dev/null' or die "unable to open /dev/null";
175   my $ssh = Net::OpenSSH->new( $opt->{'user'}.'@'.$opt->{'host'},
176                                default_stdin_fh => $def_in,
177                              );
178   die "Couldn't establish SSH connection: ". $ssh->error if $ssh->error;
179   my ($output, $errput) = $ssh->capture2( #{stdin_discard => 1},
180                                           $opt->{'command'}
181                                         );
182   die "Error running SSH command: ". $ssh->error if $ssh->error;
183
184   #who the fuck knows what freeswitch reload outputs, probably a fucking
185   # ascii advertisement for cluecon
186   #die $errput if $errput;
187   #die $output if $output;
188
189   '';
190 }
191
192 1;