Skip to content

Commit

Permalink
chore: adjust sudo handling
Browse files Browse the repository at this point in the history
chore: wip
  • Loading branch information
chrisbbreuer committed Dec 7, 2024
1 parent 5d1effc commit dd194be
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 114 deletions.
1 change: 0 additions & 1 deletion bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ cli
.example('reverse-proxy start --from localhost:5173 --to my-project.test --key-path /absolute/path/to/key --cert-path /absolute/path/to/cert')
.action(async (options?: ReverseProxyOption) => {
if (!options?.from || !options.to) {
console.log('in here', config)
return startProxies(config)
}

Expand Down
8 changes: 4 additions & 4 deletions reverse-proxy.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ const config: ReverseProxyOptions = {
proxies: [
{
from: 'localhost:5173',
to: 'test.localhost',
},
{
from: 'localhost:5174',
to: 'test.local',
},
// {
// from: 'localhost:5174',
// to: 'test.local',
// },
],
verbose: false,
}
Expand Down
165 changes: 58 additions & 107 deletions src/hosts.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,29 @@
import { spawn } from 'node:child_process'
import { exec, spawn } from 'node:child_process'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import process from 'node:process'
import { promisify } from 'node:util'
import { log } from '@stacksjs/cli'
import { debugLog } from './utils'

const execAsync = promisify(exec)

export const hostsFilePath: string = process.platform === 'win32'
? path.join(process.env.windir || 'C:\\Windows', 'System32', 'drivers', 'etc', 'hosts')
: '/etc/hosts'

async function sudoWrite(operation: 'append' | 'write', content: string): Promise<void> {
return new Promise((resolve, reject) => {
if (process.platform === 'win32') {
reject(new Error('Administrator privileges required on Windows'))
return
}

const tmpFile = path.join(os.tmpdir(), 'hosts.tmp')
// Single function to execute sudo commands
async function execSudo(command: string): Promise<void> {
if (process.platform === 'win32')
throw new Error('Administrator privileges required on Windows')

try {
if (operation === 'append') {
// For append, read current content first
const currentContent = fs.readFileSync(hostsFilePath, 'utf8')
fs.writeFileSync(tmpFile, currentContent + content, 'utf8')
}
else {
// For write, just write the new content
fs.writeFileSync(tmpFile, content, 'utf8')
}

const sudo = spawn('sudo', ['cp', tmpFile, hostsFilePath])

sudo.on('close', (code) => {
try {
fs.unlinkSync(tmpFile)
if (code === 0)
resolve()
else
reject(new Error(`sudo process exited with code ${code}`))
}
catch (err) {
reject(err)
}
})

sudo.on('error', (err) => {
try {
fs.unlinkSync(tmpFile)
}
catch { }
reject(err)
})
}
catch (err) {
reject(err)
}
})
try {
await execAsync(`sudo ${command}`)
}
catch (error) {
throw new Error(`Failed to execute sudo command: ${(error as Error).message}`)
}
}

export async function addHosts(hosts: string[], verbose?: boolean): Promise<void> {
Expand Down Expand Up @@ -85,40 +52,32 @@ export async function addHosts(hosts: string[], verbose?: boolean): Promise<void
`\n# Added by rpx\n127.0.0.1 ${host}\n::1 ${host}`,
).join('\n')

const tmpFile = path.join(os.tmpdir(), 'hosts.tmp')
await fs.promises.writeFile(tmpFile, existingContent + hostEntries, 'utf8')

try {
// Try normal write first
await fs.promises.appendFile(hostsFilePath, hostEntries, { flag: 'a' })
await execSudo(`cp "${tmpFile}" "${hostsFilePath}"`)
log.success(`Added new hosts: ${newEntries.join(', ')}`)
}
catch (writeErr) {
if ((writeErr as NodeJS.ErrnoException).code === 'EACCES') {
debugLog('hosts', 'Permission denied, attempting with sudo', verbose)
try {
await sudoWrite('append', hostEntries)
log.success(`Added new hosts with sudo: ${newEntries.join(', ')}`)
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (sudoErr) {
log.error('Failed to modify hosts file automatically')
log.warn('Please add these entries to your hosts file manually:')
hostEntries.split('\n').forEach(entry => log.warn(entry))

if (process.platform === 'win32') {
log.warn('\nOn Windows:')
log.warn('1. Run notepad as administrator')
log.warn('2. Open C:\\Windows\\System32\\drivers\\etc\\hosts')
}
else {
log.warn('\nOn Unix systems:')
log.warn(`sudo nano ${hostsFilePath}`)
}

throw new Error('Failed to modify hosts file: manual intervention required')
}
catch (error) {
log.error('Failed to modify hosts file automatically')
log.warn('Please add these entries to your hosts file manually:')
hostEntries.split('\n').forEach(entry => log.warn(entry))

if (process.platform === 'win32') {
log.warn('\nOn Windows:')
log.warn('1. Run notepad as administrator')
log.warn('2. Open C:\\Windows\\System32\\drivers\\etc\\hosts')
}
else {
throw writeErr
log.warn('\nOn Unix systems:')
log.warn(`sudo nano ${hostsFilePath}`)
}

throw new Error('Failed to modify hosts file: manual intervention required')
}
finally {
fs.unlinkSync(tmpFile)
}
}
catch (err) {
Expand Down Expand Up @@ -153,43 +112,36 @@ export async function removeHosts(hosts: string[], verbose?: boolean): Promise<v
// Ensure file ends with a single newline
const newContent = `${filteredLines.join('\n')}\n`

const tmpFile = path.join(os.tmpdir(), 'hosts.tmp')
await fs.promises.writeFile(tmpFile, newContent, 'utf8')

try {
await fs.promises.writeFile(hostsFilePath, newContent)
await execSudo(`cp "${tmpFile}" "${hostsFilePath}"`)
log.success('Hosts removed successfully')
}
catch (writeErr) {
if ((writeErr as NodeJS.ErrnoException).code === 'EACCES') {
debugLog('hosts', 'Permission denied, attempting with sudo', verbose)
try {
await sudoWrite('write', newContent)
log.success('Hosts removed successfully with sudo')
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (sudoErr) {
log.error('Failed to modify hosts file automatically')
log.warn('Please remove these entries from your hosts file manually:')
hosts.forEach((host) => {
log.warn('# Added by rpx')
log.warn(`127.0.0.1 ${host}`)
log.warn(`::1 ${host}`)
})

if (process.platform === 'win32') {
log.warn('\nOn Windows:')
log.warn('1. Run notepad as administrator')
log.warn('2. Open C:\\Windows\\System32\\drivers\\etc\\hosts')
}
else {
log.warn('\nOn Unix systems:')
log.warn(`sudo nano ${hostsFilePath}`)
}

throw new Error('Failed to modify hosts file: manual intervention required')
}
catch (error) {
log.error('Failed to modify hosts file automatically')
log.warn('Please remove these entries from your hosts file manually:')
hosts.forEach((host) => {
log.warn('# Added by rpx')
log.warn(`127.0.0.1 ${host}`)
log.warn(`::1 ${host}`)
})

if (process.platform === 'win32') {
log.warn('\nOn Windows:')
log.warn('1. Run notepad as administrator')
log.warn('2. Open C:\\Windows\\System32\\drivers\\etc\\hosts')
}
else {
throw writeErr
log.warn('\nOn Unix systems:')
log.warn(`sudo nano ${hostsFilePath}`)
}

throw new Error('Failed to modify hosts file: manual intervention required')
}
finally {
fs.unlinkSync(tmpFile)
}
}
catch (err) {
Expand All @@ -199,7 +151,6 @@ export async function removeHosts(hosts: string[], verbose?: boolean): Promise<v
}
}

// Helper function to check if hosts exist
export async function checkHosts(hosts: string[], verbose?: boolean): Promise<boolean[]> {
debugLog('hosts', `Checking hosts: ${hosts}`, verbose)

Expand Down
2 changes: 0 additions & 2 deletions src/https.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,6 @@ export async function generateCertificate(options: ReverseProxyOptions): Promise

// Generate the host certificate with all domains
const hostConfig = httpsConfig(options, options.verbose)
// eslint-disable-next-line no-console
console.log('hostConfig', hostConfig)
log.info(`Generating host certificate for: ${domains.join(', ')}`)

const hostCert = await generateCert({
Expand Down

0 comments on commit dd194be

Please sign in to comment.