@aopslabs/aops 0.3.23 → 0.3.24
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/agent-assets-release/agent-assets/core/647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09/manifest.json +2 -2
- package/agent-assets-release/agent-assets/inventory.json +3 -3
- package/agent-assets-release/agent-assets-release.json +8 -8
- package/agent-assets-release/agent-assets-release.sigstore.json +24 -27
- package/dist/commands/global-update.js +447 -22
- package/dist/lib/agent-assets/native-fs.js +2 -2
- package/dist/lib/community-cockpit-lifecycle.js +95 -1
- package/dist/lib/community-native-lifecycle.js +54 -1
- package/native/bin/darwin-arm64/aops-agent-assets-fs +0 -0
- package/native/bin/darwin-x64/aops-agent-assets-fs +0 -0
- package/native/bin/linux-arm64/aops-agent-assets-fs +0 -0
- package/native/bin/linux-x64/aops-agent-assets-fs +0 -0
- package/native/bin/win32-x64/aops-agent-assets-fs.exe +0 -0
- package/native/manifest.json +6 -6
- package/native/qualifications/win32-x64.json +1 -1
- package/native/tui/darwin-arm64/aops-tui +0 -0
- package/package.json +5 -5
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"name": "aops",
|
|
11
11
|
"optional": false,
|
|
12
12
|
"packageSha256": "647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09",
|
|
13
|
-
"version": "0.3.
|
|
14
|
-
"versionId": "community-core-0.3.
|
|
13
|
+
"version": "0.3.24",
|
|
14
|
+
"versionId": "community-core-0.3.24"
|
|
15
15
|
},
|
|
16
16
|
"gateway": {
|
|
17
17
|
"byteLength": 621,
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"kind": "aops-community-agent-assets-inventory-v1",
|
|
23
23
|
"schemaVersion": 1,
|
|
24
24
|
"source": {
|
|
25
|
-
"commit": "
|
|
25
|
+
"commit": "608842f09f9479d542bac1bb6aa5034dd0002fd4",
|
|
26
26
|
"repository": "git+https://github.com/eeemzs/aops-platform",
|
|
27
27
|
"root": "apps/aops-cli/assets/agent-assets"
|
|
28
28
|
}
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"name": "aops",
|
|
12
12
|
"optional": false,
|
|
13
13
|
"packageSha256": "647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09",
|
|
14
|
-
"version": "0.3.
|
|
15
|
-
"versionId": "community-core-0.3.
|
|
14
|
+
"version": "0.3.24",
|
|
15
|
+
"versionId": "community-core-0.3.24"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
{
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
{
|
|
124
124
|
"byteLength": 3978,
|
|
125
125
|
"ref": "agent-assets/core/647c6e84fbc48f9a36945d81660f4d2a5241f58a53a18054365bfc16868b0f09/manifest.json",
|
|
126
|
-
"sha256": "
|
|
126
|
+
"sha256": "ac7e50f6d7ee959d309784edadeed6138980307f47c895cc79f3684bb91ea566"
|
|
127
127
|
},
|
|
128
128
|
{
|
|
129
129
|
"byteLength": 621,
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
{
|
|
134
134
|
"byteLength": 926,
|
|
135
135
|
"ref": "agent-assets/inventory.json",
|
|
136
|
-
"sha256": "
|
|
136
|
+
"sha256": "eabd4cb45defd761d6c91b5383f80351a59162d49fa3996a47e134e7965fb2ab"
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
"byteLength": 281,
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
},
|
|
150
150
|
"inventory": {
|
|
151
151
|
"ref": "agent-assets/inventory.json",
|
|
152
|
-
"sha256": "
|
|
152
|
+
"sha256": "eabd4cb45defd761d6c91b5383f80351a59162d49fa3996a47e134e7965fb2ab"
|
|
153
153
|
},
|
|
154
154
|
"kind": "aops-community-agent-assets-release-v1",
|
|
155
155
|
"projection": {
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
},
|
|
159
159
|
"root": "agent-assets",
|
|
160
160
|
"schemaVersion": 1,
|
|
161
|
-
"setSha256": "
|
|
161
|
+
"setSha256": "554203cf8d06ba4ca792e79a5a616b3ceaaa3c50b840e65745d87433cc43ce95"
|
|
162
162
|
},
|
|
163
163
|
"evidence": {
|
|
164
164
|
"signature": {
|
|
@@ -166,10 +166,10 @@
|
|
|
166
166
|
}
|
|
167
167
|
},
|
|
168
168
|
"kind": "aops-community-agent-assets-bundle-v1",
|
|
169
|
-
"releaseVersion": "0.3.
|
|
169
|
+
"releaseVersion": "0.3.24",
|
|
170
170
|
"schemaVersion": 1,
|
|
171
171
|
"source": {
|
|
172
|
-
"commit": "
|
|
172
|
+
"commit": "608842f09f9479d542bac1bb6aa5034dd0002fd4",
|
|
173
173
|
"repository": "git+https://github.com/eeemzs/aops-platform",
|
|
174
174
|
"root": "apps/aops-cli/assets/agent-assets"
|
|
175
175
|
}
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
"x509CertificateChain": {
|
|
5
5
|
"certificates": [
|
|
6
6
|
{
|
|
7
|
-
"rawBytes": "
|
|
7
|
+
"rawBytes": "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"
|
|
8
8
|
}
|
|
9
9
|
]
|
|
10
10
|
},
|
|
11
11
|
"tlogEntries": [
|
|
12
12
|
{
|
|
13
|
-
"logIndex": "
|
|
13
|
+
"logIndex": "2501603598",
|
|
14
14
|
"logId": {
|
|
15
15
|
"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="
|
|
16
16
|
},
|
|
@@ -18,31 +18,28 @@
|
|
|
18
18
|
"kind": "hashedrekord",
|
|
19
19
|
"version": "0.0.1"
|
|
20
20
|
},
|
|
21
|
-
"integratedTime": "
|
|
21
|
+
"integratedTime": "1787050507",
|
|
22
22
|
"inclusionPromise": {
|
|
23
|
-
"signedEntryTimestamp": "
|
|
23
|
+
"signedEntryTimestamp": "MEQCIBmNzj9XxKBpaqvsYHJdQuuBJmC3CFTb1ymB/wT4v2FrAiB+PXmrkgK7nivM0W5CFuPrGdQanm94uTZlSYKfkantQA=="
|
|
24
24
|
},
|
|
25
25
|
"inclusionProof": {
|
|
26
|
-
"logIndex": "
|
|
27
|
-
"rootHash": "
|
|
28
|
-
"treeSize": "
|
|
26
|
+
"logIndex": "2379699336",
|
|
27
|
+
"rootHash": "YBqB3LPP7z3XiaRq2ot4MJFVi+bFDVkUMph/hC1IDfU=",
|
|
28
|
+
"treeSize": "2379699344",
|
|
29
29
|
"hashes": [
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"/
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"3Q5lN6yq8podpJsAKbBJqSGNs+rmiL9QwZbQ0UH+3zQ=",
|
|
44
|
-
"fA/Fxwaq7dI1l5khmfZO/yXBJ/beTLDtlbUbvr3BnMI=",
|
|
45
|
-
"D2gSVC/Ew66euzf7kcNDzk5ThToLAkj5W34g+R2yIAQ=",
|
|
30
|
+
"njwwRR8CkW8qKsjNsTk2DmWb1d0QYVnDjoaFjnNiZcM=",
|
|
31
|
+
"VqADwLGk4e4wYy+SINhpx8di8xs/yXzC+8hp57515DQ=",
|
|
32
|
+
"KNNPpwh23nPcjbq8p5KBXjTXlnGR4xCBaYpvJypOGIM=",
|
|
33
|
+
"WeDaME+8oVrLbuTd5pesPBQyNEAJI32luWOTYeZlAvg=",
|
|
34
|
+
"lVWszKQLwZ+PwxkyaYfq05JuVEF7HtUP2bXechNYO1w=",
|
|
35
|
+
"PMq4/z5Dji4lnm1dN9aik+bf21gQHuaV0DvEF2DRfK4=",
|
|
36
|
+
"jw/BXoahnA99/KDbOEXR342svCgXlI8fBQ1EdfoP0rY=",
|
|
37
|
+
"V2ZRaYqGvqopvORHedITMz+sBSInRp6SKdmpIyoE4c0=",
|
|
38
|
+
"2ccjkNTG2bujC4xzJzdJCckwqIh5Q4aSWapj9M4EG/Y=",
|
|
39
|
+
"TyajcYUKvFL3MiVt2E0QMAVavx7MDeqclu7zuKIHKWA=",
|
|
40
|
+
"hwAkEuPLcomSOf+KiIMjdoM4HdEqmumZzGAuuolVGdk=",
|
|
41
|
+
"lVKLLzp8tNcT7yWZhGMjc/Cer1kKYS71H/n0sNutLzk=",
|
|
42
|
+
"1dSZjpCCNpV0knAvzBTUqXzpyGFhCaJkg0vBf6yDS/s=",
|
|
46
43
|
"9PAe2QaxB0ZutC/Y+HQKC6Wnag9Df+yGa0xYuRz5f1w=",
|
|
47
44
|
"dGyfnQgNT9mPiDmeuOaMmiIIn2XY7kncEgQ/mDW3SJU=",
|
|
48
45
|
"1OEC0KMekWr1AbpIDYXYCOXzf6aK8hYAK01U3ypm0is=",
|
|
@@ -50,10 +47,10 @@
|
|
|
50
47
|
"xH/DCseLHr9eKoYT8qsORZK7zVdEGYWHuVtsVrD95wY="
|
|
51
48
|
],
|
|
52
49
|
"checkpoint": {
|
|
53
|
-
"envelope": "rekor.sigstore.dev - 1193050959916656506\
|
|
50
|
+
"envelope": "rekor.sigstore.dev - 1193050959916656506\n2379699344\nYBqB3LPP7z3XiaRq2ot4MJFVi+bFDVkUMph/hC1IDfU=\n\n— rekor.sigstore.dev wNI9ajBGAiEA74PW9f4HTjAKRYHz4Up0pM3rkbma8803/TlH/8V0IF0CIQC2DmHPhZ7X0Jf8QQNood1Ud3VsmTmJpY7aXZYQ2el1/A==\n"
|
|
54
51
|
}
|
|
55
52
|
},
|
|
56
|
-
"canonicalizedBody": "
|
|
53
|
+
"canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzY2Q0YzYzYjI3YzdhYWI1NGVmMGFjZTllYmNkNTQ5YzgyNjYyZTc1ZDUzNmVjNjRjMjcyNzNmZDRhMDUwZTM1In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUUNpb2d4R01HMkkrZ1ZudnBNanhUa0I4cTR2cEc3TFFPQjJEdVJVb3YzQmV3SWdQZG15WkpQandKZnJscDdRazM0aTltN3hYWUV5SU5PSzZGbmp0VzF0aDQ4PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaGtSRU5EUW5aeFowRjNTVUpCWjBsVlNUUm1VakU0VUdScFVuaFBPRVpoY1VkTmVtRnhORGQyV2tKUmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFpkMDlFUlRSTlZFRXhUbFJCTTFkb1kwNU5hbGwzVDBSRk5FMVVSWGRPVkVFelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZuZDFCNk5qZGhNMnh5WldFeVkzQlllakprTTJ3NGQyZFJZVWRyWWpsdGFHSmhSRklLU1RWTFNXSk5LMGxVY1RJMGNGWkRkbXg1VkdwUU4xRTNZMWd6VTJoSFVFeFZiR0pzTWxGQmNFTjZTbnBNVUROWFpVdFBRMEpvYTNkbloxbFdUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZOTUVoWENuQklUMHAzVm1oT01sWjJjaTgzWkcweVNsUlZlVGM0ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDJSQldVUldVakJTUVZGSUwwSkhiM2RoU1ZwdFlVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVERKV2JGcFhNVFpqZVRsb1lqTkNlZ3BNV0VKeldWaFNiV0l6U25STWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTA1d1dqSTBkR05JVm1saVIyeHFURmRHYmxwWE5UQk1WMFo2Q21NeVZqQmplVFUxWWxkNFFXTnRWbTFqZVRsdldsZEdhMk41T1hSWlYyeDFUVVJyUjBOcGMwZEJVVkZDWnpjNGQwRlJSVVZMTW1nd1pFaENlazlwT0hZS1pFYzVjbHBYTkhWWlYwNHdZVmM1ZFdONU5XNWhXRkp2WkZkS01XTXlWbmxaTWpsMVpFZFdkV1JETldwaU1qQjNTSGRaUzB0M1dVSkNRVWRFZG5wQlFncEJaMUZTWkRJNWVXRXlXbk5pTTJSbVdrZHNlbU5IUmpCWk1tZDNUbWRaUzB0M1dVSkNRVWRFZG5wQlFrRjNVVzlPYWtFMFQwUlJlVnBxUVRWYWFtc3dDazU2Ykd0T1ZGRjVXVzFHYWsxWFNtbE9iVVpvVGxSQmVrNUhVbXROUkVGM1RXMWFhMDVFUVhoQ1oyOXlRbWRGUlVGWlR5OU5RVVZGUWtOT1ZHRlhaSFVLU1VkV05GbFhUakJKU0VJeFdXMTRjRmw1UWtKVU1VSlVTVWRHYmxwWE5UQkpSMFo2WXpKV01HTjZRV2xDWjI5eVFtZEZSVUZaVHk5TlFVVkdRa0pTYkFwYVYxWjBaVzVOZGxsWE9YZGplVEYzWWtkR01GcHRPWGxpVkVGa1FtZHZja0puUlVWQldVOHZUVUZGUjBKQk9YbGFWMXA2VERKb2JGbFhVbnBNTWpGb0NtRlhOSGRQZDFsTFMzZFpRa0pCUjBSMmVrRkNRMEZSZEVSRGRHOWtTRkozWTNwdmRrd3pVblpoTWxaMVRHMUdhbVJIYkhaaWJrMTFXakpzTUdGSVZta0taRmhPYkdOdFRuWmlibEpzWW01UmRWa3lPWFJOU0ZsSFEybHpSMEZSVVVKbk56aDNRVkZyUldGQmVHMWhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkUXBaTWpsMFRESldiRnBYTVRaamVUbG9Zak5DZWt4WVFuTlpXRkp0WWpOS2RFeDVOVzVoV0ZKdlpGZEpkbVF5T1hsaE1scHpZak5rZWt3elRuQmFNalIwQ21OSVZtbGlSMnhxVEZkR2JscFhOVEJNVjBaNll6SldNR041TlRWaVYzaEJZMjFXYldONU9XOWFWMFpyWTNrNWRGbFhiSFZOUkdkSFEybHpSMEZSVVVJS1p6YzRkMEZSYjBWTFozZHZUbXBCTkU5RVVYbGFha0UxV21wck1FNTZiR3RPVkZGNVdXMUdhazFYU21sT2JVWm9UbFJCZWs1SFVtdE5SRUYzVFcxYWF3cE9SRUZrUW1kdmNrSm5SVVZCV1U4dlRVRkZURUpCT0UxRVYyUndaRWRvTVZscE1XOWlNMDR3V2xkUmQwNTNXVXRMZDFsQ1FrRkhSSFo2UVVKRVFWRndDa1JEWkc5a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFphVjFac1lsaHdla3d5Um5aalNFMTBZMGQ0YUdSSFduWmpiVEIzVDBGWlMwdDNXVUlLUWtGSFJIWjZRVUpFVVZGeFJFTm5NazFFWnpST1JFcHRUVVJzYlU5VVVUTlBWMUV4VGtSS2FWbFhUWGhaYlVreVdWZEZNVTFFVFRCYVIxRjNUVVJCZVFwYWJWRXdUVUk0UjBOcGMwZEJVVkZDWnpjNGQwRlJORVZGVVhkUVkyMVdiV041T1c5YVYwWnJZM2s1ZEZsWGJIVk5RbTlIUTJselIwRlJVVUpuTnpoM0NrRlJPRVZFUVhkTFRWUk5lRTVxU1RGT1ZFRjVUbXBCY0VKbmIzSkNaMFZGUVZsUEwwMUJSVkZDUW5OTlIxZG9NR1JJUW5wUGFUaDJXakpzTUdGSVZta0tURzFPZG1KVE9XeGFWMVowWlc1TmQwZFJXVXRMZDFsQ1FrRkhSSFo2UVVKRlVWRk1SRUZyZUUxRWEzcE5WRUV6VG5wUmQyUm5XVXRMZDFsQ1FrRkhSQXAyZWtGQ1JXZFNiMFJIV205a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFphVjFac1lsaHdla3d5Um5aalNFMTBZMGQ0YUdSSFduWmpiVEIyQ2t4dFpIQmtSMmd4V1drNU0ySXpTbkphYlhoMlpETk5kbU15Ykc1aWFURjNaRmRLYzJGWFRYUlpWMlJzWW01UmRGbFlUbnBhV0ZKNlRHNXNkR0pGUW5rS1dsZGFla3d5YUd4WlYxSjZUREl4YUdGWE5IZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JYZFJjVVJEWnpKTlJHYzBUa1JLYlUxRWJHMVBWRkV6VDFkUk1RcE9SRXBwV1ZkTmVGbHRTVEpaVjBVeFRVUk5NRnBIVVhkTlJFRjVXbTFSTUUxRFJVZERhWE5IUVZGUlFtYzNPSGRCVWxGRlJYZDNVbVF5T1hsaE1scHpDbUl6WkdaYVIyeDZZMGRHTUZreVozZFhkMWxMUzNkWlFrSkJSMFIyZWtGQ1JsRlNUa1JGZEc5a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFlLV2xkV2JHSlljSHBNTWtaMlkwaE5kR05IZUdoa1IxcDJZMjB3ZGxsWFRqQmhWemwxWTNrNWVXUlhOWHBNZWsxNVRWUkpOVTFFVVRGT1JFRXlUREpHTUFwa1IxWjBZMGhTZWt4NlJYZEdkMWxMUzNkWlFrSkJSMFIyZWtGQ1JtZFJTa1JCWkhkamJXd3lXVmhTYkUxR1NVZERhWE5IUVZGUlFtYzNPSGRCVW1kRkNsSkJlRU5qYlZaM1lucHdiRnBYVm5SbGJrNUJUVlJCTlUxNlJYZE9lbU13VERKR2RtTklUWFJqUjNob1pFZGFkbU50TVVGTlZFMTRUbXBKTVU1VVFYa0tUbXB3ZVZwWFdUWmpiVlp0WTNrNWIxcFhSbXRqZVRsMFdWZHNkVTFKUjB0Q1oyOXlRbWRGUlVGa1dqVkJaMUZEUWtoM1JXVm5RalJCU0ZsQk0xUXdkd3BoYzJKSVJWUktha2RTTkdOdFYyTXpRWEZLUzFoeWFtVlFTek12YURSd2VXZERPSEEzYnpSQlFVRkhaMFpKU3l0RFFVRkJRa0ZOUVZKNlFrWkJhVVZCQ25rNVMyTlpOMjlNVTBKR1pUaG5WMjFEV0VoaGFuZHJNelJzY2tkTGJWTlphVGNyV2pCM1dWcDNVRFJEU1VWTVlXNXJWV1pYWW5Sdk1HZGhLelIzVFRnS1lYQkpielpITjJvcmNVNVpaV3d2VEdnMFVISm1VR3h0VFVGdlIwTkRjVWRUVFRRNVFrRk5SRUV5WjBGTlIxVkRUVkZEWWtsRmRTOU1jMFI2VldOMlVRcEtiMm9yZGtjeWQzUXdOR2h1TlUxR1RteFZhbWxETTFKVFlucENVbFZXVjBJMk9VSnZjSG8yWkU5d1ptTkhlVk5DY0RoRFRVZ3ZjMDVyYWxCV1QwNXJDbkZsYjBaUmVVMU9LM294WkhJMlNuUkxkSFZvUW1Ga01FVlpRakJuV0dWaWJFVldTM2hTZDFweFJIRktSM1ZuV1hSMk4zSndaejA5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0="
|
|
57
54
|
}
|
|
58
55
|
],
|
|
59
56
|
"timestampVerificationData": {}
|
|
@@ -61,8 +58,8 @@
|
|
|
61
58
|
"messageSignature": {
|
|
62
59
|
"messageDigest": {
|
|
63
60
|
"algorithm": "SHA2_256",
|
|
64
|
-
"digest": "
|
|
61
|
+
"digest": "PNTGOyfHqrVO8Kzp681UnIJmLnXVNuxkwnJz/UoFDjU="
|
|
65
62
|
},
|
|
66
|
-
"signature": "
|
|
63
|
+
"signature": "MEUCIQCiogxGMG2I+gVnvpMjxTkB8q4vpG7LQOB2DuRUov3BewIgPdmyZJPjwJfrlp7Qk34i9m7xXYEyINOK6FnjtW1th48="
|
|
67
64
|
}
|
|
68
65
|
}
|
|
@@ -29,11 +29,13 @@ const GLOBAL_UPDATE_EXECUTOR_MAX_BYTES = 512 * 1024 * 1024;
|
|
|
29
29
|
class ChildCommandError extends Error {
|
|
30
30
|
status;
|
|
31
31
|
diagnostic;
|
|
32
|
-
|
|
32
|
+
stdout;
|
|
33
|
+
constructor(command, status, diagnostic, stdout = '') {
|
|
33
34
|
super(`${path.basename(command)} failed with status ${status ?? 'unknown'}`);
|
|
34
35
|
this.name = 'ChildCommandError';
|
|
35
36
|
this.status = status;
|
|
36
37
|
this.diagnostic = diagnostic;
|
|
38
|
+
this.stdout = stdout;
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
function cleanEnvironment() {
|
|
@@ -69,7 +71,7 @@ function defaultRunner(command, args, cwd = process.cwd()) {
|
|
|
69
71
|
}
|
|
70
72
|
catch {
|
|
71
73
|
}
|
|
72
|
-
throw new ChildCommandError(command, result.status, diagnostic);
|
|
74
|
+
throw new ChildCommandError(command, result.status, diagnostic, result.stdout ?? '');
|
|
73
75
|
}
|
|
74
76
|
return result.stdout;
|
|
75
77
|
}
|
|
@@ -280,10 +282,33 @@ export function inspectGlobalUpdate(run = defaultRunner) {
|
|
|
280
282
|
function runInstalledJson(run, cliPath, args) {
|
|
281
283
|
return parseJson(run(process.execPath, [cliPath, ...args]).trim(), args.join('_'));
|
|
282
284
|
}
|
|
285
|
+
function childCommandStdout(error) {
|
|
286
|
+
const stdout = error?.stdout;
|
|
287
|
+
return typeof stdout === 'string' && stdout.trim() !== '' ? stdout : undefined;
|
|
288
|
+
}
|
|
289
|
+
function readServiceStatus(run, cliPath, service) {
|
|
290
|
+
try {
|
|
291
|
+
return runInstalledJson(run, cliPath, [service, 'status', '--json']);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
const stdout = childCommandStdout(error);
|
|
295
|
+
if (stdout === undefined)
|
|
296
|
+
throw error;
|
|
297
|
+
try {
|
|
298
|
+
const parsed = JSON.parse(stdout.trim());
|
|
299
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
300
|
+
throw error;
|
|
301
|
+
return parsed;
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
283
308
|
function serviceSnapshot(run, cliPath) {
|
|
284
309
|
return {
|
|
285
|
-
server:
|
|
286
|
-
cockpit:
|
|
310
|
+
server: readServiceStatus(run, cliPath, 'server'),
|
|
311
|
+
cockpit: readServiceStatus(run, cliPath, 'cockpit'),
|
|
287
312
|
};
|
|
288
313
|
}
|
|
289
314
|
function assertHealthyServices(services) {
|
|
@@ -565,6 +590,7 @@ export function dispatchGlobalUpdate(options, run = defaultRunner) {
|
|
|
565
590
|
rollbackPerformed: false,
|
|
566
591
|
error: safeUpdateError(error),
|
|
567
592
|
events: [],
|
|
593
|
+
eventTimes: [],
|
|
568
594
|
});
|
|
569
595
|
}
|
|
570
596
|
throw new Error(`global update candidate executor failed; diagnostic record: ${resultPath}`, { cause: error });
|
|
@@ -652,24 +678,161 @@ function rollbackServerApplication(run, cliPath, updateId, serverPath, expectedR
|
|
|
652
678
|
const defaultExecutionDependencies = {
|
|
653
679
|
fetch: globalThis.fetch.bind(globalThis),
|
|
654
680
|
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
681
|
+
now: () => new Date(),
|
|
655
682
|
};
|
|
683
|
+
class GlobalUpdateEventLog {
|
|
684
|
+
now;
|
|
685
|
+
names = [];
|
|
686
|
+
times = [];
|
|
687
|
+
constructor(now) {
|
|
688
|
+
this.now = now;
|
|
689
|
+
}
|
|
690
|
+
push(event) {
|
|
691
|
+
this.names.push(event);
|
|
692
|
+
this.times.push(this.now().toISOString());
|
|
693
|
+
}
|
|
694
|
+
includes(event) {
|
|
695
|
+
return this.names.includes(event);
|
|
696
|
+
}
|
|
697
|
+
get events() {
|
|
698
|
+
return [...this.names];
|
|
699
|
+
}
|
|
700
|
+
get eventTimes() {
|
|
701
|
+
return [...this.times];
|
|
702
|
+
}
|
|
703
|
+
}
|
|
656
704
|
class StartupHealthPending extends Error {
|
|
657
|
-
|
|
705
|
+
detail;
|
|
706
|
+
constructor(cause, detail) {
|
|
658
707
|
super('global_update_health_startup_pending', { cause });
|
|
708
|
+
this.detail = detail;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function serviceHealthFacts(services) {
|
|
712
|
+
const liveness = services.server.liveness;
|
|
713
|
+
return {
|
|
714
|
+
serverState: services.server.runtimeState,
|
|
715
|
+
serverHealth: liveness?.health,
|
|
716
|
+
serverReason: services.server.error,
|
|
717
|
+
cockpitState: services.cockpit.runtimeState,
|
|
718
|
+
cockpitHealth: services.cockpit.health,
|
|
719
|
+
cockpitReason: services.cockpit.reason,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
const HEALTH_SUBJECTS = new Set([
|
|
723
|
+
'install-readback', 'server-status', 'cockpit-status', 'service-health',
|
|
724
|
+
'server', 'cockpit', 'cockpit-proxy',
|
|
725
|
+
]);
|
|
726
|
+
const HEALTH_PHASES = new Set(['startup', 'steady']);
|
|
727
|
+
const SAFE_STATE = /^[a-z][a-z0-9-]{0,40}$/u;
|
|
728
|
+
const SAFE_REASON_CODE = /\b(?:aops|community|global_update)_[a-z0-9_]+\b/iu;
|
|
729
|
+
function sanitizeHealthDetail(detail) {
|
|
730
|
+
const safe = {
|
|
731
|
+
subject: typeof detail.subject === 'string' && HEALTH_SUBJECTS.has(detail.subject)
|
|
732
|
+
? detail.subject
|
|
733
|
+
: 'unrecognized',
|
|
734
|
+
};
|
|
735
|
+
if (typeof detail.phase === 'string' && HEALTH_PHASES.has(detail.phase))
|
|
736
|
+
safe.phase = detail.phase;
|
|
737
|
+
if (typeof detail.reachable === 'boolean')
|
|
738
|
+
safe.reachable = detail.reachable;
|
|
739
|
+
for (const key of ['sample', 'attempts', 'childStatus', 'status']) {
|
|
740
|
+
const value = detail[key];
|
|
741
|
+
if (typeof value === 'number' && Number.isSafeInteger(value))
|
|
742
|
+
safe[key] = value;
|
|
743
|
+
}
|
|
744
|
+
for (const key of ['serverState', 'serverHealth', 'cockpitState', 'cockpitHealth']) {
|
|
745
|
+
const value = detail[key];
|
|
746
|
+
if (value === undefined || value === null)
|
|
747
|
+
continue;
|
|
748
|
+
safe[key] = typeof value === 'string' && SAFE_STATE.test(value) ? value : 'unrecognized';
|
|
659
749
|
}
|
|
750
|
+
for (const key of ['serverReason', 'cockpitReason']) {
|
|
751
|
+
const value = detail[key];
|
|
752
|
+
if (value === undefined || value === null)
|
|
753
|
+
continue;
|
|
754
|
+
safe[key] = typeof value === 'string'
|
|
755
|
+
? SAFE_REASON_CODE.exec(value)?.[0]?.toLowerCase() ?? 'unrecognized'
|
|
756
|
+
: 'unrecognized';
|
|
757
|
+
}
|
|
758
|
+
return safe;
|
|
759
|
+
}
|
|
760
|
+
function childCommandStatus(error) {
|
|
761
|
+
const status = error?.status;
|
|
762
|
+
return typeof status === 'number' ? status : undefined;
|
|
763
|
+
}
|
|
764
|
+
class ForwardSchemaRetainsTarget extends Error {
|
|
765
|
+
applicationRollbackError;
|
|
766
|
+
constructor(applicationRollbackError) {
|
|
767
|
+
super('global_update_forward_schema_retains_target');
|
|
768
|
+
this.applicationRollbackError = applicationRollbackError;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
export class StableHealthFailure extends Error {
|
|
772
|
+
healthFailure;
|
|
773
|
+
constructor(detail) {
|
|
774
|
+
const safe = sanitizeHealthDetail(detail);
|
|
775
|
+
super(`global_update_stable_health_failed:${String(safe.subject)}`);
|
|
776
|
+
this.healthFailure = Object.freeze(safe);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
export function resolveRecoveryClosure(state) {
|
|
780
|
+
if (!state.applicationSideEffectPossible)
|
|
781
|
+
return 'prior';
|
|
782
|
+
return state.applicationRolledBack ? 'prior' : 'target';
|
|
783
|
+
}
|
|
784
|
+
const APPLICATION_PRE_EFFECT_CODES = new Set([
|
|
785
|
+
'community_native_application_update_content_unchanged',
|
|
786
|
+
]);
|
|
787
|
+
export function applicationSideEffectPossible(attempted, updateError) {
|
|
788
|
+
if (!attempted)
|
|
789
|
+
return false;
|
|
790
|
+
const diagnostic = nestedCommunityDiagnostic(updateError);
|
|
791
|
+
if (!diagnostic)
|
|
792
|
+
return true;
|
|
793
|
+
const cause = diagnostic.cause;
|
|
794
|
+
const codes = [diagnostic.code, cause?.code];
|
|
795
|
+
return !codes.some((code) => typeof code === 'string' && APPLICATION_PRE_EFFECT_CODES.has(code));
|
|
660
796
|
}
|
|
661
797
|
async function assertStableHealth(run, plan, execution) {
|
|
662
798
|
const evidence = [];
|
|
799
|
+
const fail = (error, startup, detail) => {
|
|
800
|
+
if (startup)
|
|
801
|
+
throw new StartupHealthPending(error, detail);
|
|
802
|
+
throw new StableHealthFailure({ ...detail, phase: 'steady' });
|
|
803
|
+
};
|
|
663
804
|
const collectSample = async (sample, startup) => {
|
|
664
|
-
|
|
805
|
+
let installed;
|
|
806
|
+
try {
|
|
807
|
+
installed = inspectGlobalInstall(run);
|
|
808
|
+
}
|
|
809
|
+
catch (error) {
|
|
810
|
+
fail(error, startup, { subject: 'install-readback', sample, childStatus: childCommandStatus(error) });
|
|
811
|
+
throw error;
|
|
812
|
+
}
|
|
665
813
|
let services;
|
|
666
814
|
try {
|
|
667
|
-
services =
|
|
815
|
+
services = {
|
|
816
|
+
server: readServiceStatus(run, installed.cliPath, 'server'),
|
|
817
|
+
cockpit: { runtimeState: 'not-read' },
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
catch (error) {
|
|
821
|
+
fail(error, startup, { subject: 'server-status', sample, childStatus: childCommandStatus(error) });
|
|
822
|
+
throw error;
|
|
823
|
+
}
|
|
824
|
+
try {
|
|
825
|
+
services = { ...services, cockpit: readServiceStatus(run, installed.cliPath, 'cockpit') };
|
|
826
|
+
}
|
|
827
|
+
catch (error) {
|
|
828
|
+
fail(error, startup, { subject: 'cockpit-status', sample, childStatus: childCommandStatus(error) });
|
|
829
|
+
throw error;
|
|
830
|
+
}
|
|
831
|
+
try {
|
|
668
832
|
assertHealthyServices(services);
|
|
669
833
|
}
|
|
670
834
|
catch (error) {
|
|
671
|
-
|
|
672
|
-
throw new StartupHealthPending(error);
|
|
835
|
+
fail(error, startup, { subject: 'service-health', sample, ...serviceHealthFacts(services) });
|
|
673
836
|
throw error;
|
|
674
837
|
}
|
|
675
838
|
const serverOrigin = validateLoopbackOrigin(services.server.origin, 'AOPS Server origin');
|
|
@@ -685,14 +848,12 @@ async function assertStableHealth(run, plan, execution) {
|
|
|
685
848
|
response = await execution.fetch(url, { cache: 'no-store', signal: AbortSignal.timeout(15_000) });
|
|
686
849
|
}
|
|
687
850
|
catch (error) {
|
|
688
|
-
|
|
689
|
-
throw new StartupHealthPending(error);
|
|
851
|
+
fail(error, startup, { subject: name, sample, reachable: false });
|
|
690
852
|
throw error;
|
|
691
853
|
}
|
|
692
|
-
if (response.status !== 200
|
|
693
|
-
|
|
854
|
+
if (response.status !== 200) {
|
|
855
|
+
fail(new Error(`${name} health returned ${response.status}`), startup, { subject: name, sample, reachable: true, status: response.status });
|
|
694
856
|
}
|
|
695
|
-
assert.equal(response.status, 200, `${name} health returned ${response.status}`);
|
|
696
857
|
const body = await response.arrayBuffer();
|
|
697
858
|
endpoints.push({ name, url, status: response.status, bytes: body.byteLength });
|
|
698
859
|
}
|
|
@@ -708,7 +869,11 @@ async function assertStableHealth(run, plan, execution) {
|
|
|
708
869
|
catch (error) {
|
|
709
870
|
if (!(error instanceof StartupHealthPending))
|
|
710
871
|
throw error;
|
|
711
|
-
startupError =
|
|
872
|
+
startupError = new StableHealthFailure({
|
|
873
|
+
...error.detail,
|
|
874
|
+
phase: 'startup',
|
|
875
|
+
attempts: attempt,
|
|
876
|
+
});
|
|
712
877
|
if (attempt < plan.safety.healthStartupAttempts) {
|
|
713
878
|
await execution.sleep(plan.safety.healthStartupIntervalMs);
|
|
714
879
|
}
|
|
@@ -755,6 +920,9 @@ function nestedCommunityDiagnostic(error) {
|
|
|
755
920
|
return undefined;
|
|
756
921
|
}
|
|
757
922
|
function safeUpdateError(error) {
|
|
923
|
+
if (error instanceof StableHealthFailure) {
|
|
924
|
+
return { code: 'global_update_stable_health_failed', healthFailure: error.healthFailure };
|
|
925
|
+
}
|
|
758
926
|
const diagnostic = nestedCommunityDiagnostic(error);
|
|
759
927
|
if (diagnostic) {
|
|
760
928
|
const details = diagnostic.details;
|
|
@@ -785,6 +953,160 @@ function writeTerminalUpdateResult(plan, result) {
|
|
|
785
953
|
const resultPath = path.join(plan.operationRoot, RESULT_FILE);
|
|
786
954
|
writeFileSync(resultPath, `${JSON.stringify(result, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
787
955
|
}
|
|
956
|
+
const RECOVERY_CONTRACT = 'aops.global-update-recovery/v1';
|
|
957
|
+
const RECOVERY_RESULT_PATTERN = /^update-recovery-(0|[1-9]\d*)\.json$/u;
|
|
958
|
+
const RECOVERABLE_TERMINAL_STATUSES = new Set(['recovery-required']);
|
|
959
|
+
function recoveryAttemptFiles(plan) {
|
|
960
|
+
const operationRoot = plainDirectory(plan.operationRoot, 'global update operation root');
|
|
961
|
+
const attempts = [];
|
|
962
|
+
for (const entry of readdirSync(operationRoot)) {
|
|
963
|
+
const match = RECOVERY_RESULT_PATTERN.exec(entry);
|
|
964
|
+
if (match)
|
|
965
|
+
attempts.push({ attempt: Number(match[1]), path: path.join(operationRoot, entry) });
|
|
966
|
+
}
|
|
967
|
+
return attempts.sort((left, right) => left.attempt - right.attempt);
|
|
968
|
+
}
|
|
969
|
+
function nextRecoveryResultPath(plan) {
|
|
970
|
+
const attempts = recoveryAttemptFiles(plan);
|
|
971
|
+
const next = attempts.length === 0 ? 1 : attempts[attempts.length - 1].attempt + 1;
|
|
972
|
+
return path.join(plan.operationRoot, `update-recovery-${next}.json`);
|
|
973
|
+
}
|
|
974
|
+
function sameClosure(installed, expected) {
|
|
975
|
+
try {
|
|
976
|
+
assert.deepEqual(installed, expected);
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
979
|
+
catch {
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
function writeRecoveryResult(resultPath, result) {
|
|
984
|
+
writeFileSync(resultPath, `${JSON.stringify(result, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
985
|
+
}
|
|
986
|
+
function preflightGlobalUpdateRecovery(options, run) {
|
|
987
|
+
const plan = readGlobalUpdatePlan(options.plan);
|
|
988
|
+
assert.equal(options.confirm, plan.planId, 'global update recovery confirmation does not match the exact plan');
|
|
989
|
+
const terminalPath = path.join(plan.operationRoot, RESULT_FILE);
|
|
990
|
+
assert.equal(existsSync(terminalPath), true, 'global_update_recovery_requires_terminal_result');
|
|
991
|
+
const terminal = parseJson(readFileSync(terminalPath, 'utf8'), 'global_update_result');
|
|
992
|
+
assert.equal(terminal.planId, plan.planId, 'terminal result belongs to a different plan');
|
|
993
|
+
const terminalStatus = String(terminal.status);
|
|
994
|
+
assert.equal(RECOVERABLE_TERMINAL_STATUSES.has(terminalStatus), true, `global_update_recovery_status_not_recoverable:${terminalStatus}`);
|
|
995
|
+
const installed = inspectGlobalInstall(run);
|
|
996
|
+
assert.equal(installed.globalPrefix, plan.globalPrefix, 'global npm prefix changed after update planning');
|
|
997
|
+
const installedClosure = [installed.packages, installed.topology];
|
|
998
|
+
const atTarget = sameClosure(installedClosure, [plan.target.packages, plan.target.topology]);
|
|
999
|
+
const atPrior = sameClosure(installedClosure, [plan.prior.packages, plan.prior.topology]);
|
|
1000
|
+
assert.equal(atTarget || atPrior, true, 'global_update_recovery_closure_drifted');
|
|
1001
|
+
const succeeded = recoveryAttemptFiles(plan)
|
|
1002
|
+
.map((file) => ({
|
|
1003
|
+
...file,
|
|
1004
|
+
record: parseJson(readFileSync(file.path, 'utf8'), 'global_update_recovery_result'),
|
|
1005
|
+
}))
|
|
1006
|
+
.find((entry) => entry.record.status === 'recovered');
|
|
1007
|
+
if (succeeded) {
|
|
1008
|
+
assert.equal(atTarget, true, 'global_update_recovery_recorded_but_closure_moved');
|
|
1009
|
+
}
|
|
1010
|
+
return { plan, terminalStatus, installed, atTarget, atPrior, succeeded };
|
|
1011
|
+
}
|
|
1012
|
+
export function dispatchGlobalUpdateRecovery(options, run = defaultRunner) {
|
|
1013
|
+
const preflight = preflightGlobalUpdateRecovery(options, run);
|
|
1014
|
+
const { plan } = preflight;
|
|
1015
|
+
if (preflight.succeeded)
|
|
1016
|
+
return preflight.succeeded.record;
|
|
1017
|
+
const resultPath = nextRecoveryResultPath(plan);
|
|
1018
|
+
let executorStarted = false;
|
|
1019
|
+
try {
|
|
1020
|
+
const { receipt, receiptPath } = stageCandidateGlobalUpdateExecutor(plan, run);
|
|
1021
|
+
executorStarted = true;
|
|
1022
|
+
return parseJson(run(process.execPath, [
|
|
1023
|
+
receipt.entrypoint,
|
|
1024
|
+
'update',
|
|
1025
|
+
'recover',
|
|
1026
|
+
'--plan',
|
|
1027
|
+
options.plan,
|
|
1028
|
+
'--confirm',
|
|
1029
|
+
options.confirm,
|
|
1030
|
+
'--executor-receipt',
|
|
1031
|
+
receiptPath,
|
|
1032
|
+
'--json',
|
|
1033
|
+
]).trim(), 'global_update_recovery_result');
|
|
1034
|
+
}
|
|
1035
|
+
catch (error) {
|
|
1036
|
+
if (!existsSync(resultPath)) {
|
|
1037
|
+
writeRecoveryResult(resultPath, {
|
|
1038
|
+
schemaVersion: 1,
|
|
1039
|
+
contract: RECOVERY_CONTRACT,
|
|
1040
|
+
status: executorStarted ? 'executor-result-missing' : 'executor-staging-failed',
|
|
1041
|
+
planId: plan.planId,
|
|
1042
|
+
failedStage: executorStarted ? 'candidate-executor' : 'stage-candidate-executor',
|
|
1043
|
+
globalState: executorStarted ? 'unknown-requires-inspection' : 'unchanged',
|
|
1044
|
+
error: safeUpdateError(error),
|
|
1045
|
+
events: [],
|
|
1046
|
+
eventTimes: [],
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
throw new Error(`global update recovery executor failed; diagnostic record: ${resultPath}`, { cause: error });
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
export async function recoverGlobalUpdate(options, run = defaultRunner, execution = defaultExecutionDependencies) {
|
|
1053
|
+
const preflight = preflightGlobalUpdateRecovery(options, run);
|
|
1054
|
+
const { plan, installed: current, atTarget, terminalStatus } = preflight;
|
|
1055
|
+
const installedCliRoot = plainDirectory(path.dirname(path.dirname(current.cliPath)), 'installed global CLI root');
|
|
1056
|
+
const invokedEntrypoint = plainFile(process.argv[1], 'invoked AOPS CLI entrypoint');
|
|
1057
|
+
const relativeToInstalled = path.relative(installedCliRoot, invokedEntrypoint);
|
|
1058
|
+
assert.equal(relativeToInstalled.startsWith('..') || path.isAbsolute(relativeToInstalled), true, 'global_update_recovery_must_not_run_from_the_replaced_cli');
|
|
1059
|
+
if (preflight.succeeded)
|
|
1060
|
+
return preflight.succeeded.record;
|
|
1061
|
+
const resultPath = nextRecoveryResultPath(plan);
|
|
1062
|
+
const events = new GlobalUpdateEventLog(execution.now ?? (() => new Date()));
|
|
1063
|
+
try {
|
|
1064
|
+
stopService(run, current.cliPath, 'cockpit', true);
|
|
1065
|
+
stopService(run, current.cliPath, 'server', true);
|
|
1066
|
+
if (atTarget) {
|
|
1067
|
+
events.push('retained:target-closure');
|
|
1068
|
+
}
|
|
1069
|
+
else {
|
|
1070
|
+
installGlobalUpdateClosure(run, plan, plan.candidates, plan.target.topology);
|
|
1071
|
+
events.push('installed:target-closure');
|
|
1072
|
+
}
|
|
1073
|
+
const restored = inspectGlobalInstall(run);
|
|
1074
|
+
assert.deepEqual(restored.packages, plan.target.packages, 'recovered closure is not the exact target');
|
|
1075
|
+
assert.deepEqual(restored.topology, plan.target.topology, 'recovered topology is not canonical');
|
|
1076
|
+
startService(run, restored.cliPath, 'server');
|
|
1077
|
+
events.push('started:server');
|
|
1078
|
+
startService(run, restored.cliPath, 'cockpit');
|
|
1079
|
+
events.push('started:cockpit');
|
|
1080
|
+
const healthEvidence = await assertStableHealth(run, plan, execution);
|
|
1081
|
+
const result = {
|
|
1082
|
+
schemaVersion: 1,
|
|
1083
|
+
contract: RECOVERY_CONTRACT,
|
|
1084
|
+
status: 'recovered',
|
|
1085
|
+
planId: plan.planId,
|
|
1086
|
+
installed: plan.target.packages,
|
|
1087
|
+
recoveredFrom: terminalStatus,
|
|
1088
|
+
events: events.events,
|
|
1089
|
+
eventTimes: events.eventTimes,
|
|
1090
|
+
healthEvidence,
|
|
1091
|
+
};
|
|
1092
|
+
writeRecoveryResult(resultPath, result);
|
|
1093
|
+
return result;
|
|
1094
|
+
}
|
|
1095
|
+
catch (recoveryError) {
|
|
1096
|
+
writeRecoveryResult(resultPath, {
|
|
1097
|
+
schemaVersion: 1,
|
|
1098
|
+
contract: RECOVERY_CONTRACT,
|
|
1099
|
+
status: 'recovery-failed',
|
|
1100
|
+
planId: plan.planId,
|
|
1101
|
+
recoveredFrom: terminalStatus,
|
|
1102
|
+
globalState: 'unknown-requires-inspection',
|
|
1103
|
+
error: safeUpdateError(recoveryError),
|
|
1104
|
+
events: events.events,
|
|
1105
|
+
eventTimes: events.eventTimes,
|
|
1106
|
+
});
|
|
1107
|
+
throw recoveryError;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
788
1110
|
export async function applyGlobalUpdate(options, run = defaultRunner, execution = defaultExecutionDependencies) {
|
|
789
1111
|
const plan = readGlobalUpdatePlan(options.plan);
|
|
790
1112
|
assert.equal(options.confirm, plan.planId, 'global update confirmation does not match the exact plan');
|
|
@@ -794,8 +1116,9 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
794
1116
|
assert.deepEqual(current.topology, plan.prior.topology, 'global package topology changed after update planning');
|
|
795
1117
|
assert.equal(current.globalPrefix, plan.globalPrefix, 'global npm prefix changed after update planning');
|
|
796
1118
|
assertHealthyServices(serviceSnapshot(run, current.cliPath));
|
|
797
|
-
const events =
|
|
1119
|
+
const events = new GlobalUpdateEventLog(execution.now ?? (() => new Date()));
|
|
798
1120
|
let applicationUpdateId = null;
|
|
1121
|
+
let applicationUpdateAttempted = false;
|
|
799
1122
|
let failedStage = 'stop-cockpit';
|
|
800
1123
|
try {
|
|
801
1124
|
stopService(run, current.cliPath, 'cockpit');
|
|
@@ -817,6 +1140,7 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
817
1140
|
}
|
|
818
1141
|
else {
|
|
819
1142
|
failedStage = 'update-server-application';
|
|
1143
|
+
applicationUpdateAttempted = true;
|
|
820
1144
|
applicationUpdateId = updateServerApplication(run, updated.cliPath, {
|
|
821
1145
|
prior: plan.prior.packages.server,
|
|
822
1146
|
target: plan.target.packages.server,
|
|
@@ -835,7 +1159,8 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
835
1159
|
planId: plan.planId,
|
|
836
1160
|
installed: updated.packages,
|
|
837
1161
|
rollbackPerformed: false,
|
|
838
|
-
events,
|
|
1162
|
+
events: events.events,
|
|
1163
|
+
eventTimes: events.eventTimes,
|
|
839
1164
|
healthEvidence,
|
|
840
1165
|
next: 'Exit and reopen the AOPS TUI to run the newly installed binary.',
|
|
841
1166
|
};
|
|
@@ -844,6 +1169,7 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
844
1169
|
}
|
|
845
1170
|
catch (updateError) {
|
|
846
1171
|
const updateStage = failedStage;
|
|
1172
|
+
const sideEffectPossible = applicationSideEffectPossible(applicationUpdateAttempted, updateError);
|
|
847
1173
|
events.push('rollback:started');
|
|
848
1174
|
let rollbackError;
|
|
849
1175
|
try {
|
|
@@ -854,19 +1180,89 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
854
1180
|
const restored = inspectGlobalInstall(run);
|
|
855
1181
|
assert.deepEqual(restored.packages, plan.prior.packages, 'rollback package closure is not exact');
|
|
856
1182
|
assert.deepEqual(restored.topology, plan.prior.topology, 'rollback package topology is not exact');
|
|
1183
|
+
let applicationRollbackError;
|
|
857
1184
|
if (applicationUpdateId) {
|
|
858
|
-
|
|
859
|
-
|
|
1185
|
+
try {
|
|
1186
|
+
rollbackServerApplication(run, restored.cliPath, applicationUpdateId, restored.serverPath, plan.prior.packages.server);
|
|
1187
|
+
events.push('rolled-back:server-application');
|
|
1188
|
+
}
|
|
1189
|
+
catch (error) {
|
|
1190
|
+
applicationRollbackError = error;
|
|
1191
|
+
events.push('failed:server-application-rollback');
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
else if (sideEffectPossible) {
|
|
1195
|
+
applicationRollbackError = new Error('global_update_application_outcome_unrecorded');
|
|
1196
|
+
events.push('failed:server-application-outcome-unrecorded');
|
|
860
1197
|
}
|
|
861
1198
|
else {
|
|
862
1199
|
startService(run, restored.cliPath, 'server');
|
|
863
1200
|
events.push('started:server-restore');
|
|
864
1201
|
}
|
|
1202
|
+
if (resolveRecoveryClosure({
|
|
1203
|
+
applicationSideEffectPossible: sideEffectPossible,
|
|
1204
|
+
applicationRolledBack: applicationUpdateId !== null && applicationRollbackError === undefined,
|
|
1205
|
+
}) === 'target') {
|
|
1206
|
+
throw new ForwardSchemaRetainsTarget(applicationRollbackError);
|
|
1207
|
+
}
|
|
865
1208
|
startService(run, restored.cliPath, 'cockpit');
|
|
866
1209
|
await assertStableHealth(run, plan, execution);
|
|
867
1210
|
events.push('rollback:healthy');
|
|
868
1211
|
}
|
|
869
1212
|
catch (rollbackError) {
|
|
1213
|
+
if (rollbackError instanceof ForwardSchemaRetainsTarget) {
|
|
1214
|
+
try {
|
|
1215
|
+
installGlobalUpdateClosure(run, plan, plan.candidates, plan.target.topology);
|
|
1216
|
+
events.push('reinstalled:target-closure');
|
|
1217
|
+
const retained = inspectGlobalInstall(run);
|
|
1218
|
+
assert.deepEqual(retained.packages, plan.target.packages, 'retained target closure is not exact');
|
|
1219
|
+
assert.deepEqual(retained.topology, plan.target.topology, 'retained target topology is not canonical');
|
|
1220
|
+
startService(run, retained.cliPath, 'server');
|
|
1221
|
+
events.push('started:server-target-retained');
|
|
1222
|
+
startService(run, retained.cliPath, 'cockpit');
|
|
1223
|
+
events.push('started:cockpit-target-retained');
|
|
1224
|
+
await assertStableHealth(run, plan, execution);
|
|
1225
|
+
events.push('target-retained:healthy');
|
|
1226
|
+
}
|
|
1227
|
+
catch (retainError) {
|
|
1228
|
+
writeTerminalUpdateResult(plan, {
|
|
1229
|
+
schemaVersion: 1,
|
|
1230
|
+
contract: RESULT_CONTRACT,
|
|
1231
|
+
status: 'recovery-required',
|
|
1232
|
+
planId: plan.planId,
|
|
1233
|
+
failedStage: updateStage,
|
|
1234
|
+
rollbackPerformed: false,
|
|
1235
|
+
rollback: {
|
|
1236
|
+
status: 'failed',
|
|
1237
|
+
reason: 'application_rollback_failed_forward_schema',
|
|
1238
|
+
error: safeUpdateError(rollbackError.applicationRollbackError),
|
|
1239
|
+
},
|
|
1240
|
+
targetRetention: { status: 'failed', error: safeUpdateError(retainError) },
|
|
1241
|
+
error: safeUpdateError(updateError),
|
|
1242
|
+
events: events.events,
|
|
1243
|
+
eventTimes: events.eventTimes,
|
|
1244
|
+
});
|
|
1245
|
+
throw new AggregateError([updateError, rollbackError.applicationRollbackError, retainError], `global update failed, application rollback failed, and the schema-compatible target closure could not be restarted; services may remain stopped; recovery artifacts remain at ${plan.operationRoot}`);
|
|
1246
|
+
}
|
|
1247
|
+
writeTerminalUpdateResult(plan, {
|
|
1248
|
+
schemaVersion: 1,
|
|
1249
|
+
contract: RESULT_CONTRACT,
|
|
1250
|
+
status: 'failed-target-retained',
|
|
1251
|
+
planId: plan.planId,
|
|
1252
|
+
failedStage: updateStage,
|
|
1253
|
+
installed: plan.target.packages,
|
|
1254
|
+
rollbackPerformed: false,
|
|
1255
|
+
rollback: {
|
|
1256
|
+
status: 'not-applicable',
|
|
1257
|
+
reason: 'application_rollback_failed_forward_schema',
|
|
1258
|
+
applicationRollbackError: safeUpdateError(rollbackError.applicationRollbackError),
|
|
1259
|
+
},
|
|
1260
|
+
error: safeUpdateError(updateError),
|
|
1261
|
+
events: events.events,
|
|
1262
|
+
eventTimes: events.eventTimes,
|
|
1263
|
+
});
|
|
1264
|
+
throw new AggregateError([updateError, rollbackError.applicationRollbackError], 'global update failed and the application could not be rolled back; the schema-compatible target closure was reinstated and healthy services were restarted');
|
|
1265
|
+
}
|
|
870
1266
|
try {
|
|
871
1267
|
const restoredForRecovery = inspectGlobalInstall(run);
|
|
872
1268
|
assert.deepEqual(restoredForRecovery.packages, plan.prior.packages, 'service recovery package closure is not exact');
|
|
@@ -891,7 +1287,8 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
891
1287
|
rollback: { status: 'failed', error: safeUpdateError(rollbackError) },
|
|
892
1288
|
serviceRecovery: { status: 'failed', error: safeUpdateError(serviceRecoveryError) },
|
|
893
1289
|
error: safeUpdateError(updateError),
|
|
894
|
-
events,
|
|
1290
|
+
events: events.events,
|
|
1291
|
+
eventTimes: events.eventTimes,
|
|
895
1292
|
});
|
|
896
1293
|
throw new AggregateError([updateError, rollbackError, serviceRecoveryError], `global update, automatic rollback, and service recovery failed; services may remain stopped; recovery artifacts remain at ${plan.operationRoot}`);
|
|
897
1294
|
}
|
|
@@ -909,7 +1306,8 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
909
1306
|
applicationRollbackError: safeUpdateError(rollbackError),
|
|
910
1307
|
},
|
|
911
1308
|
error: safeUpdateError(updateError),
|
|
912
|
-
events,
|
|
1309
|
+
events: events.events,
|
|
1310
|
+
eventTimes: events.eventTimes,
|
|
913
1311
|
});
|
|
914
1312
|
throw new AggregateError([updateError, rollbackError], `global update and application rollback failed; prior exact packages and healthy services were restored; recovery artifacts remain at ${plan.operationRoot}`);
|
|
915
1313
|
}
|
|
@@ -923,7 +1321,8 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
923
1321
|
rollbackPerformed: true,
|
|
924
1322
|
rollback: { status: 'healthy' },
|
|
925
1323
|
error: safeUpdateError(updateError),
|
|
926
|
-
events,
|
|
1324
|
+
events: events.events,
|
|
1325
|
+
eventTimes: events.eventTimes,
|
|
927
1326
|
});
|
|
928
1327
|
throw new Error('global update failed; the prior exact packages were restored and are healthy', { cause: updateError });
|
|
929
1328
|
}
|
|
@@ -931,6 +1330,22 @@ export async function applyGlobalUpdate(options, run = defaultRunner, execution
|
|
|
931
1330
|
function output(value, json) {
|
|
932
1331
|
process.stdout.write(`${JSON.stringify(value, null, json ? 0 : 2)}\n`);
|
|
933
1332
|
}
|
|
1333
|
+
const defaultRecoveryCommandDependencies = {
|
|
1334
|
+
dispatch: dispatchGlobalUpdateRecovery,
|
|
1335
|
+
recover: recoverGlobalUpdate,
|
|
1336
|
+
readPlan: readGlobalUpdatePlan,
|
|
1337
|
+
validateExecutor: validateCandidateExecutorInvocation,
|
|
1338
|
+
emit: output,
|
|
1339
|
+
};
|
|
1340
|
+
export async function runGlobalUpdateRecoveryCommand(options, dependencies = defaultRecoveryCommandDependencies) {
|
|
1341
|
+
if (typeof options.executorReceipt === 'string') {
|
|
1342
|
+
const plan = dependencies.readPlan(options.plan);
|
|
1343
|
+
dependencies.validateExecutor(plan, options.executorReceipt);
|
|
1344
|
+
dependencies.emit(await dependencies.recover(options), options.json === true);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
dependencies.emit(dependencies.dispatch(options), options.json === true);
|
|
1348
|
+
}
|
|
934
1349
|
export function makeGlobalUpdateCommand() {
|
|
935
1350
|
const command = new Command('update').description('Check, prepare, and apply a guarded global AOPS npm update');
|
|
936
1351
|
command
|
|
@@ -960,6 +1375,16 @@ export function makeGlobalUpdateCommand() {
|
|
|
960
1375
|
}
|
|
961
1376
|
output(dispatchGlobalUpdate(options), options.json === true);
|
|
962
1377
|
});
|
|
1378
|
+
command
|
|
1379
|
+
.command('recover')
|
|
1380
|
+
.description('Finish one stranded exact plan at its target closure after a failed apply')
|
|
1381
|
+
.requiredOption('--plan <path>', 'Exact update-plan.json path returned by prepare')
|
|
1382
|
+
.requiredOption('--confirm <plan-id>', 'Exact sha256 plan id returned by prepare')
|
|
1383
|
+
.addOption(new Option('--executor-receipt <path>').hideHelp())
|
|
1384
|
+
.option('--json', 'Output JSON')
|
|
1385
|
+
.action(async (options) => {
|
|
1386
|
+
await runGlobalUpdateRecoveryCommand(options);
|
|
1387
|
+
});
|
|
963
1388
|
command
|
|
964
1389
|
.command('bridge')
|
|
965
1390
|
.description('Apply an old-CLI plan from an externally launched exact public candidate')
|
|
@@ -543,9 +543,9 @@ function defaultNativeRoot() {
|
|
|
543
543
|
return moduleDirectory.endsWith(`${path.sep}dist`) ? bundled : compiledOrSource;
|
|
544
544
|
}
|
|
545
545
|
function injectedNativeManifestSha256() {
|
|
546
|
-
return typeof "
|
|
546
|
+
return typeof "99724da24c7a8af713cd05f83646af0ddbf95c89fc892ce31f0edc7d21874483" === 'undefined'
|
|
547
547
|
? undefined
|
|
548
|
-
: "
|
|
548
|
+
: "99724da24c7a8af713cd05f83646af0ddbf95c89fc892ce31f0edc7d21874483";
|
|
549
549
|
}
|
|
550
550
|
function sha256(bytes) {
|
|
551
551
|
return createHash('sha256').update(bytes).digest('hex');
|
|
@@ -205,6 +205,71 @@ function terminalObservation(record, observedAt, disappeared = false) {
|
|
|
205
205
|
signal: record.signal ?? null,
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
|
+
export const COCKPIT_PID_START_TOLERANCE_MS = 300_000;
|
|
209
|
+
export function classifyTerminalCockpitPids(params) {
|
|
210
|
+
if (params.identityPresent) {
|
|
211
|
+
return { stale: false, reason: 'community_cockpit_terminal_identity_file_present', findings: [] };
|
|
212
|
+
}
|
|
213
|
+
if (params.livePids.length === 0) {
|
|
214
|
+
return { stale: false, reason: 'community_cockpit_terminal_no_live_pid', findings: [] };
|
|
215
|
+
}
|
|
216
|
+
const recordedStart = Date.parse(params.record.startedAt);
|
|
217
|
+
if (!Number.isFinite(recordedStart)) {
|
|
218
|
+
return { stale: false, reason: 'community_cockpit_pid_record_start_unparsable', findings: [] };
|
|
219
|
+
}
|
|
220
|
+
const tolerance = params.startToleranceMs ?? COCKPIT_PID_START_TOLERANCE_MS;
|
|
221
|
+
const findings = [];
|
|
222
|
+
let blocked = null;
|
|
223
|
+
for (const { pid, role } of params.livePids) {
|
|
224
|
+
let inspection;
|
|
225
|
+
try {
|
|
226
|
+
inspection = params.inspect(pid);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
inspection = {
|
|
230
|
+
outcome: 'undetermined',
|
|
231
|
+
pid,
|
|
232
|
+
reason: error instanceof Error ? error.message : 'community_cockpit_pid_inspection_threw',
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (inspection.outcome === 'absent') {
|
|
236
|
+
findings.push({ pid, role, outcome: 'absent', disagreedOn: ['identity', 'start-time', 'host-entry'], reason: null });
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (inspection.outcome === 'undetermined') {
|
|
240
|
+
findings.push({ pid, role, outcome: 'undetermined', disagreedOn: [], reason: inspection.reason });
|
|
241
|
+
blocked ??= `community_cockpit_pid_inspection_undetermined:${pid}:${inspection.reason}`;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const observedStart = Date.parse(inspection.startedAt);
|
|
245
|
+
if (!Number.isFinite(observedStart)) {
|
|
246
|
+
findings.push({ pid, role, outcome: 'inspected', disagreedOn: [], reason: 'start_time_unparsable' });
|
|
247
|
+
blocked ??= `community_cockpit_pid_start_time_unparsable:${pid}`;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (inspection.command.trim() === '') {
|
|
251
|
+
findings.push({ pid, role, outcome: 'inspected', disagreedOn: [], reason: 'command_unavailable' });
|
|
252
|
+
blocked ??= `community_cockpit_pid_command_unavailable:${pid}`;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const disagreedOn = ['identity'];
|
|
256
|
+
if (Math.abs(observedStart - recordedStart) > tolerance)
|
|
257
|
+
disagreedOn.push('start-time');
|
|
258
|
+
if (!commandNamesHostEntry(inspection.command, params.record.hostEntry))
|
|
259
|
+
disagreedOn.push('host-entry');
|
|
260
|
+
findings.push({ pid, role, outcome: 'inspected', disagreedOn, reason: null });
|
|
261
|
+
if (disagreedOn.length !== 3) {
|
|
262
|
+
blocked ??= `community_cockpit_pid_may_be_owned:${pid}:${disagreedOn.join('+')}`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (blocked)
|
|
266
|
+
return { stale: false, reason: blocked, findings };
|
|
267
|
+
return { stale: true, reason: 'community_cockpit_terminal_record_pid_reused', findings };
|
|
268
|
+
}
|
|
269
|
+
function commandNamesHostEntry(command, hostEntry) {
|
|
270
|
+
const normalise = (value) => value.split('\\').join('/').toLowerCase();
|
|
271
|
+
return normalise(command).includes(normalise(hostEntry));
|
|
272
|
+
}
|
|
208
273
|
async function observeCockpit(params) {
|
|
209
274
|
const record = params.processRecord;
|
|
210
275
|
const identityPresent = existsSync(params.paths.identityPath);
|
|
@@ -254,11 +319,40 @@ async function observeCockpit(params) {
|
|
|
254
319
|
terminalObservation: terminalObservation(record, record.updatedAt),
|
|
255
320
|
};
|
|
256
321
|
}
|
|
322
|
+
const observation = terminalObservation(record, record.updatedAt);
|
|
323
|
+
const verdict = observation.classification === 'clean-exit'
|
|
324
|
+
? classifyTerminalCockpitPids({
|
|
325
|
+
record,
|
|
326
|
+
identityPresent,
|
|
327
|
+
livePids: [
|
|
328
|
+
...(supervisorAlive ? [{ pid: record.pid, role: 'supervisor' }] : []),
|
|
329
|
+
...(hostAlive && hostPid ? [{ pid: hostPid, role: 'host' }] : []),
|
|
330
|
+
],
|
|
331
|
+
inspect: params.runtime.inspectProcess ?? ((pid) => ({
|
|
332
|
+
outcome: 'undetermined',
|
|
333
|
+
pid,
|
|
334
|
+
reason: 'community_cockpit_pid_inspection_unavailable',
|
|
335
|
+
})),
|
|
336
|
+
})
|
|
337
|
+
: {
|
|
338
|
+
stale: false,
|
|
339
|
+
reason: `community_cockpit_terminal_record_with_live_or_unowned_process:${observation.classification}`,
|
|
340
|
+
findings: [],
|
|
341
|
+
};
|
|
342
|
+
if (verdict.stale) {
|
|
343
|
+
return {
|
|
344
|
+
...base,
|
|
345
|
+
runtimeState: 'stopped', recoverable: true, process: record, identity,
|
|
346
|
+
supervisorAlive, hostAlive, health: 'not-checked',
|
|
347
|
+
reason: verdict.reason,
|
|
348
|
+
terminalObservation: observation,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
257
351
|
return {
|
|
258
352
|
...base,
|
|
259
353
|
runtimeState: 'identity-conflict', recoverable: false, process: record, identity,
|
|
260
354
|
supervisorAlive, hostAlive, health: 'not-checked',
|
|
261
|
-
reason:
|
|
355
|
+
reason: verdict.reason,
|
|
262
356
|
terminalObservation: null,
|
|
263
357
|
};
|
|
264
358
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readdirSync, readSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
@@ -68,6 +68,56 @@ const PACKAGE_BUILD_PATHS = Object.freeze({
|
|
|
68
68
|
handlerEntry: 'build/handler.js',
|
|
69
69
|
cockpitIndex: 'cockpit/index.html',
|
|
70
70
|
});
|
|
71
|
+
export function inspectHostProcess(pid, execute = (command, args) => {
|
|
72
|
+
const result = spawnSync(command, args, { encoding: 'utf8', windowsHide: true, timeout: 5_000 });
|
|
73
|
+
if (result.error)
|
|
74
|
+
throw result.error;
|
|
75
|
+
return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
|
|
76
|
+
}, platform = process.platform) {
|
|
77
|
+
if (!Number.isSafeInteger(pid) || pid < 1) {
|
|
78
|
+
return { outcome: 'undetermined', pid, reason: 'pid_not_inspectable' };
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
if (platform === 'win32') {
|
|
82
|
+
const result = execute('powershell.exe', [
|
|
83
|
+
'-NoProfile', '-NonInteractive', '-Command',
|
|
84
|
+
`$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}" -ErrorAction Stop; ` +
|
|
85
|
+
'if ($null -eq $p) { exit 3 }; ' +
|
|
86
|
+
'"{0}`t{1}" -f $p.CreationDate.ToString("o"), $p.CommandLine',
|
|
87
|
+
]);
|
|
88
|
+
if (result.status === 3)
|
|
89
|
+
return { outcome: 'absent', pid };
|
|
90
|
+
if (result.status !== 0) {
|
|
91
|
+
return { outcome: 'undetermined', pid, reason: `process_query_failed:${result.status ?? 'no_status'}` };
|
|
92
|
+
}
|
|
93
|
+
const [startedAt, ...rest] = result.stdout.trim().split('\t');
|
|
94
|
+
if (!startedAt)
|
|
95
|
+
return { outcome: 'undetermined', pid, reason: 'process_query_empty' };
|
|
96
|
+
return { outcome: 'inspected', pid, startedAt, command: rest.join('\t').trim() };
|
|
97
|
+
}
|
|
98
|
+
const result = execute('ps', ['-o', 'lstart=,args=', '-p', String(pid)]);
|
|
99
|
+
if (result.status === 1 && result.stdout.trim() === '')
|
|
100
|
+
return { outcome: 'absent', pid };
|
|
101
|
+
if (result.status !== 0) {
|
|
102
|
+
return { outcome: 'undetermined', pid, reason: `process_query_failed:${result.status ?? 'no_status'}` };
|
|
103
|
+
}
|
|
104
|
+
const line = result.stdout.split('\n')[0]?.trim() ?? '';
|
|
105
|
+
if (line === '')
|
|
106
|
+
return { outcome: 'absent', pid };
|
|
107
|
+
const fields = line.split(/\s+/u);
|
|
108
|
+
if (fields.length < 6)
|
|
109
|
+
return { outcome: 'undetermined', pid, reason: 'process_query_unparsable' };
|
|
110
|
+
const startedAt = fields.slice(0, 5).join(' ');
|
|
111
|
+
return { outcome: 'inspected', pid, startedAt, command: fields.slice(5).join(' ') };
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
return {
|
|
115
|
+
outcome: 'undetermined',
|
|
116
|
+
pid,
|
|
117
|
+
reason: error instanceof Error ? `process_query_threw:${error.message.slice(0, 80)}` : 'process_query_threw',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
71
121
|
function sha256(content) {
|
|
72
122
|
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
73
123
|
}
|
|
@@ -1437,6 +1487,9 @@ export const communityNativeRuntime = {
|
|
|
1437
1487
|
return Boolean(error && typeof error === 'object' && 'code' in error && error.code === 'EPERM');
|
|
1438
1488
|
}
|
|
1439
1489
|
},
|
|
1490
|
+
inspectProcess(pid) {
|
|
1491
|
+
return inspectHostProcess(pid);
|
|
1492
|
+
},
|
|
1440
1493
|
sleep: (milliseconds, signal) => new Promise((resolve, reject) => {
|
|
1441
1494
|
if (signal?.aborted) {
|
|
1442
1495
|
reject(new Error('community_operation_aborted'));
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/native/manifest.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"platform": "darwin",
|
|
7
7
|
"architecture": "arm64",
|
|
8
8
|
"relativePath": "bin/darwin-arm64/aops-agent-assets-fs",
|
|
9
|
-
"sha256": "
|
|
9
|
+
"sha256": "aeff0f83ff7c53f1651ccee44d857f03c5a41846f30302433ade0f3705ae0940",
|
|
10
10
|
"capabilityClass": "macos-exclusive-durable-v1",
|
|
11
11
|
"qualification": null
|
|
12
12
|
},
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"platform": "darwin",
|
|
15
15
|
"architecture": "x64",
|
|
16
16
|
"relativePath": "bin/darwin-x64/aops-agent-assets-fs",
|
|
17
|
-
"sha256": "
|
|
17
|
+
"sha256": "d04f3e6ae34c2711cf1cf6bb8a6d5edb669cf4aed4c21e315b23e9d78a0799ef",
|
|
18
18
|
"capabilityClass": "macos-exclusive-durable-v1",
|
|
19
19
|
"qualification": null
|
|
20
20
|
},
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"platform": "linux",
|
|
23
23
|
"architecture": "arm64",
|
|
24
24
|
"relativePath": "bin/linux-arm64/aops-agent-assets-fs",
|
|
25
|
-
"sha256": "
|
|
25
|
+
"sha256": "4af380c99012b8e847570cb140c70c44c4dcdcee11c17cc5cb2bb8493b82dc8c",
|
|
26
26
|
"capabilityClass": "linux-posix-durable-v1",
|
|
27
27
|
"qualification": null
|
|
28
28
|
},
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"platform": "linux",
|
|
31
31
|
"architecture": "x64",
|
|
32
32
|
"relativePath": "bin/linux-x64/aops-agent-assets-fs",
|
|
33
|
-
"sha256": "
|
|
33
|
+
"sha256": "1c36cd295f0fcfdfd4192748413483b099ac4299e0f770a982846b8915fba31d",
|
|
34
34
|
"capabilityClass": "linux-posix-durable-v1",
|
|
35
35
|
"qualification": null
|
|
36
36
|
},
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"platform": "win32",
|
|
39
39
|
"architecture": "x64",
|
|
40
40
|
"relativePath": "bin/win32-x64/aops-agent-assets-fs.exe",
|
|
41
|
-
"sha256": "
|
|
41
|
+
"sha256": "c1fcf2fe3689550f1552bdb60a064a9ac0c9e30cba4d9eb7c52140ee14794778",
|
|
42
42
|
"capabilityClass": "windows-ntfs-crash-recoverable-v1",
|
|
43
43
|
"qualification": {
|
|
44
44
|
"relativePath": "qualifications/win32-x64.json",
|
|
45
|
-
"sha256": "
|
|
45
|
+
"sha256": "ef348d39e5c9e229b21274d00f779dbe9e8fd7bdbc27cb5c797191bb1294a5ae"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
]
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"filesystem": "NTFS",
|
|
8
8
|
"primitiveProfile": "windows-createfile-flush-movefileex-v1",
|
|
9
9
|
"capabilityClass": "windows-ntfs-crash-recoverable-v1",
|
|
10
|
-
"evidenceSha256": "
|
|
10
|
+
"evidenceSha256": "d58ca525d9e4a57c111747e1912e4d74266c712b127cfb9857b7d9d2beb5d3c1",
|
|
11
11
|
"crashInjectionPoints": [
|
|
12
12
|
"authority-history",
|
|
13
13
|
"authority-current",
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aopslabs/aops",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AOPS CLI and terminal setup application.",
|
|
6
6
|
"aopsDockerServerVersion": "0.2.17",
|
|
@@ -62,17 +62,17 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@aopslabs/aops-agent-runtime": "0.1.0",
|
|
65
|
-
"@aopslabs/aops-server": "0.2.
|
|
65
|
+
"@aopslabs/aops-server": "0.2.18",
|
|
66
66
|
"@aopslabs/artifact-trust-contracts": "0.3.1",
|
|
67
67
|
"@aopslabs/cli-kit": "0.1.0",
|
|
68
|
-
"@aopslabs/aops-host-registration": "0.2.
|
|
68
|
+
"@aopslabs/aops-host-registration": "0.2.2",
|
|
69
69
|
"@aopslabs/aops-pg-bootstrap": "0.2.3",
|
|
70
|
-
"@aopslabs/aops-runtime-config": "0.2.
|
|
70
|
+
"@aopslabs/aops-runtime-config": "0.2.2",
|
|
71
71
|
"@sigstore/bundle": "4.0.0",
|
|
72
72
|
"@sigstore/protobuf-specs": "0.5.1",
|
|
73
73
|
"@sigstore/tuf": "4.0.2",
|
|
74
74
|
"@sigstore/verify": "3.1.1",
|
|
75
|
-
"@aopslabs/api-client": "0.2.
|
|
75
|
+
"@aopslabs/api-client": "0.2.2",
|
|
76
76
|
"@aopslabs/domain-product-client-chatv3": "0.4.1",
|
|
77
77
|
"@aopslabs/xf-cli-ui": "0.2.0",
|
|
78
78
|
"commander": "14.0.3",
|