{"id":22446002,"url":"https://github.com/richbl/rust-watchfile-remote","last_synced_at":"2026-04-21T16:39:34.795Z","repository":{"id":227179817,"uuid":"770642238","full_name":"richbl/rust-watchfile-remote","owner":"richbl","description":"Monitor a remote file for changes (\"heartbeat monitoring\") using a simple client-server pattern","archived":false,"fork":false,"pushed_at":"2025-04-30T00:25:59.000Z","size":94,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-30T01:25:56.435Z","etag":null,"topics":["client","client-server","compare-files","glibc","heartbeat","heartbeat-monitor","heartbeat-service","linux","monitoring","musl","musl-libc","rust","rust-lang","server","sftp","sftp-upload","ssh","ssl"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/richbl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-03-11T22:40:12.000Z","updated_at":"2025-04-30T00:22:33.000Z","dependencies_parsed_at":"2024-03-12T02:48:22.826Z","dependency_job_id":"6e95f6b4-803a-4a88-98ef-41302ae16b19","html_url":"https://github.com/richbl/rust-watchfile-remote","commit_stats":null,"previous_names":["richbl/rust-watchfile-remote"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/richbl/rust-watchfile-remote","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richbl%2Frust-watchfile-remote","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richbl%2Frust-watchfile-remote/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richbl%2Frust-watchfile-remote/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richbl%2Frust-watchfile-remote/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/richbl","download_url":"https://codeload.github.com/richbl/rust-watchfile-remote/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richbl%2Frust-watchfile-remote/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260046048,"owners_count":22950797,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","client-server","compare-files","glibc","heartbeat","heartbeat-monitor","heartbeat-service","linux","monitoring","musl","musl-libc","rust","rust-lang","server","sftp","sftp-upload","ssh","ssl"],"created_at":"2024-12-06T03:17:56.757Z","updated_at":"2026-04-21T16:39:29.746Z","avatar_url":"https://github.com/richbl.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Watchfile Remote [Rust Edition]\n\n[![Rust Report Card](https://rust-reportcard.xuri.me/badge/github.com/richbl/rust-watchfile-remote)](https://rust-reportcard.xuri.me/report/github.com/richbl/rust-watchfile-remote)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/595889e53f25475da18dea64b5a60419)](https://app.codacy.com/gh/richbl/go-ble-sync-cycle/dashboard?utm_source=gh\u0026utm_medium=referral\u0026utm_content=\u0026utm_campaign=Badge_grade)\n![GitHub Release](https://img.shields.io/github/v/release/richbl/rust-watchfile-remote?include_prereleases\u0026sort=semver)\n\n**Watchfile Remote [Rust Edition]** is a simple pattern that configures both a sender (via the `watchfile-remote-sender` executable) and a receiver (`watchfile-remote-receiver`) to monitor a single file, passed at a given interval, between them for change (called \"heartbeat monitoring\"). Both of these executables are started once on each machine, and then run indefinitely, typically as a background process or service. If no change is identified after a certain period of time--that is, the heartbeat is no longer detected--then an email is generated identifying loss of the heartbeat. Conversely, if a heartbeat is again detected, a follow-on email is generated indicating the resumption of that heartbeat.\n\n\u003cp align=\"center\"\u003e\n\u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/richbl/rust-watchfile-remote/assets/10182110/1f94d390-1c9a-4e6a-bc90-0c0e6b1446aa\"\u003e\u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/richbl/rust-watchfile-remote/assets/10182110/1f94d390-1c9a-4e6a-bc90-0c0e6b1446aa\"\u003e\u003cimg src=\"[https://github.com/richbl/rust-watchfile-remote/assets/10182110/1f94d390-1c9a-4e6a-bc90-0c0e6b1446aa](https://github.com/richbl/rust-watchfile-remote/assets/10182110/1f94d390-1c9a-4e6a-bc90-0c0e6b1446aa)\" width=700\u003e\u003c/picture\u003e\n\u003c/p\u003e\n\n## Rationale\n\nThis project was really created to resolve a very simple use case: how to know when my home internet service goes down--and, more importantly, when it comes back up again--while I'm away from home. This is important because we live in a rural region of the Pacific Northwest, and our only internet service is available via DSL, which means above-ground phone lines: and phone lines and trees on windy days just don't behave well together.\n\nSo, this project is basically broken down into two parts:\n\n- The sender, which is a local server on our home LAN. This server, as the sender component of this project, periodically (every five minutes is the default) attempts to send a \"heartbeat\" in the form of a simple file up to a remote server (a location not on our home LAN). Nothing more. Pretty simple.\n- The receiver, which is located on one of my remote web servers, will watch for any \"heartbeat\" updates to that watchfile at an interval of every (n) minutes (10 minutes is the default). If that watchfile has been modified in the intervening (n) minutes, then my home internet service is up and running. On the other hand, if that watchfile hasn't changed, it would suggest that my home internet service is down, so the receiver sends me an email with the bad news.\n\n### Wait a Second!... You Already Wrote This as a Bash Script\n\nYep, that's right. [The first version of **Watchfile Remote** was written as a set of `bash` scripts](https://github.com/richbl/watchfile-remote). I wrote this project really as an exercise to understand how Rust can be written in such a way that it abstracts away external dependencies that `bash` scripts--by their very definition--rely upon. So, here's my own take away:\n\n- It takes more code logic in Rust to accomplish what I originally did in the `bash` scripts\n- However... quite a few dependencies are removed with this project, since they've been natively (re)written in Rust to run across multiple platforms. A couple of examples:\n    - No need to rely on an external `scp` (or `sftp`) utility command\n    - No need to rely on an external mail program such as `mailx`\n    - No need to rely on an external [GNU C Library (`glibc`)](https://www.gnu.org/software/libc/): [musl `libc`](https://musl.libc.org/) used instead (*)\n\n\u003e (*) On this last point, using musl was not an intentional design decision. Instead, it was a solution to the problem of how to deploy a Rust binary to an older device that was running an old version of `glibc` (the original target server happens to be running Ubuntu 18.04, while I wrote this solution on Ubuntu 23.10).\n\nAll told, while this Rust implementation makes for a solution with fewer external dependencies, writing the original `bash` script was much quicker and simpler overall (with many fewer lines of code). So, if you're running on hardware with a known Unix-like environment, [you might find the original `bash` scripts more appropriate for your own use case](https://github.com/richbl/watchfile-remote). Otherwise, enjoy playing around with this 100% Pure Rust Edition of **Watchfile Remote**.\n\n## Requirements\n\n- Since this is a Rust project, you'll likely need to cross-compile both executables for your respective platforms. Releases here can be built using the Rust [\"target triple\"](https://doc.rust-lang.org/beta/rustc/platform-support.html) of `x86_64-unknown-linux-musl` to manage `glibc` dependencies\n- On the receiver, your email system needs to be configured so emails can be sent to you when appropriate. Since I use Gmail, [I needed to configure my gmail account with something called Google calls an \"app password\"](https://support.google.com/mail/answer/185833?hl=en)\n- Recommended: since these executables are expected to communicate securely over the internet using the [SFTP protocol](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol), it's highly recommended to establish secure credentials between sender and receiver using an SSH key exchange. Using encrypted keys is preferred, but not required: if it's not possible to use a key exchange, the `SFTP` process can be configured in these executables to pass a password between devices instead.\n\n## Building the Project\n\nTo build the project, run the following commands in the root directory of the project:\n\n```bash\ncargo build --release --bin watchfile-remote-sender\ncargo build --release --bin watchfile-remote-receiver\n```\n\nThese two commands will build the executables `watchfile-remote-sender` and `watchfile-remote-receiver`.\n\n### A Note on Building This Project for Older Operating Systems\n\nThe original target machine (Ubuntu 18.04) for the `watchfile-remote-receiver` executable used an older `glibc` library (2.27), so we needed to statically link `glibc` functionality into the binary using `musl libc` which requires the following steps on the build machine:\n\n1. `sudo apt-get install musl-tools` (one time installation)\n2. `rustup target add x86_64-unknown-linux-musl` (one time addition)\n3. Edit the `Cargo.toml` file to configure the `ssh2` dependency to use the `features = [\"vendored-openssl\"]` option\n\nThen for each compile of `watchfile-remote-receiver`, run the following:\n\n```bash\ncargo build --release --bin watchfile-remote-receiver --target x86_64-unknown-linux-musl\n```\n\nOn the other hand--and more typically--a more up-to-date target machine **does not** require `glibc` to be statically linked, so it can be dynamically linked (the project default).\n\nThe build process would then simply be:\n\n```bash\ncargo build --release --bin watchfile-remote-receiver\n```\n\nFor details of how to configure the proper libraries to be linked, review notes in the `Cargo.toml` file of this project.\n\nFor ease of building and deploying, this project now assumes the default condition of a dynamically linked `glibc` library. Whew!\n\n## Basic Usage\n\n**Watchfile Remote [Rust Edition]** is broken into two separate components: the sender and the receiver. Each component is mapped to one of two executables:\n\n- For the sender, use the `watchfile-remote-sender` executable\n- For the receiver, use the `watchfile-remote-receiver` executable\n\n### The Sender\n\nThe sender component is a local LAN computer (typically a server that's always on). Its role will be to periodically send a \"heartbeat\" file (called `the-watchfile`) to the receiver.\n\nTo configure the sender component:\n\n1. Copy the `watchfile-remote-sender` executable to the machine in question\n2. Edit the `watchfile-remote-sender.toml` file to accurately reflect your machine configuration. Note that this TOML file needs to be in the same folder as the `watchfile-remote-sender` executable\na. Importantly, determine how you want to use the `SFTP` command: either by passing a password directly into the executable, or by establishing an ssh key exchange (preferred). If you choose to use a password, edit the `password` key in the TOML file accordingly: **leaving it set to `use_ssh_keys` will do exactly what it says: use SSH keys instead of a password**\n3. Run the executable in the background with the following syntax: `./watchfile-remote-sender \u0026` (using the `nohup` command may be prepended to the command in the event the executable terminates after user logout)\na. Better still... set up a service (e.g., using `systemd` or equivalent) to run `watchfile-remote-sender` as a background service\n4. Note that nothing appears to be happening. That's good: nothing should be happening, as this executable is simply looping through a 5-minute wait, and then quietly copying a file up to the receiving server\n5. To confirm that the executable is running, on a Unix-like machine type `ps -ef | grep -i watch` and you should see the `watchfile-remote-sender` executable running\n\n#### Editing the Sender TOML File\n\nThe `watchfile-remote-sender` executable is configured through the following values, editable via the `watchfile-remote-sender.toml` file:\n\n```toml\n# Watchfile Remote [Rust Edition] TOML configuration\n# 1.2.6\n\n[app]\n  watchfile_name = \"the-watchfile\"                   # the name of the file to be passed between machines\n  watchfile_dir = \"/home/user/rust-watchfile-remote\" # the full path to this executable\n  sleep_interval = 300                               # interval (in secs) to send the watchfile to the receiver\n\n[receiver]\n  username = \"username_here\"             # remote server account username\n  server = \"yourdomain.com\"              # remote server domain name\n  password = \"use_ssh_keys\"              # password OR keep as default to \"use_ssh_keys\"\n  ssh_key = \"/home/user/.ssh/id_ed25519\" # full path (on local machine) to ssh certificate\n  dir = \"/home/user/watchfile-remote\"    # full path to receiver executable on remote server\n```\n\n### The Receiver\n\nThe receiver component is a remote computer not on the local LAN (typically a remote web server, or similar machine to which you have access). Its role will be to periodically watch for modifications to a \"heartbeat\" file (called `the-watchfile`) sent by the sender.\n\nTo configure the receiver component:\n\n1. Copy the `watchfile-remote-receiver` executable to the machine in question\n2. Edit the `watchfile-remote-receiver.toml` file to accurately reflect both your email and machine configuration. Note that this TOML file needs to be in the same folder as the `watchfile-remote-receiver` executable\n3. Run the executable in the background with the following syntax: `./watchfile-remote-receiver \u0026` (note that using the `nohup` command may be prepended to the command in the event the executable terminates after user logout)\na. Better still... set up a service (e.g., using `systemd` or equivalent) to run `watchfile-remote-receiver` as a background service\n4. Note that nothing appears to be happening. That's good: nothing should be happening, as this executable is simply looping through a 10-minute wait cycle, and then quietly checking a file called `the-watchfile` for any recent updates\n5. To confirm that the executable is running, on a Unix-like machine type `ps -ef | grep -i watch` and you should see the `watchfile-remote-receiver` executable running\n\n#### Editing the Receiver TOML File\n\nThe `watchfile-remote-receiver` executable is configured through the following values, editable via the `watchfile-remote-receiver.toml` file:\n\n```toml\n# Watchfile Remote [Rust Edition] TOML configuration\n# 1.2.6\n\n[app]\n  watchfile_name = \"the-watchfile\"                   # the name of the file to be passed between machines\n  watchfile_dir = \"/home/user/rust-watchfile-remote\" # the full path to this executable\n  sleep_interval_up = 600                            # interval (in secs) to check for the-watchfile updates when internet service is up\n  sleep_interval_down = 420                          # interval (in secs) to check for the-watchfile updates when internet service is down\n\n[email]\n  from_email_name = \"Home Internet Service Watcher\"     # sender (FROM:) text field\n  from_email_addr = \"username@yourdomain.com\"           # sender (FROM:) address\n  reply_to_email_name = \"Home Internet Service Watcher\" # REPLY: text field\n  reply_to_email_addr = \"username@yourdomain.com\"       # REPLY: address\n  to_email_name = \"End User Name\"                       # recipient (TO:) text field\n  to_email_addr = \"somebody@gmail.com\"                  # recipient (TO:) address\n\n  smtp_host = \"smtp.gmail.com\"                  # SMTP server address\n  account_username = \"username@yourdomain.com\"  # email server account address\n  account_password = \"google_app_password_here\" # email server account password\n\n```\n\n\u003e Note that the `account_password` field assumes the use of a Google Gmail account that requires an \"app password.\" If this is not the case (a different SMTP service is used), this field can be ignored. [Details of how to create a Google app password is available in this link.](https://knowledge.workspace.google.com/kb/how-to-create-app-passwords-000009237)\n\n## Roadmap\n\n- At the moment, there's not much of a roadmap to consider. In general, these are pretty simple executables doing some pretty basic stuff. But if you have any thoughts or ideas for improvement, send them my way.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichbl%2Frust-watchfile-remote","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frichbl%2Frust-watchfile-remote","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichbl%2Frust-watchfile-remote/lists"}