remove temporary port mapping, if any is left from previous execution
Massimo Melina committed
Mar 17, 2024 at 18:57 UTC
2b8b4c922a8c410ef58f7d2014ad2ed5a2e30b54
2 files changed
+22
-7
src/acme.ts
+16
-4
@@ -1,4 +1,4 @@
1
-import { DAY, Dict, HOUR, HTTP_BAD_REQUEST, HTTP_FAILED_DEPENDENCY, HTTP_OK, repeat } from './cross'
1
+import { DAY, Dict, haveTimeout, HOUR, HTTP_BAD_REQUEST, HTTP_FAILED_DEPENDENCY, HTTP_OK, MINUTE, repeat } from './cross'
2
import { createServer, RequestListener } from 'http'
3
import { Middleware } from 'koa'
4
import { getNatInfo, upnpClient } from './nat'
@@ -27,6 +27,18 @@ export const acmeMiddleware: Middleware = (ctx, next) => { // koa format
27
return next()
28
}
29
30
+const TEMP_MAP = { private: 80, public: { host: '', port: 80 }, description: 'hfs temporary', ttl: 5000 } // from my tests (zyxel VMG8825), lower values won't make a working mapping
31
+
32
+repeat(MINUTE, async stop => {
33
+ await upnpClient.getGateway() // without this, the next call will break upnp support
34
+ const res = await upnpClient.getMappings()
35
+ const leftover = res.find(x => x.description === TEMP_MAP.description) // in case the process is interrupted
36
+ if (!leftover) return void(stop()) // we are good
37
+ if (acmeMiddlewareEnabled) return // it doesn't count, as we are in the middle of something. Retry later
38
+ stop()
39
+ return upnpClient.removeMapping(TEMP_MAP)
40
+})
41
+
42
async function generateSSLCert(domain: string, email?: string, altNames?: string[]) {
43
// will answer challenge through our koa app (if on port 80) or must we spawn a dedicated server?
44
const nat = await getNatInfo()
@@ -46,7 +58,7 @@ async function generateSSLCert(domain: string, email?: string, altNames?: string
58
let check = await selfCheck(checkUrl) // some check services may not consider the domain, but we already verified that
59
if (check?.success === false && nat.upnp && !nat.mapped80) {
60
console.debug("setting temporary port forward")
49
- tempMap = await upnpClient.createMapping({ private: 80, public: { host: '', port: 80 }, description: 'hfs temporary', ttl: 0 }).catch(() => {})
61
+ tempMap = await haveTimeout(10_000, upnpClient.createMapping(TEMP_MAP).catch(() => {})).catch(() => {})
62
check = await selfCheck(checkUrl) // repeat test
63
}
64
//if (!check) throw new ApiError(HTTP_FAILED_DEPENDENCY, "couldn't test port 80")
@@ -73,7 +85,7 @@ async function generateSSLCert(domain: string, email?: string, altNames?: string
85
finally {
86
if (tempMap) {
87
console.debug("removing temporary port forward")
76
- upnpClient.removeMapping({ public: { host: '', port: 80 } }).catch(() => {})
88
+ upnpClient.removeMapping(TEMP_MAP).catch(() => {}) // clean after ourselves
89
}
90
acmeMiddlewareEnabled = false
91
if (tempSrv) await new Promise(res => tempSrv.close(res))
@@ -109,6 +121,6 @@ const renewCert = debounceAsync(async () => {
121
if (now > new Date(cert.validFrom) && now < validTo && validTo.getTime() - now.getTime() >= 30 * DAY)
122
return console.log("certificate still good")
123
await makeCert(domain, acmeEmail.get())
112
- .catch(e => console.log("error renewing certificate: ", String(e)))
124
+ .catch(e => console.log("error renewing certificate: ", String(e.message || e)))
125
}, 0, { retain: DAY, retainFailure: HOUR })
126
src/cross.ts
+6
-3
@@ -330,13 +330,16 @@ export async function asyncGeneratorToArray<T>(generator: AsyncIterable<T>): Pro
330
return ret
331
}
332
333
-export function repeat(everyMs: number, cb: Callback): Callback {
333
+export function repeat(everyMs: number, cb: Callback<Callback>): Callback {
334
let stop = false
335
setTimeout(async () => {
336
- while (!stop && await Promise.allSettled([cb()]))
336
+ while (!stop && await Promise.allSettled([cb(stopIt)]))
337
await wait(everyMs)
338
})
339
- return () => stop = true
339
+ return stopIt
340
+ function stopIt() {
341
+ stop = true
342
+ }
343
}
344
345
export function formatTimestamp(x: string | Date) {