summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLibravatar Mubashshir <ahm@jadupc.com>2023-10-03 21:59:43 +0600
committerLibravatar Mubashshir <ahm@jadupc.com>2023-10-03 21:59:43 +0600
commit047a1cf0fc63d140afc83a15d69004744e68bf3f (patch)
tree4e84b5b6bf0b37eacc7e350edda92a78081c82b9 /src
parent7fb1742900ffdaf6d0716b06e9dc92fce8031c94 (diff)
downloadjadupc-remote-support-console-047a1cf0fc63d140afc83a15d69004744e68bf3f.tar.gz
jadupc-remote-support-console-047a1cf0fc63d140afc83a15d69004744e68bf3f.zip
Add tmate Session management API
Signed-off-by: Mubashshir <ahm@jadupc.com>
Diffstat (limited to 'src')
-rw-r--r--src/tmate/config.vala53
-rw-r--r--src/tmate/meson.build5
-rw-r--r--src/tmate/session.vala207
-rw-r--r--src/tmate/sessiontype.vala28
-rw-r--r--src/window.vala24
5 files changed, 315 insertions, 2 deletions
diff --git a/src/tmate/config.vala b/src/tmate/config.vala
new file mode 100644
index 0000000..8957d02
--- /dev/null
+++ b/src/tmate/config.vala
@@ -0,0 +1,53 @@
+namespace Tmate
+{
+ [SingleInstance]
+ class Config : Object
+ {
+ private static HashTable<string, string> cfg;
+ construct {
+ cfg = new HashTable<string, string>(str_hash, str_equal);
+ cfg.insert("server-host", "tty.dev.jadupc.com");
+ cfg.insert("server-port", "10022");
+ cfg.insert("server-rsa-fingerprint", "SHA256:QdMBN/QsrS4QyGApxxjt3DZyiysgeRto5YGGjAHRO7g");
+ cfg.insert("server-ed25519-fingerprint", "SHA256:w/TRuOK0w5qDXNBKdlYlANgZwq3Xg5LSZlBYIwEH8gU");
+ }
+
+ public void @delete(string? path)
+ {
+ if (path == null) return;
+ var fcfg = File.new_for_path(path);
+ try {
+ fcfg.delete();
+ } catch (Error e) {
+ print(@"Error: $(e.message)\n");
+ }
+ }
+
+ public new string? @get()
+ {
+ bool failed = false;
+ string cfgpath = "..";
+
+ try {
+ FileIOStream scfg;
+ var fcfg = File.new_tmp(".tmate.conf.XXXXXXXXX", out scfg);
+ var cfgout = new DataOutputStream(scfg.output_stream);
+ cfgpath = fcfg.get_path();
+
+ cfg.foreach((key, val) => {
+ try {
+ if(! failed)
+ cfgout.put_string(@"set -g tmate-$(key) $(val)\n");
+ } catch (Error m) {
+ print(@"Error: $(m.message)\n");
+ failed = true;
+ }
+ });
+ } catch (Error e) {
+ print(@"Error: $(e.message)\n");
+ failed = true;
+ }
+ return failed ? null : cfgpath;
+ }
+ }
+}
diff --git a/src/tmate/meson.build b/src/tmate/meson.build
new file mode 100644
index 0000000..e42ed65
--- /dev/null
+++ b/src/tmate/meson.build
@@ -0,0 +1,5 @@
+srcs += files(
+ 'config.vala',
+ 'sessiontype.vala',
+ 'session.vala',
+)
diff --git a/src/tmate/session.vala b/src/tmate/session.vala
new file mode 100644
index 0000000..5571654
--- /dev/null
+++ b/src/tmate/session.vala
@@ -0,0 +1,207 @@
+namespace Tmate
+{
+ public interface SessionInterface : Object
+ {
+ public abstract string? @get(SessionType session);
+ public abstract bool start(string? config = null);
+ public abstract bool stop();
+ }
+
+ [SingleInstance]
+ public class Session: Object, SessionInterface
+ {
+ private string? _addr_ssh = null;
+ private string? _addr_http = null;
+ private string? _addr_ssh_ro = null;
+ private string? _addr_http_ro = null;
+ private IOChannel? stdout = null;
+ private Pid? pid = null;
+
+ // Regex
+ private Regex r_connect = /^Connecting to .+\.\.\.$/;
+ private Regex r_socket = /^To connect.+ tmate -S (.+) attach$/;
+ private Regex r_http = /^web \w+[:] (.+)$/;
+ private Regex r_ssh = /^ssh \w+[:] .+ (.+)+$/;
+ private Regex r_http_ro = /^web \w+ read \w+[:] (.+)$/;
+ private Regex r_ssh_ro = /^ssh \w+ read \w+[:] .+ (.+)+$/;
+ private Regex r_joined = /^.+ joined \(([^()]+)\) -- (\d+) clients? .+$/;
+ private Regex r_left = /^.+ left \(([^()]+)\) -- (\d+) clients? .+$/;
+
+ public SessionType flags { get; private set;}
+ public string? socket { get; private set;}
+
+ construct { reset(); }
+
+ public virtual signal void started (string socket)
+ {
+ info (_("Session: unix://%s"), socket);
+ }
+ public virtual signal void stopped ()
+ {
+ info (_("Session terminated."));
+ }
+ public override signal void address (SessionType session, string address)
+ {
+ info (_("%s address: %s"), session.to_string(), address);
+ switch(session) {
+ case SSH :
+ _addr_ssh = address;
+ break;
+ case HTTP:
+ _addr_http = address;
+ break;
+ case HTTP_READONLY:
+ _addr_http_ro = address;
+ break;
+ case SSH_READONLY :
+ _addr_ssh_ro = address;
+ break;
+ case DISCONNECTED :
+ _addr_http = _addr_ssh = _addr_http_ro = _addr_ssh_ro = null;
+ break;
+ default:
+ warn_if_reached();
+ break;
+ }
+ }
+ public virtual signal void joined (string mate, int mates)
+ {
+ info(_("%s joined, connected: %d"), mate, mates);
+ }
+ public virtual signal void left (string mate, int mates)
+ {
+ info(_("%s left, connected: %d"), mate, mates);
+ }
+
+ private void reset()
+ {
+ _addr_ssh = null;
+ _addr_http = null;
+ _addr_ssh_ro = null;
+ _addr_http_ro = null;
+ stdout = null;
+ pid = null;
+ socket = null;
+ flags = DISCONNECTED;
+ }
+
+ private bool send(string[] args)
+ {
+ if(socket == null)
+ return false;
+ var command = new GenericArray<string>();
+ command.data = {
+ "/usr/bin/tmate",
+ "-S", socket
+ };
+ foreach(var arg in args)
+ command.add(arg);
+
+ try {
+ return Process.spawn_sync(null, command.data, null,
+ STDERR_TO_DEV_NULL|STDOUT_TO_DEV_NULL,
+ null, null, null, null);
+ } catch (SpawnError e) {
+ return false;
+ }
+
+ }
+
+ public new string? @get(SessionType session)
+ {
+ switch(session) {
+ case SSH:
+ return _addr_ssh;
+ case HTTP:
+ return _addr_http;
+ case SSH_READONLY :
+ return _addr_ssh_ro;
+ case HTTP_READONLY:
+ return _addr_http_ro;
+ case DISCONNECTED :
+ warning("No running session.");
+ break;
+ default:
+ warning("Unknown Session Type %s", session.to_string());
+ break;
+ }
+
+ return null;
+ }
+
+ public bool start(string? config = null)
+ {
+ if(! (pid == null)) {
+ started.emit(socket);
+ return true;
+ }
+
+ int out_fd;
+ var args = new GenericArray<string>();
+ args.data = {"/usr/bin/tmate", "-F"};
+
+ if(! (config == null) && FileUtils.test(config, IS_REGULAR)) {
+ args.add("-f");
+ args.add(config);
+ }
+ try {
+ Process.spawn_async_with_pipes(
+ null, args.data, null,
+ STDERR_TO_DEV_NULL | DO_NOT_REAP_CHILD,
+ null, out pid, null,
+ out out_fd, null
+ );
+
+ stdout = new IOChannel.unix_new(out_fd);
+ stdout.add_watch(IOCondition.IN | IOCondition.HUP, (channel, condition) => {
+ if (condition == IOCondition.HUP)
+ return false;
+
+ try {
+ string line;
+ MatchInfo info;
+ channel.read_line (out line, null, null);
+ if(r_socket.match(line, 0, out info))
+ socket = info.fetch(1);
+ else if (r_connect.match(line) && socket != null)
+ started.emit(socket);
+ else if(r_http.match(line, 0, out info))
+ address.emit(HTTP, info.fetch(1));
+ else if(r_http_ro.match(line, 0, out info))
+ address.emit(HTTP_READONLY, info.fetch(1));
+ else if(r_ssh.match(line, 0, out info))
+ address.emit(SSH, info.fetch(1));
+ else if(r_ssh_ro.match(line, 0, out info))
+ address.emit(SSH_READONLY, info.fetch(1));
+ else
+ debug("Unprocessed line: \"%s\"", line);
+ } catch (IOChannelError e) {
+ print ("IOChannelError: %s\n", e.message);
+ return false;
+ } catch (ConvertError e) {
+ print ("ConvertError: %s\n", e.message);
+ return false;
+ }
+ return true;
+ });
+
+ ChildWatch.add(pid, (pid, status) => {
+ Process.close_pid(pid);
+ reset();
+ stopped.emit();
+ });
+
+ return true;
+ } catch (SpawnError e) {
+ warning("Error: %s", e.message);
+ reset();
+ return false;
+ }
+ }
+
+ public bool stop()
+ {
+ return false;
+ }
+ }
+}
diff --git a/src/tmate/sessiontype.vala b/src/tmate/sessiontype.vala
new file mode 100644
index 0000000..4c44aea
--- /dev/null
+++ b/src/tmate/sessiontype.vala
@@ -0,0 +1,28 @@
+namespace Tmate
+{
+ [Flags]
+ public enum SessionType {
+ DISCONNECTED,
+ SSH, HTTP,
+ SSH_READONLY,
+ HTTP_READONLY;
+
+ public string to_string() {
+ switch(this) {
+ case DISCONNECTED :
+ return "Disconnected";
+ case SSH :
+ return "SessionType.SSH";
+ case HTTP :
+ return "SessionType.HTTP";
+ case SSH_READONLY :
+ return "SessionType.SSH:ReadOnly";
+ case HTTP_READONLY:
+ return "SessionType.HTTP.ReadOnly";
+ default :
+ warn_if_reached();
+ return "SessionType.Invalid";
+ }
+ }
+ }
+}
diff --git a/src/window.vala b/src/window.vala
index 8e38676..5cdfc65 100644
--- a/src/window.vala
+++ b/src/window.vala
@@ -32,20 +32,38 @@ namespace JadupcSupport
[GtkChild] private unowned Gtk.Label infobarText;
[GtkChild] private unowned Gtk.Image infobarIcon;
+ private Tmate.Config configFactory;
+ private Tmate.Session tmate;
+ private ulong? session_handler = null;
+
public Window (Gtk.Application app)
{
Object (application: app);
utils.realize_all(this); // Realize all children to pacify Gtk.
icon_name = "com.jadupc.support";
+ configFactory = new Tmate.Config();
+ tmate = new Tmate.Session();
infobar.response.connect(() => infobar.set_revealed(false));
+
+ tmate.address.connect((stype, addr) => {
+ if(Tmate.SessionType.SSH in stype)
+ on_session_connect(addr);
+ });
+
sessionbutton.clicked.connect(() => {
if (infobar.revealed)
infobar.revealed = false;
session.label = _("Connecting...");
view.visible_child_name = "inprogress";
- Timeout.add_once(3000, () => on_session_connect("test@tty.jadupc.com"));
+
+ var config = configFactory.get();
+ session_handler = tmate.started.connect(() => {
+ configFactory.delete(config);
+ tmate.disconnect(session_handler);
+ });
+ tmate.start(config);
});
terminal.child_exited.connect(status => {
@@ -85,8 +103,10 @@ namespace JadupcSupport
session.label = @"<a href=\"ssh://$url\">ssh://$url</a>";
view.visible_child_name = "terminal";
terminal.has_focus = true;
+ debug(url);
+ debug(tmate.socket);
- terminal.spawn_async(Vte.PtyFlags.DEFAULT, "~", {"sh"}, {@"PATH=$(Config.SECURE_PATH)"},
+ terminal.spawn_async(Vte.PtyFlags.DEFAULT, "~", {"tmate", "-S", tmate.socket, "attach"}, {@"PATH=$(Config.SECURE_PATH)"},
SpawnFlags.SEARCH_PATH_FROM_ENVP, null, -1, null, spawn_async_cb);
}
}