Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Push Notifications #78

Open
brandon1024 opened this issue Sep 26, 2021 · 1 comment
Open

Push Notifications #78

brandon1024 opened this issue Sep 26, 2021 · 1 comment

Comments

@brandon1024
Copy link
Owner

brandon1024 commented Sep 26, 2021

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

thread2 1:
    while true:
        ssh -x git@server "git-chat-client-subscribe --space <space>.git --listen-port <port> --client-id <64-digit-hex-string>"
        sleep 60

thread 2:
    listen udp:<port>
    accept:
        notify

Unknowns:

  • 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.

@brandon1024
Copy link
Owner Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant