diff --git a/macapp/src/install.ts b/macapp/src/install.ts index d8036542b..07a09dc06 100644 --- a/macapp/src/install.ts +++ b/macapp/src/install.ts @@ -1,21 +1,35 @@ import * as fs from 'fs' -import { exec as cbExec } from 'child_process' +import { spawn } from 'child_process' import * as path from 'path' -import { promisify } from 'util' const app = process && process.type === 'renderer' ? require('@electron/remote').app : require('electron').app const ollama = app.isPackaged ? path.join(process.resourcesPath, 'ollama') : path.resolve(process.cwd(), '..', 'ollama') -const exec = promisify(cbExec) const symlinkPath = '/usr/local/bin/ollama' -export function installed() { +export function installed(): boolean { return fs.existsSync(symlinkPath) && fs.readlinkSync(symlinkPath) === ollama } -export async function install() { - const command = `do shell script "mkdir -p ${path.dirname( - symlinkPath - )} && ln -F -s \\"${ollama}\\" \\"${symlinkPath}\\"" with administrator privileges` - - await exec(`osascript -e '${command}'`) +function validPath(targetPath: string): boolean { + const normalized = path.normalize(targetPath) + return !(/[;&|`$(){}[\]<>]/.test(normalized) || normalized.includes('..')) +} + +export async function install(): Promise { + if (!validPath(ollama) || !validPath(symlinkPath)) { + throw new Error('Invalid path format') + } + + await fs.promises.mkdir(path.dirname(symlinkPath), { recursive: true }) + .catch(err => err.code === 'EEXIST' ? null : Promise.reject(err)) + + const process = spawn('osascript', [ + '-e', + `do shell script "ln -F -s '${path.normalize(ollama)}' '${path.normalize(symlinkPath)}'" with administrator privileges` + ]) + + await new Promise((resolve, reject) => { + process.on('error', reject) + process.on('close', code => code === 0 ? resolve() : reject(new Error(`Failed with code ${code}`))) + }) }