diff --git a/plugins/cloud/package-lock.json b/plugins/cloud/package-lock.json index d1a414d0a..cb9538fbf 100644 --- a/plugins/cloud/package-lock.json +++ b/plugins/cloud/package-lock.json @@ -1,31 +1,29 @@ { "name": "@scrypted/cloud", - "version": "0.1.27", + "version": "0.1.29", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@scrypted/cloud", - "version": "0.1.27", + "version": "0.1.29", "dependencies": { "@eneris/push-receiver": "^3.1.4", "@scrypted/common": "file:../../common", "@scrypted/sdk": "file:../../sdk", "axios": "^1.4.0", "bpmux": "^8.2.1", + "cloudflared": "^0.4.0", + "exponential-backoff": "^3.1.1", "http-proxy": "^1.18.1", + "ip": "^1.1.8", "nat-upnp": "file:./external/node-nat-upnp" }, "devDependencies": { "@types/http-proxy": "^1.17.11", + "@types/ip": "^1.1.0", "@types/nat-upnp": "^1.1.2", "@types/node": "^20.4.5" - }, - "optionalDependencies": { - "@greenlock/manager": "^3.1.0", - "@koush/greenlock": "^4.0.9", - "acme-dns-01-duckdns": "^3.0.1", - "greenlock-store-fs": "^3.2.2" } }, "../../common": { @@ -5745,59 +5743,6 @@ "node": ">=14" } }, - "node_modules/@greenlock/manager": { - "version": "3.1.0", - "license": "MPL-2.0", - "optional": true, - "dependencies": { - "greenlock-manager-fs": "^3.1.0" - } - }, - "node_modules/@koush/acme": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@koush/acme/-/acme-3.0.7.tgz", - "integrity": "sha512-xN857nFWBXKjnCwsKjoULf1BdkfK153KrCTfP0O/onXceumX0KLDd+P5NyPlkNj+n7tvjT3vPc4NBc1obHZPkg==", - "optional": true, - "dependencies": { - "@root/encoding": "^1.0.1", - "@root/keypairs": "^0.9.0", - "@root/pem": "^1.0.4", - "@root/request": "^1.3.11", - "@root/x509": "^0.7.2" - } - }, - "node_modules/@koush/greenlock": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@koush/greenlock/-/greenlock-4.0.9.tgz", - "integrity": "sha512-uWyPnxAvl+SZl5Kmi91BwqNkdXRc3T2+k9BSBHKna9HH6AifDvfuiGQ3DRBZTpAUyKqWruW45EbYY1Lie4dg6A==", - "optional": true, - "dependencies": { - "@greenlock/manager": "^3.1.0", - "@koush/acme": "^3.0.7", - "@root/csr": "^0.8.1", - "@root/keypairs": "^0.10.0", - "@root/mkdirp": "^1.0.0", - "@root/request": "^1.6.1", - "acme-http-01-standalone": "^3.0.5", - "cert-info": "^1.5.1", - "greenlock-store-fs": "^3.2.2", - "safe-replace": "^1.1.0" - }, - "bin": { - "greenlock": "bin/greenlock.js" - } - }, - "node_modules/@koush/greenlock/node_modules/@root/keypairs": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.10.3.tgz", - "integrity": "sha512-hbmjVbk/G99Z1XlUzJM4VOAaR8jm4vMnfwpZL301FA24ubJ/bOjj6enCrz9gKsQvBO6RY4/R4MgUuWpXeqeZZQ==", - "optional": true, - "dependencies": { - "@root/encoding": "^1.0.1", - "@root/pem": "^1.0.4", - "@root/x509": "^0.7.2" - } - }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "license": "BSD-3-Clause" @@ -5842,64 +5787,6 @@ "version": "1.1.0", "license": "BSD-3-Clause" }, - "node_modules/@root/asn1": { - "version": "1.0.0", - "license": "MPL-2.0", - "optional": true, - "dependencies": { - "@root/encoding": "^1.0.1" - } - }, - "node_modules/@root/csr": { - "version": "0.8.1", - "license": "MPL-2.0", - "optional": true, - "dependencies": { - "@root/asn1": "^1.0.0", - "@root/pem": "^1.0.4", - "@root/x509": "^0.7.2" - } - }, - "node_modules/@root/encoding": { - "version": "1.0.1", - "license": "MPL-2.0", - "optional": true - }, - "node_modules/@root/keypairs": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.9.0.tgz", - "integrity": "sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg==", - "optional": true, - "dependencies": { - "@root/encoding": "^1.0.1", - "@root/pem": "^1.0.4", - "@root/x509": "^0.7.2" - } - }, - "node_modules/@root/mkdirp": { - "version": "1.0.0", - "license": "MPL-2.0", - "optional": true - }, - "node_modules/@root/pem": { - "version": "1.0.4", - "license": "MPL-2.0", - "optional": true - }, - "node_modules/@root/request": { - "version": "1.9.2", - "license": "(MIT OR Apache-2.0)", - "optional": true - }, - "node_modules/@root/x509": { - "version": "0.7.2", - "license": "MPL-2.0", - "optional": true, - "dependencies": { - "@root/asn1": "^1.0.0", - "@root/encoding": "^1.0.1" - } - }, "node_modules/@scrypted/common": { "resolved": "../../common", "link": true @@ -5916,6 +5803,15 @@ "@types/node": "*" } }, + "node_modules/@types/ip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.0.tgz", + "integrity": "sha512-dwNe8gOoF70VdL6WJBwVHtQmAX4RMd62M+mAB9HQFjG1/qiCLM/meRy95Pd14FYBbEDwCq7jgJs89cHpLBu4HQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/nat-upnp": { "version": "1.1.2", "dev": true, @@ -5928,20 +5824,6 @@ "version": "20.4.5", "license": "MIT" }, - "node_modules/acme-dns-01-duckdns": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acme-dns-01-duckdns/-/acme-dns-01-duckdns-3.0.1.tgz", - "integrity": "sha512-s3BC1FFWUjTIoavRFP1mVc6YEqPazt8CVGPLGMV7r9Of70nvfYC8sWIOvol+PCrmTK7vUt8a9tS+P7qLoQE03A==", - "optional": true, - "dependencies": { - "@root/request": "^1.3.11" - } - }, - "node_modules/acme-http-01-standalone": { - "version": "3.0.5", - "license": "MPL-2.0", - "optional": true - }, "node_modules/ansi-regex": { "version": "1.1.1", "dev": true, @@ -6031,14 +5913,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cert-info": { - "version": "1.5.1", - "license": "MPL-2.0", - "optional": true, - "bin": { - "cert-info": "bin/cert-info.js" - } - }, "node_modules/chalk": { "version": "1.0.0", "dev": true, @@ -6064,6 +5938,15 @@ "node": ">= 0.2.0" } }, + "node_modules/cloudflared": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cloudflared/-/cloudflared-0.4.0.tgz", + "integrity": "sha512-dVw3FcpL2LdpiVN6al0S3yG/hLCuwzPZ608a3173EfqWPQVf1v5MdQwQuzaqaB2LZ6nhi5rVRqIBRUjCyrz0JQ==", + "hasInstallScript": true, + "bin": { + "cloudflared": "lib/cloudflared.js" + } + }, "node_modules/colors": { "version": "1.0.3", "dev": true, @@ -6231,6 +6114,11 @@ "node": ">= 0.8.0" } }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" + }, "node_modules/eyes": { "version": "0.1.8", "dev": true, @@ -6353,24 +6241,6 @@ "dev": true, "license": "MIT" }, - "node_modules/greenlock-manager-fs": { - "version": "3.1.1", - "license": "MPL-2.0", - "optional": true, - "dependencies": { - "@root/mkdirp": "^1.0.0", - "safe-replace": "^1.1.0" - } - }, - "node_modules/greenlock-store-fs": { - "version": "3.2.2", - "license": "MPL-2.0", - "optional": true, - "dependencies": { - "@root/mkdirp": "^1.0.0", - "safe-replace": "^1.1.0" - } - }, "node_modules/growl": { "version": "1.9.2", "dev": true, @@ -6520,7 +6390,8 @@ }, "node_modules/ip": { "version": "1.1.8", - "license": "MIT" + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" }, "node_modules/is-arguments": { "version": "1.1.1", @@ -7202,11 +7073,6 @@ "node": "*" } }, - "node_modules/safe-replace": { - "version": "1.1.0", - "license": "(MIT OR Apache-2.0)", - "optional": true - }, "node_modules/sax": { "version": "1.2.4", "license": "ISC" diff --git a/plugins/cloud/package.json b/plugins/cloud/package.json index 33ea63687..f1ff0506b 100644 --- a/plugins/cloud/package.json +++ b/plugins/cloud/package.json @@ -43,13 +43,17 @@ "@scrypted/sdk": "file:../../sdk", "axios": "^1.4.0", "bpmux": "^8.2.1", + "cloudflared": "^0.4.0", + "exponential-backoff": "^3.1.1", "http-proxy": "^1.18.1", + "ip": "^1.1.8", "nat-upnp": "file:./external/node-nat-upnp" }, "devDependencies": { "@types/http-proxy": "^1.17.11", + "@types/ip": "^1.1.0", "@types/nat-upnp": "^1.1.2", "@types/node": "^20.4.5" }, - "version": "0.1.27" + "version": "0.1.29" } diff --git a/plugins/cloud/src/main.ts b/plugins/cloud/src/main.ts index 6664e7006..3e813fb34 100644 --- a/plugins/cloud/src/main.ts +++ b/plugins/cloud/src/main.ts @@ -17,6 +17,11 @@ import { createSelfSignedCertificate } from '../../../server/src/cert'; import { PushManager } from './push'; import { readLine } from '../../../common/src/read-stream'; import { qsparse, qsstringify } from "./qs"; +import * as cloudflared from 'cloudflared'; +import fs, { mkdirSync } from 'fs'; +import { backOff } from "exponential-backoff"; +import ip from 'ip'; + // import { registerDuckDns } from "./greenlock"; const { deviceManager, endpointManager, systemManager } = sdk; @@ -45,6 +50,7 @@ class ScryptedPush extends ScryptedDeviceBase implements BufferConverter { } class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings, BufferConverter, DeviceProvider, HttpRequestHandler { + cloudflareTunnel: string; manager = new PushManager(DEFAULT_SENDER_ID); server: http.Server; secureServer: https.Server; @@ -313,7 +319,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings, if (this.storageSettings.values.forwardingMode === 'Custom Domain') upnpPort = 443; - this.console.log(`Mapped port https://127.0.0.1:${this.securePort} to https://${ip}:${upnpPort}`); + this.console.log(`Scrypted Cloud mapped https://${ip}:${upnpPort} to https://127.0.0.1:${this.securePort}`); // the ip is not sent, but should be checked to see if it changed. if (this.storageSettings.values.lastPersistedUpnpPort !== upnpPort || ip !== this.storageSettings.values.lastPersistedIp) { @@ -717,7 +723,8 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings, this.proxy.on('proxyRes', (res, req) => { res.headers['X-Scrypted-Cloud'] = req.headers['x-scrypted-cloud']; res.headers['X-Scrypted-Direct-Address'] = req.headers['x-scrypted-direct-address']; - res.headers['Access-Control-Expose-Headers'] = 'X-Scrypted-Cloud, X-Scrypted-Direct-Address'; + res.headers['X-Scrypted-Cloud-Address'] = this.cloudflareTunnel; + res.headers['Access-Control-Expose-Headers'] = 'X-Scrypted-Cloud, X-Scrypted-Direct-Address, X-Scrypted-Cloud-Address'; }); let backoff = 0; @@ -771,6 +778,30 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings, }); } }); + + + backOff(async () => { + try { + const pluginVolume = process.env.SCRYPTED_PLUGIN_VOLUME; + const cloudflareD = path.join(pluginVolume, 'cloudflare.d'); + mkdirSync(cloudflareD, { + recursive: true, + }) + process.chdir(cloudflareD); + + if (!fs.existsSync(cloudflared.bin)) + await cloudflared.install(cloudflared.bin); + const insecureUrl = `http://127.0.0.1:${port}`; + const cloudflareTunnel = cloudflared.tunnel({ + '--url': insecureUrl, + }); + this.cloudflareTunnel = await cloudflareTunnel.url; + this.console.log(`cloudflare url mapped ${this.cloudflareTunnel} to ${insecureUrl}`); + } + catch (e) { + this.cloudflareTunnel = undefined; + } + }); } ensureReverseConnections(registrationId: string) {