Summary
Enhance the jss init command and add complementary CLI tools to provide a polished, professional setup experience. This covers interactive setup improvements, validation, diagnostics, and output generation.
Difficulty: 35/100
Estimated Effort: 3-5 days
Dependencies: #97 (presets)
Current State
The existing jss init command (bin/jss.js:181-244) is functional but basic:
$ jss init
JavaScript Solid Server Setup
Port (3000):
Data directory (./data):
Enable content negotiation (Turtle support)? [y/N]:
Enable WebSocket notifications? [y/N]:
Configure SSL? [y/N]:
Enable built-in Identity Provider? [y/N]:
Configuration saved to: ./config.json
Data directory created: ./data
Run `jss start` to start the server.
Current Limitations
| Issue |
Impact |
| No preset selection |
Users must configure everything manually |
| No input validation |
Invalid ports, non-existent paths accepted |
| No visual feedback |
Plain text, no colors or progress indication |
| No post-setup verification |
No way to verify config works |
| Limited feature coverage |
Only covers ~5 of 30+ options |
| No output generation |
Can't generate docker-compose, systemd, nginx configs |
| Basic readline prompts |
No autocomplete, no list selection |
Proposed Enhancements
1. Preset-First Flow
$ jss init
╭──────────────────────────────────────────╮
│ JavaScript Solid Server Setup │
│ v0.0.81 │
╰──────────────────────────────────────────╯
? Choose a configuration preset:
❯ personal - Single-user production server
community - Multi-user with open registration
private - Multi-user with invite-only access
federation - Full ActivityPub + Nostr support
minimal - Development and testing
developer - All features enabled
custom - Configure everything manually
Using preset: personal ✓
? Customize settings? (y/N): y
2. Improved Prompts with Validation
? Port (443): 80
⚠ Port 80 requires root privileges. Continue? (y/N): n
? Port (443): 8443 ✓
? Data directory (./data): /var/jss/data
✓ Directory exists and is writable
? Domain name: example.com
✓ Valid domain format
? SSL certificate path: ./ssl/cert.pem
✗ File not found: ./ssl/cert.pem
? Create self-signed certificate? (y/N): y
✓ Generated self-signed certificate (valid 365 days)
⚠ Self-signed certificates will show browser warnings
3. Feature Selection with Descriptions
? Select additional features: (space to toggle, enter to confirm)
◉ Identity Provider (IdP)
└─ Email/password authentication, account management
◯ WebSocket Notifications
└─ Real-time updates for Solid apps
◉ Content Negotiation
└─ Turtle/RDF support for Linked Data
◯ Mashlib Data Browser
└─ Built-in file manager UI
◯ ActivityPub Federation
└─ Connect with Mastodon, Plesk, etc.
◯ Nostr Relay
└─ Decentralized social protocol support
◯ Git HTTP Backend
└─ Host git repositories in pods
4. Summary & Confirmation
╭──────────────────────────────────────────╮
│ Configuration Summary │
╰──────────────────────────────────────────╯
Preset: personal
Port: 8443
Data Dir: /var/jss/data
Domain: example.com
SSL: ✓ Enabled (self-signed)
Features:
✓ Identity Provider
✓ Content Negotiation
✗ WebSocket Notifications
✗ ActivityPub
✗ Nostr
Quota: 10GB per pod
? Save configuration? (Y/n): y
✓ Configuration saved to: ./config.json
✓ Data directory created: /var/jss/data
✓ SSL certificates generated
Next steps:
1. Start the server: jss start -c ./config.json
2. Verify setup: jss doctor -c ./config.json
3. View your server: https://example.com:8443
New Commands
jss doctor - Configuration Diagnostics
Verify configuration and system readiness.
$ jss doctor -c ./config.json
╭──────────────────────────────────────────╮
│ JSS Doctor - Configuration Check │
╰──────────────────────────────────────────╯
Configuration
✓ Config file found: ./config.json
✓ Config syntax valid
✓ Using preset: personal
Network
✓ Port 8443 is available
✓ Host 0.0.0.0 is valid
⚠ Port 8443 may require firewall configuration
Storage
✓ Data directory exists: /var/jss/data
✓ Directory is writable
✓ 45GB free disk space
SSL/TLS
✓ Certificate found: ./ssl/cert.pem
✓ Private key found: ./ssl/key.pem
✓ Certificate valid until: 2025-12-01
⚠ Self-signed certificate detected
Features
✓ IdP: Enabled
✓ Content Negotiation: Enabled
✗ Notifications: Disabled
✗ Federation: Disabled
──────────────────────────────────────────
Result: 12 checks passed, 2 warnings, 0 errors
Your configuration looks good! Run `jss start` to begin.
Doctor Checks
| Category |
Check |
| Config |
File exists, valid JSON, no unknown keys |
| Network |
Port available, valid host, firewall hints |
| Storage |
Directory exists, writable, disk space |
| SSL |
Files exist, valid format, expiration date, chain valid |
| Features |
Required dependencies met, valid combinations |
| System |
Node.js version, memory available |
jss generate - Output File Generation
Generate deployment configuration files.
$ jss generate docker -c ./config.json
Generated: docker-compose.yml
Usage:
docker-compose up -d
docker-compose logs -f
Supported Outputs
Docker Compose
$ jss generate docker -c ./config.json -o docker-compose.yml
version: '3.8'
services:
jss:
image: ghcr.io/javascriptsolidserver/jss:latest
ports:
- "8443:8443"
volumes:
- ./data:/app/data
- ./ssl:/app/ssl:ro
environment:
- JSS_PORT=8443
- JSS_ROOT=/app/data
- JSS_SSL_KEY=/app/ssl/key.pem
- JSS_SSL_CERT=/app/ssl/cert.pem
- JSS_IDP=true
- JSS_CONNEG=true
restart: unless-stopped
Systemd Service
$ jss generate systemd -c ./config.json -o jss.service
[Unit]
Description=JavaScript Solid Server
After=network.target
[Service]
Type=simple
User=jss
WorkingDirectory=/opt/jss
ExecStart=/usr/bin/jss start -c /etc/jss/config.json
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
Nginx Reverse Proxy
$ jss generate nginx -c ./config.json --domain example.com
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Caddy
$ jss generate caddy -c ./config.json --domain example.com
example.com {
reverse_proxy localhost:3000
}
Environment File
$ jss generate env -c ./config.json -o .env
JSS_PORT=8443
JSS_ROOT=./data
JSS_SSL_KEY=./ssl/key.pem
JSS_SSL_CERT=./ssl/cert.pem
JSS_IDP=true
JSS_CONNEG=true
jss migrate - Configuration Migration
Migrate configuration from older versions or other servers.
$ jss migrate --from node-solid-server --input .solid/config.json
╭──────────────────────────────────────────╮
│ Configuration Migration │
╰──────────────────────────────────────────╯
Source: Node Solid Server config
Migrating settings:
✓ port: 8443
✓ serverUri → computed from port
✓ root → root
✓ sslKey → sslKey
✓ sslCert → sslCert
⚠ webid: true → idp: true (similar feature)
✗ multiuser: skipped (different implementation)
Saved to: ./config.json
⚠ Manual review recommended for:
- User accounts (not migrated)
- ACL files (may need updates)
Implementation Details
Library Recommendations
| Library |
Purpose |
Size |
| @inquirer/prompts |
Modern interactive prompts |
~50KB |
| chalk |
Terminal colors |
~20KB |
| ora |
Spinners for async operations |
~15KB |
| boxen |
Boxes for headers |
~10KB |
| cli-table3 |
Tables for summaries |
~15KB |
Alternative: Zero-dependency approach
Use built-in readline with ANSI escape codes for colors. Larger code but no dependencies.
Prompt Types Needed
import {
select, // Preset selection
checkbox, // Feature toggles
input, // Text input with validation
confirm, // Yes/no questions
password // Hidden input (for tokens)
} from '@inquirer/prompts';
Validation Functions
const validators = {
port: (value) => {
const port = parseInt(value);
if (isNaN(port) || port < 1 || port > 65535) {
return 'Port must be between 1 and 65535';
}
if (port < 1024) {
return 'warning:Port below 1024 requires root privileges';
}
return true;
},
directory: async (value) => {
const dir = path.resolve(value);
try {
await fs.access(path.dirname(dir), fs.constants.W_OK);
return true;
} catch {
return 'Parent directory is not writable';
}
},
domain: (value) => {
const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/;
if (!domainRegex.test(value) && value !== 'localhost') {
return 'Invalid domain format';
}
return true;
},
sslCert: async (value) => {
try {
const cert = await fs.readFile(value, 'utf8');
if (!cert.includes('BEGIN CERTIFICATE')) {
return 'File does not appear to be a PEM certificate';
}
return true;
} catch {
return 'File not found or not readable';
}
}
};
Error Handling
// Graceful Ctrl+C handling
process.on('SIGINT', () => {
console.log('\n\nSetup cancelled.');
process.exit(0);
});
// Wrap prompts in try/catch
try {
const answers = await runWizard();
} catch (error) {
if (error.name === 'ExitPromptError') {
console.log('\nSetup cancelled.');
process.exit(0);
}
throw error;
}
Industry Comparison
| Tool |
Setup Experience |
| npm init |
Simple prompts, -y for defaults, generates package.json |
| yarn create |
Runs create-* packages, delegates to framework CLIs |
| vite create |
Select framework → variant → done, very fast |
| create-next-app |
Name → TypeScript? → ESLint? → Tailwind? → src/? → App Router? |
| create-react-app |
Just project name, everything else automatic |
| Prisma init |
Datasource selection, generates schema file |
| Docker init |
Detect project type, generate Dockerfile + compose |
| Mastodon setup |
rake mastodon:setup - domain, DB, SMTP, admin account |
Best Practices Observed
- Preset/template first (Vite, Next.js) - Offer starting points
- Sensible defaults (CRA) - Work out of box with minimal input
- Validation with recovery (Inquirer) - Don't fail, offer alternatives
- Summary before commit (Prisma) - Show what will be created
- Next steps guidance (all) - Tell user what to do after
- Skip option (
-y flag) - Allow non-interactive mode
Command Summary
| Command |
Purpose |
Priority |
jss init |
Interactive setup wizard (enhanced) |
P0 |
jss doctor |
Validate configuration and system |
P1 |
jss generate docker |
Generate docker-compose.yml |
P1 |
jss generate systemd |
Generate systemd service file |
P2 |
jss generate nginx |
Generate nginx config |
P2 |
jss generate caddy |
Generate Caddyfile |
P2 |
jss generate env |
Export config as .env |
P2 |
jss migrate |
Migrate from other configs |
P3 |
jss presets |
List/show presets (from #97) |
P1 |
Difficulty Breakdown
| Component |
Difficulty |
Notes |
| Enhanced prompts (inquirer) |
20/100 |
Library does heavy lifting |
| Input validation |
15/100 |
Straightforward checks |
| Visual improvements (chalk, boxen) |
10/100 |
Simple additions |
jss doctor command |
30/100 |
Many checks to implement |
jss generate docker |
20/100 |
Template + variable substitution |
jss generate systemd |
15/100 |
Simple template |
jss generate nginx/caddy |
20/100 |
Need to handle edge cases |
jss migrate |
40/100 |
Need to understand other formats |
| Total |
35/100 |
|
Testing Plan
- Interactive tests (manual): Run through wizard with various inputs
- Non-interactive tests:
jss init -y with env vars
- Validation tests: Invalid inputs, edge cases
- Generation tests: Compare output against expected templates
- Doctor tests: Mock various system states
describe('jss init', () => {
it('should accept preset flag', async () => {
const config = await runInit({ preset: 'personal', yes: true });
expect(config.singleUser).toBe(true);
expect(config.idp).toBe(true);
});
it('should validate port range', async () => {
const result = validators.port('70000');
expect(result).toContain('between 1 and 65535');
});
});
describe('jss doctor', () => {
it('should detect unavailable port', async () => {
// Start something on port 3000
const checks = await runDoctor({ port: 3000 });
expect(checks.network.portAvailable).toBe(false);
});
});
describe('jss generate', () => {
it('should generate valid docker-compose', async () => {
const output = await generateDocker(sampleConfig);
const parsed = yaml.parse(output);
expect(parsed.services.jss).toBeDefined();
});
});
Related Issues
Open Questions
- Should we use
@inquirer/prompts (modern, ESM) or inquirer (legacy, CJS compatible)?
- Should
jss doctor attempt fixes or just report issues?
- Should generated files include comments explaining each setting?
- Should
jss init offer to run jss doctor automatically after setup?
- How to handle
jss init in Docker environments (non-interactive)?
References
Summary
Enhance the
jss initcommand and add complementary CLI tools to provide a polished, professional setup experience. This covers interactive setup improvements, validation, diagnostics, and output generation.Difficulty: 35/100
Estimated Effort: 3-5 days
Dependencies: #97 (presets)
Current State
The existing
jss initcommand (bin/jss.js:181-244) is functional but basic:Current Limitations
Proposed Enhancements
1. Preset-First Flow
2. Improved Prompts with Validation
3. Feature Selection with Descriptions
4. Summary & Confirmation
New Commands
jss doctor- Configuration DiagnosticsVerify configuration and system readiness.
Doctor Checks
jss generate- Output File GenerationGenerate deployment configuration files.
$ jss generate docker -c ./config.json Generated: docker-compose.yml Usage: docker-compose up -d docker-compose logs -fSupported Outputs
Docker Compose
Systemd Service
Nginx Reverse Proxy
Caddy
Environment File
jss migrate- Configuration MigrationMigrate configuration from older versions or other servers.
$ jss migrate --from node-solid-server --input .solid/config.json ╭──────────────────────────────────────────╮ │ Configuration Migration │ ╰──────────────────────────────────────────╯ Source: Node Solid Server config Migrating settings: ✓ port: 8443 ✓ serverUri → computed from port ✓ root → root ✓ sslKey → sslKey ✓ sslCert → sslCert ⚠ webid: true → idp: true (similar feature) ✗ multiuser: skipped (different implementation) Saved to: ./config.json ⚠ Manual review recommended for: - User accounts (not migrated) - ACL files (may need updates)Implementation Details
Library Recommendations
Alternative: Zero-dependency approach
Use built-in
readlinewith ANSI escape codes for colors. Larger code but no dependencies.Prompt Types Needed
Validation Functions
Error Handling
Industry Comparison
-yfor defaults, generates package.jsonrake mastodon:setup- domain, DB, SMTP, admin accountBest Practices Observed
-yflag) - Allow non-interactive modeCommand Summary
jss initjss doctorjss generate dockerjss generate systemdjss generate nginxjss generate caddyjss generate envjss migratejss presetsDifficulty Breakdown
jss doctorcommandjss generate dockerjss generate systemdjss generate nginx/caddyjss migrateTesting Plan
jss init -ywith env varsRelated Issues
Open Questions
@inquirer/prompts(modern, ESM) orinquirer(legacy, CJS compatible)?jss doctorattempt fixes or just report issues?jss initoffer to runjss doctorautomatically after setup?jss initin Docker environments (non-interactive)?References