Compare commits

..

No commits in common. "21c6c27c9238c2940593d0dbf826aa664f6a4f3c" and "926e57e34e68570a8ef4f136be0d3ffc1dd35e49" have entirely different histories.

3 changed files with 18 additions and 55 deletions

View File

@ -34,18 +34,6 @@ Once complete, visit `http://<hostname>.local` on your local network.
- https://toes.local web UI for managing your projects.
- `toes` CLI for managing your projects.
## ssh cli
You can manage your toes server from any machine on your network over SSH — no install required.
```bash
ssh cli@toes.local # interactive shell with tab completion
ssh cli@toes.local list # run a single command
ssh cli@toes.local logs fog # stream logs for an app
```
The `cli` user's login shell is the `toes` binary itself. No password is needed. With no arguments, you get an interactive REPL. With arguments, it runs the command and exits.
## cli configuration
by default, the CLI connects to `localhost:3000` in dev and `toes.local:80` in production.

View File

@ -112,15 +112,6 @@ for app_dir in "$DEST"/apps/*/; do
rm -rf "$APPS_DIR/$app/.git"
done
# ── CLI + SSH ────────────────────────────────────────────
info "Installing CLI"
quiet bun run cli:build
sudo cp dist/toes /usr/local/bin/toes
info "Setting up SSH access"
quiet sudo bash "$DEST/scripts/setup-ssh.sh"
# ── Systemd ──────────────────────────────────────────────
info "Installing toes service"
@ -140,7 +131,6 @@ echo " ${b}${g}🐾 toes $VERSION is up!${r}"
echo " ${d}─────────────────────────────${r}"
echo ""
echo " Dashboard: ${c}http://$(hostname).local${r}"
echo " SSH CLI: ${c}ssh cli@$(hostname).local${r}"
echo ""
echo " ${d}Grab the CLI:${r}"
echo " ${c}curl -fsSL http://$(hostname).local/install | bash${r}"

View File

@ -103,7 +103,7 @@ let _appDiskCache: Record<string, number> = {}
let _appDiskLastUpdate = 0
const DISK_CACHE_TTL = 30000
async function getAppMetrics(): Promise<Record<string, AppMetrics>> {
function getAppMetrics(): Record<string, AppMetrics> {
const apps = allApps()
const running = apps.filter(a => a.proc?.pid)
const result: Record<string, AppMetrics> = {}
@ -117,10 +117,8 @@ async function getAppMetrics(): Promise<Record<string, AppMetrics>> {
if (pidToName.size > 0) {
try {
const pids = [...pidToName.keys()].join(',')
const proc = Bun.spawn(['ps', '-o', 'pid=,%cpu=,rss=', '-p', pids], { stdout: 'pipe', stderr: 'ignore' })
const output = await new Response(proc.stdout).text()
await proc.exited
for (const line of output.split('\n')) {
const ps = Bun.spawnSync(['ps', '-o', 'pid=,%cpu=,rss=', '-p', pids])
for (const line of ps.stdout.toString().split('\n')) {
const parts = line.trim().split(/\s+/)
if (parts.length < 3) continue
const pid = parseInt(parts[0]!, 10)
@ -137,21 +135,12 @@ async function getAppMetrics(): Promise<Record<string, AppMetrics>> {
if (now - _appDiskLastUpdate > DISK_CACHE_TTL) {
_appDiskLastUpdate = now
_appDiskCache = {}
const duResults = await Promise.all(
apps.map(async app => {
try {
const proc = Bun.spawn(['du', '-sk', join(APPS_DIR, app.name)], { stdout: 'pipe', stderr: 'ignore' })
const output = await new Response(proc.stdout).text()
await proc.exited
const kb = parseInt(output.trim().split('\t')[0]!, 10)
return { name: app.name, bytes: kb ? kb * 1024 : 0 }
} catch {
return { name: app.name, bytes: 0 }
}
})
)
for (const { name, bytes } of duResults) {
if (bytes) _appDiskCache[name] = bytes
for (const app of apps) {
try {
const du = Bun.spawnSync(['du', '-sk', join(APPS_DIR, app.name)])
const kb = parseInt(du.stdout.toString().trim().split('\t')[0]!, 10)
if (kb) _appDiskCache[app.name] = kb * 1024
} catch {}
}
}
@ -165,30 +154,26 @@ async function getAppMetrics(): Promise<Record<string, AppMetrics>> {
}
// Get current system metrics
router.get('/metrics', async c => {
router.get('/metrics', c => {
const metrics: SystemMetrics = {
cpu: getCpuUsage(),
ram: getMemoryUsage(),
disk: getDiskUsage(),
apps: await getAppMetrics(),
apps: getAppMetrics(),
}
return c.json(metrics)
})
// SSE stream for real-time metrics (updates every 2s)
router.sse('/metrics/stream', (send) => {
let queue = Promise.resolve()
const sendMetrics = () => {
queue = queue.then(async () => {
const metrics: SystemMetrics = {
cpu: getCpuUsage(),
ram: getMemoryUsage(),
disk: getDiskUsage(),
apps: await getAppMetrics(),
}
await send(metrics)
})
const metrics: SystemMetrics = {
cpu: getCpuUsage(),
ram: getMemoryUsage(),
disk: getDiskUsage(),
apps: getAppMetrics(),
}
send(metrics)
}
// Initial send