Add abort signals; rename guest to toes-cli
This commit is contained in:
parent
dc570cc6e9
commit
a87f0a9651
|
|
@ -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 <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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",
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<VersionsResponse | null> {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<T>(url: string): Promise<T | undefined> {
|
||||
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<T>(url: string): Promise<T | undefined> {
|
|||
|
||||
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<T, B = unknown>(url: string, body?: B): Promise<T | u
|
|||
method: 'POST',
|
||||
headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
signal: activeSignal,
|
||||
})
|
||||
if (!res.ok) {
|
||||
const text = await res.text()
|
||||
|
|
@ -85,6 +92,7 @@ export async function put(url: string, body: Buffer | Uint8Array): Promise<boole
|
|||
const res = await fetch(makeUrl(url), {
|
||||
method: 'PUT',
|
||||
body: body as BodyInit,
|
||||
signal: activeSignal,
|
||||
})
|
||||
if (!res.ok) {
|
||||
const text = await res.text()
|
||||
|
|
@ -101,7 +109,7 @@ export async function put(url: string, body: Buffer | Uint8Array): Promise<boole
|
|||
export async function download(url: string): Promise<Buffer | undefined> {
|
||||
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<boolean> {
|
|||
try {
|
||||
const res = await fetch(makeUrl(url), {
|
||||
method: 'DELETE',
|
||||
signal: activeSignal,
|
||||
})
|
||||
if (!res.ok) {
|
||||
const text = await res.text()
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
|||
|
||||
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<void> {
|
|||
handleError(err)
|
||||
}
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch
|
||||
clearSignal()
|
||||
activeAbort = null
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user