Compare commits
19 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5276accf6e | ||
|
|
7f0ff05748 | ||
|
|
b31a908750 | ||
|
|
deb8b8dc69 | ||
|
|
bf2b3539ac | ||
|
|
fdb40117df | ||
|
|
ee3029486f | ||
|
|
841a75288b | ||
|
|
3286cc045f | ||
|
|
f96da3e2b4 | ||
|
|
a32ca79e02 | ||
|
|
36ff52ae5b | ||
|
|
b60f0b759a | ||
|
|
d13b5933d0 | ||
|
|
21711b960c | ||
|
|
fcdb7a4ad3 | ||
|
|
7a2ee30791 | ||
|
|
dccb7512f8 | ||
|
|
660cf9c1e3 |
8 changed files with 112 additions and 56 deletions
3
.npmignore
Normal file
3
.npmignore
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
.env
|
||||
yarn.lock
|
||||
57
README.md
57
README.md
|
|
@ -1,27 +1,64 @@
|
|||
# syncthing-hooks
|
||||
|
||||
Very early experiment for running event hooks when SyncThing detects changes in a folder.
|
||||
Run shell scripts via event hook files (similar to Git hooks) when changes are detected in a [Syncthing](https://syncthing.net/) folder.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
A somewhat recent version of Node.js.
|
||||
[Node.js >= 10](https://nodejs.org/en/)
|
||||
|
||||
## 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
|
||||
npm i
|
||||
# or
|
||||
yarn
|
||||
npm i -g syncthing-hooks
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
You can experiment with it by just running it via Node.js and monitoring the output.
|
||||
You can simply run the watcher process via:
|
||||
|
||||
```sh
|
||||
API_KEY=mykey node index.js
|
||||
API_KEY=mykey syncthing-hooks
|
||||
```
|
||||
|
||||
Then change some files in one of your monitored folders.
|
||||
Don't forget to substitute `mykey` with your syncthing API key, which can be found in the settings in the GUI.
|
||||
|
||||
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
5
api.js
|
|
@ -1,9 +1,10 @@
|
|||
const got = require('got');
|
||||
const { getEnvVar } = require('./env.js');
|
||||
|
||||
const fetchEvents = () =>
|
||||
got('http://localhost:8384/rest/events', {
|
||||
got(getEnvVar('ST_URL','http://localhost:8384/rest/events'), {
|
||||
headers: {
|
||||
'X-API-Key': process.env.API_KEY,
|
||||
'X-API-Key': getEnvVar('API_KEY','invalid')
|
||||
},
|
||||
}).json();
|
||||
|
||||
|
|
|
|||
9
env.js
Normal file
9
env.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
getEnvVar: function (varname, defaultvalue){
|
||||
var result = process.env[varname];
|
||||
if(result!=undefined)
|
||||
return result;
|
||||
else
|
||||
return defaultvalue;
|
||||
}
|
||||
};
|
||||
5
hooks.js
5
hooks.js
|
|
@ -3,8 +3,9 @@ const ms = require('ms');
|
|||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
const { getEnvVar } = require('./env.js');
|
||||
|
||||
const getHooksRoot = () => path.join(os.homedir(), '/.syncthing-hooks');
|
||||
const getHooksRoot = () => getEnvVar('ST_HOOK_ROOT', path.join(os.homedir(), '/.syncthing-hooks'))
|
||||
|
||||
const readHooksRoot = async root => {
|
||||
try {
|
||||
|
|
@ -20,7 +21,7 @@ const parseHooks = (root, hooks) =>
|
|||
hooks
|
||||
.map(x => ({
|
||||
path: path.join(root, x),
|
||||
match: x.match(/(?<folder>.{11})-(?<time>.*)/),
|
||||
match: x.match(/(?<folder>.*)-(?<time>.*)/),
|
||||
}))
|
||||
.filter(x => x.match)
|
||||
.map(x => ({
|
||||
|
|
|
|||
10
index.js
10
index.js
|
|
@ -41,7 +41,7 @@ const convertRecentEventDatesToDelta = () => {
|
|||
}, {});
|
||||
};
|
||||
|
||||
setInterval(async () => {
|
||||
const poll = async () => {
|
||||
const { events, seenIds } = await fetchNewEvents(state.seenIds);
|
||||
const hooks = await collectHooks();
|
||||
const monitoredFolders = new Set(hooks.map(x => x.folder));
|
||||
|
|
@ -53,7 +53,6 @@ setInterval(async () => {
|
|||
.filter(x => deltaForFolders[x.folder])
|
||||
.forEach(hook => {
|
||||
const timeToWait = hook.time - deltaForFolders[hook.folder];
|
||||
console.log(`scheduled hook "${hook.path}" to run in ${timeToWait}ms`);
|
||||
if (timeToWait < 0) {
|
||||
const existingPromise = state.promisesForHooks[hook.path];
|
||||
if (existingPromise) {
|
||||
|
|
@ -76,8 +75,13 @@ setInterval(async () => {
|
|||
delete state.promisesForHooks[hook.path];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(`scheduled hook "${hook.path}" to run in ${timeToWait}ms`);
|
||||
}
|
||||
});
|
||||
|
||||
state.seenIds = seenIds;
|
||||
}, 30000);
|
||||
};
|
||||
|
||||
setInterval(poll, 30000);
|
||||
poll();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "syncthing-hooks",
|
||||
"version": "0.1.0",
|
||||
"version": "0.4.0",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
"syncthing-hooks": "index.js"
|
||||
|
|
@ -14,6 +14,6 @@
|
|||
"dependencies": {
|
||||
"dotenv": "8.2.0",
|
||||
"got": "10.6.0",
|
||||
"ms": "2.1.2"
|
||||
"ms": "2.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
75
yarn.lock
75
yarn.lock
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
|
||||
"@sindresorhus/is@^2.0.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.0.tgz#6ad4ca610f696098e92954ab431ff83bea0ce13f"
|
||||
integrity sha512-lXKXfypKo644k4Da4yXkPCrwcvn6SlUW2X2zFbuflKHNjf0w9htru01bo26uMhleMXsDmnZ12eJLdrAZa9MANg==
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1"
|
||||
integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==
|
||||
|
||||
"@szmarczak/http-timer@^4.0.0":
|
||||
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"
|
||||
integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==
|
||||
|
||||
"@types/keyv@*":
|
||||
"@types/keyv@*", "@types/keyv@^3.1.1":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
|
||||
integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==
|
||||
|
|
@ -37,9 +37,9 @@
|
|||
"@types/node" "*"
|
||||
|
||||
"@types/node@*":
|
||||
version "13.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.1.tgz#96f606f8cd67fb018847d9b61e93997dabdefc72"
|
||||
integrity sha512-E6M6N0blf/jiZx8Q3nb0vNaswQeEyn0XlupO+xN6DtJ6r6IT4nXrTry7zhIfYvFCl3/8Cu6WIysmUBKiqV0bqQ==
|
||||
version "15.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.0.2.tgz#51e9c0920d1b45936ea04341aa3e2e58d339fb67"
|
||||
integrity sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==
|
||||
|
||||
"@types/responselike@*":
|
||||
version "1.0.0"
|
||||
|
|
@ -49,10 +49,11 @@
|
|||
"@types/node" "*"
|
||||
|
||||
cacheable-lookup@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-2.0.0.tgz#33b1e56f17507f5cf9bb46075112d65473fb7713"
|
||||
integrity sha512-s2piO6LvA7xnL1AR03wuEdSx3BZT3tIJpZ56/lcJwzO/6DTJZlTs7X3lrvPxk6d1PlDe6PrVe2TjlUIZNFglAQ==
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz#87be64a18b925234875e10a9bb1ebca4adce6b38"
|
||||
integrity sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==
|
||||
dependencies:
|
||||
"@types/keyv" "^3.1.1"
|
||||
keyv "^4.0.0"
|
||||
|
||||
cacheable-request@^7.0.1:
|
||||
|
|
@ -83,9 +84,9 @@ decompress-response@^5.0.0:
|
|||
mimic-response "^2.0.0"
|
||||
|
||||
defer-to-connect@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1"
|
||||
integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587"
|
||||
integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
|
||||
|
||||
dotenv@8.2.0:
|
||||
version "8.2.0"
|
||||
|
|
@ -105,9 +106,9 @@ end-of-stream@^1.1.0:
|
|||
once "^1.4.0"
|
||||
|
||||
get-stream@^5.0.0, get-stream@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9"
|
||||
integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
|
||||
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
|
|
@ -143,9 +144,9 @@ json-buffer@3.0.1:
|
|||
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
|
||||
|
||||
keyv@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.0.tgz#2d1dab694926b2d427e4c74804a10850be44c12f"
|
||||
integrity sha512-U7ioE8AimvRVLfw4LffyOIRhL2xVgmE8T22L6i0BucSnBUyv4w+I7VN/zVZwRKHOI6ZRUcdMdWHQ8KSUvGpEog==
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254"
|
||||
integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==
|
||||
dependencies:
|
||||
json-buffer "3.0.1"
|
||||
|
||||
|
|
@ -164,15 +165,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"
|
||||
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
ms@2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
normalize-url@^4.1.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
|
||||
integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
|
||||
version "4.5.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
|
||||
integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
|
||||
|
||||
once@^1.3.1, once@^1.4.0:
|
||||
version "1.4.0"
|
||||
|
|
@ -182,26 +183,26 @@ once@^1.3.1, once@^1.4.0:
|
|||
wrappy "1"
|
||||
|
||||
p-cancelable@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e"
|
||||
integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf"
|
||||
integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==
|
||||
|
||||
p-event@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.1.0.tgz#e92bb866d7e8e5b732293b1c8269d38e9982bf8e"
|
||||
integrity sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5"
|
||||
integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==
|
||||
dependencies:
|
||||
p-timeout "^2.0.1"
|
||||
p-timeout "^3.1.0"
|
||||
|
||||
p-finally@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
|
||||
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
|
||||
|
||||
p-timeout@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038"
|
||||
integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==
|
||||
p-timeout@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
|
||||
integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==
|
||||
dependencies:
|
||||
p-finally "^1.0.0"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue