diff --git a/scripts/nss/libnss_toes.c b/scripts/nss/libnss_toes.c index 25feadf..1370214 100644 --- a/scripts/nss/libnss_toes.c +++ b/scripts/nss/libnss_toes.c @@ -1,8 +1,8 @@ /* * libnss_toes - NSS module that resolves unknown usernames to a shared account. * - * Any username not found in /etc/passwd gets mapped to a toes-guest entry - * with home=/home/toes and shell=/usr/local/bin/toes. + * Any username not found in /etc/passwd gets mapped to a toes-cli entry + * with home=/home/toes-cli and shell=/usr/local/bin/toes. * * Build: * gcc -shared -o libnss_toes.so.2 libnss_toes.c -fPIC @@ -17,14 +17,14 @@ #include #include -#define GUEST_UID 65534 -#define GUEST_GID 65534 -#define GUEST_HOME "/home/toes" +#define GUEST_UID 3001 +#define GUEST_GID 3001 +#define GUEST_HOME "/home/toes-cli" #define GUEST_SHELL "/usr/local/bin/toes" -#define GUEST_GECOS "Toes Guest" +#define GUEST_GECOS "Toes CLI" static const char *skip_users[] = { - "root", "toes", "nobody", "daemon", "bin", "sys", "sync", + "root", "toes", "toes-cli", "nobody", "daemon", "bin", "sys", "sync", "games", "man", "lp", "mail", "news", "uucp", "proxy", "www-data", "backup", "list", "irc", "gnats", "systemd-network", "systemd-resolve", "messagebus", "sshd", "_apt", diff --git a/scripts/setup-ssh.sh b/scripts/setup-ssh.sh index 723ced2..89c2481 100755 --- a/scripts/setup-ssh.sh +++ b/scripts/setup-ssh.sh @@ -75,11 +75,19 @@ if [ ! -f "$TOES_SHELL" ]; then echo " Create it with: ln -sf /path/to/toes/cli $TOES_SHELL" fi -# Ensure /home/toes exists for guest sessions -if [ ! -d /home/toes ]; then - mkdir -p /home/toes - chmod 755 /home/toes - echo " Created /home/toes" +# Create toes-cli system user for guest SSH sessions +if ! id toes-cli &>/dev/null; then + useradd --system --uid 3001 --home-dir /home/toes-cli --shell /usr/local/bin/toes --create-home toes-cli + echo " Created toes-cli user" +else + echo " toes-cli user already exists" +fi + +# Ensure /home/toes-cli exists for guest sessions +if [ ! -d /home/toes-cli ]; then + mkdir -p /home/toes-cli + chmod 755 /home/toes-cli + echo " Created /home/toes-cli" fi # 6. Restart sshd @@ -87,4 +95,5 @@ echo " Restarting sshd..." systemctl restart sshd || service ssh restart || true echo "==> Done. Any SSH user will now get the toes CLI." +echo " SSH users are mapped to the toes-cli account (UID 3001)." echo " toes@toes.local still gets a regular shell." diff --git a/src/cli/commands/cron.ts b/src/cli/commands/cron.ts index 2f82ad8..976e810 100644 --- a/src/cli/commands/cron.ts +++ b/src/cli/commands/cron.ts @@ -1,6 +1,6 @@ import type { LogLine } from '@types' import color from 'kleur' -import { get, handleError, makeUrl, post } from '../http' +import { activeSignal, get, handleError, makeUrl, post } from '../http' import { resolveAppName } from '../name' interface CronJobSummary { @@ -195,7 +195,7 @@ const printCronLog = (line: LogLine) => async function tailCronLogs(app: string, grep?: string) { try { const url = makeUrl(`/api/apps/${app}/logs/stream`) - const res = await fetch(url) + const res = await fetch(url, { signal: activeSignal }) if (!res.ok) { console.error(`App not found: ${app}`) return diff --git a/src/cli/commands/logs.ts b/src/cli/commands/logs.ts index 23798d0..f9f09fb 100644 --- a/src/cli/commands/logs.ts +++ b/src/cli/commands/logs.ts @@ -1,5 +1,5 @@ import type { LogLine } from '@types' -import { get, handleError, makeUrl } from '../http' +import { activeSignal, get, handleError, makeUrl } from '../http' import { resolveAppName } from '../name' interface LogOptions { @@ -120,7 +120,7 @@ export async function logApp(arg: string | undefined, options: LogOptions) { export async function tailLogs(name: string, grep?: string) { try { const url = makeUrl(`/api/apps/${name}/logs/stream`) - const res = await fetch(url) + const res = await fetch(url, { signal: activeSignal }) if (!res.ok) { console.error(`App not found: ${name}`) return diff --git a/src/cli/commands/sync.ts b/src/cli/commands/sync.ts index d5b0e09..4cb7761 100644 --- a/src/cli/commands/sync.ts +++ b/src/cli/commands/sync.ts @@ -5,7 +5,7 @@ import color from 'kleur' import { diffLines } from 'diff' import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, unlinkSync, watch, writeFileSync } from 'fs' import { dirname, join } from 'path' -import { del, download, get, getManifest, handleError, makeUrl, post, put } from '../http' +import { activeSignal, del, download, get, getManifest, handleError, makeUrl, post, put } from '../http' import { confirm, prompt } from '../prompts' import { getAppName, getAppPackage, isApp, resolveAppName } from '../name' @@ -592,7 +592,7 @@ export async function syncApp() { const url = makeUrl(`/api/sync/apps/${appName}/watch`) let res: Response try { - res = await fetch(url) + res = await fetch(url, { signal: activeSignal }) if (!res.ok) { console.error(`Failed to connect to server: ${res.status} ${res.statusText}`) watcher.close() @@ -967,7 +967,7 @@ interface VersionsResponse { async function getVersions(appName: string): Promise { try { - const res = await fetch(makeUrl(`/api/sync/apps/${appName}/versions`)) + const res = await fetch(makeUrl(`/api/sync/apps/${appName}/versions`), { signal: activeSignal }) if (res.status === 404) { console.error(`App not found: ${appName}`) return null diff --git a/src/cli/http.ts b/src/cli/http.ts index 85a642f..7907c27 100644 --- a/src/cli/http.ts +++ b/src/cli/http.ts @@ -1,6 +1,8 @@ import type { Manifest } from '@types' const DEFAULT_HOST = process.env.DEV ? 'http://localhost:3000' : 'http://toes.local' +export let activeSignal: AbortSignal | undefined + const normalizeUrl = (url: string) => url.startsWith('http://') || url.startsWith('https://') ? url : `http://${url}` @@ -17,6 +19,10 @@ export const HOST = process.env.TOES_URL ? normalizeUrl(process.env.TOES_URL) : DEFAULT_HOST +export const clearSignal = () => { activeSignal = undefined } + +export const setSignal = (signal: AbortSignal) => { activeSignal = signal } + export function makeUrl(path: string): string { return `${HOST}${path}` } @@ -36,7 +42,7 @@ export function handleError(error: unknown): void { export async function get(url: string): Promise { try { - const res = await fetch(makeUrl(url)) + const res = await fetch(makeUrl(url), { signal: activeSignal }) if (!res.ok) { const text = await res.text() const msg = tryParseError(text) ?? `${res.status} ${res.statusText}` @@ -50,7 +56,7 @@ export async function get(url: string): Promise { export async function getManifest(appName: string): Promise<{ exists: boolean, manifest?: Manifest, version?: string } | null> { try { - const res = await fetch(makeUrl(`/api/sync/apps/${appName}/manifest`)) + const res = await fetch(makeUrl(`/api/sync/apps/${appName}/manifest`), { signal: activeSignal }) if (res.status === 404) return { exists: false } if (!res.ok) throw new Error(`${res.status} ${res.statusText}`) const data = await res.json() @@ -68,6 +74,7 @@ export async function post(url: string, body?: B): Promise { try { const fullUrl = makeUrl(url) - const res = await fetch(fullUrl) + const res = await fetch(fullUrl, { signal: activeSignal }) if (!res.ok) { const text = await res.text() const msg = tryParseError(text) ?? `${res.status} ${res.statusText}` @@ -117,6 +125,7 @@ export async function del(url: string): Promise { try { const res = await fetch(makeUrl(url), { method: 'DELETE', + signal: activeSignal, }) if (!res.ok) { const text = await res.text() diff --git a/src/cli/shell.ts b/src/cli/shell.ts index 995a9cd..8f83619 100644 --- a/src/cli/shell.ts +++ b/src/cli/shell.ts @@ -4,7 +4,7 @@ import * as readline from 'readline' import color from 'kleur' -import { get, handleError, HOST } from './http' +import { clearSignal, get, handleError, HOST, setSignal } from './http' import { program } from './setup' import { STATE_ICONS } from './commands/manage' @@ -185,12 +185,10 @@ export async function shell(): Promise { const tokens = tokenize(input) - // Wrap fetch with AbortController for this command + // Set up AbortController for this command activeAbort = new AbortController() - const originalFetch = globalThis.fetch const signal = activeAbort.signal - globalThis.fetch = (url, init) => - originalFetch(url, { ...init, signal }) + setSignal(signal) // Pause readline so commands can use their own prompts rl.pause() @@ -215,7 +213,7 @@ export async function shell(): Promise { handleError(err) } } finally { - globalThis.fetch = originalFetch + clearSignal() activeAbort = null }