-
Notifications
You must be signed in to change notification settings - Fork 100
/
build-appimage.ts
108 lines (89 loc) · 3.76 KB
/
build-appimage.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import escapeStringRegexp from "escape-string-regexp";
import fs from "fs";
import mkdirp from "mkdirp";
import path from "path";
import {BINARY_NAME} from "src/shared/constants";
import {DISABLE_SANDBOX_ARGS_LINE, build, ensureFileHasNoSuidBit} from "scripts/electron-builder/lib";
import {LOG, LOG_LEVELS, execShell} from "scripts/lib";
// TODO pass destination directory instead of hardcoding it ("--appimage-extract" doesn't support destination parameter at the moment)
const extractedImageFolderName = "squashfs-root";
(async () => {
await postProcess(
await build("appimage"),
);
})().catch((error) => {
LOG(error);
process.exit(1);
});
async function postProcess({packageFile}: { packageFile: string }) {
const {packageDir} = await unpack({packageFile});
disableSandbox({packageDir});
ensureFileHasNoSuidBit(path.join(packageDir, BINARY_NAME));
await packAndCleanup({packageDir, packageFile});
}
function disableSandbox({packageDir}: { packageDir: string; }): void {
const shFile = path.join(packageDir, "./AppRun");
const shContentOriginal = fs.readFileSync(shFile).toString();
const {content: shContentPatched, count: shContentPatchedCount} = (() => {
const searchValue = `exec "$BIN"`;
const replaceWith = `${searchValue} ${DISABLE_SANDBOX_ARGS_LINE}`;
let count = 0;
const content = shContentOriginal.replace(
new RegExp(escapeStringRegexp(searchValue), "g"),
() => (count++, replaceWith),
);
return {count, content};
})();
if (shContentPatched === shContentOriginal || shContentPatchedCount !== 2) {
throw new Error(`Failed to patch content of the "${shFile}" file`);
}
LOG(
LOG_LEVELS.title(`Writing ${LOG_LEVELS.value(shFile)} file with content:`),
LOG_LEVELS.value(shContentPatched),
);
fs.writeFileSync(shFile, shContentPatched);
}
async function unpack({packageFile}: { packageFile: string; }): Promise<{ packageDir: string }> {
const cwd = path.dirname(packageFile);
const packageDir = path.join(
path.dirname(packageFile),
extractedImageFolderName,
);
await execShell(["npx", ["rimraf", packageDir]]);
await execShell([packageFile, ["--appimage-extract"], {cwd}]);
return {packageDir};
}
async function packAndCleanup({packageFile, packageDir}: { packageFile: string; packageDir: string; }) {
const {appImageTool} = await resolveAppImageTool({packageFile});
await execShell(["rm", ["--force", packageFile]]);
await execShell([appImageTool, ["-n", "--comp", "xz", packageDir, packageFile]]);
await execShell(["npx", ["rimraf", packageDir]]);
}
async function resolveAppImageTool({packageFile}: { packageFile: string }): Promise<{ appImageTool: string }> {
const appImageFile = path.join(
path.join(path.dirname(packageFile), "./appimagetool"),
"./appimagetool-x86_64.AppImage",
);
const cwd = path.dirname(appImageFile);
mkdirp.sync(cwd);
// TODO cache the "appimagetool"
await execShell([
"curl",
[
"--fail",
"--location",
"--output", appImageFile,
`https://github.com/AppImage/AppImageKit/releases/download/continuous/${path.basename(appImageFile)}`,
],
]);
await execShell(["chmod", ["+x", appImageFile]]);
// unpacking the image in order to prevent the following error: AppImages require FUSE to run
// https://docs.appimage.org/user-guide/run-appimages.html?highlight=fuse#the-appimage-tells-me-it-needs-fuse-to-run
await execShell([appImageFile, ["--appimage-extract"], {cwd}]);
return {
appImageTool: path.join(
path.dirname(appImageFile),
path.join(extractedImageFolderName, "AppRun"),
),
};
}