diff --git a/package/secubox/secubox-app-streamlit-forge/README.md b/package/secubox/secubox-app-streamlit-forge/README.md index 8f95cac5..95193daa 100644 --- a/package/secubox/secubox-app-streamlit-forge/README.md +++ b/package/secubox/secubox-app-streamlit-forge/README.md @@ -52,6 +52,11 @@ slforge hide # Remove public access slforge publish # Add to mesh catalog slforge unpublish # Remove from mesh +# Launcher Integration (on-demand startup) +slforge launcher status # Show launcher status +slforge launcher priority # Set app priority (1-100) +slforge launcher always-on # Mark as always-on + # Templates slforge templates # List available templates ``` @@ -182,3 +187,44 @@ Published apps create a manifest at `/usr/share/secubox/plugins/catalog/`: } } ``` + +## On-Demand Launcher + +Install `secubox-app-streamlit-launcher` for resource optimization: + +- **Lazy Loading** - Apps start only when first accessed +- **Idle Shutdown** - Stop apps after configurable timeout (default: 30 min) +- **Memory Management** - Force-stop low-priority apps when memory is low +- **Priority System** - Keep critical apps running longer + +### Launcher Commands + +```bash +# Check launcher status +slforge launcher status + +# Set app priority (higher = keep running longer, max 100) +slforge launcher priority myapp 75 + +# Mark app as always-on (never auto-stopped) +slforge launcher always-on dashboard +``` + +### Priority Levels + +| Priority | Behavior | +|----------|----------| +| 100 + always_on | Never auto-stopped | +| 80-99 | Stopped last during memory pressure | +| 50 (default) | Normal priority | +| 1-49 | Stopped first during memory pressure | + +### How It Works + +1. User accesses `https://app.example.com` +2. If app is stopped, launcher starts it on-demand +3. App access time is tracked +4. After idle timeout, app is automatically stopped +5. Memory pressure triggers low-priority app shutdown + +See `secubox-app-streamlit-launcher` README for full configuration. diff --git a/package/secubox/secubox-app-streamlit-forge/files/usr/sbin/slforge b/package/secubox/secubox-app-streamlit-forge/files/usr/sbin/slforge index 3da396fa..edc193c9 100644 --- a/package/secubox/secubox-app-streamlit-forge/files/usr/sbin/slforge +++ b/package/secubox/secubox-app-streamlit-forge/files/usr/sbin/slforge @@ -32,6 +32,24 @@ log_ok() { echo -e "${GREEN}[OK]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_err() { echo -e "${RED}[ERROR]${NC} $1"; } +# Launcher integration +LAUNCHER_TRACKING_DIR="/tmp/streamlit-access" + +# Track app access (for launcher idle detection) +track_access() { + local name="$1" + mkdir -p "$LAUNCHER_TRACKING_DIR" + touch "$LAUNCHER_TRACKING_DIR/$name" +} + +# Check if launcher is managing this app +is_launcher_managed() { + local enabled + config_load streamlit-launcher 2>/dev/null || return 1 + config_get enabled global enabled '0' + [ "$enabled" = "1" ] +} + usage() { cat < Install from mesh +Launcher Integration: + launcher status Show launcher status + launcher priority Set app priority (higher=keep longer) + launcher always-on Mark app as always-on (never auto-stop) + Templates: templates List available templates @@ -664,6 +687,8 @@ STARTSCRIPT if netstat -tln 2>/dev/null | grep -q ":$port "; then [ "$quiet" = "0" ] && log_ok "Started $name" [ "$quiet" = "0" ] && log_info "URL: http://192.168.255.1:$port" + # Track access for launcher idle detection + track_access "$name" return 0 fi @@ -940,6 +965,56 @@ cmd_unpublish() { log_ok "Unpublished $name from mesh catalog" } +# Launcher integration commands +cmd_launcher() { + local action="$1" + shift + + case "$action" in + status) + if command -v streamlit-launcherctl >/dev/null 2>&1; then + streamlit-launcherctl status + else + log_warn "Launcher not installed" + log_info "Install with: opkg install secubox-app-streamlit-launcher" + fi + ;; + priority) + local app="$1" + local priority="$2" + [ -z "$app" ] || [ -z "$priority" ] && { + log_err "Usage: slforge launcher priority " + return 1 + } + if command -v streamlit-launcherctl >/dev/null 2>&1; then + streamlit-launcherctl priority "$app" "$priority" + else + log_err "Launcher not installed" + return 1 + fi + ;; + always-on) + local app="$1" + [ -z "$app" ] && { + log_err "Usage: slforge launcher always-on " + return 1 + } + if command -v streamlit-launcherctl >/dev/null 2>&1; then + streamlit-launcherctl priority "$app" 100 1 + log_ok "App $app marked as always-on" + else + log_err "Launcher not installed" + return 1 + fi + ;; + *) + log_err "Unknown launcher action: $action" + echo "Usage: slforge launcher " + return 1 + ;; + esac +} + # Main command router case "$1" in create) shift; cmd_create "$@" ;; @@ -961,6 +1036,7 @@ case "$1" in publish) shift; cmd_publish "$@" ;; unpublish) shift; cmd_unpublish "$@" ;; templates) cmd_templates ;; + launcher) shift; cmd_launcher "$@" ;; help|--help|-h|"") usage ;; *) diff --git a/package/secubox/secubox-app-streamlit-launcher/Makefile b/package/secubox/secubox-app-streamlit-launcher/Makefile new file mode 100644 index 00000000..c4d3a4b3 --- /dev/null +++ b/package/secubox/secubox-app-streamlit-launcher/Makefile @@ -0,0 +1,48 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=secubox-app-streamlit-launcher +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_MAINTAINER:=SecuBox Team +PKG_LICENSE:=MIT + +include $(INCLUDE_DIR)/package.mk + +define Package/secubox-app-streamlit-launcher + SECTION:=secubox + CATEGORY:=SecuBox + TITLE:=Streamlit On-Demand Launcher + DEPENDS:=+secubox-app-streamlit-forge + PKGARCH:=all +endef + +define Package/secubox-app-streamlit-launcher/description + On-demand Streamlit app launcher with idle shutdown. + Starts apps only when accessed, stops after idle timeout. + Monitors memory and can force-stop low-priority apps. +endef + +define Build/Compile +endef + +define Package/secubox-app-streamlit-launcher/install + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/streamlit-launcher $(1)/etc/config/ + + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/etc/init.d/streamlit-launcher $(1)/etc/init.d/ + + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) ./files/usr/sbin/streamlit-launcherctl $(1)/usr/sbin/ + + $(INSTALL_DIR) $(1)/usr/share/streamlit-launcher + $(INSTALL_DATA) ./files/usr/share/streamlit-launcher/loading.html $(1)/usr/share/streamlit-launcher/ +endef + +define Package/secubox-app-streamlit-launcher/postinst +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || /etc/init.d/streamlit-launcher enable +exit 0 +endef + +$(eval $(call BuildPackage,secubox-app-streamlit-launcher)) diff --git a/package/secubox/secubox-app-streamlit-launcher/README.md b/package/secubox/secubox-app-streamlit-launcher/README.md new file mode 100644 index 00000000..19c898e9 --- /dev/null +++ b/package/secubox/secubox-app-streamlit-launcher/README.md @@ -0,0 +1,191 @@ +# SecuBox Streamlit Launcher + +On-demand Streamlit app launcher with idle shutdown and memory management. + +## Overview + +The Streamlit Launcher optimizes resource usage on constrained devices by: + +- **Starting apps on-demand** when first accessed (lazy loading) +- **Stopping idle apps** after configurable timeout (default: 30 min) +- **Managing memory pressure** by stopping low-priority apps when memory is low +- **Priority system** to keep critical apps running longer + +## Architecture + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ +│ HAProxy │────▶│ mitmproxy │────▶│ Streamlit │ +│ (vhost) │ │ (WAF+track) │ │ Launcher Daemon │ +└─────────────┘ └──────────────┘ └────────┬────────┘ + │ │ + Track access Start/Stop + │ │ + ┌──────▼──────┐ ┌─────▼─────┐ + │ /tmp/access │ │ slforge │ + │ (touch) │ │ start/ │ + └─────────────┘ │ stop │ + └───────────┘ +``` + +## Installation + +```bash +opkg install secubox-app-streamlit-launcher +``` + +## CLI Reference + +```bash +# Show status +streamlit-launcherctl status + +# List all apps with details +streamlit-launcherctl list + +# Manually start/stop an app +streamlit-launcherctl start +streamlit-launcherctl stop + +# Set app priority (higher = keep running longer) +streamlit-launcherctl priority + +# Set always-on (never auto-stop) +streamlit-launcherctl priority 100 1 + +# Run idle check manually +streamlit-launcherctl check + +# Run memory pressure check +streamlit-launcherctl check-memory +``` + +## Configuration + +Edit `/etc/config/streamlit-launcher`: + +``` +config global 'global' + # Enable the launcher daemon + option enabled '1' + + # Enable on-demand startup (vs always-running) + option on_demand '1' + + # Minutes of inactivity before stopping an app + option idle_timeout '30' + + # Seconds between idle checks + option check_interval '60' + + # Minimum free memory (MB) before force-stopping apps + option memory_threshold '100' + + # Max seconds to wait for app startup + option startup_timeout '30' + +# App priorities (higher = keep running longer) +config priority 'control' + option app 'control' + option value '100' + option always_on '1' + +config priority 'ytdownload' + option app 'ytdownload' + option value '30' +``` + +## Priority System + +| Priority | Behavior | +|----------|----------| +| 100 + always_on | Never auto-stopped | +| 80-99 | Stopped last during memory pressure | +| 50 (default) | Normal priority | +| 1-49 | Stopped first during memory pressure | + +## Integration with slforge + +The launcher works alongside `slforge` (Streamlit Forge): + +- `slforge` manages app configuration, creation, and basic start/stop +- `streamlit-launcherctl` adds on-demand and idle management + +When on-demand is enabled: +1. User accesses `https://app.example.com` +2. HAProxy routes to mitmproxy +3. If app is down, mitmproxy can trigger startup via hook +4. Launcher starts app and waits for ready +5. Request is served +6. Access is tracked +7. After idle timeout, app is stopped + +## Access Tracking + +The launcher tracks app access via touch files in `/tmp/streamlit-access/`: + +```bash +# Track access (reset idle timer) +streamlit-launcherctl track + +# Or directly +touch /tmp/streamlit-access/ +``` + +This can be triggered by: +- mitmproxy request hook +- HAProxy health check script +- Cron job parsing access logs + +## Memory Management + +When free memory drops below threshold: + +1. Apps are sorted by priority (lowest first) +2. Low-priority apps are stopped one by one +3. Stops when memory recovers above threshold +4. Always-on apps are never stopped + +## Service Control + +```bash +# Enable/start the daemon +/etc/init.d/streamlit-launcher enable +/etc/init.d/streamlit-launcher start + +# Check daemon status +/etc/init.d/streamlit-launcher status + +# View logs +logread -e streamlit-launcher +``` + +## Files + +| Path | Description | +|------|-------------| +| `/usr/sbin/streamlit-launcherctl` | CLI tool | +| `/etc/config/streamlit-launcher` | UCI configuration | +| `/etc/init.d/streamlit-launcher` | Procd init script | +| `/tmp/streamlit-access/` | Access tracking files | +| `/usr/share/streamlit-launcher/loading.html` | Loading page template | + +## Example: Optimize for Low Memory + +```bash +# Set aggressive timeout (10 min) +uci set streamlit-launcher.global.idle_timeout='10' + +# Lower memory threshold (trigger cleanup at 150MB free) +uci set streamlit-launcher.global.memory_threshold='150' + +# Make dashboard always-on +streamlit-launcherctl priority dashboard 100 1 + +# Lower priority for heavy apps +streamlit-launcherctl priority jupyter 20 +streamlit-launcherctl priority analytics 30 + +uci commit streamlit-launcher +/etc/init.d/streamlit-launcher restart +``` diff --git a/package/secubox/secubox-app-streamlit-launcher/files/etc/config/streamlit-launcher b/package/secubox/secubox-app-streamlit-launcher/files/etc/config/streamlit-launcher new file mode 100644 index 00000000..abfea384 --- /dev/null +++ b/package/secubox/secubox-app-streamlit-launcher/files/etc/config/streamlit-launcher @@ -0,0 +1,28 @@ +config global 'global' + option enabled '1' + # Idle timeout in minutes before stopping an app + option idle_timeout '30' + # Check interval in seconds + option check_interval '60' + # Minimum free memory (MB) before force-stopping low-priority apps + option memory_threshold '100' + # Enable on-demand startup (vs always-running) + option on_demand '1' + # Startup timeout in seconds (max wait for app to be ready) + option startup_timeout '30' + # Directory for access tracking files + option tracking_dir '/tmp/streamlit-access' + # Log level: debug, info, warn, error + option log_level 'info' + +# App priority (higher = keep running longer) +# Apps not listed default to priority 50 +config priority 'default' + option app '*' + option value '50' + +# Example: keep control dashboard always running +#config priority 'control' +# option app 'control' +# option value '100' +# option always_on '1' diff --git a/package/secubox/secubox-app-streamlit-launcher/files/etc/init.d/streamlit-launcher b/package/secubox/secubox-app-streamlit-launcher/files/etc/init.d/streamlit-launcher new file mode 100644 index 00000000..0189da45 --- /dev/null +++ b/package/secubox/secubox-app-streamlit-launcher/files/etc/init.d/streamlit-launcher @@ -0,0 +1,31 @@ +#!/bin/sh /etc/rc.common + +START=95 +STOP=10 +USE_PROCD=1 + +PROG=/usr/sbin/streamlit-launcherctl + +start_service() { + local enabled + config_load streamlit-launcher + config_get enabled global enabled '0' + + [ "$enabled" = "1" ] || return 0 + + procd_open_instance + procd_set_param command "$PROG" daemon + procd_set_param respawn 3600 5 5 + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger "streamlit-launcher" +} + +reload_service() { + stop + start +} diff --git a/package/secubox/secubox-app-streamlit-launcher/files/usr/sbin/streamlit-launcherctl b/package/secubox/secubox-app-streamlit-launcher/files/usr/sbin/streamlit-launcherctl new file mode 100644 index 00000000..58228ffa --- /dev/null +++ b/package/secubox/secubox-app-streamlit-launcher/files/usr/sbin/streamlit-launcherctl @@ -0,0 +1,494 @@ +#!/bin/sh +# Streamlit On-Demand Launcher Controller +# Manages lazy-loading of Streamlit apps with idle shutdown + +. /lib/functions.sh + +TRACKING_DIR="/tmp/streamlit-access" +STARTUP_DIR="/tmp/streamlit-startup" +LOG_TAG="streamlit-launcher" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[0;33m' +NC='\033[0m' + +# Load config +load_config() { + config_load streamlit-launcher + config_get ENABLED global enabled '1' + config_get IDLE_TIMEOUT global idle_timeout '30' + config_get CHECK_INTERVAL global check_interval '60' + config_get MEMORY_THRESHOLD global memory_threshold '100' + config_get ON_DEMAND global on_demand '1' + config_get STARTUP_TIMEOUT global startup_timeout '30' + config_get TRACKING_DIR global tracking_dir '/tmp/streamlit-access' + config_get LOG_LEVEL global log_level 'info' +} + +log() { + local level="$1" + shift + case "$LOG_LEVEL" in + debug) ;; + info) [ "$level" = "debug" ] && return ;; + warn) [ "$level" = "debug" -o "$level" = "info" ] && return ;; + error) [ "$level" != "error" ] && return ;; + esac + logger -t "$LOG_TAG" -p "daemon.$level" "$*" + [ -t 1 ] && echo "[$level] $*" +} + +# Get app priority (higher = more important) +get_priority() { + local app="$1" + local priority=50 + local always_on=0 + + get_app_priority() { + local section="$1" + local cfg_app cfg_value cfg_always + config_get cfg_app "$section" app + config_get cfg_value "$section" value '50' + config_get cfg_always "$section" always_on '0' + + if [ "$cfg_app" = "$app" ] || [ "$cfg_app" = "*" ]; then + priority="$cfg_value" + always_on="$cfg_always" + fi + } + + config_foreach get_app_priority priority + echo "$priority $always_on" +} + +# Track app access (called by mitmproxy hook or cron) +track_access() { + local app="$1" + mkdir -p "$TRACKING_DIR" + touch "$TRACKING_DIR/$app" + log debug "Tracked access for $app" +} + +# Get last access time (seconds since epoch) +get_last_access() { + local app="$1" + local file="$TRACKING_DIR/$app" + if [ -f "$file" ]; then + stat -c %Y "$file" 2>/dev/null || echo 0 + else + echo 0 + fi +} + +# Get idle time in minutes +get_idle_minutes() { + local app="$1" + local last_access=$(get_last_access "$app") + local now=$(date +%s) + if [ "$last_access" -gt 0 ]; then + echo $(( (now - last_access) / 60 )) + else + echo 9999 + fi +} + +# Check if app is running +is_running() { + local app="$1" + local port + port=$(uci -q get streamlit-forge."$app".port) + [ -z "$port" ] && return 1 + netstat -tln 2>/dev/null | grep -q ":$port " && return 0 + return 1 +} + +# Get free memory in MB +get_free_memory() { + awk '/MemAvailable/{print int($2/1024)}' /proc/meminfo +} + +# Start an app (on-demand) +start_app() { + local app="$1" + local wait="${2:-0}" + + if is_running "$app"; then + log debug "App $app already running" + track_access "$app" + return 0 + fi + + log info "Starting app $app on-demand" + mkdir -p "$STARTUP_DIR" + touch "$STARTUP_DIR/$app.starting" + + # Start via slforge + slforge start "$app" >/dev/null 2>&1 + local rc=$? + + if [ "$wait" = "1" ] && [ $rc -eq 0 ]; then + # Wait for app to be ready + local port=$(uci -q get streamlit-forge."$app".port) + local timeout="$STARTUP_TIMEOUT" + local elapsed=0 + + while [ $elapsed -lt $timeout ]; do + if netstat -tln 2>/dev/null | grep -q ":$port "; then + log info "App $app ready on port $port (${elapsed}s)" + rm -f "$STARTUP_DIR/$app.starting" + track_access "$app" + return 0 + fi + sleep 1 + elapsed=$((elapsed + 1)) + done + + log error "App $app failed to start within ${timeout}s" + rm -f "$STARTUP_DIR/$app.starting" + return 1 + fi + + rm -f "$STARTUP_DIR/$app.starting" + [ $rc -eq 0 ] && track_access "$app" + return $rc +} + +# Stop an app +stop_app() { + local app="$1" + local reason="${2:-idle}" + + if ! is_running "$app"; then + log debug "App $app not running" + return 0 + fi + + log info "Stopping app $app (reason: $reason)" + slforge stop "$app" >/dev/null 2>&1 +} + +# Check idle apps and stop them +check_idle() { + load_config + [ "$ENABLED" = "1" ] || return 0 + + local app idle priority always_on + + # Get list of apps from streamlit-forge + for app in $(uci -q show streamlit-forge 2>/dev/null | grep '=app$' | cut -d. -f2 | cut -d= -f1); do + [ -z "$app" ] && continue + + if ! is_running "$app"; then + continue + fi + + # Get priority info + read priority always_on < ${IDLE_TIMEOUT}m" + fi + done +} + +# Check memory pressure and stop low-priority apps +check_memory() { + load_config + [ "$ENABLED" = "1" ] || return 0 + + local free_mb=$(get_free_memory) + if [ "$free_mb" -ge "$MEMORY_THRESHOLD" ]; then + return 0 + fi + + log warn "Low memory: ${free_mb}MB < ${MEMORY_THRESHOLD}MB threshold" + + # Build list of running apps with priorities + local apps_by_priority="" + local app priority always_on + + for app in $(uci -q show streamlit-forge 2>/dev/null | grep '=app$' | cut -d. -f2 | cut -d= -f1); do + [ -z "$app" ] && continue + is_running "$app" || continue + + read priority always_on </dev/null | grep '=app$' | cut -d. -f2 | cut -d= -f1); do + [ -z "$app" ] && continue + + read priority always_on </dev/null | grep '=app$' | cut -d. -f2 | cut -d= -f1); do + [ -z "$app" ] && continue + + port=$(uci -q get streamlit-forge."$app".port) + domain=$(uci -q get streamlit-forge."$app".domain) + enabled=$(uci -q get streamlit-forge."$app".enabled) + + read priority always_on < [always_on]" + return 1 + } + + # Check if app exists + uci -q get streamlit-forge."$app" >/dev/null || { + echo "Error: App '$app' not found" + return 1 + } + + # Find or create priority section + local section="" + find_section() { + local s="$1" + local cfg_app + config_get cfg_app "$s" app + [ "$cfg_app" = "$app" ] && section="$s" + } + config_load streamlit-launcher + config_foreach find_section priority + + if [ -z "$section" ]; then + section=$(uci add streamlit-launcher priority) + uci set streamlit-launcher."$section".app="$app" + fi + + uci set streamlit-launcher."$section".value="$priority" + uci set streamlit-launcher."$section".always_on="$always_on" + uci commit streamlit-launcher + + echo "Set priority for $app: $priority (always_on: $always_on)" +} + +# Stop all managed apps +stop_all() { + log info "Stopping all managed apps" + for app in $(uci -q show streamlit-forge 2>/dev/null | grep '=app$' | cut -d. -f2 | cut -d= -f1); do + [ -z "$app" ] && continue + is_running "$app" && stop_app "$app" "stop_all" + done +} + +# Start all enabled apps (for non-on-demand mode) +start_all() { + log info "Starting all enabled apps" + for app in $(uci -q show streamlit-forge 2>/dev/null | grep '=app$' | cut -d. -f2 | cut -d= -f1); do + [ -z "$app" ] && continue + local enabled=$(uci -q get streamlit-forge."$app".enabled) + [ "$enabled" = "1" ] && start_app "$app" 0 + done +} + +usage() { + cat < [options] + +COMMANDS: + daemon Run the launcher daemon (called by init.d) + status Show launcher status and app states + list List all apps with details + start Start an app on-demand + stop Stop an app + stop-all Stop all managed apps + start-all Start all enabled apps + track Track access for an app (resets idle timer) + priority Set app priority (higher = keep running longer) + Add '1' as third arg for always-on + check Run idle check once + check-memory Run memory pressure check once + +EXAMPLES: + $0 status + $0 start ytdownload + $0 priority control 100 1 # Always keep 'control' running + $0 priority ytdownload 30 # Lower priority, stop sooner + +CONFIG: /etc/config/streamlit-launcher +EOF +} + +# Main +case "$1" in + daemon) + daemon + ;; + status) + status + ;; + list) + list + ;; + start) + [ -z "$2" ] && { echo "Usage: $0 start "; exit 1; } + load_config + start_app "$2" 1 + ;; + stop) + [ -z "$2" ] && { echo "Usage: $0 stop "; exit 1; } + load_config + stop_app "$2" "manual" + ;; + stop-all) + load_config + stop_all + ;; + start-all) + load_config + start_all + ;; + track) + [ -z "$2" ] && { echo "Usage: $0 track "; exit 1; } + load_config + track_access "$2" + ;; + priority) + set_priority "$2" "$3" "$4" + ;; + check) + check_idle + ;; + check-memory) + check_memory + ;; + request) + [ -z "$2" ] && { echo "Usage: $0 request "; exit 1; } + request_start "$2" + ;; + *) + usage + exit 1 + ;; +esac diff --git a/package/secubox/secubox-app-streamlit-launcher/files/usr/share/streamlit-launcher/loading.html b/package/secubox/secubox-app-streamlit-launcher/files/usr/share/streamlit-launcher/loading.html new file mode 100644 index 00000000..6615be14 --- /dev/null +++ b/package/secubox/secubox-app-streamlit-launcher/files/usr/share/streamlit-launcher/loading.html @@ -0,0 +1,90 @@ + + + + + + + Starting App... + + + +
+
+

Starting Application

+

The app is being launched on-demand...

+
+
+
+

This page will refresh automatically

+
+ +