Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
chore: wip

chore: wip

chore: wip
  • Loading branch information
chrisbbreuer committed Nov 14, 2024
1 parent c095ad1 commit a10ee56
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 373 deletions.
45 changes: 36 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
- Custom Domains _(with wildcard support)_
- Dependency-Free
- Zero-Config Setup
<!-- - SSL Support _(HTTPS by default)_ -->
<!-- - Auto HTTP-to-HTTPS Redirection -->
<!-- - `/etc/hosts` Management _(auto-updating)_ -->
- SSL Support _(HTTPS by default)_
- Auto HTTP-to-HTTPS Redirection

## Install

Expand All @@ -41,13 +40,29 @@ There are two ways of using this reverse proxy: _as a library or as a CLI._

Given the npm package is installed:

```js
```ts
import { startProxy } from '@stacksjs/reverse-proxy'

startProxy({
export interface ReverseProxyConfig {
from: string // domain to proxy from, defaults to localhost:3000
to: string // domain to proxy to, defaults to stacks.localhost
key?: string // content of the key
keyPath?: string // absolute path to the key
cert?: string // content of the cert
certPath?: string // absolute path to the cert
caCertPath?: string // absolute path to the ca cert
https: boolean // use https, defaults to true, also redirects http to https
tls: TlsConfig // the tls configuration
verbose: boolean // log verbose output, defaults to false
}

const config: ReverseProxyOptions = {
from: 'localhost:3000',
to: 'my-project.localhost' // or try 'my-project.test'
})
to: 'my-project.localhost',
https: true,
}

startProxy(config)
```

### CLI
Expand All @@ -65,9 +80,21 @@ The Reverse Proxy can be configured using a `reverse-proxy.config.ts` _(or `reve

```ts
// reverse-proxy.config.ts (or reverse-proxy.config.js)
export default {
'localhost:3000': 'stacks.localhost'
import type { ReverseProxyOptions } from './src/types'
import os from 'node:os'
import path from 'node:path'

const config: ReverseProxyOptions = {
from: 'localhost:5173',
to: 'stacks.localhost',
keyPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.crt.key`),
certPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.crt`),
caCertPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.ca.crt`),
https: true,
verbose: false,
}

export default config
```

_Then run:_
Expand Down
62 changes: 1 addition & 61 deletions bin/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { ReverseProxyOption } from '../src/types'
import os from 'node:os'
import { CAC, log } from '@stacksjs/cli'
import { readFileSync, writeFileSync } from '@stacksjs/storage'
import { CAC } from '@stacksjs/cli'
import { version } from '../package.json'
import { config } from '../src/config'
import { startProxy } from '../src/start'
Expand Down Expand Up @@ -29,64 +27,6 @@ cli
})
})

cli
.command(
'update:etc-hosts',
'Update the /etc/hosts file with the proxy domains. Please note, this command requires sudo/admin permissions.',
)
.alias('update-etc-hosts')
.example('sudo reverse-proxy update:etc-hosts')
.example('sudo reverse-proxy update-etc-hosts')
.action(async () => {
log.info('Ensuring /etc/hosts file covers the proxy domain/s...')

const hostsFilePath = os.platform() === 'win32' ? 'C:\\Windows\\System32\\drivers\\etc\\hosts' : '/etc/hosts'

if (config && typeof config === 'object') {
const entriesToAdd = Object.entries(config).map(
([from, to]) => `127.0.0.1 ${to} # reverse-proxy mapping for ${from}`,
)
// Ensure "127.0.0.1 localhost" is in the array
entriesToAdd.push('127.0.0.1 localhost # essential localhost mapping')

try {
let currentHostsContent = readFileSync(hostsFilePath, 'utf8')
let updated = false

for (const entry of entriesToAdd) {
const [ip, host] = entry.split(' ', 2)
// Use a regex to match the line with any amount of whitespace between IP and host
const regex = new RegExp(`^${ip}\\s+${host.split(' ')[0]}(\\s|$)`, 'm')
// Check if the entry (domain) is already in the file
if (!regex.test(currentHostsContent)) {
// If not, append it
currentHostsContent += `\n${entry}`
updated = true
}
else {
log.info(`Entry for ${host} already exists in the hosts file.`)
}
}

if (updated) {
writeFileSync(hostsFilePath, currentHostsContent, 'utf8')
log.success('Hosts file updated with latest proxy domains.')
}
else {
log.info('No new entries were added to the hosts file.')
}
}
catch (error: unknown) {
if ((error as NodeJS.ErrnoException).code === 'EACCES')
console.error('Permission denied. Please run this command with administrative privileges.')
else console.error(`An error occurred: ${(error as NodeJS.ErrnoException).message}`)
}
}
else {
console.log('No proxies found. Is your config configured properly?')
}
})

