@fyresmith/hive-server 2.1.0 → 2.3.0
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/README.md +3 -0
- package/cli/main.js +72 -4
- package/cli/tunnel.js +56 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -127,8 +127,11 @@ Diagnostics:
|
|
|
127
127
|
```bash
|
|
128
128
|
hive doctor
|
|
129
129
|
hive status
|
|
130
|
+
hive update
|
|
130
131
|
```
|
|
131
132
|
|
|
133
|
+
`hive update` installs the latest npm release for the current package and then restarts the Hive OS service and cloudflared service when they are installed.
|
|
134
|
+
|
|
132
135
|
## Migration Notes
|
|
133
136
|
|
|
134
137
|
On first `hive setup`, if legacy `server/.env` exists and no `~/.hive/config.json` exists, setup will offer to import legacy env values.
|
package/cli/main.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
|
-
import { access } from 'fs/promises';
|
|
2
|
+
import { access, readFile } from 'fs/promises';
|
|
3
3
|
import { constants as fsConstants } from 'fs';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
4
6
|
import process from 'process';
|
|
5
7
|
import { Command, CommanderError } from 'commander';
|
|
6
8
|
import prompts from 'prompts';
|
|
@@ -26,11 +28,12 @@ import {
|
|
|
26
28
|
writeEnvFile,
|
|
27
29
|
} from './env-file.js';
|
|
28
30
|
import { isPortAvailable, pathExists, validateDomain } from './checks.js';
|
|
29
|
-
import { run } from './exec.js';
|
|
31
|
+
import { run, runInherit } from './exec.js';
|
|
30
32
|
import {
|
|
31
33
|
cloudflaredServiceStatus,
|
|
32
34
|
installCloudflaredService,
|
|
33
35
|
runTunnelForeground,
|
|
36
|
+
restartCloudflaredServiceIfInstalled,
|
|
34
37
|
setupTunnel,
|
|
35
38
|
tunnelStatus,
|
|
36
39
|
getCloudflaredPath,
|
|
@@ -85,6 +88,54 @@ function resolveServiceConfig(config) {
|
|
|
85
88
|
};
|
|
86
89
|
}
|
|
87
90
|
|
|
91
|
+
async function loadPackageMeta() {
|
|
92
|
+
const raw = await readFile(new URL('../package.json', import.meta.url), 'utf-8');
|
|
93
|
+
const parsed = JSON.parse(raw);
|
|
94
|
+
const name = String(parsed?.name ?? '').trim();
|
|
95
|
+
const version = String(parsed?.version ?? '').trim() || 'unknown';
|
|
96
|
+
if (!name) {
|
|
97
|
+
throw new CliError('Could not resolve package name from package.json', EXIT.FAIL);
|
|
98
|
+
}
|
|
99
|
+
return { name, version };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isHiveServiceInstalled({ servicePlatform, serviceName }) {
|
|
103
|
+
if (servicePlatform === 'launchd') {
|
|
104
|
+
return existsSync(join(homedir(), 'Library', 'LaunchAgents', `${serviceName}.plist`));
|
|
105
|
+
}
|
|
106
|
+
return existsSync(`/etc/systemd/system/${serviceName}.service`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function runUpdateFlow(options = {}) {
|
|
110
|
+
section('Hive Update');
|
|
111
|
+
|
|
112
|
+
const { config } = await resolveContext({});
|
|
113
|
+
const pkg = await loadPackageMeta();
|
|
114
|
+
const packageName = requiredOrFallback(options.package, pkg.name);
|
|
115
|
+
const hiveService = resolveServiceConfig(config);
|
|
116
|
+
|
|
117
|
+
info(`Current CLI version: ${pkg.version}`);
|
|
118
|
+
info(`Updating ${packageName} from npm (latest)`);
|
|
119
|
+
await runInherit('npm', ['install', '-g', `${packageName}@latest`]);
|
|
120
|
+
success(`Installed latest ${packageName}`);
|
|
121
|
+
|
|
122
|
+
if (isHiveServiceInstalled(hiveService)) {
|
|
123
|
+
info(`Restarting Hive service: ${hiveService.serviceName}`);
|
|
124
|
+
await restartHiveService(hiveService);
|
|
125
|
+
success('Hive service restarted');
|
|
126
|
+
} else {
|
|
127
|
+
info(`Hive service not installed: ${hiveService.serviceName}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
info('Restarting cloudflared service if installed');
|
|
131
|
+
const tunnelRestart = await restartCloudflaredServiceIfInstalled();
|
|
132
|
+
if (tunnelRestart.installed) {
|
|
133
|
+
success('cloudflared service restarted');
|
|
134
|
+
} else {
|
|
135
|
+
info('cloudflared service not installed');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
88
139
|
async function loadValidatedEnv(envFile, { requireFile = true } = {}) {
|
|
89
140
|
if (requireFile && !existsSync(envFile)) {
|
|
90
141
|
throw new CliError(`Env file not found: ${envFile}. Run: hive env init`, EXIT.FAIL);
|
|
@@ -645,6 +696,12 @@ function registerRootCommands(program) {
|
|
|
645
696
|
.option('--yes', 'non-interactive mode', false)
|
|
646
697
|
.action(runSetupWizard);
|
|
647
698
|
|
|
699
|
+
program
|
|
700
|
+
.command('update')
|
|
701
|
+
.description('Update Hive from npm and restart installed services')
|
|
702
|
+
.option('--package <name>', 'npm package override')
|
|
703
|
+
.action(runUpdateFlow);
|
|
704
|
+
|
|
648
705
|
program
|
|
649
706
|
.command('doctor')
|
|
650
707
|
.description('Run prerequisite and configuration checks')
|
|
@@ -703,6 +760,11 @@ export async function runCli(argv = process.argv) {
|
|
|
703
760
|
registerTunnelCommands(program);
|
|
704
761
|
registerServiceCommands(program);
|
|
705
762
|
|
|
763
|
+
if ((argv?.length ?? 0) <= 2) {
|
|
764
|
+
program.outputHelp();
|
|
765
|
+
return EXIT.OK;
|
|
766
|
+
}
|
|
767
|
+
|
|
706
768
|
program.exitOverride();
|
|
707
769
|
|
|
708
770
|
try {
|
|
@@ -710,7 +772,13 @@ export async function runCli(argv = process.argv) {
|
|
|
710
772
|
return EXIT.OK;
|
|
711
773
|
} catch (err) {
|
|
712
774
|
if (err instanceof CommanderError) {
|
|
713
|
-
if (
|
|
775
|
+
if (
|
|
776
|
+
err.code === 'commander.helpDisplayed'
|
|
777
|
+
|| err.code === 'commander.help'
|
|
778
|
+
|| err.message === '(outputHelp)'
|
|
779
|
+
) {
|
|
780
|
+
return EXIT.OK;
|
|
781
|
+
}
|
|
714
782
|
throw new CliError(err.message, err.exitCode ?? EXIT.FAIL);
|
|
715
783
|
}
|
|
716
784
|
throw err;
|
|
@@ -720,7 +788,7 @@ export async function runCli(argv = process.argv) {
|
|
|
720
788
|
export async function runCliOrExit(argv = process.argv) {
|
|
721
789
|
try {
|
|
722
790
|
const code = await runCli(argv);
|
|
723
|
-
process.
|
|
791
|
+
process.exitCode = code;
|
|
724
792
|
} catch (err) {
|
|
725
793
|
const exitCode = err instanceof CliError ? err.exitCode : EXIT.FAIL;
|
|
726
794
|
const message = err instanceof Error ? err.message : String(err);
|
package/cli/tunnel.js
CHANGED
|
@@ -160,11 +160,65 @@ export async function installCloudflaredService() {
|
|
|
160
160
|
await runInherit('sudo', ['cloudflared', 'service', 'install']);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
function isMissingCloudflaredService(output) {
|
|
164
|
+
const text = String(output ?? '').toLowerCase();
|
|
165
|
+
return (
|
|
166
|
+
text.includes('could not find service')
|
|
167
|
+
|| text.includes('service does not exist')
|
|
168
|
+
|| text.includes('unit cloudflared.service not found')
|
|
169
|
+
|| text.includes('could not be found')
|
|
170
|
+
|| text.includes('not loaded')
|
|
171
|
+
|| text.includes('no such file or directory')
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function restartCloudflaredServiceIfInstalled() {
|
|
176
|
+
const platform = detectPlatform();
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
if (platform === 'darwin') {
|
|
180
|
+
await runInherit('sudo', ['launchctl', 'kickstart', '-k', 'system/com.cloudflare.cloudflared']);
|
|
181
|
+
} else {
|
|
182
|
+
await runInherit('sudo', ['systemctl', 'restart', 'cloudflared']);
|
|
183
|
+
}
|
|
184
|
+
return { installed: true, restarted: true };
|
|
185
|
+
} catch (err) {
|
|
186
|
+
const output = [
|
|
187
|
+
err?.stdout,
|
|
188
|
+
err?.stderr,
|
|
189
|
+
err?.shortMessage,
|
|
190
|
+
err?.message,
|
|
191
|
+
]
|
|
192
|
+
.filter(Boolean)
|
|
193
|
+
.join('\n');
|
|
194
|
+
if (isMissingCloudflaredService(output)) {
|
|
195
|
+
return { installed: false, restarted: false };
|
|
196
|
+
}
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
163
201
|
export async function cloudflaredServiceStatus() {
|
|
164
202
|
const platform = detectPlatform();
|
|
165
203
|
if (platform === 'darwin') {
|
|
166
|
-
const
|
|
167
|
-
|
|
204
|
+
const userList = await run('launchctl', ['list']).catch(() => ({ stdout: '' }));
|
|
205
|
+
if (userList.stdout.toLowerCase().includes('cloudflared')) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const systemLabel = 'com.cloudflare.cloudflared';
|
|
210
|
+
const systemPrint = await run('launchctl', ['print', `system/${systemLabel}`])
|
|
211
|
+
.catch((err) => ({ stdout: err?.stdout ?? '', stderr: err?.stderr ?? '' }));
|
|
212
|
+
const combined = `${systemPrint.stdout}\n${systemPrint.stderr}`.toLowerCase();
|
|
213
|
+
if (
|
|
214
|
+
combined
|
|
215
|
+
&& !combined.includes('could not find service')
|
|
216
|
+
&& !combined.includes('service does not exist')
|
|
217
|
+
) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return existsSync('/Library/LaunchDaemons/com.cloudflare.cloudflared.plist');
|
|
168
222
|
}
|
|
169
223
|
const { stdout } = await run('systemctl', ['is-active', 'cloudflared']);
|
|
170
224
|
return stdout.trim() === 'active';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fyresmith/hive-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Collaborative Obsidian vault server",
|
|
6
6
|
"main": "index.js",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"test": "npm run verify"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"@fyresmith/hive-server": "^2.2.0",
|
|
34
35
|
"chalk": "^5.6.2",
|
|
35
36
|
"chokidar": "^3.6.0",
|
|
36
37
|
"commander": "^13.1.0",
|