mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-06-30 14:10:44 +00:00
Compare commits
No commits in common. "81168ff49aab1643c24ca8968924f487ea980e30" and "39b0665678a7e97f1c43f1e2aacd585590f45fa3" have entirely different histories.
81168ff49a
...
39b0665678
|
|
@ -1,14 +1,3 @@
|
||||||
secubox-droplet (1.0.2-1~bookworm1) bookworm; urgency=medium
|
|
||||||
|
|
||||||
* Forge dropletctl (issue #181) — third routing verb on the publishing
|
|
||||||
layer, parallel to giteactl repo mirror (#176) and mitmproxyctl route
|
|
||||||
(#173). Subcommands: lifecycle (start/stop/restart/status/logs),
|
|
||||||
Three-fold JSON (components/access), and file noun verbs (upload,
|
|
||||||
remove, rename, list, info) wrapping the /api/v1/droplet/* endpoints
|
|
||||||
over the Unix socket.
|
|
||||||
|
|
||||||
-- Gerald KERMA <devel@cybermind.fr> Sun, 17 May 2026 11:20:54 +0200
|
|
||||||
|
|
||||||
secubox-droplet (1.0.1-1~bookworm1) bookworm; urgency=medium
|
secubox-droplet (1.0.1-1~bookworm1) bookworm; urgency=medium
|
||||||
|
|
||||||
* Add dynamic menu system with menu.d JSON definitions
|
* Add dynamic menu system with menu.d JSON definitions
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,3 @@ override_dh_auto_install:
|
||||||
# Modular nginx config
|
# Modular nginx config
|
||||||
install -d debian/secubox-droplet/etc/nginx/secubox.d
|
install -d debian/secubox-droplet/etc/nginx/secubox.d
|
||||||
[ -f nginx/droplet.conf ] && cp nginx/droplet.conf debian/secubox-droplet/etc/nginx/secubox.d/ || true
|
[ -f nginx/droplet.conf ] && cp nginx/droplet.conf debian/secubox-droplet/etc/nginx/secubox.d/ || true
|
||||||
# dropletctl (issue #181)
|
|
||||||
install -d debian/secubox-droplet/usr/sbin
|
|
||||||
install -m 755 sbin/dropletctl debian/secubox-droplet/usr/sbin/
|
|
||||||
|
|
|
||||||
|
|
@ -1,225 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
|
||||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
|
||||||
# Source-Disclosed License — All rights reserved except as expressly granted.
|
|
||||||
# See LICENCE-CMSD-1.0.md for terms.
|
|
||||||
#
|
|
||||||
# dropletctl — SecuBox Droplet File Publisher control (issue #181)
|
|
||||||
#
|
|
||||||
# Third routing verb on the publishing layer, parallel to:
|
|
||||||
# haproxyctl vhost add/remove (routing)
|
|
||||||
# mitmproxyctl route add/remove (interception, #173)
|
|
||||||
# giteactl repo mirror add (replication, #176)
|
|
||||||
# dropletctl file upload/list (publishing, this)
|
|
||||||
#
|
|
||||||
# The Droplet API exposes /upload, /list, /remove, /rename over a Unix
|
|
||||||
# socket; this ctl wraps those endpoints under a coherent <noun> <verb>
|
|
||||||
# grammar so operators don't have to curl by hand.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
VERSION="0.1.0"
|
|
||||||
SOCKET="${DROPLET_SOCKET:-/run/secubox/droplet.sock}"
|
|
||||||
API_BASE="http://localhost/api/v1/droplet"
|
|
||||||
SERVICE="secubox-droplet.service"
|
|
||||||
|
|
||||||
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
|
|
||||||
log() { printf "${GREEN}[DROPLET]${NC} %s\n" "$*"; }
|
|
||||||
warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*"; }
|
|
||||||
error() { printf "${RED}[ERROR]${NC} %s\n" "$*" >&2; }
|
|
||||||
|
|
||||||
# ── Helpers ───────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
require_socket() {
|
|
||||||
if [ ! -S "$SOCKET" ]; then
|
|
||||||
error "API socket $SOCKET not present — start $SERVICE first"
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
api() {
|
|
||||||
local method="$1" path="$2"
|
|
||||||
shift 2
|
|
||||||
require_socket
|
|
||||||
curl --unix-socket "$SOCKET" -sS -X "$method" \
|
|
||||||
-w "\nHTTP_CODE:%{http_code}\n" \
|
|
||||||
"${API_BASE}${path}" "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
api_code() {
|
|
||||||
echo "$1" | grep '^HTTP_CODE:' | cut -d: -f2
|
|
||||||
}
|
|
||||||
|
|
||||||
api_body() {
|
|
||||||
echo "$1" | sed '/^HTTP_CODE:/d'
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Lifecycle (top-level, mirrors mitmproxyctl/giteactl) ──────────────────
|
|
||||||
|
|
||||||
cmd_install() { log "install via dpkg; this ctl assumes package already installed"; return 0; }
|
|
||||||
cmd_start() { systemctl start "$SERVICE" && log "started"; }
|
|
||||||
cmd_stop() { systemctl stop "$SERVICE" && log "stopped"; }
|
|
||||||
cmd_restart() { systemctl restart "$SERVICE" && log "restarted";}
|
|
||||||
cmd_status() {
|
|
||||||
systemctl is-active "$SERVICE" >/dev/null 2>&1 \
|
|
||||||
&& echo "active" || echo "inactive"
|
|
||||||
}
|
|
||||||
cmd_logs() {
|
|
||||||
journalctl -u "$SERVICE" -n "${1:-50}" --no-pager
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Three-fold (giteactl convention: components / status / access JSON) ───
|
|
||||||
|
|
||||||
cmd_components() {
|
|
||||||
cat <<EOF
|
|
||||||
{
|
|
||||||
"service": "$SERVICE",
|
|
||||||
"socket": "$SOCKET",
|
|
||||||
"api_base": "$API_BASE",
|
|
||||||
"ctl_version": "$VERSION"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_access() {
|
|
||||||
cat <<EOF
|
|
||||||
{
|
|
||||||
"socket": "$SOCKET",
|
|
||||||
"api_paths": {
|
|
||||||
"upload": "POST /api/v1/droplet/upload",
|
|
||||||
"list": "GET /api/v1/droplet/list",
|
|
||||||
"remove": "POST /api/v1/droplet/remove",
|
|
||||||
"rename": "POST /api/v1/droplet/rename",
|
|
||||||
"info": "GET /api/v1/droplet/droplet/{name}",
|
|
||||||
"stats": "GET /api/v1/droplet/stats",
|
|
||||||
"storage":"GET /api/v1/droplet/storage"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── file noun verbs (the missing grammar — issue #181) ────────────────────
|
|
||||||
|
|
||||||
cmd_file() {
|
|
||||||
local action="${1:-}"; shift || true
|
|
||||||
case "$action" in
|
|
||||||
upload) cmd_file_upload "$@" ;;
|
|
||||||
remove|rm|del) cmd_file_remove "$@" ;;
|
|
||||||
rename) cmd_file_rename "$@" ;;
|
|
||||||
list|ls) cmd_file_list "$@" ;;
|
|
||||||
info) cmd_file_info "$@" ;;
|
|
||||||
*)
|
|
||||||
cat <<EOF
|
|
||||||
File commands:
|
|
||||||
file upload <path> [--public] [--ttl <e.g. 7d>]
|
|
||||||
file remove <name>
|
|
||||||
file rename <old> <new>
|
|
||||||
file list [--limit N]
|
|
||||||
file info <name>
|
|
||||||
EOF
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_file_upload() {
|
|
||||||
local path="$1"; shift || { error "file upload <path> required"; return 1; }
|
|
||||||
[ -f "$path" ] || { error "file not found: $path"; return 1; }
|
|
||||||
local public=false ttl=""
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case "$1" in
|
|
||||||
--public) public=true ;;
|
|
||||||
--ttl) ttl="$2"; shift ;;
|
|
||||||
*) error "unknown flag: $1"; return 1 ;;
|
|
||||||
esac; shift
|
|
||||||
done
|
|
||||||
log "uploading $path (public=$public ttl=${ttl:-default})"
|
|
||||||
local args=("-F" "file=@${path}")
|
|
||||||
$public && args+=("-F" "public=true")
|
|
||||||
[ -n "$ttl" ] && args+=("-F" "ttl=$ttl")
|
|
||||||
local out
|
|
||||||
out=$(api POST "/upload" "${args[@]}")
|
|
||||||
local code; code=$(api_code "$out")
|
|
||||||
case "$code" in
|
|
||||||
200|201) api_body "$out" ;;
|
|
||||||
*) error "upload failed (HTTP $code): $(api_body "$out" | head -3)"; return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_file_remove() {
|
|
||||||
local name="$1"
|
|
||||||
[ -z "$name" ] && { error "file remove <name> required"; return 1; }
|
|
||||||
log "removing $name"
|
|
||||||
local out
|
|
||||||
out=$(api POST "/remove" -H "Content-Type: application/json" \
|
|
||||||
-d "$(printf '{"name":"%s"}' "$name")")
|
|
||||||
local code; code=$(api_code "$out")
|
|
||||||
[ "$code" = "200" ] || { error "remove failed (HTTP $code): $(api_body "$out")"; return 1; }
|
|
||||||
log "removed $name"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_file_rename() {
|
|
||||||
local old="$1" new="$2"
|
|
||||||
[ -z "$old" ] || [ -z "$new" ] && { error "file rename <old> <new> required"; return 1; }
|
|
||||||
log "renaming $old -> $new"
|
|
||||||
local out
|
|
||||||
out=$(api POST "/rename" -H "Content-Type: application/json" \
|
|
||||||
-d "$(printf '{"old":"%s","new":"%s"}' "$old" "$new")")
|
|
||||||
local code; code=$(api_code "$out")
|
|
||||||
[ "$code" = "200" ] || { error "rename failed (HTTP $code): $(api_body "$out")"; return 1; }
|
|
||||||
log "renamed"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_file_list() {
|
|
||||||
local limit=""
|
|
||||||
[ "${1:-}" = "--limit" ] && { limit="?limit=$2"; }
|
|
||||||
local out
|
|
||||||
out=$(api GET "/list${limit}")
|
|
||||||
api_body "$out"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_file_info() {
|
|
||||||
local name="$1"
|
|
||||||
[ -z "$name" ] && { error "file info <name> required"; return 1; }
|
|
||||||
local out
|
|
||||||
out=$(api GET "/droplet/${name}")
|
|
||||||
api_body "$out"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Main dispatch ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
show_help() {
|
|
||||||
cat <<EOF
|
|
||||||
SecuBox Droplet Controller v$VERSION (issue #181)
|
|
||||||
File publisher CLI — parallel to giteactl, mitmproxyctl
|
|
||||||
|
|
||||||
Usage: dropletctl <command> [options]
|
|
||||||
|
|
||||||
Lifecycle:
|
|
||||||
install / start / stop / restart / status / logs
|
|
||||||
|
|
||||||
Three-fold (JSON):
|
|
||||||
components / access
|
|
||||||
|
|
||||||
Files (issue #181):
|
|
||||||
file upload <path> [--public] [--ttl 7d]
|
|
||||||
file remove <name>
|
|
||||||
file rename <old> <new>
|
|
||||||
file list [--limit N]
|
|
||||||
file info <name>
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
dropletctl file upload /tmp/report.pdf --public --ttl 7d
|
|
||||||
dropletctl file list
|
|
||||||
dropletctl access | jq .api_paths
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
install|start|stop|restart|status) cmd="$1"; shift; cmd_$cmd "$@" ;;
|
|
||||||
logs) shift; cmd_logs "$@" ;;
|
|
||||||
components) cmd_components ;;
|
|
||||||
access) cmd_access ;;
|
|
||||||
file) shift; cmd_file "$@" ;;
|
|
||||||
help|--help|-h|'') show_help ;;
|
|
||||||
*) error "unknown command: $1"; show_help; exit 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
@ -1,18 +1,3 @@
|
||||||
secubox-metablogizer (1.1.1-1~bookworm1) bookworm; urgency=medium
|
|
||||||
|
|
||||||
* metablogizerctl: forge tor noun verbs (issue #184) — the Emancipate
|
|
||||||
verb of the Punk Exposure Engine at the publishing layer.
|
|
||||||
Subcommands:
|
|
||||||
tor expose <site> Publish site via Tor hidden service
|
|
||||||
tor revoke <site> Stop publishing via Tor
|
|
||||||
tor list List Tor-exposed sites with onion addresses
|
|
||||||
tor status <site> Show stanza + onion + tor service state
|
|
||||||
When secubox-exposure is installed, delegates to it for consistency
|
|
||||||
with other exposure channels (DNS+SSL, Mesh). Otherwise falls back
|
|
||||||
to direct /etc/tor/secubox-metablogizer.d/ stanza management.
|
|
||||||
|
|
||||||
-- Gerald KERMA <devel@cybermind.fr> Sun, 17 May 2026 11:23:12 +0200
|
|
||||||
|
|
||||||
secubox-metablogizer (1.1.0-1~bookworm1) bookworm; urgency=medium
|
secubox-metablogizer (1.1.0-1~bookworm1) bookworm; urgency=medium
|
||||||
|
|
||||||
* Add metablogizerctl three-fold commands: components, access (JSON output)
|
* Add metablogizerctl three-fold commands: components, access (JSON output)
|
||||||
|
|
|
||||||
|
|
@ -194,155 +194,6 @@ site_unpublish() {
|
||||||
log "Site unpublished: $name"
|
log "Site unpublished: $name"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# Tor — Emancipate verb (Punk Exposure Engine, issue #184)
|
|
||||||
#
|
|
||||||
# Per CLAUDE.md the Punk Exposure Engine has three verbs (Peek/Poke/Emancipate)
|
|
||||||
# and Tor is one of the three exposure channels. `metablogizerctl tor expose`
|
|
||||||
# is the Emancipate verb at the publishing layer for static sites.
|
|
||||||
#
|
|
||||||
# Implementation: write a per-site Tor HiddenService stanza, reload tor,
|
|
||||||
# read the generated .onion hostname back. If secubox-exposure is installed,
|
|
||||||
# delegate to it for consistency with other channels; otherwise fall back
|
|
||||||
# to direct torrc manipulation.
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
TOR_DROPIN_DIR="${TOR_DROPIN_DIR:-/etc/tor/secubox-metablogizer.d}"
|
|
||||||
TOR_DATA_DIR="${TOR_DATA_DIR:-/var/lib/tor/secubox-metablogizer}"
|
|
||||||
|
|
||||||
_tor_drop_path() { echo "$TOR_DROPIN_DIR/${1}.conf"; }
|
|
||||||
_tor_data_path() { echo "$TOR_DATA_DIR/${1}"; }
|
|
||||||
|
|
||||||
_have_exposure() { command -v secubox-exposure >/dev/null 2>&1; }
|
|
||||||
_have_tor() { command -v tor >/dev/null 2>&1; }
|
|
||||||
|
|
||||||
tor_expose() {
|
|
||||||
local name="$1"
|
|
||||||
[ -z "$name" ] && { error "Usage: metablogizerctl tor expose <site>"; return 1; }
|
|
||||||
|
|
||||||
local site_dir="$SITES_ROOT/$name"
|
|
||||||
[ -d "$site_dir" ] || { error "site not found: $name (run: metablogizerctl site create $name first)"; return 1; }
|
|
||||||
|
|
||||||
# Prefer secubox-exposure when available so all 3 exposure channels share
|
|
||||||
# the same orchestration (Tor / DNS+SSL / Mesh) and Peek shows it.
|
|
||||||
if _have_exposure; then
|
|
||||||
log "delegating to secubox-exposure emancipate (tor channel)"
|
|
||||||
# The static site is served via nginx on port 80 (per site_publish);
|
|
||||||
# secubox-exposure handles the HiddenService wiring and revocation.
|
|
||||||
secubox-exposure emancipate "metablogizer-${name}" 80 --tor
|
|
||||||
return $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Fallback: write a tor drop-in directly.
|
|
||||||
_have_tor || { error "tor not installed and secubox-exposure unavailable"; return 1; }
|
|
||||||
mkdir -p "$TOR_DROPIN_DIR"
|
|
||||||
mkdir -p "$TOR_DATA_DIR"
|
|
||||||
local data; data=$(_tor_data_path "$name")
|
|
||||||
local drop; drop=$(_tor_drop_path "$name")
|
|
||||||
log "writing Tor HiddenService stanza for $name"
|
|
||||||
cat > "$drop" <<EOF
|
|
||||||
# metablogizer site: $name (#184 — Emancipate via Tor)
|
|
||||||
HiddenServiceDir $data
|
|
||||||
HiddenServicePort 80 127.0.0.1:80
|
|
||||||
EOF
|
|
||||||
chown -R debian-tor:debian-tor "$TOR_DATA_DIR" 2>/dev/null || true
|
|
||||||
chmod 700 "$data" 2>/dev/null || true
|
|
||||||
log "reloading tor"
|
|
||||||
systemctl reload tor 2>/dev/null || systemctl restart tor
|
|
||||||
# Wait briefly for tor to publish the hostname file
|
|
||||||
local i=0
|
|
||||||
while [ $i -lt 10 ] && [ ! -f "$data/hostname" ]; do sleep 1; i=$((i+1)); done
|
|
||||||
if [ -f "$data/hostname" ]; then
|
|
||||||
local onion; onion=$(cat "$data/hostname")
|
|
||||||
log "site emancipated via Tor: $onion"
|
|
||||||
# Persist the address back into site.json for future Peek calls
|
|
||||||
if [ -f "$site_dir/site.json" ] && command -v python3 >/dev/null 2>&1; then
|
|
||||||
python3 -c "
|
|
||||||
import json, sys
|
|
||||||
p='$site_dir/site.json'
|
|
||||||
d=json.load(open(p))
|
|
||||||
d.setdefault('exposure',{})['tor']='$onion'
|
|
||||||
json.dump(d, open(p,'w'), indent=2)
|
|
||||||
" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
warn "tor reload OK but hostname not yet written; check 'metablogizerctl tor status $name'"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
tor_revoke() {
|
|
||||||
local name="$1"
|
|
||||||
[ -z "$name" ] && { error "Usage: metablogizerctl tor revoke <site>"; return 1; }
|
|
||||||
if _have_exposure; then
|
|
||||||
log "delegating to secubox-exposure revoke"
|
|
||||||
secubox-exposure revoke "metablogizer-${name}" --tor
|
|
||||||
return $?
|
|
||||||
fi
|
|
||||||
local drop; drop=$(_tor_drop_path "$name")
|
|
||||||
[ -f "$drop" ] || { warn "no Tor stanza for $name"; return 0; }
|
|
||||||
rm -f "$drop"
|
|
||||||
systemctl reload tor 2>/dev/null || systemctl restart tor
|
|
||||||
log "tor stanza removed for $name (data dir kept under $TOR_DATA_DIR/$name — delete manually if desired)"
|
|
||||||
}
|
|
||||||
|
|
||||||
tor_list() {
|
|
||||||
if _have_exposure; then
|
|
||||||
log "(delegate) secubox-exposure list --tor"
|
|
||||||
secubox-exposure list --tor 2>/dev/null && return
|
|
||||||
fi
|
|
||||||
if [ ! -d "$TOR_DROPIN_DIR" ]; then
|
|
||||||
echo "(no Tor-exposed sites)"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
local any=0
|
|
||||||
for d in "$TOR_DROPIN_DIR"/*.conf; do
|
|
||||||
[ -f "$d" ] || continue
|
|
||||||
any=1
|
|
||||||
local n; n=$(basename "$d" .conf)
|
|
||||||
local h="$TOR_DATA_DIR/$n/hostname"
|
|
||||||
if [ -f "$h" ]; then
|
|
||||||
printf " %-30s -> %s\n" "$n" "$(cat "$h")"
|
|
||||||
else
|
|
||||||
printf " %-30s -> (publishing...)\n" "$n"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
[ $any = 0 ] && echo "(no Tor-exposed sites)"
|
|
||||||
}
|
|
||||||
|
|
||||||
tor_status() {
|
|
||||||
local name="$1"
|
|
||||||
[ -z "$name" ] && { error "Usage: metablogizerctl tor status <site>"; return 1; }
|
|
||||||
local data; data=$(_tor_data_path "$name")
|
|
||||||
local drop; drop=$(_tor_drop_path "$name")
|
|
||||||
echo "site: $name"
|
|
||||||
echo "stanza present: $([ -f "$drop" ] && echo yes || echo no)"
|
|
||||||
if [ -f "$data/hostname" ]; then
|
|
||||||
echo "onion: $(cat "$data/hostname")"
|
|
||||||
else
|
|
||||||
echo "onion: (not yet published)"
|
|
||||||
fi
|
|
||||||
systemctl is-active tor >/dev/null 2>&1 && echo "tor service: active" || echo "tor service: inactive"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_tor() {
|
|
||||||
local action="${1:-}"; shift || true
|
|
||||||
case "$action" in
|
|
||||||
expose) tor_expose "$@" ;;
|
|
||||||
revoke|remove) tor_revoke "$@" ;;
|
|
||||||
list|ls) tor_list ;;
|
|
||||||
status) tor_status "$@" ;;
|
|
||||||
*)
|
|
||||||
cat <<EOF
|
|
||||||
Tor commands (Punk Exposure / Emancipate verb, issue #184):
|
|
||||||
tor expose <site> - publish site via Tor hidden service
|
|
||||||
tor revoke <site> - stop publishing via Tor
|
|
||||||
tor list - list Tor-exposed sites + their onion addresses
|
|
||||||
tor status <site> - show stanza presence + onion + tor service state
|
|
||||||
EOF
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
site_list() {
|
site_list() {
|
||||||
echo "MetaBlogizer Sites:"
|
echo "MetaBlogizer Sites:"
|
||||||
echo "==================="
|
echo "==================="
|
||||||
|
|
@ -536,12 +387,6 @@ Sites:
|
||||||
site unpublish <name> Unpublish site
|
site unpublish <name> Unpublish site
|
||||||
site list List all sites
|
site list List all sites
|
||||||
|
|
||||||
Tor (Punk Exposure / Emancipate, issue #184):
|
|
||||||
tor expose <site> Publish site via Tor hidden service
|
|
||||||
tor revoke <site> Stop publishing via Tor
|
|
||||||
tor list List Tor-exposed sites + onions
|
|
||||||
tor status <site> Stanza + onion + tor service state
|
|
||||||
|
|
||||||
Service:
|
Service:
|
||||||
migrate [host] Migrate from OpenWrt
|
migrate [host] Migrate from OpenWrt
|
||||||
|
|
||||||
|
|
@ -549,7 +394,6 @@ Examples:
|
||||||
metablogizerctl components # JSON components
|
metablogizerctl components # JSON components
|
||||||
metablogizerctl site create myblog blog.example.com
|
metablogizerctl site create myblog blog.example.com
|
||||||
metablogizerctl site publish myblog
|
metablogizerctl site publish myblog
|
||||||
metablogizerctl tor expose myblog # Emancipate via Tor
|
|
||||||
metablogizerctl migrate 192.168.255.1
|
metablogizerctl migrate 192.168.255.1
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|
@ -578,8 +422,6 @@ case "${1:-}" in
|
||||||
*) echo "Usage: metablogizerctl site create|delete|publish|unpublish|list" ;;
|
*) echo "Usage: metablogizerctl site create|delete|publish|unpublish|list" ;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
# Tor (Emancipate, issue #184)
|
|
||||||
tor) shift; cmd_tor "$@" ;;
|
|
||||||
migrate) shift; cmd_migrate "$@" ;;
|
migrate) shift; cmd_migrate "$@" ;;
|
||||||
help|--help|-h|'') show_help ;;
|
help|--help|-h|'') show_help ;;
|
||||||
*) error "Unknown: $1"; exit 1 ;;
|
*) error "Unknown: $1"; exit 1 ;;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,9 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
# SecuBox MetaCtl — ISP Home Publish CLI
|
||||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
# CyberMind — https://cybermind.fr
|
||||||
#
|
|
||||||
# publishctl — SecuBox ISP Home Publish CLI (issue #180)
|
|
||||||
#
|
|
||||||
# Renamed from `metactl` for naming consistency with the rest of the
|
|
||||||
# SecuBox grammar (haproxyctl/giteactl/mitmproxyctl/metablogizerctl/
|
|
||||||
# dropletctl/streamlitctl/streamforgectl). The old `metactl` name remains
|
|
||||||
# as a symlink for backward compatibility — to drop in a future major.
|
|
||||||
#
|
|
||||||
# Flat verbs are now also reachable under the `post` noun dispatch
|
|
||||||
# for grammar consistency (publishctl post upload <file>, etc). Flat
|
|
||||||
# top-level verbs preserved for backward compatibility.
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="2.0.0"
|
VERSION="1.0.0"
|
||||||
API_BASE="${SECUBOX_API_BASE:-http://127.0.0.1/api/v1/publish}"
|
API_BASE="${SECUBOX_API_BASE:-http://127.0.0.1/api/v1/publish}"
|
||||||
METABLOGIZER_API="${SECUBOX_METABLOGIZER_API:-http://127.0.0.1/api/v1/metablogizer}"
|
METABLOGIZER_API="${SECUBOX_METABLOGIZER_API:-http://127.0.0.1/api/v1/metablogizer}"
|
||||||
TOKEN_FILE="${SECUBOX_TOKEN_FILE:-/etc/secubox/secrets/jwt-token}"
|
TOKEN_FILE="${SECUBOX_TOKEN_FILE:-/etc/secubox/secrets/jwt-token}"
|
||||||
|
|
@ -419,39 +408,8 @@ cmd_health() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# post noun dispatch (issue #180 — grammar consistency, parallel to
|
|
||||||
# `giteactl repo`, `mitmproxyctl route`, `dropletctl file`, etc).
|
|
||||||
# Delegates to the existing flat cmd_* functions; both grammars supported.
|
|
||||||
cmd_post() {
|
|
||||||
local act="${1:-}"; shift || true
|
|
||||||
case "$act" in
|
|
||||||
upload) cmd_upload "$@" ;;
|
|
||||||
publish) cmd_publish "$@" ;;
|
|
||||||
unpublish) cmd_unpublish "$@" ;;
|
|
||||||
list|ls) cmd_list ;;
|
|
||||||
download) cmd_download "$@" ;;
|
|
||||||
qrcode|qr) cmd_qrcode "$@" ;;
|
|
||||||
health) cmd_health "$@" ;;
|
|
||||||
*)
|
|
||||||
cat <<EOF
|
|
||||||
Post commands (issue #180):
|
|
||||||
post upload <file.zip> [name] [--domain=D] [--auto-publish]
|
|
||||||
post publish <name>
|
|
||||||
post unpublish <name>
|
|
||||||
post list
|
|
||||||
post download <name> [output.zip]
|
|
||||||
post qrcode <name>
|
|
||||||
post health <domain>
|
|
||||||
EOF
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
case "${1:-help}" in
|
case "${1:-help}" in
|
||||||
# noun-verb grammar (issue #180)
|
|
||||||
post) shift; cmd_post "$@" ;;
|
|
||||||
# flat verbs (backward-compat — same callbacks)
|
|
||||||
upload) shift; cmd_upload "$@" ;;
|
upload) shift; cmd_upload "$@" ;;
|
||||||
publish) shift; cmd_publish "$@" ;;
|
publish) shift; cmd_publish "$@" ;;
|
||||||
unpublish) shift; cmd_unpublish "$@" ;;
|
unpublish) shift; cmd_unpublish "$@" ;;
|
||||||
|
|
@ -461,10 +419,10 @@ case "${1:-help}" in
|
||||||
status) cmd_status ;;
|
status) cmd_status ;;
|
||||||
health) shift; cmd_health "$@" ;;
|
health) shift; cmd_health "$@" ;;
|
||||||
-h|--help|help) usage ;;
|
-h|--help|help) usage ;;
|
||||||
-v|--version) echo "publishctl v${VERSION}" ;;
|
-v|--version) echo "metactl v${VERSION}" ;;
|
||||||
*)
|
*)
|
||||||
echo -e "${RED}Unknown command:${NC} $1"
|
echo -e "${RED}Unknown command:${NC} $1"
|
||||||
echo "Run 'publishctl --help' for usage"
|
echo "Run 'metactl --help' for usage"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
@ -1,20 +1,3 @@
|
||||||
secubox-publish (2.0.0-1~bookworm1) bookworm; urgency=medium
|
|
||||||
|
|
||||||
* Rename `metactl` -> `publishctl` for naming consistency with the rest
|
|
||||||
of the SecuBox ctl grammar (issue #180). The `metactl` name remains
|
|
||||||
as a symlink for backward compatibility — to drop in a future major.
|
|
||||||
* publishctl: add `post` noun dispatch so verbs are grouped under a
|
|
||||||
coherent <noun> <verb> schema parallel to giteactl/dropletctl/
|
|
||||||
metablogizerctl. Flat top-level verbs preserved as alias.
|
|
||||||
|
|
||||||
publishctl post upload <file.zip> [name] [--auto-publish]
|
|
||||||
publishctl post publish/unpublish <name>
|
|
||||||
publishctl post list/download/qrcode/health ...
|
|
||||||
|
|
||||||
* Bumped to 2.0.0 (CLI surface rename).
|
|
||||||
|
|
||||||
-- Gerald KERMA <devel@cybermind.fr> Sun, 17 May 2026 11:38:19 +0200
|
|
||||||
|
|
||||||
secubox-publish (1.0.0-1~bookworm1) bookworm; urgency=medium
|
secubox-publish (1.0.0-1~bookworm1) bookworm; urgency=medium
|
||||||
|
|
||||||
* Initial release
|
* Initial release
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,9 @@ override_dh_auto_install:
|
||||||
# Modular nginx config
|
# Modular nginx config
|
||||||
install -d debian/secubox-publish/etc/nginx/secubox.d
|
install -d debian/secubox-publish/etc/nginx/secubox.d
|
||||||
[ -f nginx/publish.conf ] && cp nginx/publish.conf debian/secubox-publish/etc/nginx/secubox.d/ || true
|
[ -f nginx/publish.conf ] && cp nginx/publish.conf debian/secubox-publish/etc/nginx/secubox.d/ || true
|
||||||
# CLI tool — primary `publishctl` + `metactl` symlink for backward compat (#180)
|
# CLI tool
|
||||||
install -d debian/secubox-publish/usr/sbin
|
install -d debian/secubox-publish/usr/sbin
|
||||||
[ -f bin/publishctl ] && install -m 755 bin/publishctl debian/secubox-publish/usr/sbin/publishctl || true
|
[ -f bin/metactl ] && install -m 755 bin/metactl debian/secubox-publish/usr/sbin/metactl || true
|
||||||
[ -f debian/secubox-publish/usr/sbin/publishctl ] && \
|
|
||||||
ln -sf publishctl debian/secubox-publish/usr/sbin/metactl || true
|
|
||||||
# Plugins directory
|
# Plugins directory
|
||||||
install -d debian/secubox-publish/srv/secubox/modules/publish/plugins
|
install -d debian/secubox-publish/srv/secubox/modules/publish/plugins
|
||||||
[ -d plugins ] && cp -r plugins/. debian/secubox-publish/srv/secubox/modules/publish/plugins/ || true
|
[ -d plugins ] && cp -r plugins/. debian/secubox-publish/srv/secubox/modules/publish/plugins/ || true
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,3 @@
|
||||||
secubox-streamforge (1.0.2-1~bookworm1) bookworm; urgency=medium
|
|
||||||
|
|
||||||
* Forge streamforgectl (issue #183). Subcommands: lifecycle (start/stop/
|
|
||||||
restart/status/logs), Three-fold JSON (components/access), project
|
|
||||||
noun (create/remove/list/start/stop/restart/info/templates) wrapping
|
|
||||||
the /api/v1/streamforge/app* endpoints over the Unix socket.
|
|
||||||
* Paired with streamlitctl on the hosting side — forge -> host workflow
|
|
||||||
expressible end-to-end (forge create/edit -> export to git -> stream
|
|
||||||
deploy).
|
|
||||||
|
|
||||||
-- Gerald KERMA <devel@cybermind.fr> Sun, 17 May 2026 11:34:28 +0200
|
|
||||||
|
|
||||||
secubox-streamforge (1.0.1-1~bookworm1) bookworm; urgency=medium
|
secubox-streamforge (1.0.1-1~bookworm1) bookworm; urgency=medium
|
||||||
|
|
||||||
* Add dynamic menu system with menu.d JSON definitions
|
* Add dynamic menu system with menu.d JSON definitions
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,3 @@ override_dh_auto_install:
|
||||||
# Modular nginx config
|
# Modular nginx config
|
||||||
install -d debian/secubox-streamforge/etc/nginx/secubox.d
|
install -d debian/secubox-streamforge/etc/nginx/secubox.d
|
||||||
[ -f nginx/streamforge.conf ] && cp nginx/streamforge.conf debian/secubox-streamforge/etc/nginx/secubox.d/ || true
|
[ -f nginx/streamforge.conf ] && cp nginx/streamforge.conf debian/secubox-streamforge/etc/nginx/secubox.d/ || true
|
||||||
# streamforgectl (#183)
|
|
||||||
install -d debian/secubox-streamforge/usr/sbin
|
|
||||||
install -m 755 sbin/streamforgectl debian/secubox-streamforge/usr/sbin/
|
|
||||||
|
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
|
||||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
|
||||||
# streamforgectl — SecuBox StreamForge (Streamlit dev workbench) control (#183)
|
|
||||||
#
|
|
||||||
# Parallel to streamlitctl (the hosting side). The Forge ↔ Streamlit pair:
|
|
||||||
# streamforgectl project create <name> --template hello
|
|
||||||
# streamforgectl project export <name> <gitea_url>
|
|
||||||
# streamlitctl app deploy <name> <gitea_url>
|
|
||||||
# Three verbs, two layers (dev → hosting), one expressible workflow.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
VERSION="0.1.0"
|
|
||||||
SOCKET="${STREAMFORGE_SOCKET:-/run/secubox/streamforge.sock}"
|
|
||||||
API="http://localhost/api/v1/streamforge"
|
|
||||||
SERVICE="secubox-streamforge.service"
|
|
||||||
|
|
||||||
G='\033[0;32m'; Y='\033[1;33m'; R='\033[0;31m'; N='\033[0m'
|
|
||||||
log() { printf "${G}[FORGE]${N} %s\n" "$*"; }
|
|
||||||
warn() { printf "${Y}[WARN]${N} %s\n" "$*"; }
|
|
||||||
error() { printf "${R}[ERROR]${N} %s\n" "$*" >&2; }
|
|
||||||
|
|
||||||
api() {
|
|
||||||
local m="$1" p="$2"; shift 2
|
|
||||||
[ -S "$SOCKET" ] || { error "socket $SOCKET absent — start $SERVICE"; exit 2; }
|
|
||||||
curl --unix-socket "$SOCKET" -sS -X "$m" -w "\nHTTP_CODE:%{http_code}\n" "${API}${p}" "$@"
|
|
||||||
}
|
|
||||||
api_code() { echo "$1" | grep '^HTTP_CODE:' | cut -d: -f2; }
|
|
||||||
api_body() { echo "$1" | sed '/^HTTP_CODE:/d'; }
|
|
||||||
|
|
||||||
# Lifecycle
|
|
||||||
cmd_start() { systemctl start "$SERVICE" && log started; }
|
|
||||||
cmd_stop() { systemctl stop "$SERVICE" && log stopped; }
|
|
||||||
cmd_restart() { systemctl restart "$SERVICE" && log restarted; }
|
|
||||||
cmd_status() { systemctl is-active "$SERVICE" >/dev/null && echo active || echo inactive; }
|
|
||||||
cmd_logs() { journalctl -u "$SERVICE" -n "${1:-50}" --no-pager; }
|
|
||||||
|
|
||||||
cmd_components() {
|
|
||||||
cat <<EOF
|
|
||||||
{"service":"$SERVICE","socket":"$SOCKET","api_base":"$API","ctl_version":"$VERSION"}
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
cmd_access() {
|
|
||||||
cat <<EOF
|
|
||||||
{"socket":"$SOCKET","endpoints":{
|
|
||||||
"apps":"GET /apps","templates":"GET /templates",
|
|
||||||
"create":"POST /app","get":"GET /app/{name}","remove":"DELETE /app/{name}",
|
|
||||||
"start":"POST /app/{name}/start","stop":"POST /app/{name}/stop","restart":"POST /app/{name}/restart",
|
|
||||||
"file_get":"GET /app/{name}/file/{path}","file_put":"PUT /app/{name}/file/{path}"
|
|
||||||
}}
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Project (the noun, #183)
|
|
||||||
cmd_project() {
|
|
||||||
local act="${1:-}"; shift || true
|
|
||||||
case "$act" in
|
|
||||||
create) project_create "$@" ;;
|
|
||||||
remove|rm|delete) project_remove "$@" ;;
|
|
||||||
list|ls) project_list "$@" ;;
|
|
||||||
start) project_start "$@" ;;
|
|
||||||
stop) project_stop "$@" ;;
|
|
||||||
restart) project_restart "$@" ;;
|
|
||||||
info) project_info "$@" ;;
|
|
||||||
templates) project_templates ;;
|
|
||||||
*)
|
|
||||||
cat <<EOF
|
|
||||||
Project commands:
|
|
||||||
project create <name> [--template hello] [--description "..."]
|
|
||||||
project remove <name>
|
|
||||||
project list
|
|
||||||
project start <name> (start the project's streamlit dev server)
|
|
||||||
project stop <name>
|
|
||||||
project restart <name>
|
|
||||||
project info <name>
|
|
||||||
project templates (list available templates)
|
|
||||||
EOF
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
project_create() {
|
|
||||||
local name="$1"; shift || { error "project create <name> required"; return 1; }
|
|
||||||
local template="hello" desc=""
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case "$1" in
|
|
||||||
--template) template="$2"; shift ;;
|
|
||||||
--description) desc="$2"; shift ;;
|
|
||||||
*) error "unknown flag: $1"; return 1 ;;
|
|
||||||
esac; shift
|
|
||||||
done
|
|
||||||
log "creating project '$name' from template '$template'"
|
|
||||||
local body
|
|
||||||
body=$(printf '{"name":"%s","template":"%s","description":"%s"}' "$name" "$template" "$desc")
|
|
||||||
local out; out=$(api POST "/app" -H "Content-Type: application/json" -d "$body")
|
|
||||||
[ "$(api_code "$out")" = "200" ] || { error "create failed: $(api_body "$out" | head -2)"; return 1; }
|
|
||||||
log "created"; api_body "$out"
|
|
||||||
}
|
|
||||||
|
|
||||||
project_remove() {
|
|
||||||
local name="$1"; [ -z "$name" ] && { error "project remove <name>"; return 1; }
|
|
||||||
local out; out=$(api DELETE "/app/${name}")
|
|
||||||
case "$(api_code "$out")" in
|
|
||||||
200|204) log "removed $name" ;;
|
|
||||||
*) error "remove failed: $(api_body "$out")"; return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
project_list() { api_body "$(api GET "/apps")"; }
|
|
||||||
|
|
||||||
project_start() { local n="$1"; [ -z "$n" ] && return 1; api_body "$(api POST "/app/${n}/start")"; }
|
|
||||||
project_stop() { local n="$1"; [ -z "$n" ] && return 1; api_body "$(api POST "/app/${n}/stop")"; }
|
|
||||||
project_restart() { local n="$1"; [ -z "$n" ] && return 1; api_body "$(api POST "/app/${n}/restart")"; }
|
|
||||||
project_info() { local n="$1"; [ -z "$n" ] && return 1; api_body "$(api GET "/app/${n}")"; }
|
|
||||||
project_templates() { api_body "$(api GET "/templates")"; }
|
|
||||||
|
|
||||||
show_help() {
|
|
||||||
cat <<EOF
|
|
||||||
SecuBox StreamForge Controller v$VERSION (issue #183)
|
|
||||||
Streamlit dev workbench CLI — paired with streamlitctl (hosting layer)
|
|
||||||
|
|
||||||
Lifecycle: start / stop / restart / status / logs
|
|
||||||
Three-fold: components / access (JSON)
|
|
||||||
Project (#183): project create / remove / list / start / stop / restart / info / templates
|
|
||||||
|
|
||||||
Example workflow (forge → host):
|
|
||||||
streamforgectl project create dashboard --template basic
|
|
||||||
streamforgectl project start dashboard
|
|
||||||
# ...iterate via the webui at /streamforge/...
|
|
||||||
streamforgectl project export dashboard gitea://secubox/dashboard.git # TODO
|
|
||||||
streamlitctl app deploy dashboard gitea://secubox/dashboard.git
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
start|stop|restart|status) c="$1"; shift; cmd_$c "$@" ;;
|
|
||||||
logs) shift; cmd_logs "$@" ;;
|
|
||||||
components) cmd_components ;;
|
|
||||||
access) cmd_access ;;
|
|
||||||
project) shift; cmd_project "$@" ;;
|
|
||||||
help|--help|-h|'') show_help ;;
|
|
||||||
*) error "unknown: $1"; show_help; exit 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
@ -1,13 +1,3 @@
|
||||||
secubox-streamlit (1.2.1-1~bookworm1) bookworm; urgency=medium
|
|
||||||
|
|
||||||
* streamlitctl: add `app info <name>` and `app restart <name>` verbs
|
|
||||||
(issue #182). The original audit underestimated the existing ctl
|
|
||||||
surface — app list/start/stop/deploy/remove/logs were already wired.
|
|
||||||
What was actually missing: `info` (metadata + runtime state) and
|
|
||||||
`restart` (stop+start with port preservation from .streamlit.toml).
|
|
||||||
|
|
||||||
-- Gerald KERMA <devel@cybermind.fr> Sun, 17 May 2026 11:36:06 +0200
|
|
||||||
|
|
||||||
secubox-streamlit (1.2.0-1~bookworm1) bookworm; urgency=medium
|
secubox-streamlit (1.2.0-1~bookworm1) bookworm; urgency=medium
|
||||||
|
|
||||||
* streamlitctl v1.0.0: Full Debian LXC installation support
|
* streamlitctl v1.0.0: Full Debian LXC installation support
|
||||||
|
|
|
||||||
|
|
@ -318,51 +318,6 @@ EOF
|
||||||
echo ']}'
|
echo ']}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# app info <name> — print metadata + runtime state from manifest + pid file (#182)
|
|
||||||
cmd_app_info() {
|
|
||||||
local name="$1"
|
|
||||||
[ -z "$name" ] && { error "Usage: streamlitctl app info <name>"; return 1; }
|
|
||||||
local d="$APPS_PATH/$name"
|
|
||||||
[ -d "$d" ] || { error "app not found: $name"; return 1; }
|
|
||||||
# Entry point detection (same logic as cmd_app_start)
|
|
||||||
local entry=""
|
|
||||||
for c in app.py main.py streamlit_app.py; do
|
|
||||||
[ -f "$d/$c" ] && entry="$c" && break
|
|
||||||
done
|
|
||||||
local port=""
|
|
||||||
[ -f "$d/.streamlit.toml" ] && port=$(grep -E "^port" "$d/.streamlit.toml" 2>/dev/null | cut -d= -f2 | tr -d ' ')
|
|
||||||
local pidf="/var/run/streamlit-${name}.pid"
|
|
||||||
local pid="" alive="no"
|
|
||||||
if lxc_running; then
|
|
||||||
pid=$(lxc-attach -n "$LXC_NAME" -- cat "$pidf" 2>/dev/null || true)
|
|
||||||
if [ -n "$pid" ]; then
|
|
||||||
lxc-attach -n "$LXC_NAME" -- kill -0 "$pid" >/dev/null 2>&1 && alive="yes"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
cat <<EOF
|
|
||||||
name: $name
|
|
||||||
path: $d
|
|
||||||
entrypoint: ${entry:-(none)}
|
|
||||||
port: ${port:-(unset)}
|
|
||||||
pid_file: $pidf
|
|
||||||
pid: ${pid:-(none)}
|
|
||||||
running: $alive
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# app restart <name> — stop + start (#182)
|
|
||||||
cmd_app_restart() {
|
|
||||||
local name="$1"
|
|
||||||
[ -z "$name" ] && { error "Usage: streamlitctl app restart <name>"; return 1; }
|
|
||||||
cmd_app_stop "$name" || true
|
|
||||||
sleep 1
|
|
||||||
# Recover the previous port from .streamlit.toml so restart preserves it
|
|
||||||
local port=""
|
|
||||||
[ -f "$APPS_PATH/$name/.streamlit.toml" ] && \
|
|
||||||
port=$(grep -E "^port" "$APPS_PATH/$name/.streamlit.toml" 2>/dev/null | cut -d= -f2 | tr -d ' ')
|
|
||||||
cmd_app_start "$name" "${port:-8501}"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_app_start() {
|
cmd_app_start() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
local port="${2:-8501}"
|
local port="${2:-8501}"
|
||||||
|
|
@ -739,9 +694,7 @@ case "${1:-}" in
|
||||||
deploy) cmd_app_deploy "$3" "$4" ;;
|
deploy) cmd_app_deploy "$3" "$4" ;;
|
||||||
remove) cmd_app_remove "$3" ;;
|
remove) cmd_app_remove "$3" ;;
|
||||||
logs) cmd_app_logs "$3" "$4" ;;
|
logs) cmd_app_logs "$3" "$4" ;;
|
||||||
info) cmd_app_info "$3" ;;
|
*) echo "Usage: streamlitctl app {list|start|stop|deploy|remove|logs} [args]" ;;
|
||||||
restart) cmd_app_restart "$3" ;;
|
|
||||||
*) echo "Usage: streamlitctl app {list|start|stop|restart|deploy|remove|logs|info} [args]" ;;
|
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user