diff --git a/PVE/API2.pm b/PVE/API2.pm index 53d72861..6544ab62 100644 --- a/PVE/API2.pm +++ b/PVE/API2.pm @@ -83,7 +83,7 @@ __PACKAGE__->register_method ({ path => 'version', method => 'GET', permissions => { user => 'all' }, - description => "API version details", + description => "API version details. The result also includes the global datacenter confguration.", parameters => { additionalProperties => 0, properties => {}, @@ -99,6 +99,13 @@ __PACKAGE__->register_method ({ code => sub { my ($resp, $param) = @_; - return PVE::pvecfg::version_info(); + my $res = PVE::Cluster::cfs_read_file('datacenter.cfg'); + + my $vi = PVE::pvecfg::version_info(); + foreach my $k (qw(version release repoid)) { + $res->{$k} = $vi->{$k}; + } + + return $res; }}); 1; diff --git a/PVE/API2/Nodes.pm b/PVE/API2/Nodes.pm index 37d834db..29e44524 100644 --- a/PVE/API2/Nodes.pm +++ b/PVE/API2/Nodes.pm @@ -119,6 +119,7 @@ __PACKAGE__->register_method ({ { name => 'rrd' }, # fixme: remove? { name => 'rrddata' },# fixme: remove? { name => 'vncshell' }, + { name => 'spiceshell' }, { name => 'time' }, { name => 'dns' }, { name => 'services' }, @@ -767,6 +768,137 @@ __PACKAGE__->register_method ({ }; }}); +__PACKAGE__->register_method ({ + name => 'spiceshell', + path => 'spiceshell', + method => 'POST', + protected => 1, + proxyto => 'node', + permissions => { + description => "Restricted to users on realm 'pam'", + check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]], + }, + description => "Creates a spice shell.", + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + proxy => { + description => "This can be used by the client to specify the proxy server. All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one. By default, we return the node where the VM is currently running. As resonable setting is to use same node you use to connect to the API (This is window.location.hostname for the JS GUI).", + type => 'string', format => 'dns-name', + optional => 1, + }, + upgrade => { + type => 'boolean', + description => "Run 'apt-get dist-upgrade' instead of normal shell.", + optional => 1, + default => 0, + }, + }, + }, + returns => { + description => "Returned values can be directly passed to the 'remote-viewer' application.", + additionalProperties => 1, + properties => { + type => { type => 'string' }, + password => { type => 'string' }, + proxy => { type => 'string' }, + host => { type => 'string' }, + 'tls-port' => { type => 'integer' }, + }, + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my ($user, undef, $realm) = PVE::AccessControl::verify_username($authuser); + + raise_perm_exc("realm != pam") if $realm ne 'pam'; + + raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam'; + + my $node = $param->{node}; + my $proxy = $param->{proxy}; + if (!$proxy) { + my $host = `hostname -f` || PVE::INotify::nodename(); + chomp $host; + $proxy = $host; + } + + my $authpath = "/nodes/$node"; + + my ($ticket, $proxyticket) = PVE::AccessControl::assemble_spice_ticket($authuser, 0, $node); + + my $filename = "/etc/pve/local/pve-ssl.pem"; + my $subject = PVE::QemuServer::read_x509_subject_spice($filename); + + my $cacert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192); + $cacert =~ s/\n/\\n/g; + + my $port = PVE::Tools::next_spice_port(); + + my $shcmd; + + if ($user eq 'root@pam') { + if ($param->{upgrade}) { + my $upgradecmd = "pveupgrade --shell"; + $shcmd = [ '/bin/bash', '-c', $upgradecmd ]; + } else { + $shcmd = [ '/bin/bash', '-l' ]; + } + } else { + $shcmd = [ '/bin/login' ]; + } + + my $timeout = 10; + + my $cmd = ['/usr/bin/spiceterm', '--port', $port, '--addr', '127.0.0.1', + '--timeout', $timeout, '--authpath', $authpath, + '--permissions', 'Sys.Console', '--', @$shcmd]; + + my $realcmd = sub { + my $upid = shift; + + syslog ('info', "starting spiceterm $upid\n"); + + my $cmdstr = join (' ', @$cmd); + syslog ('info', "launch command: $cmdstr"); + + eval { + foreach my $k (keys %ENV) { + next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME'; + delete $ENV{$k}; + } + $ENV{PWD} = '/'; + $ENV{SPICE_TICKET} = $ticket; + PVE::Tools::run_command($cmd, errmsg => "spiceterm failed"); + }; + if (my $err = $@) { + syslog ('err', $err); + } + + return; + }; + + my $upid = $rpcenv->fork_worker('spiceshell', "", $user, $realcmd); + + PVE::Tools::wait_for_vnc_port($port); + + return { + type => 'spice', + title => "Shell on '$node'", + host => $proxyticket, # this break tls hostname verification, so we need to use 'host-subject' + proxy => "http://$proxy:3128", + 'tls-port' => $port, + 'host-subject' => $subject, + ca => $cacert, + password => $ticket, + 'delete-this-file' => 1, + }; + }}); + __PACKAGE__->register_method({ name => 'dns', path => 'dns', diff --git a/debian/control.in b/debian/control.in index a70c3191..cf281bcc 100644 --- a/debian/control.in +++ b/debian/control.in @@ -3,7 +3,7 @@ Version: @VERSION@-@PACKAGERELEASE@ Section: admin Priority: optional Architecture: amd64 -Depends: perl (>= 5.10.0-19), libtimedate-perl, libauthen-pam-perl, libintl-perl, rsync, libjson-perl, liblockfile-simple-perl, vncterm, qemu-server (>= 1.1-1), libwww-perl (>= 6.04-1), libnet-http-perl (>= 6.06-1), libhttp-daemon-perl, wget, libnet-dns-perl, vlan, ifenslave-2.6 (>= 1.1.0-10), liblinux-inotify2-perl, debconf (>= 0.5) | debconf-2.0, netcat-traditional, pve-cluster (>= 1.0-29), libpve-common-perl, libpve-storage-perl, libterm-readline-gnu-perl, libpve-access-control (>= 3.0-2), libio-socket-ssl-perl, libfilesys-df-perl, libfile-readbackwards-perl, libfile-sync-perl, redhat-cluster-pve, resource-agents-pve, fence-agents-pve, cstream, postfix | mail-transport-agent, libxml-parser-perl, lzop, dtach, libanyevent-perl, liburi-perl, logrotate, libanyevent-http-perl, apt-transport-https, libapt-pkg-perl, libcrypt-ssleay-perl, liblwp-protocol-https-perl +Depends: perl (>= 5.10.0-19), libtimedate-perl, libauthen-pam-perl, libintl-perl, rsync, libjson-perl, liblockfile-simple-perl, vncterm, qemu-server (>= 1.1-1), libwww-perl (>= 6.04-1), libnet-http-perl (>= 6.06-1), libhttp-daemon-perl, wget, libnet-dns-perl, vlan, ifenslave-2.6 (>= 1.1.0-10), liblinux-inotify2-perl, debconf (>= 0.5) | debconf-2.0, netcat-traditional, pve-cluster (>= 1.0-29), libpve-common-perl, libpve-storage-perl, libterm-readline-gnu-perl, libpve-access-control (>= 3.0-2), libio-socket-ssl-perl, libfilesys-df-perl, libfile-readbackwards-perl, libfile-sync-perl, redhat-cluster-pve, resource-agents-pve, fence-agents-pve, cstream, postfix | mail-transport-agent, libxml-parser-perl, lzop, dtach, libanyevent-perl, liburi-perl, logrotate, libanyevent-http-perl, apt-transport-https, libapt-pkg-perl, libcrypt-ssleay-perl, liblwp-protocol-https-perl, spiceterm Conflicts: netcat-openbsd, vzdump Replaces: vzdump Provides: vzdump diff --git a/www/manager/Utils.js b/www/manager/Utils.js index 09c18786..f4c136fd 100644 --- a/www/manager/Utils.js +++ b/www/manager/Utils.js @@ -250,6 +250,18 @@ Ext.define('PVE.Utils', { statics: { return data; }, + render_console_viewer: function(value) { + if (!value) { + return PVE.Utils.defaultText + ' (Java VNC Applet)'; + } else if (value === 'applet') { + return 'Java VNC Applet'; + } else if (value === 'vv') { + return 'SPICE (remote-viewer)'; + } else { + return value; + } + }, + language_map: { zh_CN: 'Chinese', ca: 'Catalan', @@ -507,6 +519,7 @@ Ext.define('PVE.Utils', { statics: { vncproxy: [ 'VM/CT', gettext('Console') ], spiceproxy: [ 'VM/CT', gettext('Console') + ' (Spice)' ], vncshell: [ '', gettext('Shell') ], + spiceshell: [ '', gettext('Shell') + ' (Spice)' ], qmsnapshot: [ 'VM', gettext('Snapshot') ], qmrollback: [ 'VM', gettext('Rollback') ], qmdelsnapshot: [ 'VM', gettext('Delete Snapshot') ], @@ -911,6 +924,31 @@ Ext.define('PVE.Utils', { statics: { nw.focus(); }, + defaultViewer: function(){ + return PVE.VersionInfo.console || 'applet'; + }, + + openSpiceViewer: function(url, params){ + PVE.Utils.API2Request({ + url: url, + params: params, + method: 'POST', + failure: function(response, opts){ + Ext.Msg.alert('Error', response.htmlStatus); + }, + success: function(response, opts){ + var raw = "[virt-viewer]\n"; + Ext.Object.each(response.result.data, function(k, v) { + raw += k + "=" + v + "\n"; + }); + var url = 'data:application/x-virt-viewer;charset=UTF-8,' + + encodeURIComponent(raw); + + window.open(url, "_top"); + } + }); + }, + // comp.setLoading() is buggy in ExtJS 4.0.7, so we // use el.mask() instead setErrorMask: function(comp, msg) { diff --git a/www/manager/dc/OptionView.js b/www/manager/dc/OptionView.js index 1f992656..ed426fae 100644 --- a/www/manager/dc/OptionView.js +++ b/www/manager/dc/OptionView.js @@ -45,6 +45,35 @@ Ext.define('PVE.dc.KeyboardEdit', { } }); +Ext.define('PVE.dc.ConsoleViewerEdit', { + extend: 'PVE.window.Edit', + + initComponent : function() { + var me = this; + + var data = []; + + Ext.Array.each(['', 'applet', 'vv'], function(value) { + data.push([value, PVE.Utils.render_console_viewer(value)]); + }); + + Ext.applyIf(me, { + subject: gettext('Console Viewer'), + items: { + xtype: 'pveKVComboBox', + name: 'console', + data: data, + value: '', + fieldLabel: gettext('Console Viewer') + } + }); + + me.callParent(); + + me.load(); + } +}); + Ext.define('PVE.dc.OptionView', { extend: 'PVE.grid.ObjectGrid', alias: ['widget.pveDcOptionView'], @@ -75,6 +104,12 @@ Ext.define('PVE.dc.OptionView', { } return value; } + }, + console: { + header: gettext('Console Viewer'), + editor: 'PVE.dc.ConsoleViewerEdit', + required: true, + renderer: PVE.Utils.render_console_viewer } }; diff --git a/www/manager/node/Config.js b/www/manager/node/Config.js index d80c725d..06cd7d55 100644 --- a/www/manager/node/Config.js +++ b/www/manager/node/Config.js @@ -51,13 +51,12 @@ Ext.define('PVE.node.Config', { text: gettext('Shell'), disabled: !caps.nodes['Sys.Console'], handler: function() { - var url = Ext.urlEncode({ - console: 'shell', - node: nodename - }); - var nw = window.open("?" + url, '_blank', - "innerWidth=745,innerheight=427"); - nw.focus(); + if (PVE.Utils.defaultViewer() === 'vv') { + var params = { proxy: window.location.hostname }; + PVE.Utils.openSpiceViewer('/nodes/' + nodename + '/spiceshell', params); + } else { + PVE.Utils.openConoleWindow('shell', undefined, nodename); + } } }); diff --git a/www/manager/qemu/Config.js b/www/manager/qemu/Config.js index 0819e26a..375a9aab 100644 --- a/www/manager/qemu/Config.js +++ b/www/manager/qemu/Config.js @@ -145,10 +145,10 @@ Ext.define('PVE.qemu.Config', { text: gettext('Console'), disabled: !caps.vms['VM.Console'], handler: function() { - if (spice) { - openSpiceConsole(vmid, nodename, vmname); - } else { + if (PVE.VersionInfo.console === 'applet' || !spice) { PVE.Utils.openConoleWindow('kvm', vmid, nodename, vmname); + } else { + openSpiceConsole(vmid, nodename, vmname); } }, menu: new Ext.menu.Menu({