update command
Massimo Melina committed
Jul 2, 2022 at 19:27 UTC
8feeb8a94da1e0ccec5cfc0c429b441e4907cff1
7 files changed
+104
-11
package.json
+1
-1
@@ -27,7 +27,7 @@
27
"test": "mocha -r ts-node/register 'tests/**/*.ts'",
28
"dist-node": "cd dist && zip hfs-node.zip -r * -x *.zip *.exe hfs-*",
29
"dist-win": "pkg . -C gzip -t node16-win && cd dist && zip hfs-windows.zip hfs.exe -r plugins",
30
- "dist-bin": "pkg . -C gzip && cd dist && mv -f hfs-win.exe hfs.exe && zip hfs-windows.zip hfs.exe -r plugins && cp -f hfs-linux hfs && zip hfs-linux.zip hfs -r plugins && cp -f hfs-macos hfs && zip hfs-macos.zip hfs -r plugins && rm hfs",
30
+ "dist-bin": "pkg . -C gzip && cd dist && mv -f hfs-win.exe hfs.exe && zip hfs-windows.zip hfs.exe -r plugins && cp -f hfs-linux hfs && zip hfs-linux.zip hfs -r plugins && cp -f hfs-macos hfs && zip hfs-mac.zip hfs -r plugins && rm hfs",
31
"dist": "npm run build-all && npm run dist-node && npm run dist-bin"
32
},
33
"engines": {
server/src/commands.ts
+13
-1
@@ -1,11 +1,12 @@
1
import { addAccount, getAccount, updateAccount } from './perm'
2
import { getConfigDefinition, setConfig } from './config'
3
import _ from 'lodash'
4
+import { getUpdate, update } from './update'
5
6
console.log(`HINT: type "help" for help`)
7
require('readline').createInterface({ input: process.stdin }).on('line', (line: string) => {
8
if (!line) return
8
- const [name, ...params] = line.split(/ +/)
9
+ const [name, ...params] = line.trim().split(/ +/)
10
const cmd = (commands as any)[name]
11
if (!cmd)
12
return console.error("cannot understand entered command, try 'help'")
@@ -68,5 +69,16 @@ const commands = {
69
async cb() {
70
process.exit(0)
71
}
72
+ },
73
+ update: {
74
+ params: '',
75
+ cb: update
76
+ },
77
+ 'check-update': {
78
+ params: '',
79
+ async cb() {
80
+ const update = await getUpdate()
81
+ console.log("new version available", update.name)
82
+ }
83
}
84
}
server/src/const.ts
+2
-3
@@ -4,7 +4,8 @@ import minimist from 'minimist'
4
import * as fs from 'fs'
5
import _ from 'lodash'
6
7
-export const DEV = process.env.DEV ? 'DEV' : ''
7
+export const argv = minimist(process.argv.slice(2))
8
+export const DEV = process.env.DEV || argv.dev ? 'DEV' : ''
9
export const HFS_STARTED = new Date()
10
export const BUILD_TIMESTAMP = ''
11
export const VERSION = ''
@@ -21,8 +22,6 @@ export const ADMIN_URI = SPECIAL_URI + 'admin/'
22
export const API_URI = SPECIAL_URI + 'api/'
23
export const PLUGINS_PUB_URI = SPECIAL_URI + 'plugins/'
24
24
-export const argv = minimist(process.argv.slice(2))
25
-
25
export const METHOD_NOT_ALLOWED = 405
26
export const NO_CONTENT = 204
27
export const FORBIDDEN = 403
server/src/plugins.ts
+1
-1
@@ -187,7 +187,7 @@ const rescanAsap = debounceAsync(rescan, 1000)
187
if (!existsSync(PATH))
188
try { mkdirSync(PATH) }
189
catch {}
190
-watchDir(PATH, rescanAsap)
190
+export const pluginsWatcher = watchDir(PATH, rescanAsap)
191
192
export const enablePlugins = defineConfig('enable_plugins', ['antibrute'])
193
enablePlugins.sub(rescanAsap)
server/src/update.ts
new
+67
@@ -0,0 +1,67 @@
1
+import { getRepoInfo } from './github'
2
+import { argv, IS_WINDOWS, VERSION } from './const'
3
+import { basename, dirname, join } from 'path'
4
+import { spawn } from 'child_process'
5
+import { httpsStream, onProcessExit, unzip } from './misc'
6
+import { renameSync, unlinkSync } from 'fs'
7
+import { pluginsWatcher } from './plugins'
8
+import { chmod, stat } from 'fs/promises'
9
+
10
+const HFS_REPO = 'rejetto/hfs'
11
+
12
+export async function getUpdate() {
13
+ const [latest] = await getRepoInfo(HFS_REPO + '/releases?per_page=1')
14
+ if (latest.name === VERSION)
15
+ throw "you already have the latest version: " + VERSION
16
+ return latest
17
+}
18
+
19
+export async function update() {
20
+ if (process.argv0.endsWith('node'))
21
+ throw "only binary versions are supported for now"
22
+ const update = await getUpdate()
23
+ const assetSearch = ({ win32: 'windows', darwin: 'mac', linux: 'linux' } as any)[process.platform]
24
+ if (!assetSearch)
25
+ throw "this feature doesn't support your platform: " + process.platform
26
+ const asset = update.assets.find((x: any) => x.name.endsWith('.zip') && x.name.includes(assetSearch))
27
+ if (!asset)
28
+ throw "asset not found"
29
+ const url = asset.browser_download_url
30
+ console.log("downloading", url)
31
+ const bin = process.argv[0]
32
+ const binPath = dirname(bin)
33
+ const binFile = basename(bin)
34
+ const newBinFile = 'new-' + binFile
35
+ pluginsWatcher.pause()
36
+ try {
37
+ await unzip(await httpsStream(url), path =>
38
+ join(binPath, path === binFile ? newBinFile : path))
39
+ const newBin = join(binPath, newBinFile)
40
+ if (!IS_WINDOWS) {
41
+ const { mode } = await stat(bin)
42
+ await chmod(newBin, mode).catch(console.error)
43
+ }
44
+ onProcessExit(() => {
45
+ const oldBinFile = 'old-' + binFile
46
+ console.log("old version is kept as", oldBinFile)
47
+ const oldBin = join(binPath, oldBinFile)
48
+ try { unlinkSync(oldBin) }
49
+ catch {}
50
+ renameSync(bin, oldBin)
51
+ console.log("launching new version in background", newBinFile)
52
+ spawn(newBin, ['--updating', binFile], { detached: true, shell: true })
53
+ .on('error', console.error)
54
+ })
55
+ console.log('quitting')
56
+ process.exit()
57
+ }
58
+ catch {
59
+ pluginsWatcher.unpause()
60
+ }
61
+}
62
+
63
+if (argv.updating) { // we were launched with a temporary name, restore original name to avoid breaking references
64
+ const bin = process.argv[0]
65
+ renameSync(bin, join(dirname(bin), argv.updating))
66
+ console.log("renamed binary file to", argv.updating)
67
+}
server/src/util-files.ts
+19
-5
@@ -29,26 +29,40 @@ export async function readFileBusy(path: string): Promise<string> {
29
}
30
31
export function watchDir(dir: string, cb: ()=>void) {
32
- try { watch(dir, cb) }
32
+ let watcher: ReturnType<typeof watch>
33
+ let paused = false
34
+ try {
35
+ watcher = watch(dir, controlledCb)
36
+ }
37
catch {
38
// failing watching the content of the dir, we try to monitor its parent, but filtering events only for our target dir
39
const base = basename(dir)
40
try {
37
- const watcher = watch(dirname(dir), (event,name) => {
41
+ watcher = watch(dirname(dir), (event,name) => {
42
if (name !== base) return
43
try {
40
- watch(dir, cb) // attempt at passing to a more specific watching
44
watcher.close() // if we succeed, we give up the parent watching
45
+ watcher = watch(dir, controlledCb) // attempt at passing to a more specific watching
46
}
47
catch {}
44
- cb()
48
+ controlledCb()
49
})
50
}
51
catch (e) {
52
console.debug(String(e))
49
- return false
53
}
54
}
55
+ return {
56
+ working() { return Boolean(watcher) },
57
+ stop() { watcher?.close() },
58
+ pause() { paused = true },
59
+ unpause() { paused = false },
60
+ }
61
+
62
+ function controlledCb() {
63
+ if (!paused)
64
+ cb()
65
+ }
66
}
67
68
export function dirTraversal(s?: string) {
todo.md
+1
@@ -1,4 +1,5 @@
1
# To do
2
+- admin: check + update
3
- admin/monitor: account column
4
- frontend: hide closer button on login dialog accessing a protected resource, as it's no use
5
- easier nat life