This is an automated email from the git hooks/post-receive script. x2go pushed a change to branch master in repository python-x2go. from 61de9d7 Make sure that the x2gosuspend-session/x2goterminate-session commands are sent to the X2Go Server before we take down the NX proxy subprocess. new ce50d23 Create a "session.window" file in the session directory. This file for now contains one line "ID:<window-id>". The file appears once a session window comes up (start/resume), and disappears once the session window closes (suspend/terminate). new a9caabf Only enable Telekinesis client debugging if the logger instance is in debug mode. new dd92083 Performance tests have shown, that enabling SSH compression is not a good idea. NX should handle that instead (and does). new 894917e Better control the startup bootstrap of the Telekinesis client subsystem. new 16814f4 remove debug code from forward.py new cea41b3 Newly understand our own Paramiko/SSH forwarding tunnel code. Become aware of handling multiple connects on the same tunnel. new 0d155fe take down telekinesis before taking down nxproxy when sessions suspend/terminate The 7 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Summary of changes: debian/changelog | 12 +++++ x2go/backends/control/plain.py | 2 +- x2go/backends/terminal/plain.py | 32 ++++++++++++- x2go/forward.py | 52 +++++++++++----------- x2go/sshproxy.py | 2 +- x2go/telekinesis.py | 94 ++++++++++++++++++++++----------------- 6 files changed, 124 insertions(+), 70 deletions(-) -- Alioth's /srv/git/_hooks_/post-receive-email on /srv/git/code.x2go.org/python-x2go.git
This is an automated email from the git hooks/post-receive script. x2go pushed a commit to branch master in repository python-x2go. commit ce50d23f4fbb58381cf722b95c8afa4d4c772ae0 Author: Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Date: Tue Jun 24 16:09:06 2014 +0200 Create a "session.window" file in the session directory. This file for now contains one line "ID:<window-id>". The file appears once a session window comes up (start/resume), and disappears once the session window closes (suspend/terminate). --- debian/changelog | 4 ++++ x2go/backends/terminal/plain.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/debian/changelog b/debian/changelog index ed786a6..afe54ec 100644 --- a/debian/changelog +++ b/debian/changelog @@ -64,6 +64,10 @@ python-x2go (0.5.0.0-0x2go1) UNRELEASED; urgency=low (Fixes: #358). - Make sure that the x2gosuspend-session/x2goterminate-session commands are sent to the X2Go Server before we take down the NX proxy subprocess. + - Create a "session.window" file in the session directory. This file for now + contains one line "ID:<window-id>". The file appears once a session window + comes up (start/resume), and disappears once the session window closes + (suspend/terminate). * debian/control: + Add dependencies: python-requests, python-simplejson. * python-x2go.spec: diff --git a/x2go/backends/terminal/plain.py b/x2go/backends/terminal/plain.py index 67fb650..1b0b1e5 100644 --- a/x2go/backends/terminal/plain.py +++ b/x2go/backends/terminal/plain.py @@ -426,6 +426,8 @@ class X2GoTerminalSession(object): """ self.release_telekinesis() self.release_proxy() + self.session_window = None + self.update_session_window_file() try: @@ -1091,10 +1093,36 @@ class X2GoTerminalSession(object): else: self.logger('Session window ID for session %s is: %s' % (self.session_info.name, window.id), loglevel=log.loglevel_DEBUG) self.session_window = window + + self.update_session_window_file() break gevent.sleep(1) + def update_session_window_file(self): + """\ + Create a file that contains information on the session window. + . + If the file already exists, its content gets update. + + """ + session_window_file = os.path.join(self.session_info.local_container, 'session.window') + if self.session_window is not None: + f = open(session_window_file,'w') + if _X2GOCLIENT_OS != "Windows": + _id = self.session_window.id + else: + _id = self.session_window + f.write('ID:{window_id}\n'.format(window_id=_id)) + f.close() + self.logger('Updating session.window file %s: Window-ID->%s' % (session_window_file, _id), loglevel=log.loglevel_DEBUG) + else: + try: + os.remove(session_window_file) + except OSError,e: + # this is no error in most cases... + self.logger('Failed to remove session.window file %s with error: %s' % (session_window_file, str(e)), loglevel=log.loglevel_INFO) + def set_session_window_title(self, title, timeout=60): """\ Modify the session window title. -- Alioth's /srv/git/_hooks_/post-receive-email on /srv/git/code.x2go.org/python-x2go.git
This is an automated email from the git hooks/post-receive script. x2go pushed a commit to branch master in repository python-x2go. commit a9caabfe441d42fae0b64e31b3ad0aea5b9f94bf Author: Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Date: Tue Jun 24 16:14:38 2014 +0200 Only enable Telekinesis client debugging if the logger instance is in debug mode. --- debian/changelog | 2 ++ x2go/telekinesis.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index afe54ec..aef8cf1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -68,6 +68,8 @@ python-x2go (0.5.0.0-0x2go1) UNRELEASED; urgency=low contains one line "ID:<window-id>". The file appears once a session window comes up (start/resume), and disappears once the session window closes (suspend/terminate). + - Only enable Telekinesis client debugging if the logger instance is in + debug mode. * debian/control: + Add dependencies: python-requests, python-simplejson. * python-x2go.spec: diff --git a/x2go/telekinesis.py b/x2go/telekinesis.py index 72d1a05..b32b30f 100644 --- a/x2go/telekinesis.py +++ b/x2go/telekinesis.py @@ -58,7 +58,7 @@ class X2GoTelekinesisClient(threading.Thread): """ TEKICLIENT_CMD = 'telekinesis-client' """Telekinesis client command. Might be OS specific.""" - TEKICLIENT_ARGS = ['-setDEBUG=1', '-setWORMHOLEPORT={port}', '-setX2GOSID={sid}', ] + TEKICLIENT_ARGS = ['-setWORMHOLEPORT={port}', '-setX2GOSID={sid}', ] """Arguments to be passed to the Telekinesis client.""" TEKICLIENT_ENV = {} """Provide environment variables to the Telekinesis client command.""" @@ -110,6 +110,9 @@ class X2GoTelekinesisClient(threading.Thread): self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ + if self.logger.get_loglevel() & log.loglevel_DEBUG: + self.TEKICLIENT_ARGS.extend(['-setDEBUG=1',]) + self.sessions_rootdir = sessions_rootdir self.session_info = session_info self.session_name = self.session_info.name -- Alioth's /srv/git/_hooks_/post-receive-email on /srv/git/code.x2go.org/python-x2go.git
This is an automated email from the git hooks/post-receive script. x2go pushed a commit to branch master in repository python-x2go. commit dd92083f4351b456f21357e798b5cbbeb842b085 Author: Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Date: Tue Jun 24 16:16:00 2014 +0200 Performance tests have shown, that enabling SSH compression is not a good idea. NX should handle that instead (and does). --- debian/changelog | 2 ++ x2go/backends/control/plain.py | 2 +- x2go/sshproxy.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index aef8cf1..2cf3eaf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -70,6 +70,8 @@ python-x2go (0.5.0.0-0x2go1) UNRELEASED; urgency=low (suspend/terminate). - Only enable Telekinesis client debugging if the logger instance is in debug mode. + - Performance tests have shown, that enabling SSH compression is not a + good idea. NX should handle that instead (and does). * debian/control: + Add dependencies: python-requests, python-simplejson. * python-x2go.spec: diff --git a/x2go/backends/control/plain.py b/x2go/backends/control/plain.py index 75b6c11..5409198 100644 --- a/x2go/backends/control/plain.py +++ b/x2go/backends/control/plain.py @@ -1066,7 +1066,7 @@ class X2GoControlSession(paramiko.SSHClient): # since Paramiko 1.7.7.1 there is compression available, let's use it if present... if x2go._paramiko.PARAMIKO_FEATURE['use-compression']: - ssh_transport.use_compression(compress=True) + ssh_transport.use_compression(compress=False) # enable keep alive callbacks ssh_transport.set_keepalive(5) diff --git a/x2go/sshproxy.py b/x2go/sshproxy.py index 39a852d..a70f931 100644 --- a/x2go/sshproxy.py +++ b/x2go/sshproxy.py @@ -348,7 +348,7 @@ class X2GoSSHProxy(paramiko.SSHClient, threading.Thread): # since Paramiko 1.7.7.1 there is compression available, let's use it if present... t = self.get_transport() if x2go._paramiko.PARAMIKO_FEATURE['use-compression']: - t.use_compression(compress=True) + t.use_compression(compress=False) t.set_keepalive(5) # if there is no private key, we will use the given password, if any -- Alioth's /srv/git/_hooks_/post-receive-email on /srv/git/code.x2go.org/python-x2go.git
This is an automated email from the git hooks/post-receive script. x2go pushed a commit to branch master in repository python-x2go. commit 894917eee64a58a7a5d337991c8623d6b097e919 Author: Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Date: Tue Jun 24 16:22:06 2014 +0200 Better control the startup bootstrap of the Telekinesis client subsystem. --- debian/changelog | 2 ++ x2go/telekinesis.py | 89 +++++++++++++++++++++++++++++---------------------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/debian/changelog b/debian/changelog index 2cf3eaf..8e0a24c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -72,6 +72,8 @@ python-x2go (0.5.0.0-0x2go1) UNRELEASED; urgency=low debug mode. - Performance tests have shown, that enabling SSH compression is not a good idea. NX should handle that instead (and does). + - Better control the startup bootstrap of the Telekinesis client + subsystem. * debian/control: + Add dependencies: python-requests, python-simplejson. * python-x2go.spec: diff --git a/x2go/telekinesis.py b/x2go/telekinesis.py index b32b30f..f14c4e7 100644 --- a/x2go/telekinesis.py +++ b/x2go/telekinesis.py @@ -165,6 +165,11 @@ class X2GoTelekinesisClient(threading.Thread): self.logger('Telekinesis client shutdown gave a message that we may ignore: %s' % str(e), loglevel=log.loglevel_WARN) self.tekiclient = None + if self.fw_ctrl_tunnel is not None: + self.logger('Shutting down Telekinesis wormhole', loglevel=log.loglevel_DEBUG) + forward.stop_forward_tunnel(self.fw_ctrl_tunnel) + self.fw_ctrl_tunnel = None + if self.telekinesis_sshfs is not None: telekinesis_sshfs_command = ['fusermount', '-u', '/tmp/.x2go-{local_user}/telekinesis/S-{sid}/'.format(local_user=_CURRENT_LOCAL_USER, sid=self.session_info), ] self.logger('Umounting SSHFS mount for Telekinesis via forking a threaded subprocess: %s' % " ".join(telekinesis_sshfs_command), loglevel=log.loglevel_DEBUG) @@ -176,10 +181,6 @@ class X2GoTelekinesisClient(threading.Thread): shell=False) self.telekinesis_sshfs = None - if self.fw_ctrl_tunnel is not None: - self.logger('Shutting down Telekinesis wormhole', loglevel=log.loglevel_DEBUG) - forward.stop_forward_tunnel(self.fw_ctrl_tunnel) - self.fw_ctrl_tunnel = None if self.fw_data_tunnel is not None: self.logger('Shutting down Telekinesis DATA tunnel', loglevel=log.loglevel_DEBUG) forward.stop_forward_tunnel(self.fw_data_tunnel) @@ -248,29 +249,30 @@ class X2GoTelekinesisClient(threading.Thread): cmd_line = self._generate_cmdline() - self.tekiclient_log_stdout = open('%s/%s' % (self.session_info.local_container, self.tekiclient_log, ), 'a') - self.tekiclient_log_stderr = open('%s/%s' % (self.session_info.local_container, self.tekiclient_log, ), 'a') - self.logger('forking threaded subprocess: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG) + if self.session_info and self.session_info.local_container: + self.tekiclient_log_stdout = open('%s/%s' % (self.session_info.local_container, self.tekiclient_log, ), 'a') + self.tekiclient_log_stderr = open('%s/%s' % (self.session_info.local_container, self.tekiclient_log, ), 'a') + self.logger('forking threaded subprocess: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG) - while not self.tekiclient: - gevent.sleep(.2) - p = self.tekiclient = subprocess.Popen(cmd_line, - env=self.TEKICLIENT_ENV, - stdin=None, - stdout=self.tekiclient_log_stdout, - stderr=self.tekiclient_log_stderr, - shell=False) + while not self.tekiclient: + gevent.sleep(.2) + p = self.tekiclient = subprocess.Popen(cmd_line, + env=self.TEKICLIENT_ENV, + stdin=None, + stdout=self.tekiclient_log_stdout, + stderr=self.tekiclient_log_stderr, + shell=False) - while self._keepalive: - gevent.sleep(.05) + while self._keepalive: + gevent.sleep(1) - try: - p.terminate() - self.logger('terminating Telekinesis client: %s' % p, loglevel=log.loglevel_DEBUG) - except OSError, e: - if e.errno == 3: - # No such process - pass + try: + p.terminate() + self.logger('terminating Telekinesis client: %s' % p, loglevel=log.loglevel_DEBUG) + except OSError, e: + if e.errno == 3: + # No such process + pass self.tekiclient = None @@ -347,23 +349,32 @@ class X2GoTelekinesisClient(threading.Thread): self.logger('waiting for Telekinesis data tunnel to come up: 0.5s x %s' % _count, loglevel=log.loglevel_DEBUG) gevent.sleep(.5) - gevent.sleep(1) - threading.Thread.start(self) + # only start TeKi client if the data connection is up and running... + if self.fw_data_tunnel.is_active and self.telekinesis_sshfs: - self.logger('Telekinesis client tries to connect to host=127.0.0.1, port=%s.' % (self.session_info.tekictrl_port,), loglevel=log.loglevel_DEBUG) - self.logger('Telekinesis client writes its log to %s.' % os.path.join(self.session_info.local_container, self.tekiclient_log), loglevel=log.loglevel_DEBUG) - while self.tekiclient is None and _count < _maxwait: - _count += 1 - self.logger('waiting for Telekinesis client to come up: 0.4s x %s' % _count, loglevel=log.loglevel_DEBUG) - gevent.sleep(.4) + gevent.sleep(1) + threading.Thread.start(self) - # also wait for telekinesis wormhole to become active - _count = 0 - _maxwait = 40 - while self.fw_ctrl_tunnel and (not self.fw_ctrl_tunnel.is_active) and (not self.fw_ctrl_tunnel.failed) and (_count < _maxwait): - _count += 1 - self.logger('waiting for Telekinesis wormhole to come up: 0.5s x %s' % _count, loglevel=log.loglevel_DEBUG) - gevent.sleep(.5) + self.logger('Telekinesis client tries to connect to host=127.0.0.1, port=%s.' % (self.session_info.tekictrl_port,), loglevel=log.loglevel_DEBUG) + self.logger('Telekinesis client writes its log to %s.' % os.path.join(self.session_info.local_container, self.tekiclient_log), loglevel=log.loglevel_DEBUG) + while self.tekiclient is None and _count < _maxwait: + _count += 1 + self.logger('waiting for Telekinesis client to come up: 0.4s x %s' % _count, loglevel=log.loglevel_DEBUG) + gevent.sleep(.4) + + # only wait for the TeKi wormhole tunnel (ctrl tunnel) if TeKi could be started successfully... + if self.tekiclient is not None: + + # also wait for telekinesis wormhole to become active + _count = 0 + _maxwait = 40 + while self.fw_ctrl_tunnel and (not self.fw_ctrl_tunnel.is_active) and (not self.fw_ctrl_tunnel.failed) and (_count < _maxwait): + _count += 1 + self.logger('waiting for Telekinesis wormhole to come up: 0.5s x %s' % _count, loglevel=log.loglevel_DEBUG) + gevent.sleep(.5) + + else: + self.logger('Aborting Telekinesis client startup for session %s, because the Telekinesis data connection failed to be established.' % (self.session_info,), loglevel=log.loglevel_WARN) return self.tekiclient, bool(self.tekiclient) and (self.fw_ctrl_tunnel and self.fw_ctrl_tunnel.is_active) -- Alioth's /srv/git/_hooks_/post-receive-email on /srv/git/code.x2go.org/python-x2go.git
This is an automated email from the git hooks/post-receive script. x2go pushed a commit to branch master in repository python-x2go. commit 16814f464225da64766a678536a9ff3b42202215 Author: Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Date: Tue Jun 24 16:49:45 2014 +0200 remove debug code from forward.py --- x2go/forward.py | 1 - 1 file changed, 1 deletion(-) diff --git a/x2go/forward.py b/x2go/forward.py index afc0b3c..89f5df9 100644 --- a/x2go/forward.py +++ b/x2go/forward.py @@ -137,7 +137,6 @@ class X2GoFwServer(StreamServer): loglevel=log.loglevel_ERROR) if self.session_instance: self.session_instance.set_session_name(self.session_name) - print "FORWARD: ", self.subsystem self.session_instance.HOOK_forwarding_tunnel_setup_failed(chain_host=self.chain_host, chain_port=self.chain_port, subsystem=self.subsystem) self.failed = True -- Alioth's /srv/git/_hooks_/post-receive-email on /srv/git/code.x2go.org/python-x2go.git
This is an automated email from the git hooks/post-receive script. x2go pushed a commit to branch master in repository python-x2go. commit cea41b3ca3dc7be6baa68eecead6d7c3877d2669 Author: Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Date: Wed Jun 25 01:19:20 2014 +0200 Newly understand our own Paramiko/SSH forwarding tunnel code. Become aware of handling multiple connects on the same tunnel. --- debian/changelog | 2 ++ x2go/forward.py | 51 ++++++++++++++++++++++++++------------------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/debian/changelog b/debian/changelog index 8e0a24c..f63c5e7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -74,6 +74,8 @@ python-x2go (0.5.0.0-0x2go1) UNRELEASED; urgency=low good idea. NX should handle that instead (and does). - Better control the startup bootstrap of the Telekinesis client subsystem. + - Newly understand our own Paramiko/SSH forwarding tunnel code. Become + aware of handling multiple connects on the same tunnel. * debian/control: + Add dependencies: python-requests, python-simplejson. * python-x2go.spec: diff --git a/x2go/forward.py b/x2go/forward.py index 89f5df9..b633a50 100644 --- a/x2go/forward.py +++ b/x2go/forward.py @@ -43,7 +43,10 @@ class X2GoFwServer(StreamServer): through an external proxy command launched by a C{X2GoProxy*} backend. """ - def __init__ (self, listener, remote_host, remote_port, ssh_transport, session_instance=None, session_name=None, subsystem=None, logger=None, loglevel=log.loglevel_DEFAULT,): + def __init__ (self, listener, + remote_host, remote_port, + ssh_transport, session_instance=None, session_name=None, + subsystem=None, logger=None, loglevel=log.loglevel_DEFAULT,): """\ @param listener: listen on TCP/IP socket C{(<IP>, <Port>)} @type listener: C{tuple} @@ -75,7 +78,7 @@ class X2GoFwServer(StreamServer): self.chan = None self.is_active = False self.failed = False - self.keepalive = False + self.keepalive = None self.listener = listener self.chain_host = remote_host self.chain_port = remote_port @@ -88,6 +91,10 @@ class X2GoFwServer(StreamServer): StreamServer.__init__(self, self.listener, self.x2go_forward_tunnel_handle) + def start(self): + self.keepalive = True + return StreamServer.start(self) + def x2go_forward_tunnel_handle(self, fw_socket, address): """\ Handle for SSH/Paramiko forwarding tunnel. @@ -107,12 +114,7 @@ class X2GoFwServer(StreamServer): _count = 0 _maxwait = 20 - while not _success and _count < _maxwait: - - # it is recommended here to have passed on the session instance to this object... - if self.session_instance: - if not self.session_instance.is_connected(): - break + while not _success and _count < _maxwait and self.keepalive: _count += 1 try: @@ -122,35 +124,34 @@ class X2GoFwServer(StreamServer): chan_peername = self.chan.getpeername() _success = True except Exception, e: - self.logger('incoming request to %s:%d failed on attempt %d of %d: %s' % (self.chain_host, - self.chain_port, - _count, - _maxwait, - repr(e)), - loglevel=log.loglevel_WARN) + if self.keepalive: + self.logger('incoming request to %s:%d failed on attempt %d of %d: %s' % (self.chain_host, + self.chain_port, + _count, + _maxwait, + repr(e)), + loglevel=log.loglevel_WARN) gevent.sleep(.4) if not _success: - self.logger('incoming request to %s:%d failed after %d attempts' % (self.chain_host, - self.chain_port, - _count), - loglevel=log.loglevel_ERROR) - if self.session_instance: - self.session_instance.set_session_name(self.session_name) - self.session_instance.HOOK_forwarding_tunnel_setup_failed(chain_host=self.chain_host, chain_port=self.chain_port, subsystem=self.subsystem) + if self.keepalive: + self.logger('incoming request to %s:%d failed after %d attempts' % (self.chain_host, + self.chain_port, + _count), + loglevel=log.loglevel_ERROR) + if self.session_instance: + self.session_instance.set_session_name(self.session_name) + self.session_instance.HOOK_forwarding_tunnel_setup_failed(chain_host=self.chain_host, chain_port=self.chain_port, subsystem=self.subsystem) self.failed = True else: - self.logger('connected! Tunnel open %r -> %r (on master connection %r -> %r)' % ( self.listener, (self.chain_host, self.chain_port), self.fw_socket.getpeername(), chan_peername), loglevel=log.loglevel_INFO) - # once we are here, we can presume the tunnel to be active... self.is_active = True - self.keepalive = True try: while self.keepalive: r, w, x = select.select([self.fw_socket, self.chan], [], []) @@ -169,7 +170,6 @@ class X2GoFwServer(StreamServer): except socket.error: pass - self.is_active = False self.logger('Tunnel closed from %r' % (chan_peername,), loglevel=log.loglevel_INFO) @@ -216,6 +216,7 @@ class X2GoFwServer(StreamServer): Stop the forwarding tunnel. """ + self.is_active = False self.close_socket() StreamServer.stop(self) -- Alioth's /srv/git/_hooks_/post-receive-email on /srv/git/code.x2go.org/python-x2go.git
This is an automated email from the git hooks/post-receive script. x2go pushed a commit to branch master in repository python-x2go. commit 0d155fe891f23691d03cc3ab76769b23968ffca1 Author: Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Date: Wed Jun 25 01:20:15 2014 +0200 take down telekinesis before taking down nxproxy when sessions suspend/terminate --- x2go/backends/terminal/plain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x2go/backends/terminal/plain.py b/x2go/backends/terminal/plain.py index 1b0b1e5..123d6db 100644 --- a/x2go/backends/terminal/plain.py +++ b/x2go/backends/terminal/plain.py @@ -1659,8 +1659,8 @@ class X2GoTerminalSession(object): @rtype: C{bool} """ - self.control_session.suspend(session_name=self.session_info.name) self.release_telekinesis() + self.control_session.suspend(session_name=self.session_info.name) self.release_proxy() # TODO: check if session has really suspended @@ -1676,8 +1676,8 @@ class X2GoTerminalSession(object): @rtype: C{bool} """ - self.control_session.terminate(session_name=self.session_info.name, destroy_terminals=False) self.release_telekinesis() + self.control_session.terminate(session_name=self.session_info.name, destroy_terminals=False) self.release_proxy() self.post_terminate_cleanup() self.__del__() -- Alioth's /srv/git/_hooks_/post-receive-email on /srv/git/code.x2go.org/python-x2go.git