@openape/cli 0.1.0 → 0.1.1

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.
@@ -0,0 +1,188 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { execSync } from 'node:child_process';
3
+ import { ask, closePrompt, requireRoot, ensureDir, writeSecureFile, isMacOS, isLinux, run } from '../utils.js';
4
+ const CONFIG_DIR = '/etc/openape-proxy';
5
+ const CONFIG_PATH = `${CONFIG_DIR}/config.toml`;
6
+ const LAUNCHD_LABEL = 'ai.openape.proxy';
7
+ const LAUNCHD_PLIST = `/Library/LaunchDaemons/${LAUNCHD_LABEL}.plist`;
8
+ const SYSTEMD_UNIT = '/etc/systemd/system/openape-proxy.service';
9
+ export async function installProxy() {
10
+ requireRoot();
11
+ console.log('\n🐾 OpenApe Proxy Installer\n');
12
+ // Check if bun is available
13
+ try {
14
+ run('which bun');
15
+ }
16
+ catch {
17
+ throw new Error('Bun is required but not found. Install it: https://bun.sh');
18
+ }
19
+ // Check if already installed
20
+ if (existsSync(CONFIG_PATH)) {
21
+ const overwrite = await ask('Config already exists. Overwrite?', 'n');
22
+ if (overwrite.toLowerCase() !== 'y') {
23
+ console.log('Aborted.');
24
+ closePrompt();
25
+ return;
26
+ }
27
+ }
28
+ // Gather config
29
+ const idpUrl = await ask('IdP URL', 'https://id.test.openape.at');
30
+ const agentEmail = await ask('Agent email');
31
+ const listen = await ask('Listen address', '127.0.0.1:9090');
32
+ const defaultAction = await ask('Default action (block/request/request-async)', 'block');
33
+ const auditLog = await ask('Audit log path', '/var/log/openape-proxy/audit.log');
34
+ console.log('\nConfiguring rules...');
35
+ console.log('(You can edit the config later at ' + CONFIG_PATH + ')\n');
36
+ // Generate config
37
+ const config = `# OpenApe Proxy Configuration
38
+ # Generated by: openape install-proxy
39
+ # Edit this file to add/remove rules. Restart the service after changes.
40
+
41
+ [proxy]
42
+ listen = "${listen}"
43
+ idp_url = "${idpUrl}"
44
+ agent_email = "${agentEmail}"
45
+ default_action = "${defaultAction}"
46
+ audit_log = "${auditLog}"
47
+
48
+ # Example rules — customize these for your use case:
49
+
50
+ # [[allow]]
51
+ # domain = "api.github.com"
52
+ # methods = ["GET"]
53
+ # note = "GitHub API read-only"
54
+
55
+ # [[deny]]
56
+ # domain = "*.malware.example.com"
57
+ # note = "Known bad domain"
58
+
59
+ # [[grant_required]]
60
+ # domain = "api.github.com"
61
+ # methods = ["POST", "PUT", "DELETE"]
62
+ # grant_type = "allow_once"
63
+ # note = "GitHub API write operations need approval"
64
+ `;
65
+ // Write config
66
+ ensureDir(CONFIG_DIR, 0o700);
67
+ writeSecureFile(CONFIG_PATH, config);
68
+ console.log(`āœ… Config written to ${CONFIG_PATH}`);
69
+ // Create audit log directory
70
+ const auditDir = auditLog.substring(0, auditLog.lastIndexOf('/'));
71
+ if (auditDir) {
72
+ ensureDir(auditDir, 0o755);
73
+ console.log(`āœ… Audit log directory created: ${auditDir}`);
74
+ }
75
+ // Install proxy source
76
+ const installDir = '/opt/openape-proxy';
77
+ ensureDir(installDir, 0o755);
78
+ // Clone or update proxy source
79
+ if (existsSync(`${installDir}/package.json`)) {
80
+ console.log('Updating proxy source...');
81
+ execSync(`cd ${installDir} && git pull`, { stdio: 'inherit' });
82
+ }
83
+ else {
84
+ console.log('Downloading proxy source...');
85
+ execSync(`git clone https://github.com/openape-ai/proxy.git ${installDir}`, { stdio: 'inherit' });
86
+ }
87
+ // Install dependencies
88
+ console.log('Installing dependencies...');
89
+ execSync(`cd ${installDir} && bun install`, { stdio: 'inherit' });
90
+ // Set up system service
91
+ if (isMacOS()) {
92
+ await setupLaunchd(installDir);
93
+ }
94
+ else if (isLinux()) {
95
+ await setupSystemd(installDir);
96
+ }
97
+ else {
98
+ console.log('\nāš ļø Unsupported platform for service setup. Start manually:');
99
+ console.log(` cd ${installDir} && bun run src/index.ts --config ${CONFIG_PATH}`);
100
+ }
101
+ closePrompt();
102
+ console.log('\nšŸŽ‰ OpenApe Proxy installed!\n');
103
+ console.log(` Config: ${CONFIG_PATH}`);
104
+ console.log(` Proxy: ${installDir}`);
105
+ console.log(` Audit log: ${auditLog}`);
106
+ console.log(` Listening: ${listen}`);
107
+ console.log(`\n Edit ${CONFIG_PATH} to configure rules.`);
108
+ console.log(` The proxy must be restarted after config changes.\n`);
109
+ // Quick test
110
+ try {
111
+ const port = listen.split(':')[1] || '9090';
112
+ const host = listen.split(':')[0] || '127.0.0.1';
113
+ const res = execSync(`curl -s http://${host}:${port}/healthz`, { timeout: 3000, encoding: 'utf-8' });
114
+ console.log(` Health check: ${res}`);
115
+ }
116
+ catch {
117
+ console.log(' āš ļø Health check failed — service may still be starting.');
118
+ }
119
+ }
120
+ async function setupLaunchd(installDir) {
121
+ const bunPath = run('which bun');
122
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
123
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
124
+ <plist version="1.0">
125
+ <dict>
126
+ <key>Label</key>
127
+ <string>${LAUNCHD_LABEL}</string>
128
+ <key>ProgramArguments</key>
129
+ <array>
130
+ <string>${bunPath}</string>
131
+ <string>run</string>
132
+ <string>${installDir}/src/index.ts</string>
133
+ <string>--config</string>
134
+ <string>${CONFIG_PATH}</string>
135
+ </array>
136
+ <key>RunAtLoad</key>
137
+ <true/>
138
+ <key>KeepAlive</key>
139
+ <true/>
140
+ <key>StandardOutPath</key>
141
+ <string>/var/log/openape-proxy/stdout.log</string>
142
+ <key>StandardErrorPath</key>
143
+ <string>/var/log/openape-proxy/stderr.log</string>
144
+ <key>WorkingDirectory</key>
145
+ <string>${installDir}</string>
146
+ </dict>
147
+ </plist>`;
148
+ writeSecureFile(LAUNCHD_PLIST, plist, 0o644);
149
+ console.log(`āœ… launchd service created: ${LAUNCHD_LABEL}`);
150
+ // Stop if already running
151
+ try {
152
+ execSync(`launchctl unload ${LAUNCHD_PLIST}`, { stdio: 'ignore' });
153
+ }
154
+ catch { }
155
+ execSync(`launchctl load ${LAUNCHD_PLIST}`);
156
+ console.log('āœ… Service started');
157
+ }
158
+ async function setupSystemd(installDir) {
159
+ const bunPath = run('which bun');
160
+ const unit = `[Unit]
161
+ Description=OpenApe HTTP Proxy
162
+ After=network.target
163
+
164
+ [Service]
165
+ Type=simple
166
+ ExecStart=${bunPath} run ${installDir}/src/index.ts --config ${CONFIG_PATH}
167
+ WorkingDirectory=${installDir}
168
+ Restart=on-failure
169
+ RestartSec=5
170
+ StandardOutput=journal
171
+ StandardError=journal
172
+
173
+ # Security hardening
174
+ NoNewPrivileges=true
175
+ ProtectSystem=strict
176
+ ProtectHome=true
177
+ ReadOnlyPaths=/
178
+ ReadWritePaths=/var/log/openape-proxy
179
+
180
+ [Install]
181
+ WantedBy=multi-user.target
182
+ `;
183
+ writeSecureFile(SYSTEMD_UNIT, unit, 0o644);
184
+ execSync('systemctl daemon-reload');
185
+ execSync('systemctl enable openape-proxy');
186
+ execSync('systemctl start openape-proxy');
187
+ console.log('āœ… systemd service created and started: openape-proxy');
188
+ }
@@ -0,0 +1,86 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { execSync } from 'node:child_process';
3
+ import { ask, closePrompt, requireRoot, ensureDir, writeSecureFile, run } from '../utils.js';
4
+ const CONFIG_DIR = '/etc/apes';
5
+ const CONFIG_PATH = `${CONFIG_DIR}/config.toml`;
6
+ const BIN_PATH = '/usr/local/bin/apes';
7
+ export async function installSudo() {
8
+ requireRoot();
9
+ console.log('\n🐾 OpenApe Sudo (apes) Installer\n');
10
+ // Check if already installed
11
+ if (existsSync(BIN_PATH)) {
12
+ const overwrite = await ask('apes is already installed. Reinstall?', 'n');
13
+ if (overwrite.toLowerCase() !== 'y') {
14
+ console.log('Aborted.');
15
+ closePrompt();
16
+ return;
17
+ }
18
+ }
19
+ // Check for Rust toolchain
20
+ let hasCargo = false;
21
+ try {
22
+ run('cargo --version');
23
+ hasCargo = true;
24
+ }
25
+ catch { }
26
+ if (!hasCargo) {
27
+ console.log('āš ļø Rust/Cargo not found. Checking for pre-built binary...');
28
+ // TODO: download pre-built binary from GitHub releases
29
+ throw new Error('Rust toolchain required to build apes. Install it: https://rustup.rs\n' +
30
+ 'Pre-built binaries will be available in a future release.');
31
+ }
32
+ // Build from source
33
+ const buildDir = '/tmp/openape-sudo-build';
34
+ if (existsSync(`${buildDir}/.git`)) {
35
+ console.log('Updating source...');
36
+ execSync(`cd ${buildDir} && git pull`, { stdio: 'inherit' });
37
+ }
38
+ else {
39
+ console.log('Downloading source...');
40
+ execSync(`rm -rf ${buildDir}`, { stdio: 'ignore' });
41
+ execSync(`git clone https://github.com/openape-ai/sudo.git ${buildDir}`, { stdio: 'inherit' });
42
+ }
43
+ console.log('Building apes (release mode)...');
44
+ execSync(`cd ${buildDir} && cargo build --release`, { stdio: 'inherit' });
45
+ // Install binary with setuid
46
+ execSync(`cp ${buildDir}/target/release/apes ${BIN_PATH}`);
47
+ execSync(`chown root:wheel ${BIN_PATH}`);
48
+ execSync(`chmod u+s ${BIN_PATH}`);
49
+ console.log(`āœ… apes installed at ${BIN_PATH} (setuid root)`);
50
+ // Config setup
51
+ if (existsSync(CONFIG_PATH)) {
52
+ const overwriteConfig = await ask('Config already exists. Overwrite?', 'n');
53
+ if (overwriteConfig.toLowerCase() !== 'y') {
54
+ closePrompt();
55
+ console.log('\nšŸŽ‰ apes binary updated! Existing config preserved.\n');
56
+ return;
57
+ }
58
+ }
59
+ const serverUrl = await ask('IdP URL', 'https://id.test.openape.at');
60
+ const timeoutSecs = await ask('Poll timeout (seconds)', '300');
61
+ const intervalSecs = await ask('Poll interval (seconds)', '2');
62
+ const config = `# OpenApe Sudo (apes) Configuration
63
+ # Generated by: openape install-sudo
64
+
65
+ server_url = "${serverUrl}"
66
+
67
+ # Agent identity is determined by the key passed via --key flag.
68
+ # Each user provides their own key: apes --key ~/.ssh/id_ed25519 -- <command>
69
+
70
+ [poll]
71
+ interval_secs = ${intervalSecs}
72
+ timeout_secs = ${timeoutSecs}
73
+ `;
74
+ ensureDir(CONFIG_DIR, 0o700);
75
+ writeSecureFile(CONFIG_PATH, config);
76
+ console.log(`āœ… Config written to ${CONFIG_PATH}`);
77
+ closePrompt();
78
+ console.log('\nšŸŽ‰ apes installed!\n');
79
+ console.log(` Binary: ${BIN_PATH} (setuid root)`);
80
+ console.log(` Config: ${CONFIG_PATH}`);
81
+ console.log('\n Next steps:');
82
+ console.log(' 1. Register this agent on the IdP:');
83
+ console.log(` sudo apes enroll --server ${serverUrl} --agent-email <email> --agent-name <name> --key <path>`);
84
+ console.log(' 2. Ask your admin to approve the enrollment');
85
+ console.log(' 3. Use it: apes --key ~/.ssh/id_ed25519 --reason "why" -- <command>\n');
86
+ }
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ import { installProxy } from './commands/install-proxy.js';
3
+ import { installSudo } from './commands/install-sudo.js';
4
+ const command = process.argv[2];
5
+ const HELP = `
6
+ OpenApe CLI — install and manage OpenApe components
7
+
8
+ Usage: openape <command> [options]
9
+
10
+ Commands:
11
+ install-proxy Install and configure the OpenApe HTTP proxy
12
+ install-sudo Install and configure apes (privilege elevation)
13
+ help Show this help message
14
+
15
+ Examples:
16
+ sudo openape install-proxy
17
+ sudo openape install-sudo
18
+ `;
19
+ async function main() {
20
+ switch (command) {
21
+ case 'install-proxy':
22
+ await installProxy();
23
+ break;
24
+ case 'install-sudo':
25
+ await installSudo();
26
+ break;
27
+ case 'help':
28
+ case '--help':
29
+ case '-h':
30
+ case undefined:
31
+ console.log(HELP);
32
+ process.exit(0);
33
+ default:
34
+ console.error(`Unknown command: ${command}`);
35
+ console.log(HELP);
36
+ process.exit(1);
37
+ }
38
+ }
39
+ main().catch((err) => {
40
+ console.error(`\nāŒ ${err.message}`);
41
+ process.exit(1);
42
+ });
package/dist/utils.js ADDED
@@ -0,0 +1,57 @@
1
+ import { createInterface } from 'node:readline';
2
+ import { execSync } from 'node:child_process';
3
+ import { existsSync, mkdirSync, writeFileSync, chmodSync, chownSync } from 'node:fs';
4
+ import { platform } from 'node:os';
5
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
6
+ export function ask(question, defaultValue) {
7
+ const suffix = defaultValue ? ` [${defaultValue}]` : '';
8
+ return new Promise((resolve) => {
9
+ rl.question(`${question}${suffix}: `, (answer) => {
10
+ resolve(answer.trim() || defaultValue || '');
11
+ });
12
+ });
13
+ }
14
+ export function closePrompt() {
15
+ rl.close();
16
+ }
17
+ export function requireRoot() {
18
+ if (process.getuid?.() !== 0) {
19
+ throw new Error('This command must be run as root (use sudo)');
20
+ }
21
+ }
22
+ export function ensureDir(path, mode = 0o755) {
23
+ if (!existsSync(path)) {
24
+ mkdirSync(path, { recursive: true });
25
+ }
26
+ chmodSync(path, mode);
27
+ }
28
+ export function writeSecureFile(path, content, mode = 0o600) {
29
+ writeFileSync(path, content, 'utf-8');
30
+ chmodSync(path, mode);
31
+ // Ensure root ownership
32
+ try {
33
+ chownSync(path, 0, 0);
34
+ }
35
+ catch { }
36
+ }
37
+ export function run(cmd) {
38
+ return execSync(cmd, { encoding: 'utf-8' }).trim();
39
+ }
40
+ export function isMacOS() {
41
+ return platform() === 'darwin';
42
+ }
43
+ export function isLinux() {
44
+ return platform() === 'linux';
45
+ }
46
+ export function serviceExists(name) {
47
+ if (isMacOS()) {
48
+ return existsSync(`/Library/LaunchDaemons/${name}.plist`);
49
+ }
50
+ try {
51
+ execSync(`systemctl cat ${name}`, { stdio: 'ignore' });
52
+ return true;
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@openape/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenApe CLI — install and manage OpenApe components (proxy, sudo, auth)",
5
5
  "type": "module",
6
6
  "bin": {
7
- "openape": "./src/index.ts"
7
+ "openape": "./dist/index.js"
8
8
  },
9
9
  "scripts": {
10
- "start": "bun run src/index.ts",
10
+ "build": "tsc",
11
+ "prepublishOnly": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "bun run src/index.ts",
11
14
  "typecheck": "tsc --noEmit"
12
15
  },
13
16
  "keywords": [
@@ -43,7 +46,7 @@
43
46
  "node": ">=18"
44
47
  },
45
48
  "files": [
46
- "src/",
49
+ "dist/",
47
50
  "README.md",
48
51
  "LICENSE"
49
52
  ]
@@ -1,205 +0,0 @@
1
- import { existsSync } from 'node:fs'
2
- import { execSync } from 'node:child_process'
3
- import { ask, closePrompt, requireRoot, ensureDir, writeSecureFile, isMacOS, isLinux, run } from '../utils.js'
4
-
5
- const CONFIG_DIR = '/etc/openape-proxy'
6
- const CONFIG_PATH = `${CONFIG_DIR}/config.toml`
7
- const LAUNCHD_LABEL = 'ai.openape.proxy'
8
- const LAUNCHD_PLIST = `/Library/LaunchDaemons/${LAUNCHD_LABEL}.plist`
9
- const SYSTEMD_UNIT = '/etc/systemd/system/openape-proxy.service'
10
-
11
- export async function installProxy(): Promise<void> {
12
- requireRoot()
13
-
14
- console.log('\n🐾 OpenApe Proxy Installer\n')
15
-
16
- // Check if bun is available
17
- try {
18
- run('which bun')
19
- } catch {
20
- throw new Error('Bun is required but not found. Install it: https://bun.sh')
21
- }
22
-
23
- // Check if already installed
24
- if (existsSync(CONFIG_PATH)) {
25
- const overwrite = await ask('Config already exists. Overwrite?', 'n')
26
- if (overwrite.toLowerCase() !== 'y') {
27
- console.log('Aborted.')
28
- closePrompt()
29
- return
30
- }
31
- }
32
-
33
- // Gather config
34
- const idpUrl = await ask('IdP URL', 'https://id.test.openape.at')
35
- const agentEmail = await ask('Agent email')
36
- const listen = await ask('Listen address', '127.0.0.1:9090')
37
- const defaultAction = await ask('Default action (block/request/request-async)', 'block')
38
- const auditLog = await ask('Audit log path', '/var/log/openape-proxy/audit.log')
39
-
40
- console.log('\nConfiguring rules...')
41
- console.log('(You can edit the config later at ' + CONFIG_PATH + ')\n')
42
-
43
- // Generate config
44
- const config = `# OpenApe Proxy Configuration
45
- # Generated by: openape install-proxy
46
- # Edit this file to add/remove rules. Restart the service after changes.
47
-
48
- [proxy]
49
- listen = "${listen}"
50
- idp_url = "${idpUrl}"
51
- agent_email = "${agentEmail}"
52
- default_action = "${defaultAction}"
53
- audit_log = "${auditLog}"
54
-
55
- # Example rules — customize these for your use case:
56
-
57
- # [[allow]]
58
- # domain = "api.github.com"
59
- # methods = ["GET"]
60
- # note = "GitHub API read-only"
61
-
62
- # [[deny]]
63
- # domain = "*.malware.example.com"
64
- # note = "Known bad domain"
65
-
66
- # [[grant_required]]
67
- # domain = "api.github.com"
68
- # methods = ["POST", "PUT", "DELETE"]
69
- # grant_type = "allow_once"
70
- # note = "GitHub API write operations need approval"
71
- `
72
-
73
- // Write config
74
- ensureDir(CONFIG_DIR, 0o700)
75
- writeSecureFile(CONFIG_PATH, config)
76
- console.log(`āœ… Config written to ${CONFIG_PATH}`)
77
-
78
- // Create audit log directory
79
- const auditDir = auditLog.substring(0, auditLog.lastIndexOf('/'))
80
- if (auditDir) {
81
- ensureDir(auditDir, 0o755)
82
- console.log(`āœ… Audit log directory created: ${auditDir}`)
83
- }
84
-
85
- // Install proxy source
86
- const installDir = '/opt/openape-proxy'
87
- ensureDir(installDir, 0o755)
88
-
89
- // Clone or update proxy source
90
- if (existsSync(`${installDir}/package.json`)) {
91
- console.log('Updating proxy source...')
92
- execSync(`cd ${installDir} && git pull`, { stdio: 'inherit' })
93
- } else {
94
- console.log('Downloading proxy source...')
95
- execSync(`git clone https://github.com/openape-ai/proxy.git ${installDir}`, { stdio: 'inherit' })
96
- }
97
-
98
- // Install dependencies
99
- console.log('Installing dependencies...')
100
- execSync(`cd ${installDir} && bun install`, { stdio: 'inherit' })
101
-
102
- // Set up system service
103
- if (isMacOS()) {
104
- await setupLaunchd(installDir)
105
- } else if (isLinux()) {
106
- await setupSystemd(installDir)
107
- } else {
108
- console.log('\nāš ļø Unsupported platform for service setup. Start manually:')
109
- console.log(` cd ${installDir} && bun run src/index.ts --config ${CONFIG_PATH}`)
110
- }
111
-
112
- closePrompt()
113
-
114
- console.log('\nšŸŽ‰ OpenApe Proxy installed!\n')
115
- console.log(` Config: ${CONFIG_PATH}`)
116
- console.log(` Proxy: ${installDir}`)
117
- console.log(` Audit log: ${auditLog}`)
118
- console.log(` Listening: ${listen}`)
119
- console.log(`\n Edit ${CONFIG_PATH} to configure rules.`)
120
- console.log(` The proxy must be restarted after config changes.\n`)
121
-
122
- // Quick test
123
- try {
124
- const port = listen.split(':')[1] || '9090'
125
- const host = listen.split(':')[0] || '127.0.0.1'
126
- const res = execSync(`curl -s http://${host}:${port}/healthz`, { timeout: 3000, encoding: 'utf-8' })
127
- console.log(` Health check: ${res}`)
128
- } catch {
129
- console.log(' āš ļø Health check failed — service may still be starting.')
130
- }
131
- }
132
-
133
- async function setupLaunchd(installDir: string): Promise<void> {
134
- const bunPath = run('which bun')
135
-
136
- const plist = `<?xml version="1.0" encoding="UTF-8"?>
137
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
138
- <plist version="1.0">
139
- <dict>
140
- <key>Label</key>
141
- <string>${LAUNCHD_LABEL}</string>
142
- <key>ProgramArguments</key>
143
- <array>
144
- <string>${bunPath}</string>
145
- <string>run</string>
146
- <string>${installDir}/src/index.ts</string>
147
- <string>--config</string>
148
- <string>${CONFIG_PATH}</string>
149
- </array>
150
- <key>RunAtLoad</key>
151
- <true/>
152
- <key>KeepAlive</key>
153
- <true/>
154
- <key>StandardOutPath</key>
155
- <string>/var/log/openape-proxy/stdout.log</string>
156
- <key>StandardErrorPath</key>
157
- <string>/var/log/openape-proxy/stderr.log</string>
158
- <key>WorkingDirectory</key>
159
- <string>${installDir}</string>
160
- </dict>
161
- </plist>`
162
-
163
- writeSecureFile(LAUNCHD_PLIST, plist, 0o644)
164
- console.log(`āœ… launchd service created: ${LAUNCHD_LABEL}`)
165
-
166
- // Stop if already running
167
- try { execSync(`launchctl unload ${LAUNCHD_PLIST}`, { stdio: 'ignore' }) } catch {}
168
-
169
- execSync(`launchctl load ${LAUNCHD_PLIST}`)
170
- console.log('āœ… Service started')
171
- }
172
-
173
- async function setupSystemd(installDir: string): Promise<void> {
174
- const bunPath = run('which bun')
175
-
176
- const unit = `[Unit]
177
- Description=OpenApe HTTP Proxy
178
- After=network.target
179
-
180
- [Service]
181
- Type=simple
182
- ExecStart=${bunPath} run ${installDir}/src/index.ts --config ${CONFIG_PATH}
183
- WorkingDirectory=${installDir}
184
- Restart=on-failure
185
- RestartSec=5
186
- StandardOutput=journal
187
- StandardError=journal
188
-
189
- # Security hardening
190
- NoNewPrivileges=true
191
- ProtectSystem=strict
192
- ProtectHome=true
193
- ReadOnlyPaths=/
194
- ReadWritePaths=/var/log/openape-proxy
195
-
196
- [Install]
197
- WantedBy=multi-user.target
198
- `
199
-
200
- writeSecureFile(SYSTEMD_UNIT, unit, 0o644)
201
- execSync('systemctl daemon-reload')
202
- execSync('systemctl enable openape-proxy')
203
- execSync('systemctl start openape-proxy')
204
- console.log('āœ… systemd service created and started: openape-proxy')
205
- }
@@ -1,102 +0,0 @@
1
- import { existsSync } from 'node:fs'
2
- import { execSync } from 'node:child_process'
3
- import { arch, platform } from 'node:os'
4
- import { ask, closePrompt, requireRoot, ensureDir, writeSecureFile, run } from '../utils.js'
5
-
6
- const CONFIG_DIR = '/etc/apes'
7
- const CONFIG_PATH = `${CONFIG_DIR}/config.toml`
8
- const BIN_PATH = '/usr/local/bin/apes'
9
-
10
- export async function installSudo(): Promise<void> {
11
- requireRoot()
12
-
13
- console.log('\n🐾 OpenApe Sudo (apes) Installer\n')
14
-
15
- // Check if already installed
16
- if (existsSync(BIN_PATH)) {
17
- const overwrite = await ask('apes is already installed. Reinstall?', 'n')
18
- if (overwrite.toLowerCase() !== 'y') {
19
- console.log('Aborted.')
20
- closePrompt()
21
- return
22
- }
23
- }
24
-
25
- // Check for Rust toolchain
26
- let hasCargo = false
27
- try {
28
- run('cargo --version')
29
- hasCargo = true
30
- } catch {}
31
-
32
- if (!hasCargo) {
33
- console.log('āš ļø Rust/Cargo not found. Checking for pre-built binary...')
34
- // TODO: download pre-built binary from GitHub releases
35
- throw new Error(
36
- 'Rust toolchain required to build apes. Install it: https://rustup.rs\n' +
37
- 'Pre-built binaries will be available in a future release.'
38
- )
39
- }
40
-
41
- // Build from source
42
- const buildDir = '/tmp/openape-sudo-build'
43
- if (existsSync(`${buildDir}/.git`)) {
44
- console.log('Updating source...')
45
- execSync(`cd ${buildDir} && git pull`, { stdio: 'inherit' })
46
- } else {
47
- console.log('Downloading source...')
48
- execSync(`rm -rf ${buildDir}`, { stdio: 'ignore' })
49
- execSync(`git clone https://github.com/openape-ai/sudo.git ${buildDir}`, { stdio: 'inherit' })
50
- }
51
-
52
- console.log('Building apes (release mode)...')
53
- execSync(`cd ${buildDir} && cargo build --release`, { stdio: 'inherit' })
54
-
55
- // Install binary with setuid
56
- execSync(`cp ${buildDir}/target/release/apes ${BIN_PATH}`)
57
- execSync(`chown root:wheel ${BIN_PATH}`)
58
- execSync(`chmod u+s ${BIN_PATH}`)
59
- console.log(`āœ… apes installed at ${BIN_PATH} (setuid root)`)
60
-
61
- // Config setup
62
- if (existsSync(CONFIG_PATH)) {
63
- const overwriteConfig = await ask('Config already exists. Overwrite?', 'n')
64
- if (overwriteConfig.toLowerCase() !== 'y') {
65
- closePrompt()
66
- console.log('\nšŸŽ‰ apes binary updated! Existing config preserved.\n')
67
- return
68
- }
69
- }
70
-
71
- const serverUrl = await ask('IdP URL', 'https://id.test.openape.at')
72
- const timeoutSecs = await ask('Poll timeout (seconds)', '300')
73
- const intervalSecs = await ask('Poll interval (seconds)', '2')
74
-
75
- const config = `# OpenApe Sudo (apes) Configuration
76
- # Generated by: openape install-sudo
77
-
78
- server_url = "${serverUrl}"
79
-
80
- # Agent identity is determined by the key passed via --key flag.
81
- # Each user provides their own key: apes --key ~/.ssh/id_ed25519 -- <command>
82
-
83
- [poll]
84
- interval_secs = ${intervalSecs}
85
- timeout_secs = ${timeoutSecs}
86
- `
87
-
88
- ensureDir(CONFIG_DIR, 0o700)
89
- writeSecureFile(CONFIG_PATH, config)
90
- console.log(`āœ… Config written to ${CONFIG_PATH}`)
91
-
92
- closePrompt()
93
-
94
- console.log('\nšŸŽ‰ apes installed!\n')
95
- console.log(` Binary: ${BIN_PATH} (setuid root)`)
96
- console.log(` Config: ${CONFIG_PATH}`)
97
- console.log('\n Next steps:')
98
- console.log(' 1. Register this agent on the IdP:')
99
- console.log(` sudo apes enroll --server ${serverUrl} --agent-email <email> --agent-name <name> --key <path>`)
100
- console.log(' 2. Ask your admin to approve the enrollment')
101
- console.log(' 3. Use it: apes --key ~/.ssh/id_ed25519 --reason "why" -- <command>\n')
102
- }
package/src/index.ts DELETED
@@ -1,47 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { parseArgs } from 'node:util'
3
- import { installProxy } from './commands/install-proxy.js'
4
- import { installSudo } from './commands/install-sudo.js'
5
-
6
- const command = process.argv[2]
7
-
8
- const HELP = `
9
- OpenApe CLI — install and manage OpenApe components
10
-
11
- Usage: openape <command> [options]
12
-
13
- Commands:
14
- install-proxy Install and configure the OpenApe HTTP proxy
15
- install-sudo Install and configure apes (privilege elevation)
16
- help Show this help message
17
-
18
- Examples:
19
- sudo openape install-proxy
20
- sudo openape install-sudo
21
- `
22
-
23
- async function main() {
24
- switch (command) {
25
- case 'install-proxy':
26
- await installProxy()
27
- break
28
- case 'install-sudo':
29
- await installSudo()
30
- break
31
- case 'help':
32
- case '--help':
33
- case '-h':
34
- case undefined:
35
- console.log(HELP)
36
- process.exit(0)
37
- default:
38
- console.error(`Unknown command: ${command}`)
39
- console.log(HELP)
40
- process.exit(1)
41
- }
42
- }
43
-
44
- main().catch((err) => {
45
- console.error(`\nāŒ ${err.message}`)
46
- process.exit(1)
47
- })
package/src/utils.ts DELETED
@@ -1,63 +0,0 @@
1
- import { createInterface } from 'node:readline'
2
- import { execSync } from 'node:child_process'
3
- import { existsSync, mkdirSync, writeFileSync, chmodSync, chownSync } from 'node:fs'
4
- import { platform } from 'node:os'
5
-
6
- const rl = createInterface({ input: process.stdin, output: process.stdout })
7
-
8
- export function ask(question: string, defaultValue?: string): Promise<string> {
9
- const suffix = defaultValue ? ` [${defaultValue}]` : ''
10
- return new Promise((resolve) => {
11
- rl.question(`${question}${suffix}: `, (answer) => {
12
- resolve(answer.trim() || defaultValue || '')
13
- })
14
- })
15
- }
16
-
17
- export function closePrompt(): void {
18
- rl.close()
19
- }
20
-
21
- export function requireRoot(): void {
22
- if (process.getuid?.() !== 0) {
23
- throw new Error('This command must be run as root (use sudo)')
24
- }
25
- }
26
-
27
- export function ensureDir(path: string, mode: number = 0o755): void {
28
- if (!existsSync(path)) {
29
- mkdirSync(path, { recursive: true })
30
- }
31
- chmodSync(path, mode)
32
- }
33
-
34
- export function writeSecureFile(path: string, content: string, mode: number = 0o600): void {
35
- writeFileSync(path, content, 'utf-8')
36
- chmodSync(path, mode)
37
- // Ensure root ownership
38
- try { chownSync(path, 0, 0) } catch {}
39
- }
40
-
41
- export function run(cmd: string): string {
42
- return execSync(cmd, { encoding: 'utf-8' }).trim()
43
- }
44
-
45
- export function isMacOS(): boolean {
46
- return platform() === 'darwin'
47
- }
48
-
49
- export function isLinux(): boolean {
50
- return platform() === 'linux'
51
- }
52
-
53
- export function serviceExists(name: string): boolean {
54
- if (isMacOS()) {
55
- return existsSync(`/Library/LaunchDaemons/${name}.plist`)
56
- }
57
- try {
58
- execSync(`systemctl cat ${name}`, { stdio: 'ignore' })
59
- return true
60
- } catch {
61
- return false
62
- }
63
- }