@fastybird/smart-panel-backend 1.1.0-alpha.61 → 1.1.0-alpha.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/utils/privileged-helper.utils.d.ts +8 -0
- package/dist/common/utils/privileged-helper.utils.js +28 -0
- package/dist/common/utils/privileged-helper.utils.js.map +1 -0
- package/dist/modules/platform/services/platform.service.d.ts +2 -0
- package/dist/modules/platform/services/platform.service.js +34 -2
- package/dist/modules/platform/services/platform.service.js.map +1 -1
- package/dist/modules/system/scripts/privileged/helper.py +266 -0
- package/dist/modules/system/scripts/privileged/install-helper.py +238 -0
- package/dist/modules/system/scripts/update-worker.sh +296 -16
- package/dist/modules/system/services/privileged-worker.service.d.ts +3 -0
- package/dist/modules/system/services/privileged-worker.service.js +53 -11
- package/dist/modules/system/services/privileged-worker.service.js.map +1 -1
- package/dist/modules/system/services/update-executor.service.d.ts +2 -0
- package/dist/modules/system/services/update-executor.service.js +95 -45
- package/dist/modules/system/services/update-executor.service.js.map +1 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/scripts/cloudflared-setup.sh +17 -2
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-setup.service.js +9 -3
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-setup.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/scripts/tailscale-setup.sh +8 -0
- package/dist/plugins/remote-access-tailscale/services/tailscale-login.service.js +9 -0
- package/dist/plugins/remote-access-tailscale/services/tailscale-login.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-setup.service.js +9 -3
- package/dist/plugins/remote-access-tailscale/services/tailscale-setup.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const PRIVILEGED_HELPER_PATH = "/usr/local/libexec/smart-panel/helper";
|
|
2
|
+
export declare const PRIVILEGED_STATE_DIR = "/var/lib/smart-panel-privileged";
|
|
3
|
+
export type PrivilegedOperation = 'update' | 'tailscale-setup' | 'cloudflared-setup';
|
|
4
|
+
export declare const PRIVILEGED_OPERATIONS: Record<PrivilegedOperation, {
|
|
5
|
+
unit: string;
|
|
6
|
+
statusFile: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function privilegedHelperPresent(): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PRIVILEGED_OPERATIONS = exports.PRIVILEGED_STATE_DIR = exports.PRIVILEGED_HELPER_PATH = void 0;
|
|
4
|
+
exports.privilegedHelperPresent = privilegedHelperPresent;
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
exports.PRIVILEGED_HELPER_PATH = '/usr/local/libexec/smart-panel/helper';
|
|
7
|
+
exports.PRIVILEGED_STATE_DIR = '/var/lib/smart-panel-privileged';
|
|
8
|
+
exports.PRIVILEGED_OPERATIONS = {
|
|
9
|
+
update: { unit: 'smart-panel-update', statusFile: `${exports.PRIVILEGED_STATE_DIR}/update-status.json` },
|
|
10
|
+
'tailscale-setup': {
|
|
11
|
+
unit: 'smart-panel-remote-access',
|
|
12
|
+
statusFile: `${exports.PRIVILEGED_STATE_DIR}/tailscale-setup-status.json`,
|
|
13
|
+
},
|
|
14
|
+
'cloudflared-setup': {
|
|
15
|
+
unit: 'smart-panel-remote-access-cloudflare',
|
|
16
|
+
statusFile: `${exports.PRIVILEGED_STATE_DIR}/cloudflare-tunnel-setup-status.json`,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
function privilegedHelperPresent() {
|
|
20
|
+
try {
|
|
21
|
+
(0, fs_1.lstatSync)(exports.PRIVILEGED_HELPER_PATH);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return error.code !== 'ENOENT';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=privileged-helper.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privileged-helper.utils.js","sourceRoot":"","sources":["../../../src/common/utils/privileged-helper.utils.ts"],"names":[],"mappings":";;;AAmBA,0DAQC;AA3BD,2BAA+B;AAElB,QAAA,sBAAsB,GAAG,uCAAuC,CAAC;AACjE,QAAA,oBAAoB,GAAG,iCAAiC,CAAC;AAGzD,QAAA,qBAAqB,GAAsE;IACvG,MAAM,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,UAAU,EAAE,GAAG,4BAAoB,qBAAqB,EAAE;IAChG,iBAAiB,EAAE;QAClB,IAAI,EAAE,2BAA2B;QACjC,UAAU,EAAE,GAAG,4BAAoB,8BAA8B;KACjE;IACD,mBAAmB,EAAE;QACpB,IAAI,EAAE,sCAAsC;QAC5C,UAAU,EAAE,GAAG,4BAAoB,sCAAsC;KACzE;CACD,CAAC;AAGF,SAAgB,uBAAuB;IACtC,IAAI,CAAC;QACJ,IAAA,cAAS,EAAC,8BAAsB,CAAC,CAAC;QAElC,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAQ,KAA+B,CAAC,IAAI,KAAK,QAAQ,CAAC;IAC3D,CAAC;AACF,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PlatformType } from '../platform.constants';
|
|
2
2
|
export interface PrivilegedWorkerSupport {
|
|
3
|
+
transport?: 'helper' | 'legacy';
|
|
3
4
|
supported: boolean;
|
|
4
5
|
reason: string | null;
|
|
5
6
|
checkedAt: string;
|
|
@@ -20,6 +21,7 @@ export declare class PlatformService {
|
|
|
20
21
|
isPlatformCapableOfPrivilegedWorkers(): boolean;
|
|
21
22
|
private getFreshPrivilegedWorkerSupportCache;
|
|
22
23
|
private probePrivilegedWorkerSupport;
|
|
24
|
+
private probeHelper;
|
|
23
25
|
getSystemInfo(): Promise<import("../dto/system-info.dto").SystemInfoDto>;
|
|
24
26
|
getThrottleStatus(): Promise<import("../dto/throttle-status.dto").ThrottleStatusDto>;
|
|
25
27
|
getTemperature(): Promise<import("../dto/temperature.dto").TemperatureDto>;
|
|
@@ -18,6 +18,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
18
18
|
const systeminformation_1 = __importDefault(require("systeminformation"));
|
|
19
19
|
const common_1 = require("@nestjs/common");
|
|
20
20
|
const logger_1 = require("../../../common/logger");
|
|
21
|
+
const privileged_helper_utils_1 = require("../../../common/utils/privileged-helper.utils");
|
|
21
22
|
const platform_constants_1 = require("../platform.constants");
|
|
22
23
|
const development_platform_1 = require("../platforms/development.platform");
|
|
23
24
|
const docker_platform_1 = require("../platforms/docker.platform");
|
|
@@ -80,6 +81,9 @@ let PlatformService = class PlatformService {
|
|
|
80
81
|
result.supported || PRIVILEGED_WORKERS_UNSUPPORTED_PLATFORMS.has(this.platformType)
|
|
81
82
|
? null
|
|
82
83
|
: Date.now() + PRIVILEGED_WORKER_SUPPORT_NEGATIVE_CACHE_TTL_MS;
|
|
84
|
+
if (this.isPlatformCapableOfPrivilegedWorkers() && (result.transport === 'helper') !== (0, privileged_helper_utils_1.privilegedHelperPresent)()) {
|
|
85
|
+
return this.getPrivilegedWorkerSupport();
|
|
86
|
+
}
|
|
83
87
|
return result;
|
|
84
88
|
}
|
|
85
89
|
isPlatformCapableOfPrivilegedWorkers() {
|
|
@@ -89,6 +93,10 @@ let PlatformService = class PlatformService {
|
|
|
89
93
|
if (!this.privilegedWorkerSupportCache) {
|
|
90
94
|
return null;
|
|
91
95
|
}
|
|
96
|
+
if (this.isPlatformCapableOfPrivilegedWorkers() &&
|
|
97
|
+
(this.privilegedWorkerSupportCache.transport === 'helper') !== (0, privileged_helper_utils_1.privilegedHelperPresent)()) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
92
100
|
const fresh = this.privilegedWorkerSupportCacheExpiresAt === null || Date.now() < this.privilegedWorkerSupportCacheExpiresAt;
|
|
93
101
|
return fresh ? this.privilegedWorkerSupportCache : null;
|
|
94
102
|
}
|
|
@@ -101,8 +109,32 @@ let PlatformService = class PlatformService {
|
|
|
101
109
|
checkedAt,
|
|
102
110
|
};
|
|
103
111
|
}
|
|
104
|
-
const
|
|
105
|
-
|
|
112
|
+
const transport = (0, privileged_helper_utils_1.privilegedHelperPresent)() ? 'helper' : 'legacy';
|
|
113
|
+
const probe = transport === 'helper' ? await this.probeHelper() : await this.probeSystemdRunScope();
|
|
114
|
+
return { supported: probe.success, reason: probe.success ? null : probe.reason, checkedAt, transport };
|
|
115
|
+
}
|
|
116
|
+
probeHelper() {
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
(0, node_child_process_1.execFile)('sudo', ['-n', privileged_helper_utils_1.PRIVILEGED_HELPER_PATH, 'probe'], { timeout: PRIVILEGED_WORKER_PROBE_TIMEOUT_MS }, (error, stdout, stderr) => {
|
|
119
|
+
if (error) {
|
|
120
|
+
resolve({ success: false, reason: (stderr ?? '').toString().trim() || error.message });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const result = JSON.parse(stdout.toString());
|
|
125
|
+
const operations = result.operations;
|
|
126
|
+
if (result.version === 1 &&
|
|
127
|
+
Array.isArray(operations) &&
|
|
128
|
+
Object.keys(privileged_helper_utils_1.PRIVILEGED_OPERATIONS).every((operation) => operations.includes(operation))) {
|
|
129
|
+
resolve({ success: true, reason: null });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
}
|
|
135
|
+
resolve({ success: false, reason: 'Installed privileged helper returned an unsupported probe response.' });
|
|
136
|
+
});
|
|
137
|
+
});
|
|
106
138
|
}
|
|
107
139
|
getSystemInfo() {
|
|
108
140
|
this.logger.debug('Fetching system information');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.service.js","sourceRoot":"","sources":["../../../../src/modules/platform/services/platform.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2BAA8C;AAC9C,2DAA8C;AAC9C,0EAAmC;AAEnC,2CAA4C;AAE5C,mDAA+D;AAC/D,8DAA8F;AAE9F,4EAAwE;AACxE,kEAA8D;AAC9D,oEAAgE;AAChE,kFAA6E;AAC7E,wEAAoE;AAKpE,MAAM,wCAAwC,GAA8B,IAAI,GAAG,CAAC;IACnF,iCAAY,CAAC,MAAM;IACnB,iCAAY,CAAC,cAAc;IAC3B,iCAAY,CAAC,WAAW;CACxB,CAAC,CAAC;AAMH,MAAM,+CAA+C,GAAG,MAAM,CAAC;AAI/D,MAAM,kCAAkC,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"platform.service.js","sourceRoot":"","sources":["../../../../src/modules/platform/services/platform.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/python3 -I
|
|
2
|
+
"""Fixed root boundary for Smart Panel. No imports or commands from application state."""
|
|
3
|
+
|
|
4
|
+
import fcntl
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import pwd
|
|
9
|
+
import re
|
|
10
|
+
import stat
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
|
|
15
|
+
ROOT = Path('/usr/local/libexec/smart-panel')
|
|
16
|
+
STATE = Path('/var/lib/smart-panel-privileged')
|
|
17
|
+
CONFIG = ROOT / 'config.json'
|
|
18
|
+
SAFE_ENV = {'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
|
|
19
|
+
'HOME': '/root', 'LANG': 'C.UTF-8', 'LC_ALL': 'C.UTF-8'}
|
|
20
|
+
OPERATIONS = {
|
|
21
|
+
'update': ('smart-panel-update', 'update-worker.sh', 'update-status.json', 630),
|
|
22
|
+
'tailscale-setup': ('smart-panel-remote-access', 'tailscale-setup.sh', 'tailscale-setup-status.json', 600),
|
|
23
|
+
'cloudflared-setup': ('smart-panel-remote-access-cloudflare', 'cloudflared-setup.sh',
|
|
24
|
+
'cloudflare-tunnel-setup-status.json', 600),
|
|
25
|
+
}
|
|
26
|
+
UUID = re.compile(r'[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}')
|
|
27
|
+
VERSION = re.compile(r'(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-(?:alpha|beta|rc)\.[0-9]+)?')
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Refused(ValueError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def trusted(path, directory=False):
|
|
35
|
+
"""Reject symlinks and writable ancestors, including the installation's parent."""
|
|
36
|
+
path = Path(path)
|
|
37
|
+
if not path.is_absolute() or '..' in path.parts:
|
|
38
|
+
raise Refused('Non-canonical trusted path')
|
|
39
|
+
for part in reversed((path,) + tuple(path.parents)):
|
|
40
|
+
info = part.lstat()
|
|
41
|
+
if info.st_uid != 0 or info.st_mode & 0o022 or stat.S_ISLNK(info.st_mode):
|
|
42
|
+
raise Refused('Unsafe ownership or permissions: ' + str(part))
|
|
43
|
+
if part != path and not stat.S_ISDIR(info.st_mode):
|
|
44
|
+
raise Refused('Non-directory trusted ancestor')
|
|
45
|
+
info = path.lstat()
|
|
46
|
+
if directory and not stat.S_ISDIR(info.st_mode):
|
|
47
|
+
raise Refused('Expected trusted directory')
|
|
48
|
+
if not directory and (not stat.S_ISREG(info.st_mode) or info.st_nlink != 1):
|
|
49
|
+
raise Refused('Expected singly linked regular file')
|
|
50
|
+
return path
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def read_json(path):
|
|
54
|
+
trusted(path)
|
|
55
|
+
with open(path, encoding='utf-8') as stream:
|
|
56
|
+
raw = stream.read(65537)
|
|
57
|
+
if len(raw) > 65536:
|
|
58
|
+
raise Refused('Oversized metadata')
|
|
59
|
+
result = json.loads(raw)
|
|
60
|
+
if not isinstance(result, dict):
|
|
61
|
+
raise Refused('Invalid metadata')
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def load_config():
|
|
66
|
+
config = read_json(CONFIG)
|
|
67
|
+
if config.get('version') != 1 or config.get('installType') not in ('npm', 'image'):
|
|
68
|
+
raise Refused('Unsupported helper configuration')
|
|
69
|
+
user = config.get('serviceUser', '')
|
|
70
|
+
if not isinstance(user, str) or not re.fullmatch(r'[a-z_][a-z0-9_-]{0,31}', user):
|
|
71
|
+
raise Refused('Invalid service user')
|
|
72
|
+
account = pwd.getpwnam(user)
|
|
73
|
+
if account.pw_uid == 0 or account.pw_gid == 0:
|
|
74
|
+
raise Refused('Service identity must not be root')
|
|
75
|
+
config.setdefault('dbDir', config.get('dataDir', '') + '/data')
|
|
76
|
+
config.setdefault('configDir', config.get('dataDir', '') + '/config')
|
|
77
|
+
for key in ('dataDir', 'imageBaseDir', 'dbDir', 'configDir'):
|
|
78
|
+
value = config.get(key, '')
|
|
79
|
+
if not isinstance(value, str) or not re.fullmatch(r'/[a-zA-Z0-9_./-]+', value):
|
|
80
|
+
raise Refused('Invalid configured path')
|
|
81
|
+
if '..' in Path(value).parts or str(Path(value)) != value:
|
|
82
|
+
raise Refused('Non-canonical configured path')
|
|
83
|
+
if type(config.get('port')) is not int or not 1 <= config['port'] <= 65535:
|
|
84
|
+
raise Refused('Invalid backend port')
|
|
85
|
+
return config, account
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def command(args, timeout=10):
|
|
89
|
+
return subprocess.run(args, env=SAFE_ENV, stdin=subprocess.DEVNULL,
|
|
90
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
91
|
+
text=True, timeout=timeout, check=False)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def unit_active(unit, unit_type='service'):
|
|
95
|
+
result = command(['/usr/bin/systemctl', 'show', unit + '.' + unit_type,
|
|
96
|
+
'--property=ActiveState', '--value'])
|
|
97
|
+
state = result.stdout.strip()
|
|
98
|
+
if result.returncode or state not in ('active', 'activating', 'deactivating', 'reloading',
|
|
99
|
+
'inactive', 'failed'):
|
|
100
|
+
raise Refused('Cannot establish worker unit state')
|
|
101
|
+
return state not in ('inactive', 'failed')
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def atomic_json(path, value, gid):
|
|
105
|
+
trusted(path.parent, directory=True)
|
|
106
|
+
if path.exists() or path.is_symlink():
|
|
107
|
+
trusted(path)
|
|
108
|
+
fd, temporary = tempfile.mkstemp(prefix='.' + path.name + '-', dir=str(path.parent))
|
|
109
|
+
temp = Path(temporary)
|
|
110
|
+
try:
|
|
111
|
+
os.fchown(fd, 0, gid)
|
|
112
|
+
os.fchmod(fd, 0o640)
|
|
113
|
+
with os.fdopen(fd, 'w', encoding='utf-8') as stream:
|
|
114
|
+
json.dump(value, stream)
|
|
115
|
+
stream.write('\n')
|
|
116
|
+
stream.flush()
|
|
117
|
+
os.fsync(stream.fileno())
|
|
118
|
+
os.replace(temp, path)
|
|
119
|
+
finally:
|
|
120
|
+
if temp.exists():
|
|
121
|
+
temp.unlink()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def recovery_held():
|
|
125
|
+
attempt = STATE / 'update-attempt' / 'attempt.json'
|
|
126
|
+
if attempt.exists() or attempt.is_symlink():
|
|
127
|
+
value = read_json(attempt)
|
|
128
|
+
if (value.get('state') not in ('active', 'recovery_required', 'complete', 'failed_preflight', 'reconciled')
|
|
129
|
+
or type(value.get('recoveryRequired')) is not bool):
|
|
130
|
+
raise Refused('Invalid update attempt; operator recovery is required')
|
|
131
|
+
return value.get('state') in ('active', 'recovery_required') or value.get('recoveryRequired') is True
|
|
132
|
+
lock = STATE / 'update-attempt' / 'lock'
|
|
133
|
+
return lock.exists() or lock.is_symlink()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def clear_completed_attempt():
|
|
137
|
+
lock = STATE / 'update-attempt' / 'lock'
|
|
138
|
+
if lock.exists() or lock.is_symlink():
|
|
139
|
+
trusted(lock, directory=True)
|
|
140
|
+
# Only called with an inactive unit and a resolved attempt. Do not erase the
|
|
141
|
+
# completion evidence if a nonempty/unsafe lock needs operator inspection.
|
|
142
|
+
lock.rmdir()
|
|
143
|
+
status_remove(STATE / 'update-attempt' / 'attempt.json')
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def status_remove(path):
|
|
147
|
+
if path.exists() or path.is_symlink():
|
|
148
|
+
trusted(path)
|
|
149
|
+
path.unlink()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def plan_job(operation, job_id, version, config, account):
|
|
153
|
+
if operation not in OPERATIONS or not UUID.fullmatch(job_id):
|
|
154
|
+
raise Refused('Invalid job identity')
|
|
155
|
+
if operation == 'update':
|
|
156
|
+
if not isinstance(version, str) or len(version) > 64 or not VERSION.fullmatch(version):
|
|
157
|
+
raise Refused('Update requires an exact release version')
|
|
158
|
+
elif version is not None:
|
|
159
|
+
raise Refused('Setup does not accept arguments')
|
|
160
|
+
unit, worker, status, duration = OPERATIONS[operation]
|
|
161
|
+
script = trusted(ROOT / 'jobs' / worker)
|
|
162
|
+
env = dict(SAFE_ENV, STATUS_FILE=str(STATE / status), PRIVILEGED_WORKER_JOB_ID=job_id,
|
|
163
|
+
SMART_PANEL_USER=config['serviceUser'], FB_SERVICE_USER=config['serviceUser'],
|
|
164
|
+
FB_BACKEND_PORT=str(config['port']), FB_DATA_DIR=config['dataDir'],
|
|
165
|
+
FB_DB_PATH=config.get('dbDir', config['dataDir'] + '/data'),
|
|
166
|
+
FB_CONFIG_PATH=config.get('configDir', config['dataDir'] + '/config'),
|
|
167
|
+
UPDATE_OBSERVER_GID=str(account.pw_gid), HELPER_MANAGED='1')
|
|
168
|
+
args = []
|
|
169
|
+
if operation == 'update':
|
|
170
|
+
if config['installType'] == 'image':
|
|
171
|
+
trusted(Path(config['imageBaseDir']), directory=True)
|
|
172
|
+
env.update(UPDATE_VERSION=version, INSTALL_TYPE=config['installType'],
|
|
173
|
+
IMAGE_BASE_DIR=config['imageBaseDir'], UPDATE_START_MODE='running-service',
|
|
174
|
+
ATTEMPT_DIR=str(STATE / 'update-attempt'),
|
|
175
|
+
HEALTH_URL='http://127.0.0.1:' + str(config['port']) + '/api/v1/modules/system/system/health',
|
|
176
|
+
HEALTH_EXPECTED_VERSION=version,
|
|
177
|
+
DOWNLOAD_URL='https://github.com/FastyBird/smart-panel/releases/download/v' + version +
|
|
178
|
+
'/smart-panel-server-' + version + '-arm64.tar.gz')
|
|
179
|
+
args = [version]
|
|
180
|
+
launch = ['/usr/bin/systemd-run', '--collect', '--service-type=exec', '--unit=' + unit,
|
|
181
|
+
'--property=UMask=0027', '--property=RuntimeMaxSec=' + str(duration),
|
|
182
|
+
'--property=TimeoutStopSec=15', '--property=KillMode=control-group',
|
|
183
|
+
'--property=WorkingDirectory=/', '--property=Group=' + str(account.pw_gid)]
|
|
184
|
+
# env -i prevents manager-environment injection before bash starts (including BASH_ENV).
|
|
185
|
+
launch += ['/usr/bin/env', '-i'] + [key + '=' + value for key, value in env.items()]
|
|
186
|
+
launch += ['/bin/bash', '--noprofile', '--norc', str(script)] + args
|
|
187
|
+
return launch
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def dispatch(argv, config, account):
|
|
191
|
+
if argv == ['probe']:
|
|
192
|
+
for _, worker, _, _ in OPERATIONS.values():
|
|
193
|
+
trusted(ROOT / 'jobs' / worker)
|
|
194
|
+
result = command(['/usr/bin/systemd-run', '--quiet', '--wait', '--collect',
|
|
195
|
+
'--service-type=exec', '--unit=smart-panel-helper-probe-' + str(os.getpid()),
|
|
196
|
+
'--property=RuntimeMaxSec=3', '--property=TimeoutStopSec=1',
|
|
197
|
+
'/usr/bin/env', '-i', '/bin/true'], timeout=4)
|
|
198
|
+
if result.returncode:
|
|
199
|
+
raise Refused('System manager cannot run a privileged worker')
|
|
200
|
+
print(json.dumps({'version': 1, 'operations': list(OPERATIONS)}))
|
|
201
|
+
return
|
|
202
|
+
if argv == ['clear-update-status']:
|
|
203
|
+
if unit_active(OPERATIONS['update'][0]) or recovery_held():
|
|
204
|
+
raise Refused('Update is active or requires operator recovery')
|
|
205
|
+
path = STATE / 'update-status.json'
|
|
206
|
+
if path.exists() and read_json(path).get('status') not in ('complete', 'failed'):
|
|
207
|
+
raise Refused('Update status is not terminal')
|
|
208
|
+
clear_completed_attempt()
|
|
209
|
+
status_remove(path)
|
|
210
|
+
return
|
|
211
|
+
if len(argv) not in (3, 4) or argv[0] not in ('run-job', 'stop-job'):
|
|
212
|
+
raise Refused('Unsupported helper command')
|
|
213
|
+
verb, operation, job_id = argv[:3]
|
|
214
|
+
version = argv[3] if len(argv) == 4 else None
|
|
215
|
+
if operation not in OPERATIONS or not UUID.fullmatch(job_id):
|
|
216
|
+
raise Refused('Invalid job identity')
|
|
217
|
+
unit, _, status, _ = OPERATIONS[operation]
|
|
218
|
+
record = STATE / (operation + '.json')
|
|
219
|
+
if verb == 'stop-job':
|
|
220
|
+
if version is not None or not record.exists() or read_json(record).get('jobId') != job_id:
|
|
221
|
+
raise Refused('Worker identity does not match')
|
|
222
|
+
result = command(['/usr/bin/systemctl', 'stop', unit + '.service'], timeout=15)
|
|
223
|
+
if result.returncode:
|
|
224
|
+
raise Refused('Could not stop worker')
|
|
225
|
+
return
|
|
226
|
+
launch = plan_job(operation, job_id, version, config, account)
|
|
227
|
+
# Fixed unit names and this lock cover concurrent backend processes/restarts.
|
|
228
|
+
if any(unit_active(item[0]) for item in OPERATIONS.values()):
|
|
229
|
+
raise Refused('Another privileged operation is active')
|
|
230
|
+
if recovery_held():
|
|
231
|
+
raise Refused('Update requires operator recovery')
|
|
232
|
+
if operation == 'update':
|
|
233
|
+
clear_completed_attempt()
|
|
234
|
+
status_remove(STATE / status)
|
|
235
|
+
atomic_json(record, {'jobId': job_id, 'operation': operation}, account.pw_gid)
|
|
236
|
+
result = command(launch, timeout=15)
|
|
237
|
+
if result.returncode:
|
|
238
|
+
raise Refused('System manager refused worker launch')
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def main():
|
|
242
|
+
if os.geteuid() != 0:
|
|
243
|
+
raise Refused('Root execution through the installed sudo policy is required')
|
|
244
|
+
os.environ.clear()
|
|
245
|
+
os.environ.update(SAFE_ENV)
|
|
246
|
+
os.chdir('/')
|
|
247
|
+
os.umask(0o027)
|
|
248
|
+
trusted(ROOT, directory=True)
|
|
249
|
+
trusted(STATE, directory=True)
|
|
250
|
+
config, account = load_config()
|
|
251
|
+
lock_path = STATE / 'helper.lock'
|
|
252
|
+
fd = os.open(lock_path, os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW, 0o600)
|
|
253
|
+
try:
|
|
254
|
+
trusted(lock_path)
|
|
255
|
+
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
256
|
+
dispatch(sys.argv[1:], config, account)
|
|
257
|
+
finally:
|
|
258
|
+
os.close(fd)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if __name__ == '__main__':
|
|
262
|
+
try:
|
|
263
|
+
main()
|
|
264
|
+
except (Refused, OSError, ValueError, KeyError, subprocess.SubprocessError) as error:
|
|
265
|
+
print('Smart Panel helper: ' + str(error), file=sys.stderr)
|
|
266
|
+
sys.exit(1)
|