cli.command('version', 'Show the version of the Reverse Proxy CLI').action(() => {
console.log(version)
})
Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"preview:docs": "vitepress preview docs"
},
"dependencies": {
"@stacksjs/tlsx": "^0.3.3"
"@stacksjs/tlsx": "^0.4.3"
},
"devDependencies": {
"@stacksjs/cli": "^0.68.2",
Expand Down
12 changes: 6 additions & 6 deletions reverse-proxy.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { ReverseProxyOptions } from './src/types'
import os from 'node:os'
import path from 'node:path'
// import os from 'node:os'
// import path from 'node:path'

const config: ReverseProxyOptions = {
from: 'localhost:5173',
to: 'stacks.localhost',
keyPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.crt.key`),
certPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.crt`),
caCertPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.ca.crt`),
httpsRedirect: false,
// keyPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.crt.key`),
// certPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.crt`),
// caCertPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.ca.crt`),
https: true,
verbose: false,
}

Expand Down
45 changes: 45 additions & 0 deletions src/certificate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { TlsOption } from '@stacksjs/tlsx'
import * as fs from 'node:fs'
import { log } from '@stacksjs/cli'
import { addCertToSystemTrustStoreAndSaveCert, createRootCA, generateCert } from '@stacksjs/tlsx'
import { config } from './config'

export async function generateCertficate(options?: TlsOption): Promise<{ key: string, cert: string, ca: string }> {
const conf = config?.tls
const mergedOptions = {
...conf,
...(options || {}),
}

const domain = mergedOptions.domain || 'localhost'
log.info(`Generating a self-signed SSL certificate for: ${domain}`)

const caCert = await createRootCA()
const hostCert = await generateCert({
hostCertCN: mergedOptions?.commonName ?? mergedOptions.commonName ?? domain,
domain,
altNameIPs: mergedOptions?.altNameIPs?.filter((ip): ip is string => ip !== undefined) ?? [],
altNameURIs: mergedOptions?.altNameURIs?.filter((uri): uri is string => uri !== undefined) ?? [],
countryName: mergedOptions.countryName,
stateName: mergedOptions.stateName,
localityName: mergedOptions.localityName,
organizationName: mergedOptions.organizationName,
validityDays: mergedOptions.validityDays,
rootCAObject: {
certificate: caCert.certificate,
privateKey: caCert.privateKey,
},
})

await addCertToSystemTrustStoreAndSaveCert(hostCert, caCert.certificate)

log.success('Certificate generated')

const [key, cert, ca] = await Promise.all([
fs.promises.readFile(mergedOptions.keyPath, 'utf8'),
fs.promises.readFile(mergedOptions.certPath, 'utf8'),
fs.promises.readFile(mergedOptions.certPath, 'utf8'),
])

return { key, cert, ca }
}
21 changes: 20 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { ReverseProxyConfig } from './types'
import os from 'node:os'
import path from 'node:path'
import { loadConfig } from 'bun-config'

// eslint-disable-next-line antfu/no-top-level-await
Expand All @@ -7,7 +9,24 @@ export const config: ReverseProxyConfig = await loadConfig({
defaultConfig: {
from: 'localhost:5173',
to: 'stacks.localhost',
httpsRedirect: false,
https: true,
tls: {
altNameIPs: ['127.0.0.1'],
altNameURIs: ['localhost'],
organizationName: 'stacksjs.org',
countryName: 'US',
stateName: 'California',
localityName: 'Playa Vista',
commonName: 'stacks.localhost',
validityDays: 180,
hostCertCN: 'stacks.localhost',
domain: 'localhost',
rootCAObject: { certificate: '', privateKey: '' },
caCertPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.ca.crt`),
certPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.crt`),
keyPath: path.join(os.homedir(), '.stacks', 'ssl', `stacks.localhost.crt.key`),
verbose: false,
},
verbose: true,
},
})
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './certificate'
export { config } from './config'
export * from './start'
export * from './types'
Loading

0 comments on commit a10ee56

Please sign in to comment.