@factiii/stack 0.1.2 → 0.1.8

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.
Files changed (89) hide show
  1. package/bin/factiii +13 -0
  2. package/dist/cli/pr-check.d.ts +24 -0
  3. package/dist/cli/pr-check.d.ts.map +1 -0
  4. package/dist/cli/pr-check.js +153 -0
  5. package/dist/cli/pr-check.js.map +1 -0
  6. package/dist/plugins/addons/server-mode/index.d.ts.map +1 -1
  7. package/dist/plugins/addons/server-mode/index.js +3 -0
  8. package/dist/plugins/addons/server-mode/index.js.map +1 -1
  9. package/dist/plugins/addons/server-mode/scanfix/mac.d.ts +20 -3
  10. package/dist/plugins/addons/server-mode/scanfix/mac.d.ts.map +1 -1
  11. package/dist/plugins/addons/server-mode/scanfix/mac.js +304 -177
  12. package/dist/plugins/addons/server-mode/scanfix/mac.js.map +1 -1
  13. package/dist/plugins/addons/server-mode/scanfix/tart.d.ts +19 -0
  14. package/dist/plugins/addons/server-mode/scanfix/tart.d.ts.map +1 -0
  15. package/dist/plugins/addons/server-mode/scanfix/tart.js +350 -0
  16. package/dist/plugins/addons/server-mode/scanfix/tart.js.map +1 -0
  17. package/dist/plugins/pipelines/aws/configs/free-tier.d.ts.map +1 -1
  18. package/dist/plugins/pipelines/aws/configs/free-tier.js +3 -38
  19. package/dist/plugins/pipelines/aws/configs/free-tier.js.map +1 -1
  20. package/dist/plugins/pipelines/aws/index.d.ts +4 -1
  21. package/dist/plugins/pipelines/aws/index.d.ts.map +1 -1
  22. package/dist/plugins/pipelines/aws/index.js +101 -29
  23. package/dist/plugins/pipelines/aws/index.js.map +1 -1
  24. package/dist/plugins/pipelines/aws/scanfix/credentials.d.ts +9 -0
  25. package/dist/plugins/pipelines/aws/scanfix/credentials.d.ts.map +1 -0
  26. package/dist/plugins/pipelines/aws/scanfix/credentials.js +196 -0
  27. package/dist/plugins/pipelines/aws/scanfix/credentials.js.map +1 -0
  28. package/dist/plugins/pipelines/aws/scanfix/db-replication.d.ts +13 -0
  29. package/dist/plugins/pipelines/aws/scanfix/db-replication.d.ts.map +1 -0
  30. package/dist/plugins/pipelines/aws/scanfix/db-replication.js +136 -0
  31. package/dist/plugins/pipelines/aws/scanfix/db-replication.js.map +1 -0
  32. package/dist/plugins/pipelines/aws/scanfix/ec2.d.ts +10 -0
  33. package/dist/plugins/pipelines/aws/scanfix/ec2.d.ts.map +1 -0
  34. package/dist/plugins/pipelines/aws/scanfix/ec2.js +279 -0
  35. package/dist/plugins/pipelines/aws/scanfix/ec2.js.map +1 -0
  36. package/dist/plugins/pipelines/aws/scanfix/ecr.d.ts +9 -0
  37. package/dist/plugins/pipelines/aws/scanfix/ecr.d.ts.map +1 -0
  38. package/dist/plugins/pipelines/aws/scanfix/ecr.js +100 -0
  39. package/dist/plugins/pipelines/aws/scanfix/ecr.js.map +1 -0
  40. package/dist/plugins/pipelines/aws/scanfix/iam.d.ts +10 -0
  41. package/dist/plugins/pipelines/aws/scanfix/iam.d.ts.map +1 -0
  42. package/dist/plugins/pipelines/aws/scanfix/iam.js +255 -0
  43. package/dist/plugins/pipelines/aws/scanfix/iam.js.map +1 -0
  44. package/dist/plugins/pipelines/aws/scanfix/rds.d.ts +10 -0
  45. package/dist/plugins/pipelines/aws/scanfix/rds.d.ts.map +1 -0
  46. package/dist/plugins/pipelines/aws/scanfix/rds.js +261 -0
  47. package/dist/plugins/pipelines/aws/scanfix/rds.js.map +1 -0
  48. package/dist/plugins/pipelines/aws/scanfix/s3.d.ts +9 -0
  49. package/dist/plugins/pipelines/aws/scanfix/s3.d.ts.map +1 -0
  50. package/dist/plugins/pipelines/aws/scanfix/s3.js +134 -0
  51. package/dist/plugins/pipelines/aws/scanfix/s3.js.map +1 -0
  52. package/dist/plugins/pipelines/aws/scanfix/security-groups.d.ts +10 -0
  53. package/dist/plugins/pipelines/aws/scanfix/security-groups.d.ts.map +1 -0
  54. package/dist/plugins/pipelines/aws/scanfix/security-groups.js +225 -0
  55. package/dist/plugins/pipelines/aws/scanfix/security-groups.js.map +1 -0
  56. package/dist/plugins/pipelines/aws/scanfix/ses.d.ts +9 -0
  57. package/dist/plugins/pipelines/aws/scanfix/ses.d.ts.map +1 -0
  58. package/dist/plugins/pipelines/aws/scanfix/ses.js +174 -0
  59. package/dist/plugins/pipelines/aws/scanfix/ses.js.map +1 -0
  60. package/dist/plugins/pipelines/aws/scanfix/vpc.d.ts +9 -0
  61. package/dist/plugins/pipelines/aws/scanfix/vpc.d.ts.map +1 -0
  62. package/dist/plugins/pipelines/aws/scanfix/vpc.js +237 -0
  63. package/dist/plugins/pipelines/aws/scanfix/vpc.js.map +1 -0
  64. package/dist/plugins/pipelines/aws/utils/aws-helpers.d.ts +50 -0
  65. package/dist/plugins/pipelines/aws/utils/aws-helpers.d.ts.map +1 -0
  66. package/dist/plugins/pipelines/aws/utils/aws-helpers.js +137 -0
  67. package/dist/plugins/pipelines/aws/utils/aws-helpers.js.map +1 -0
  68. package/dist/plugins/pipelines/factiii/index.d.ts.map +1 -1
  69. package/dist/plugins/pipelines/factiii/index.js +11 -0
  70. package/dist/plugins/pipelines/factiii/index.js.map +1 -1
  71. package/dist/plugins/pipelines/factiii/pr-check.d.ts +35 -0
  72. package/dist/plugins/pipelines/factiii/pr-check.d.ts.map +1 -0
  73. package/dist/plugins/pipelines/factiii/pr-check.js +202 -0
  74. package/dist/plugins/pipelines/factiii/pr-check.js.map +1 -0
  75. package/dist/plugins/pipelines/factiii/utils/workflows.d.ts.map +1 -1
  76. package/dist/plugins/pipelines/factiii/utils/workflows.js +1 -0
  77. package/dist/plugins/pipelines/factiii/utils/workflows.js.map +1 -1
  78. package/dist/plugins/pipelines/factiii/workflows/factiii-cicd-staging.yml +8 -3
  79. package/dist/plugins/pipelines/factiii/workflows/factiii-pr-check.yml +103 -0
  80. package/dist/plugins/servers/mac/staging.d.ts.map +1 -1
  81. package/dist/plugins/servers/mac/staging.js +304 -52
  82. package/dist/plugins/servers/mac/staging.js.map +1 -1
  83. package/dist/types/config.d.ts +11 -0
  84. package/dist/types/config.d.ts.map +1 -1
  85. package/dist/utils/github-status.d.ts +39 -0
  86. package/dist/utils/github-status.d.ts.map +1 -0
  87. package/dist/utils/github-status.js +172 -0
  88. package/dist/utils/github-status.js.map +1 -0
  89. package/package.json +3 -3
