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