ash-sh 0.1.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/.env.example +28 -0
- package/bin/ash.js +15 -0
- package/package.json +30 -0
- package/setup/index.js +110 -0
package/.env.example
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# ash — Self Hosting Harness
|
|
2
|
+
# Copy to .env and fill in your values: cp .env.example .env
|
|
3
|
+
|
|
4
|
+
# --- Domain ---
|
|
5
|
+
ASH_DOMAIN=yourdomain.com
|
|
6
|
+
ASH_EMAIL=you@example.com
|
|
7
|
+
|
|
8
|
+
# --- DNS Provider (for wildcard TLS) ---
|
|
9
|
+
# Supported: route53, cloudflare
|
|
10
|
+
ASH_DNS_PROVIDER=route53
|
|
11
|
+
AWS_REGION=us-east-1
|
|
12
|
+
AWS_ACCESS_KEY_ID=
|
|
13
|
+
AWS_SECRET_ACCESS_KEY=
|
|
14
|
+
# CLOUDFLARE_API_TOKEN=
|
|
15
|
+
|
|
16
|
+
# --- Tailscale (optional, for private network) ---
|
|
17
|
+
# TAILSCALE_AUTH_KEY=
|
|
18
|
+
|
|
19
|
+
# --- LLM Providers ---
|
|
20
|
+
OLLAMA_API_KEY=ollama
|
|
21
|
+
# OPENROUTER_API_KEY=
|
|
22
|
+
# OPENAI_API_KEY=
|
|
23
|
+
# ANTHROPIC_API_KEY=
|
|
24
|
+
|
|
25
|
+
# --- Services auth ---
|
|
26
|
+
WEBDAV_USER=
|
|
27
|
+
WEBDAV_PASS_HASH=
|
|
28
|
+
CODE_SERVER_PASSWORD=
|
package/bin/ash.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const command = process.argv[2] || 'setup';
|
|
8
|
+
|
|
9
|
+
if (command === 'setup') {
|
|
10
|
+
await import(join(__dirname, '..', 'setup', 'index.js'));
|
|
11
|
+
} else {
|
|
12
|
+
console.log(`ash: unknown command '${command}'`);
|
|
13
|
+
console.log('Usage: ash setup');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ash-sh",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ash — Self Hosting Harness",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ash": "./bin/ash.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/ash.js",
|
|
11
|
+
"setup": "node setup/index.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"self-hosted",
|
|
15
|
+
"ai",
|
|
16
|
+
"caddy",
|
|
17
|
+
"ollama",
|
|
18
|
+
"dev-environment",
|
|
19
|
+
"cli"
|
|
20
|
+
],
|
|
21
|
+
"author": "Aman Agrawal <workwithamaagra@gmail.com>",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@clack/prompts": "^0.11.0",
|
|
28
|
+
"open": "^10.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/setup/index.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
|
+
import open from 'open';
|
|
5
|
+
import { setTimeout } from 'timers/promises';
|
|
6
|
+
|
|
7
|
+
p.intro('ash — Self Hosting Harness');
|
|
8
|
+
|
|
9
|
+
const name = await p.text({
|
|
10
|
+
message: 'What is your name?',
|
|
11
|
+
placeholder: 'your name',
|
|
12
|
+
validate: (val) => {
|
|
13
|
+
if (!val) return 'Name is required';
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (p.isCancel(name)) {
|
|
18
|
+
p.cancel('Setup cancelled.');
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const domain = await p.text({
|
|
23
|
+
message: 'Your domain:',
|
|
24
|
+
placeholder: 'yourdomain.com',
|
|
25
|
+
validate: (val) => {
|
|
26
|
+
if (!val) return 'Domain is required';
|
|
27
|
+
if (!val.includes('.')) return 'Enter a valid domain';
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (p.isCancel(domain)) {
|
|
32
|
+
p.cancel('Setup cancelled.');
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const dnsProvider = await p.select({
|
|
37
|
+
message: 'DNS provider (for wildcard TLS):',
|
|
38
|
+
options: [
|
|
39
|
+
{ value: 'route53', label: 'AWS Route 53' },
|
|
40
|
+
{ value: 'cloudflare', label: 'Cloudflare' },
|
|
41
|
+
{ value: 'manual', label: 'Manual (I\'ll configure DNS myself)' },
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (p.isCancel(dnsProvider)) {
|
|
46
|
+
p.cancel('Setup cancelled.');
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const services = await p.multiselect({
|
|
51
|
+
message: 'Which services to enable?',
|
|
52
|
+
options: [
|
|
53
|
+
{ value: 'code-server', label: 'VS Code Server', hint: 'code.domain.com' },
|
|
54
|
+
{ value: 'ollama', label: 'Ollama (local LLM)', hint: 'local inference' },
|
|
55
|
+
{ value: 'dsh', label: 'DeepSeek Harness', hint: 'dsh.domain.com' },
|
|
56
|
+
{ value: 'webdav', label: 'Obsidian WebDAV', hint: 'obsidian.domain.com' },
|
|
57
|
+
],
|
|
58
|
+
required: false,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (p.isCancel(services)) {
|
|
62
|
+
p.cancel('Setup cancelled.');
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const tailscale = await p.confirm({
|
|
67
|
+
message: 'Enable Tailscale (private network)?',
|
|
68
|
+
initialValue: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (p.isCancel(tailscale)) {
|
|
72
|
+
p.cancel('Setup cancelled.');
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Simulate browser OAuth flow
|
|
77
|
+
if (dnsProvider === 'route53') {
|
|
78
|
+
const authAws = await p.confirm({
|
|
79
|
+
message: 'Authenticate with AWS? (opens browser)',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (authAws && !p.isCancel(authAws)) {
|
|
83
|
+
const s = p.spinner();
|
|
84
|
+
s.start('Opening browser for AWS authentication...');
|
|
85
|
+
// In real impl: open OAuth URL, start local server for callback
|
|
86
|
+
// await open('https://signin.aws.amazon.com/...');
|
|
87
|
+
await setTimeout(1500);
|
|
88
|
+
s.stop('AWS authentication placeholder (will connect in impl)');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Summary
|
|
93
|
+
p.note(
|
|
94
|
+
[
|
|
95
|
+
`Name: ${name}`,
|
|
96
|
+
`Domain: ${domain}`,
|
|
97
|
+
`DNS: ${dnsProvider}`,
|
|
98
|
+
`Services: ${services.length ? services.join(', ') : 'none'}`,
|
|
99
|
+
`Tailscale: ${tailscale ? 'yes' : 'no'}`,
|
|
100
|
+
].join('\n'),
|
|
101
|
+
'Configuration'
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Hand off to impl
|
|
105
|
+
const s = p.spinner();
|
|
106
|
+
s.start('Initializing...');
|
|
107
|
+
await setTimeout(1000);
|
|
108
|
+
s.stop('Ready');
|
|
109
|
+
|
|
110
|
+
p.outro(`Hello ${name}! Your harness will be at https://*.${domain}`);
|