In the time I've been using git-chat, I've found that the solution is in desperately need of push notifications. Whenever someone git-pushes a new message, I want to be notified so I can git-pull and read the message.
Implementing a pull-based notification scheme is pretty easy, and I've been doing that as a solution in the interim. The basic idea is to create a post-merge hook that creates the notification, and then periodically git-pull in a separate shell:
$ cat .git/hooks/post-merge
#!/usr/bin/bash
zenity --notification --window-icon="info" --text "New git-chat message received!"
$ while true; do pushd ~/Downloads/gc-repo/; git pull --ff-only; popd; sleep 60; done
While this is fine for now, but it's not that elegant. I'd like to instead setup some kind of push-based model where the client no longer needs to poll for new messages. Of course, this will only be usable when git-chat is used against a self-hosted git server (since we need full control on the server side).
Here's what I'm thinking.
Client Side
Introduce a new daemon application that runs as a systemd service. The service periodically sends a heartbeat message to the server so that the git server knows of this particular client. The service also listens for push notification UDP messages from the server.
The heartbeat message should include the following:
an ephemeral port that the daemon is listening to for notifications,
a client identifier (arbitrary 256-bit string, chosen at daemon startup)
In pseudocode, the client daemon will work as follows
The daemon will communicate with the git server over SSH, so we'll need to find a way for this to work. How will the daemon authenticate with the git server without a tty and without a password? We definitely want to make sure the daemon can use existing SSH credentials, so that we don't need to create and add SSH keys explicitly for notify daemons.
Server Side
git-shell supports custom commands under a ~/git-shell-commands directory on the server. We will implement a new custom (non-interactive) shell command here that accepts a client heartbeat signal and updates an internal store of recipients. The client's address can be determined by the SSH_CONNECTION environment variable, since the connection happens over SSH.
We will also install a server-side post-receive hook that pushes a notification to all registered clients. Contents of the notification message are still not fleshed out, but I'd like to include the following information:
the ref updated,
a commit hash (?),
some kind of repository identifier (directory? configurable string?),
client identifier
The internal recipient store should be located under /var/git-chat-notif-db/, and organized similar to how objects are stored in the git db. Each object key will be the client-provided identifier (in hex), and the value will be a file containing the client information in a strictly-structured format:
<unix epoch of last heartbeat> <NUL> <IP Address> <NUL> <PORT>
Notes
I decided to go the UDP-based push notification route because it should offer great server-side performance. No long-running processes will need to be deployed, just a new git shell command and a server-side hook. UDP is nice because it supports multicast, so we should be able to quickly and efficiently broadcast to hundreds of users efficiently. I considered doing long-lived TCP connections instead, but this would require a long-lived server-side process and I'd prefer to avoid that; that solution would be a pain to implement, and would be difficult to scale.
The biggest drawback of the UDP route is dealing with firewalls. It looks like clients will have to open a port on their router firewall. That kinda sucks.
It's going to be tricky to get security right with the client daemon.
When it's time to take this on, I think I'll implement it in a new project repository. I'd like for this to be supported as an optional package that the client (and server) can install.
After a bit of research, NAT hole punching would be the way to get through the firewall issues. This is how peer-to-peer communication typically works.
In the time I've been using git-chat, I've found that the solution is in desperately need of push notifications. Whenever someone
git-push
es a new message, I want to be notified so I cangit-pull
and read the message.Implementing a pull-based notification scheme is pretty easy, and I've been doing that as a solution in the interim. The basic idea is to create a post-merge hook that creates the notification, and then periodically
git-pull
in a separate shell:While this is fine for now, but it's not that elegant. I'd like to instead setup some kind of push-based model where the client no longer needs to poll for new messages. Of course, this will only be usable when git-chat is used against a self-hosted git server (since we need full control on the server side).
Here's what I'm thinking.
Client Side
Introduce a new daemon application that runs as a systemd service. The service periodically sends a heartbeat message to the server so that the git server knows of this particular client. The service also listens for push notification UDP messages from the server.
The heartbeat message should include the following:
In pseudocode, the client daemon will work as follows
Unknowns:
Server Side
git-shell
supports custom commands under a~/git-shell-commands
directory on the server. We will implement a new custom (non-interactive) shell command here that accepts a client heartbeat signal and updates an internal store of recipients. The client's address can be determined by theSSH_CONNECTION
environment variable, since the connection happens over SSH.We will also install a server-side
post-receive
hook that pushes a notification to all registered clients. Contents of the notification message are still not fleshed out, but I'd like to include the following information:The internal recipient store should be located under
/var/git-chat-notif-db/
, and organized similar to how objects are stored in the git db. Each object key will be the client-provided identifier (in hex), and the value will be a file containing the client information in a strictly-structured format:Notes
I decided to go the UDP-based push notification route because it should offer great server-side performance. No long-running processes will need to be deployed, just a new git shell command and a server-side hook. UDP is nice because it supports multicast, so we should be able to quickly and efficiently broadcast to hundreds of users efficiently. I considered doing long-lived TCP connections instead, but this would require a long-lived server-side process and I'd prefer to avoid that; that solution would be a pain to implement, and would be difficult to scale.
The biggest drawback of the UDP route is dealing with firewalls. It looks like clients will have to open a port on their router firewall. That kinda sucks.
It's going to be tricky to get security right with the client daemon.
When it's time to take this on, I think I'll implement it in a new project repository. I'd like for this to be supported as an optional package that the client (and server) can install.