Compare commits

..

No commits in common. "master" and "v0.1.0" have entirely different histories.

8 changed files with 56 additions and 112 deletions

View file

@ -1,3 +0,0 @@
node_modules/
.env
yarn.lock

View file

@ -1,64 +1,27 @@
# syncthing-hooks # syncthing-hooks
Run shell scripts via event hook files (similar to Git hooks) when changes are detected in a [Syncthing](https://syncthing.net/) folder. Very early experiment for running event hooks when SyncThing detects changes in a folder.
## Prerequisites ## Prerequisites
[Node.js >= 10](https://nodejs.org/en/) A somewhat recent version of Node.js.
## Installation ## Installation
None yet, there's no point installing this as a daemon right now. But you'll need to install the dependencies to make it run:
```sh ```sh
npm i -g syncthing-hooks npm i
# or
yarn
``` ```
## Usage ## Usage
You can simply run the watcher process via: You can experiment with it by just running it via Node.js and monitoring the output.
```sh ```sh
API_KEY=mykey syncthing-hooks API_KEY=mykey node index.js
``` ```
Don't forget to substitute `mykey` with your syncthing API key, which can be found in the settings in the GUI. Then change some files in one of your monitored folders.
If Syncthing runs on another host or listens to a non-default port, you can specify an URL by using `ST_URL`.
Note that this URL has to include the protocol, hostname, port and path, e.g.:
```
ST_URL=http://<ip>:8384/rest/events
```
It won't install itself as a daemon by default, however. In order to run it as a service, it is recommended to install [pm2](https://pm2.keymetrics.io/):
```sh
npm i -g pm2
```
You can then register it as a daemon via:
```sh
pm2 start "API_KEY=mykey syncthing-hooks" --name sthooks
```
To create the daemon automatically on startup, consult [this documentation](https://pm2.keymetrics.io/docs/usage/startup/).
You can follow the output of your hooks by using:
```sh
pm2 logs
```
## Hooks
Create a folder in your home directory called `.syncthing-hooks`.
A different directory can be set using `ST_HOOK_ROOT`.
Each hook is a file with the following naming scheme:
`folder-name-delay`
The folder name is the 11 character unique string found in the syncthing GUI. The delay is a string (anything parseable by the [ms module](https://github.com/zeit/ms)) indicating the idle time after an event, so that hooks aren't executed multiple times on successive changes in a short interval.
An example: a script at the location `~/.syncthing-hooks/night-owlzz-5m` will be executed five minutes after the most recent event in the folder with the identifier `night-owlzz`.
Don't forget to `chmod +x` the script.

5
api.js
View file

@ -1,10 +1,9 @@
const got = require('got'); const got = require('got');
const { getEnvVar } = require('./env.js');
const fetchEvents = () => const fetchEvents = () =>
got(getEnvVar('ST_URL','http://localhost:8384/rest/events'), { got('http://localhost:8384/rest/events', {
headers: { headers: {
'X-API-Key': getEnvVar('API_KEY','invalid') 'X-API-Key': process.env.API_KEY,
}, },
}).json(); }).json();

9
env.js
View file

@ -1,9 +0,0 @@
module.exports = {
getEnvVar: function (varname, defaultvalue){
var result = process.env[varname];
if(result!=undefined)
return result;
else
return defaultvalue;
}
};

View file

@ -3,9 +3,8 @@ const ms = require('ms');
const os = require('os'); const os = require('os');
const path = require('path'); const path = require('path');
const { spawn } = require('child_process'); const { spawn } = require('child_process');
const { getEnvVar } = require('./env.js');
const getHooksRoot = () => getEnvVar('ST_HOOK_ROOT', path.join(os.homedir(), '/.syncthing-hooks')) const getHooksRoot = () => path.join(os.homedir(), '/.syncthing-hooks');
const readHooksRoot = async root => { const readHooksRoot = async root => {
try { try {
@ -21,7 +20,7 @@ const parseHooks = (root, hooks) =>
hooks hooks
.map(x => ({ .map(x => ({
path: path.join(root, x), path: path.join(root, x),
match: x.match(/(?<folder>.*)-(?<time>.*)/), match: x.match(/(?<folder>.{11})-(?<time>.*)/),
})) }))
.filter(x => x.match) .filter(x => x.match)
.map(x => ({ .map(x => ({

View file

@ -41,7 +41,7 @@ const convertRecentEventDatesToDelta = () => {
}, {}); }, {});
}; };
const poll = async () => { setInterval(async () => {
const { events, seenIds } = await fetchNewEvents(state.seenIds); const { events, seenIds } = await fetchNewEvents(state.seenIds);
const hooks = await collectHooks(); const hooks = await collectHooks();
const monitoredFolders = new Set(hooks.map(x => x.folder)); const monitoredFolders = new Set(hooks.map(x => x.folder));
@ -53,6 +53,7 @@ const poll = async () => {
.filter(x => deltaForFolders[x.folder]) .filter(x => deltaForFolders[x.folder])
.forEach(hook => { .forEach(hook => {
const timeToWait = hook.time - deltaForFolders[hook.folder]; const timeToWait = hook.time - deltaForFolders[hook.folder];
console.log(`scheduled hook "${hook.path}" to run in ${timeToWait}ms`);
if (timeToWait < 0) { if (timeToWait < 0) {
const existingPromise = state.promisesForHooks[hook.path]; const existingPromise = state.promisesForHooks[hook.path];
if (existingPromise) { if (existingPromise) {
@ -75,13 +76,8 @@ const poll = async () => {
delete state.promisesForHooks[hook.path]; delete state.promisesForHooks[hook.path];
}); });
} }
} else {
console.log(`scheduled hook "${hook.path}" to run in ${timeToWait}ms`);
} }
}); });
state.seenIds = seenIds; state.seenIds = seenIds;
}; }, 30000);
setInterval(poll, 30000);
poll();

View file

@ -1,6 +1,6 @@
{ {
"name": "syncthing-hooks", "name": "syncthing-hooks",
"version": "0.4.0", "version": "0.1.0",
"main": "index.js", "main": "index.js",
"bin": { "bin": {
"syncthing-hooks": "index.js" "syncthing-hooks": "index.js"
@ -14,6 +14,6 @@
"dependencies": { "dependencies": {
"dotenv": "8.2.0", "dotenv": "8.2.0",
"got": "10.6.0", "got": "10.6.0",
"ms": "2.1.3" "ms": "2.1.2"
} }
} }

View file

@ -3,9 +3,9 @@
"@sindresorhus/is@^2.0.0": "@sindresorhus/is@^2.0.0":
version "2.1.1" version "2.1.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.0.tgz#6ad4ca610f696098e92954ab431ff83bea0ce13f"
integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg== integrity sha512-lXKXfypKo644k4Da4yXkPCrwcvn6SlUW2X2zFbuflKHNjf0w9htru01bo26uMhleMXsDmnZ12eJLdrAZa9MANg==
"@szmarczak/http-timer@^4.0.0": "@szmarczak/http-timer@^4.0.0":
version "4.0.5" version "4.0.5"
@ -29,7 +29,7 @@
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a"
integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==
"@types/keyv@*", "@types/keyv@^3.1.1": "@types/keyv@*":
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==
@ -37,9 +37,9 @@
"@types/node" "*" "@types/node" "*"
"@types/node@*": "@types/node@*":
version "15.0.2" version "13.9.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.0.2.tgz#51e9c0920d1b45936ea04341aa3e2e58d339fb67" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.1.tgz#96f606f8cd67fb018847d9b61e93997dabdefc72"
integrity sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA== integrity sha512-E6M6N0blf/jiZx8Q3nb0vNaswQeEyn0XlupO+xN6DtJ6r6IT4nXrTry7zhIfYvFCl3/8Cu6WIysmUBKiqV0bqQ==
"@types/responselike@*": "@types/responselike@*":
version "1.0.0" version "1.0.0"
@ -49,11 +49,10 @@
"@types/node" "*" "@types/node" "*"
cacheable-lookup@^2.0.0: cacheable-lookup@^2.0.0:
version "2.0.1" version "2.0.0"
resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz#87be64a18b925234875e10a9bb1ebca4adce6b38" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-2.0.0.tgz#33b1e56f17507f5cf9bb46075112d65473fb7713"
integrity sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg== integrity sha512-s2piO6LvA7xnL1AR03wuEdSx3BZT3tIJpZ56/lcJwzO/6DTJZlTs7X3lrvPxk6d1PlDe6PrVe2TjlUIZNFglAQ==
dependencies: dependencies:
"@types/keyv" "^3.1.1"
keyv "^4.0.0" keyv "^4.0.0"
cacheable-request@^7.0.1: cacheable-request@^7.0.1:
@ -84,9 +83,9 @@ decompress-response@^5.0.0:
mimic-response "^2.0.0" mimic-response "^2.0.0"
defer-to-connect@^2.0.0: defer-to-connect@^2.0.0:
version "2.0.1" version "2.0.0"
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1"
integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==
dotenv@8.2.0: dotenv@8.2.0:
version "8.2.0" version "8.2.0"
@ -106,9 +105,9 @@ end-of-stream@^1.1.0:
once "^1.4.0" once "^1.4.0"
get-stream@^5.0.0, get-stream@^5.1.0: get-stream@^5.0.0, get-stream@^5.1.0:
version "5.2.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9"
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==
dependencies: dependencies:
pump "^3.0.0" pump "^3.0.0"
@ -144,9 +143,9 @@ json-buffer@3.0.1:
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
keyv@^4.0.0: keyv@^4.0.0:
version "4.0.3" version "4.0.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.0.tgz#2d1dab694926b2d427e4c74804a10850be44c12f"
integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== integrity sha512-U7ioE8AimvRVLfw4LffyOIRhL2xVgmE8T22L6i0BucSnBUyv4w+I7VN/zVZwRKHOI6ZRUcdMdWHQ8KSUvGpEog==
dependencies: dependencies:
json-buffer "3.0.1" json-buffer "3.0.1"
@ -165,15 +164,15 @@ mimic-response@^2.0.0, mimic-response@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
ms@2.1.3: ms@2.1.2:
version "2.1.3" version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
normalize-url@^4.1.0: normalize-url@^4.1.0:
version "4.5.1" version "4.5.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
once@^1.3.1, once@^1.4.0: once@^1.3.1, once@^1.4.0:
version "1.4.0" version "1.4.0"
@ -183,26 +182,26 @@ once@^1.3.1, once@^1.4.0:
wrappy "1" wrappy "1"
p-cancelable@^2.0.0: p-cancelable@^2.0.0:
version "2.1.1" version "2.0.0"
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e"
integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==
p-event@^4.0.0: p-event@^4.0.0:
version "4.2.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.1.0.tgz#e92bb866d7e8e5b732293b1c8269d38e9982bf8e"
integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ== integrity sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==
dependencies: dependencies:
p-timeout "^3.1.0" p-timeout "^2.0.1"
p-finally@^1.0.0: p-finally@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
p-timeout@^3.1.0: p-timeout@^2.0.1:
version "3.2.0" version "2.0.1"
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038"
integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==
dependencies: dependencies:
p-finally "^1.0.0" p-finally "^1.0.0"