@@ -5,13 +5,30 @@
5
5
  * Fixes for configuring Mac as a deployment server HOST (no Docker/dev tools).
6
6
  * Focus: what makes a Mac reliable as a server.
7
7
  *
8
+ * Power Management (pmset):
8
9
  * - Disable sleep (system, disk, display)
9
10
  * - Auto-restart on power loss
10
- * - Don't turn off when inactive (disablesleep)
11
- * - Disable screensaver
11
+ * - Fully disable sleep (disablesleep)
12
+ * - Wake on LAN (womp)
13
+ * - Disable hibernate
14
+ * - Disable standby
15
+ * - Disable Power Nap
16
+ * - Disable proximity wake
17
+ * - Keep awake during SSH (ttyskeepawake)
18
+ *
19
+ * System Services:
20
+ * - Disable screensaver (-currentHost)
12
21
  * - Enable SSH (Remote Login)
13
22
  * - Disable App Nap
14
- * - Auto-login on boot (optional)
23
+ * - Disable Spotlight indexing
24
+ * - Disable Time Machine
25
+ * - Disable auto-update restart
26
+ * - Disable Bluetooth (manual)
27
+ * - Auto-login on boot (manual)
28
+ *
29
+ * Network/Security:
30
+ * - Enable NTP (network time)
31
+ * - Disable file sharing (manual)
15
32
  */
16
33
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
34
  if (k2 === undefined) k2 = k;
@@ -50,27 +67,57 @@ Object.defineProperty(exports, "__esModule", { value: true });
50
67
  exports.macFixes = void 0;
51
68
  const child_process_1 = require("child_process");
52
69
  const fs = __importStar(require("fs"));
70
+ function macFixPair(def) {
71
+ return ['staging', 'prod'].map(stage => ({
72
+ id: def.idBase + '-' + stage,
73
+ stage,
74
+ os: 'mac',
75
+ severity: def.severity,
76
+ description: def.description,
77
+ scan: def.scan,
78
+ fix: def.fix,
79
+ manualFix: def.manualFix,
80
+ }));
81
+ }
82
+ // ============================================================
83
+ // Helper: check a pmset value
84
+ // ============================================================
85
+ function pmsetValueIs(key, expected) {
86
+ try {
87
+ const result = (0, child_process_1.execSync)('pmset -g 2>/dev/null | grep -i ' + key + ' || true', {
88
+ encoding: 'utf8',
89
+ stdio: ['pipe', 'pipe', 'pipe'],
90
+ });
91
+ // If key not found in pmset output, the setting doesn't exist on this hardware (e.g. desktop Macs
92
+ // don't have hibernatemode, disablesleep, proximitywake). Treat as not applicable = OK.
93
+ if (!result.trim())
94
+ return true;
95
+ return result.trim().includes(expected);
96
+ }
97
+ catch {
98
+ return false;
99
+ }
100
+ }
53
101
  exports.macFixes = [
54
102
  // ============================================================
55
- // STAGING FIXES
103
+ // POWER MANAGEMENT (pmset)
56
104
  // ============================================================
57
- {
58
- id: 'macos-sleep-enabled-staging',
59
- stage: 'staging',
60
- os: 'mac',
105
+ ...macFixPair({
106
+ idBase: 'macos-sleep-enabled',
61
107
  severity: 'warning',
62
108
  description: 'macOS sleep is enabled (server may go offline)',
63
109
  scan: async (_config, _rootDir) => {
64
110
  try {
65
- const result = (0, child_process_1.execSync)('pmset -g | grep -E "^\\s*sleep\\s+"', {
111
+ const result = (0, child_process_1.execSync)('pmset -g | grep -E "^\\s*sleep\\s+" || true', {
66
112
  encoding: 'utf8',
67
113
  stdio: ['pipe', 'pipe', 'pipe'],
68
114
  });
69
- // Check if sleep is not 0
115
+ if (!result.trim())
116
+ return false; // Key not found = not applicable
70
117
  return !result.includes('sleep 0');
71
118
  }
72
119
  catch {
73
- return false; // Can't determine, assume OK
120
+ return false;
74
121
  }
75
122
  },
76
123
  fix: async (_config, _rootDir) => {
@@ -84,139 +131,119 @@ exports.macFixes = [
84
131
  }
85
132
  },
86
133
  manualFix: 'Run: sudo pmset -a sleep 0 disksleep 0 displaysleep 0',
87
- },
88
- {
89
- id: 'macos-screensaver-enabled-staging',
90
- stage: 'staging',
91
- os: 'mac',
92
- severity: 'info',
93
- description: 'macOS screensaver is enabled',
134
+ }),
135
+ ...macFixPair({
136
+ idBase: 'macos-autorestart-disabled',
137
+ severity: 'critical',
138
+ description: 'Auto-restart on power loss is disabled (server may stay off after outage)',
94
139
  scan: async (_config, _rootDir) => {
95
140
  try {
96
- const result = (0, child_process_1.execSync)('defaults read com.apple.screensaver idleTime 2>/dev/null || echo "300"', {
141
+ const result = (0, child_process_1.execSync)('pmset -g 2>/dev/null | grep -i autorestart || true', {
97
142
  encoding: 'utf8',
143
+ stdio: ['pipe', 'pipe', 'pipe'],
98
144
  });
99
- const idleTime = parseInt(result.trim(), 10);
100
- return idleTime > 0;
145
+ if (!result.trim())
146
+ return false; // Key not found = not applicable
147
+ return !result.trim().includes('1');
101
148
  }
102
149
  catch {
103
- return false;
150
+ return true;
104
151
  }
105
152
  },
106
153
  fix: async (_config, _rootDir) => {
107
154
  try {
108
- console.log(' Disabling macOS screensaver...');
109
- (0, child_process_1.execSync)('defaults write com.apple.screensaver idleTime 0', { stdio: 'inherit' });
155
+ console.log(' Enabling auto-restart on power loss...');
156
+ (0, child_process_1.execSync)('sudo pmset -a autorestart 1', { stdio: 'inherit' });
110
157
  return true;
111
158
  }
112
159
  catch {
113
160
  return false;
114
161
  }
115
162
  },
116
- manualFix: 'Run: defaults write com.apple.screensaver idleTime 0',
117
- },
118
- {
119
- id: 'macos-ssh-disabled-staging',
120
- stage: 'staging',
121
- os: 'mac',
122
- severity: 'critical',
123
- description: 'macOS Remote Login (SSH) is disabled',
163
+ manualFix: 'Run: sudo pmset -a autorestart 1',
164
+ }),
165
+ ...macFixPair({
166
+ idBase: 'macos-disablesleep-disabled',
167
+ severity: 'warning',
168
+ description: 'System sleep is not fully disabled (disablesleep=0 allows Sleep menu)',
124
169
  scan: async (_config, _rootDir) => {
125
170
  try {
126
- const result = (0, child_process_1.execSync)('sudo systemsetup -getremotelogin 2>/dev/null', {
171
+ const result = (0, child_process_1.execSync)('pmset -g custom 2>/dev/null | grep -i disablesleep || true', {
127
172
  encoding: 'utf8',
173
+ stdio: ['pipe', 'pipe', 'pipe'],
128
174
  });
129
- return result.toLowerCase().includes('off');
175
+ if (!result.trim())
176
+ return false; // Key not found on desktop Macs = not applicable
177
+ return !result.trim().includes('1');
130
178
  }
131
179
  catch {
132
- return false;
180
+ return true;
133
181
  }
134
182
  },
135
183
  fix: async (_config, _rootDir) => {
136
184
  try {
137
- console.log(' Enabling macOS Remote Login (SSH)...');
138
- (0, child_process_1.execSync)('sudo systemsetup -setremotelogin on', { stdio: 'inherit' });
185
+ console.log(' Disabling system sleep (server mode)...');
186
+ (0, child_process_1.execSync)('sudo pmset -a disablesleep 1', { stdio: 'inherit' });
139
187
  return true;
140
188
  }
141
189
  catch {
142
190
  return false;
143
191
  }
144
192
  },
145
- manualFix: 'Run: sudo systemsetup -setremotelogin on',
146
- },
147
- {
148
- id: 'macos-app-nap-enabled-staging',
149
- stage: 'staging',
150
- os: 'mac',
151
- severity: 'info',
152
- description: 'macOS App Nap may pause background processes',
193
+ manualFix: 'Run: sudo pmset -a disablesleep 1',
194
+ }),
195
+ ...macFixPair({
196
+ idBase: 'macos-wake-on-lan',
197
+ severity: 'warning',
198
+ description: 'Wake on LAN is disabled (cannot remotely wake server via magic packet)',
153
199
  scan: async (_config, _rootDir) => {
154
- try {
155
- const result = (0, child_process_1.execSync)('defaults read NSGlobalDomain NSAppSleepDisabled 2>/dev/null || echo "0"', {
156
- encoding: 'utf8',
157
- });
158
- return result.trim() !== '1';
159
- }
160
- catch {
161
- return true; // If can't read, assume App Nap is enabled
162
- }
200
+ return !pmsetValueIs('womp', '1');
163
201
  },
164
202
  fix: async (_config, _rootDir) => {
165
203
  try {
166
- console.log(' Disabling macOS App Nap...');
167
- (0, child_process_1.execSync)('defaults write NSGlobalDomain NSAppSleepDisabled -bool YES', { stdio: 'inherit' });
204
+ console.log(' Enabling Wake on LAN...');
205
+ (0, child_process_1.execSync)('sudo pmset -a womp 1', { stdio: 'inherit' });
168
206
  return true;
169
207
  }
170
208
  catch {
171
209
  return false;
172
210
  }
173
211
  },
174
- manualFix: 'Run: defaults write NSGlobalDomain NSAppSleepDisabled -bool YES',
175
- },
176
- {
177
- id: 'macos-autorestart-disabled-staging',
178
- stage: 'staging',
179
- os: 'mac',
212
+ manualFix: 'Run: sudo pmset -a womp 1 (requires Ethernet and compatible hardware)',
213
+ }),
214
+ ...macFixPair({
215
+ idBase: 'macos-hibernate-enabled',
180
216
  severity: 'warning',
181
- description: 'Auto-restart on power loss is disabled (server may stay off after outage)',
217
+ description: 'Hibernate mode is enabled (server writes RAM to disk and powers off)',
182
218
  scan: async (_config, _rootDir) => {
183
- try {
184
- const result = (0, child_process_1.execSync)('pmset -g 2>/dev/null | grep -i autorestart || true', {
185
- encoding: 'utf8',
186
- stdio: ['pipe', 'pipe', 'pipe'],
187
- });
188
- // Problem if autorestart is 0 or line doesn't contain 1
189
- return !result.trim().includes('1');
190
- }
191
- catch {
192
- return true; // Assume needs fix if we can't read
193
- }
219
+ return !pmsetValueIs('hibernatemode', '0');
194
220
  },
195
221
  fix: async (_config, _rootDir) => {
196
222
  try {
197
- console.log(' Enabling auto-restart on power loss...');
198
- (0, child_process_1.execSync)('sudo pmset -a autorestart 1', { stdio: 'inherit' });
223
+ console.log(' Disabling hibernate mode...');
224
+ (0, child_process_1.execSync)('sudo pmset -a hibernatemode 0', { stdio: 'inherit' });
199
225
  return true;
200
226
  }
201
227
  catch {
202
228
  return false;
203
229
  }
204
230
  },
205
- manualFix: 'Run: sudo pmset -a autorestart 1',
206
- },
207
- {
208
- id: 'macos-disablesleep-disabled-staging',
209
- stage: 'staging',
210
- os: 'mac',
211
- severity: 'info',
212
- description: 'System sleep is not fully disabled (disablesleep=0 allows Sleep menu)',
231
+ manualFix: 'Run: sudo pmset -a hibernatemode 0',
232
+ }),
233
+ ...macFixPair({
234
+ idBase: 'macos-standby-enabled',
235
+ severity: 'warning',
236
+ description: 'Standby mode is enabled (server enters deep sleep after extended idle)',
213
237
  scan: async (_config, _rootDir) => {
214
238
  try {
215
- const result = (0, child_process_1.execSync)('pmset -g custom 2>/dev/null | grep -i disablesleep || true', {
239
+ // Use 'standby ' with trailing space to avoid matching standbydelayhigh/standbydelaylow
240
+ const result = (0, child_process_1.execSync)('pmset -g 2>/dev/null | grep -E "^\\s*standby\\s+" || true', {
216
241
  encoding: 'utf8',
217
242
  stdio: ['pipe', 'pipe', 'pipe'],
218
243
  });
219
- return !result.trim().includes('1');
244
+ if (!result.trim())
245
+ return false; // Key not found on desktop Macs = not applicable
246
+ return !result.trim().includes('0');
220
247
  }
221
248
  catch {
222
249
  return true;
@@ -224,77 +251,78 @@ exports.macFixes = [
224
251
  },
225
252
  fix: async (_config, _rootDir) => {
226
253
  try {
227
- console.log(' Disabling system sleep (server mode)...');
228
- (0, child_process_1.execSync)('sudo pmset -a disablesleep 1', { stdio: 'inherit' });
254
+ console.log(' Disabling standby mode...');
255
+ (0, child_process_1.execSync)('sudo pmset -a standby 0', { stdio: 'inherit' });
229
256
  return true;
230
257
  }
231
258
  catch {
232
259
  return false;
233
260
  }
234
261
  },
235
- manualFix: 'Run: sudo pmset -a disablesleep 1',
236
- },
237
- {
238
- id: 'macos-autologin-disabled-staging',
239
- stage: 'staging',
240
- os: 'mac',
262
+ manualFix: 'Run: sudo pmset -a standby 0',
263
+ }),
264
+ ...macFixPair({
265
+ idBase: 'macos-powernap-enabled',
241
266
  severity: 'info',
242
- description: 'Auto-login on boot is not configured (may require manual login after power loss)',
267
+ description: 'Power Nap is enabled (wastes CPU for iCloud/Mail checks on server)',
243
268
  scan: async (_config, _rootDir) => {
269
+ return !pmsetValueIs('powernap', '0');
270
+ },
271
+ fix: async (_config, _rootDir) => {
244
272
  try {
245
- const plist = '/Library/Preferences/com.apple.loginwindow.plist';
246
- if (!fs.existsSync(plist))
247
- return true;
248
- const result = (0, child_process_1.execSync)(`defaults read ${plist} autoLoginUser 2>/dev/null || echo ""`, {
249
- encoding: 'utf8',
250
- });
251
- return !result.trim();
273
+ console.log(' Disabling Power Nap...');
274
+ (0, child_process_1.execSync)('sudo pmset -a powernap 0', { stdio: 'inherit' });
275
+ return true;
252
276
  }
253
277
  catch {
254
- return true;
278
+ return false;
255
279
  }
256
280
  },
257
- fix: null,
258
- manualFix: 'Set auto-login: System Settings > Users & Groups > Login Options > Automatic login > select user. ' +
259
- 'Or: sudo defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser -string "admin" (then reboot)',
260
- },
261
- // ============================================================
262
- // PROD FIXES (same as staging)
263
- // ============================================================
264
- {
265
- id: 'macos-sleep-enabled-prod',
266
- stage: 'prod',
267
- os: 'mac',
268
- severity: 'warning',
269
- description: 'macOS sleep is enabled (server may go offline)',
281
+ manualFix: 'Run: sudo pmset -a powernap 0',
282
+ }),
283
+ ...macFixPair({
284
+ idBase: 'macos-proximitywake-enabled',
285
+ severity: 'info',
286
+ description: 'Proximity wake is enabled (nearby Apple devices can wake the Mac)',
270
287
  scan: async (_config, _rootDir) => {
288
+ return !pmsetValueIs('proximitywake', '0');
289
+ },
290
+ fix: async (_config, _rootDir) => {
271
291
  try {
272
- const result = (0, child_process_1.execSync)('pmset -g | grep -E "^\\s*sleep\\s+"', {
273
- encoding: 'utf8',
274
- stdio: ['pipe', 'pipe', 'pipe'],
275
- });
276
- return !result.includes('sleep 0');
292
+ console.log(' Disabling proximity wake...');
293
+ (0, child_process_1.execSync)('sudo pmset -a proximitywake 0', { stdio: 'inherit' });
294
+ return true;
277
295
  }
278
296
  catch {
279
297
  return false;
280
298
  }
281
299
  },
300
+ manualFix: 'Run: sudo pmset -a proximitywake 0',
301
+ }),
302
+ ...macFixPair({
303
+ idBase: 'macos-ttyskeepawake-disabled',
304
+ severity: 'warning',
305
+ description: 'TTY keep-awake is disabled (Mac may sleep during active SSH sessions)',
306
+ scan: async (_config, _rootDir) => {
307
+ return !pmsetValueIs('ttyskeepawake', '1');
308
+ },
282
309
  fix: async (_config, _rootDir) => {
283
310
  try {
284
- console.log(' Disabling macOS sleep...');
285
- (0, child_process_1.execSync)('sudo pmset -a sleep 0 disksleep 0 displaysleep 0', { stdio: 'inherit' });
311
+ console.log(' Enabling TTY keep-awake...');
312
+ (0, child_process_1.execSync)('sudo pmset -a ttyskeepawake 1', { stdio: 'inherit' });
286
313
  return true;
287
314
  }
288
315
  catch {
289
316
  return false;
290
317
  }
291
318
  },
292
- manualFix: 'Run: sudo pmset -a sleep 0 disksleep 0 displaysleep 0',
293
- },
294
- {
295
- id: 'macos-ssh-disabled-prod',
296
- stage: 'prod',
297
- os: 'mac',
319
+ manualFix: 'Run: sudo pmset -a ttyskeepawake 1',
320
+ }),
321
+ // ============================================================
322
+ // SYSTEM SERVICES
323
+ // ============================================================
324
+ ...macFixPair({
325
+ idBase: 'macos-ssh-disabled',
298
326
  severity: 'critical',
299
327
  description: 'macOS Remote Login (SSH) is disabled',
300
328
  scan: async (_config, _rootDir) => {
@@ -319,19 +347,18 @@ exports.macFixes = [
319
347
  }
320
348
  },
321
349
  manualFix: 'Run: sudo systemsetup -setremotelogin on',
322
- },
323
- {
324
- id: 'macos-screensaver-enabled-prod',
325
- stage: 'prod',
326
- os: 'mac',
350
+ }),
351
+ ...macFixPair({
352
+ idBase: 'macos-screensaver-enabled',
327
353
  severity: 'info',
328
- description: 'macOS screensaver is enabled',
354
+ description: 'macOS screensaver is enabled (wasted GPU cycles on headless host)',
329
355
  scan: async (_config, _rootDir) => {
330
356
  try {
331
- const result = (0, child_process_1.execSync)('defaults read com.apple.screensaver idleTime 2>/dev/null || echo "300"', {
357
+ const result = (0, child_process_1.execSync)('defaults -currentHost read com.apple.screensaver idleTime 2>/dev/null || echo "300"', {
332
358
  encoding: 'utf8',
333
359
  });
334
- return parseInt(result.trim(), 10) > 0;
360
+ const idleTime = parseInt(result.trim(), 10);
361
+ return idleTime > 0;
335
362
  }
336
363
  catch {
337
364
  return false;
@@ -339,19 +366,18 @@ exports.macFixes = [
339
366
  },
340
367
  fix: async (_config, _rootDir) => {
341
368
  try {
342
- (0, child_process_1.execSync)('defaults write com.apple.screensaver idleTime 0', { stdio: 'inherit' });
369
+ console.log(' Disabling macOS screensaver...');
370
+ (0, child_process_1.execSync)('defaults -currentHost write com.apple.screensaver idleTime 0', { stdio: 'inherit' });
343
371
  return true;
344
372
  }
345
373
  catch {
346
374
  return false;
347
375
  }
348
376
  },
349
- manualFix: 'Run: defaults write com.apple.screensaver idleTime 0',
350
- },
351
- {
352
- id: 'macos-app-nap-enabled-prod',
353
- stage: 'prod',
354
- os: 'mac',
377
+ manualFix: 'Run: defaults -currentHost write com.apple.screensaver idleTime 0',
378
+ }),
379
+ ...macFixPair({
380
+ idBase: 'macos-app-nap-enabled',
355
381
  severity: 'info',
356
382
  description: 'macOS App Nap may pause background processes',
357
383
  scan: async (_config, _rootDir) => {
@@ -367,6 +393,7 @@ exports.macFixes = [
367
393
  },
368
394
  fix: async (_config, _rootDir) => {
369
395
  try {
396
+ console.log(' Disabling macOS App Nap...');
370
397
  (0, child_process_1.execSync)('defaults write NSGlobalDomain NSAppSleepDisabled -bool YES', { stdio: 'inherit' });
371
398
  return true;
372
399
  }
@@ -375,77 +402,117 @@ exports.macFixes = [
375
402
  }
376
403
  },
377
404
  manualFix: 'Run: defaults write NSGlobalDomain NSAppSleepDisabled -bool YES',
378
- },
379
- {
380
- id: 'macos-autorestart-disabled-prod',
381
- stage: 'prod',
382
- os: 'mac',
383
- severity: 'warning',
384
- description: 'Auto-restart on power loss is disabled',
405
+ }),
406
+ ...macFixPair({
407
+ idBase: 'macos-spotlight-enabled',
408
+ severity: 'info',
409
+ description: 'Spotlight indexing is enabled (burns CPU/disk on headless server)',
385
410
  scan: async (_config, _rootDir) => {
386
411
  try {
387
- const result = (0, child_process_1.execSync)('pmset -g 2>/dev/null | grep -i autorestart || true', {
412
+ const result = (0, child_process_1.execSync)('mdutil -s / 2>/dev/null', {
388
413
  encoding: 'utf8',
389
- stdio: ['pipe', 'pipe', 'pipe'],
390
414
  });
391
- return !result.trim().includes('1');
415
+ return result.toLowerCase().includes('indexing enabled');
392
416
  }
393
417
  catch {
394
- return true;
418
+ return false;
395
419
  }
396
420
  },
397
421
  fix: async (_config, _rootDir) => {
398
422
  try {
399
- (0, child_process_1.execSync)('sudo pmset -a autorestart 1', { stdio: 'inherit' });
423
+ console.log(' Disabling Spotlight indexing...');
424
+ (0, child_process_1.execSync)('sudo mdutil -a -i off', { stdio: 'inherit' });
400
425
  return true;
401
426
  }
402
427
  catch {
403
428
  return false;
404
429
  }
405
430
  },
406
- manualFix: 'Run: sudo pmset -a autorestart 1',
407
- },
408
- {
409
- id: 'macos-disablesleep-disabled-prod',
410
- stage: 'prod',
411
- os: 'mac',
431
+ manualFix: 'Run: sudo mdutil -a -i off',
432
+ }),
433
+ ...macFixPair({
434
+ idBase: 'macos-timemachine-enabled',
412
435
  severity: 'info',
413
- description: 'System sleep is not fully disabled',
436
+ description: 'Time Machine is enabled (causes periodic heavy disk I/O)',
414
437
  scan: async (_config, _rootDir) => {
415
438
  try {
416
- const result = (0, child_process_1.execSync)('pmset -g custom 2>/dev/null | grep -i disablesleep || true', {
439
+ const result = (0, child_process_1.execSync)('defaults read /Library/Preferences/com.apple.TimeMachine AutoBackup 2>/dev/null || echo "0"', {
417
440
  encoding: 'utf8',
418
- stdio: ['pipe', 'pipe', 'pipe'],
419
441
  });
420
- return !result.trim().includes('1');
442
+ return result.trim() === '1';
421
443
  }
422
444
  catch {
445
+ return false;
446
+ }
447
+ },
448
+ fix: async (_config, _rootDir) => {
449
+ try {
450
+ console.log(' Disabling Time Machine...');
451
+ (0, child_process_1.execSync)('sudo tmutil disable', { stdio: 'inherit' });
423
452
  return true;
424
453
  }
454
+ catch {
455
+ return false;
456
+ }
457
+ },
458
+ manualFix: 'Run: sudo tmutil disable',
459
+ }),
460
+ ...macFixPair({
461
+ idBase: 'macos-autoupdate-restart',
462
+ severity: 'critical',
463
+ description: 'macOS auto-update may reboot the server without warning',
464
+ scan: async (_config, _rootDir) => {
465
+ try {
466
+ const result = (0, child_process_1.execSync)('defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates 2>/dev/null || echo "0"', {
467
+ encoding: 'utf8',
468
+ });
469
+ return result.trim() === '1';
470
+ }
471
+ catch {
472
+ return false;
473
+ }
425
474
  },
426
475
  fix: async (_config, _rootDir) => {
427
476
  try {
428
- (0, child_process_1.execSync)('sudo pmset -a disablesleep 1', { stdio: 'inherit' });
477
+ console.log(' Disabling automatic macOS update installs...');
478
+ (0, child_process_1.execSync)('sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -bool false', { stdio: 'inherit' });
429
479
  return true;
430
480
  }
431
481
  catch {
432
482
  return false;
433
483
  }
434
484
  },
435
- manualFix: 'Run: sudo pmset -a disablesleep 1',
436
- },
437
- {
438
- id: 'macos-autologin-disabled-prod',
439
- stage: 'prod',
440
- os: 'mac',
485
+ manualFix: 'Run: sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -bool false',
486
+ }),
487
+ ...macFixPair({
488
+ idBase: 'macos-bluetooth-enabled',
441
489
  severity: 'info',
442
- description: 'Auto-login on boot is not configured',
490
+ description: 'Bluetooth is enabled (unnecessary attack surface on headless server)',
491
+ scan: async (_config, _rootDir) => {
492
+ try {
493
+ const result = (0, child_process_1.execSync)('defaults read /Library/Preferences/com.apple.Bluetooth ControllerPowerState 2>/dev/null || echo "1"', {
494
+ encoding: 'utf8',
495
+ });
496
+ return result.trim() !== '0';
497
+ }
498
+ catch {
499
+ return false;
500
+ }
501
+ },
502
+ fix: null,
503
+ manualFix: 'Disable Bluetooth (only if no BT keyboard/mouse): System Settings > Bluetooth > Turn Off. ' +
504
+ 'Or: sudo defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 0 && sudo killall -HUP bluetoothd',
505
+ }),
506
+ ...macFixPair({
507
+ idBase: 'macos-autologin-disabled',
508
+ severity: 'info',
509
+ description: 'Auto-login on boot is not configured (may require manual login after power loss)',
443
510
  scan: async (_config, _rootDir) => {
444
511
  try {
445
512
  const plist = '/Library/Preferences/com.apple.loginwindow.plist';
446
513
  if (!fs.existsSync(plist))
447
514
  return true;
448
- const result = (0, child_process_1.execSync)(`defaults read ${plist} autoLoginUser 2>/dev/null || echo ""`, {
515
+ const result = (0, child_process_1.execSync)('defaults read ' + plist + ' autoLoginUser 2>/dev/null || echo ""', {
449
516
  encoding: 'utf8',
450
517
  });
451
518
  return !result.trim();
@@ -455,7 +522,67 @@ exports.macFixes = [
455
522
  }
456
523
  },
457
524
  fix: null,
458
- manualFix: 'System Settings > Users & Groups > Login Options > Automatic login. Or: sudo defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser -string "admin"',
459
- },
525
+ manualFix: 'Set auto-login: System Settings > Users & Groups > Login Options > Automatic login > select user. ' +
526
+ 'Or: sudo defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser -string "admin" (then reboot)',
527
+ }),
528
+ // ============================================================
529
+ // NETWORK / SECURITY
530
+ // ============================================================
531
+ ...macFixPair({
532
+ idBase: 'macos-ntp-disabled',
533
+ severity: 'warning',
534
+ description: 'Network time (NTP) is disabled (accurate time needed for TLS, logs, cron)',
535
+ scan: async (_config, _rootDir) => {
536
+ try {
537
+ const result = (0, child_process_1.execSync)('sudo systemsetup -getusingnetworktime 2>/dev/null', {
538
+ encoding: 'utf8',
539
+ });
540
+ return result.toLowerCase().includes('off');
541
+ }
542
+ catch {
543
+ return false;
544
+ }
545
+ },
546
+ fix: async (_config, _rootDir) => {
547
+ try {
548
+ console.log(' Enabling network time (NTP)...');
549
+ (0, child_process_1.execSync)('sudo systemsetup -setusingnetworktime on', { stdio: 'inherit' });
550
+ return true;
551
+ }
552
+ catch {
553
+ return false;
554
+ }
555
+ },
556
+ manualFix: 'Run: sudo systemsetup -setusingnetworktime on',
557
+ }),
558
+ ...macFixPair({
559
+ idBase: 'macos-file-sharing-enabled',
560
+ severity: 'info',
561
+ description: 'File sharing (AFP/SMB) is enabled (unnecessary attack surface on server)',
562
+ scan: async (_config, _rootDir) => {
563
+ try {
564
+ // Check if smbd is running (SMB file sharing)
565
+ (0, child_process_1.execSync)('launchctl list 2>/dev/null | grep com.apple.smbd', {
566
+ stdio: ['pipe', 'pipe', 'pipe'],
567
+ });
568
+ return true;
569
+ }
570
+ catch {
571
+ // smbd not running, check AFP
572
+ try {
573
+ const result = (0, child_process_1.execSync)('defaults read /Library/Preferences/com.apple.AppleFileServer guestAccess 2>/dev/null || echo "0"', {
574
+ encoding: 'utf8',
575
+ });
576
+ return result.trim() === '1';
577
+ }
578
+ catch {
579
+ return false;
580
+ }
581
+ }
582
+ },
583
+ fix: null,
584
+ manualFix: 'Disable file sharing: System Settings > General > Sharing > File Sharing > Off. ' +
585
+ 'Only disable if file sharing is not intentionally used.',
586
+ }),
460
587
  ];
461
588
  //# sourceMappingURL=mac.js.map