feat: Add container restart functionality from web UI

- Added restart button to settings page
- Created /api/restart endpoint (file-flag based)
- Implemented watch-restart.sh daemon
- Added systemd service for restart watcher
- Updated README with restart setup instructions
- Container automatically restarts when settings changed

Settings flow:
1. User edits settings in web UI
2. Click 'Save Settings' to persist to .env
3. Click 'Restart Bot' to apply changes
4. Watcher detects flag and restarts container
5. New settings loaded automatically
This commit is contained in:
mindesbunister
2025-10-24 15:06:26 +02:00
parent 9e0d9b88f9
commit 26864c10f2
7 changed files with 174 additions and 10 deletions

View File

@@ -46,14 +46,15 @@ RUN npm run build
# ================================
FROM node:20-alpine AS runner
# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init
# Install dumb-init for proper signal handling and Docker CLI for restart capability
RUN apk add --no-cache dumb-init docker-cli
WORKDIR /app
# Create non-root user
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
adduser --system --uid 1001 nextjs && \
addgroup nextjs root
# Copy necessary files from builder
COPY --from=builder /app/next.config.js ./

View File

@@ -178,6 +178,24 @@ POST /api/trading/check-risk
# Get current settings
GET /api/settings
# Update settings
POST /api/settings
{
"MAX_POSITION_SIZE_USD": 100,
"LEVERAGE": 10,
"STOP_LOSS_PERCENT": -1.5,
...
}
# Restart bot container (apply settings)
POST /api/restart
```
**Notes:**
- Settings changes require container restart to take effect
- Use the web UI's "Restart Bot" button or call `/api/restart`
- Restart watcher must be running (see setup below)
# Update settings
POST /api/settings
{
@@ -223,6 +241,26 @@ docker compose restart trading-bot
docker compose down
```
### Restart Watcher (Required for Web UI Restart Button)
The restart watcher monitors for restart requests from the web UI:
```bash
# Start watcher manually
cd /home/icke/traderv4
nohup ./watch-restart.sh > logs/restart-watcher.log 2>&1 &
# OR install as systemd service (recommended)
sudo cp trading-bot-restart-watcher.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable trading-bot-restart-watcher
sudo systemctl start trading-bot-restart-watcher
# Check watcher status
sudo systemctl status trading-bot-restart-watcher
```
The watcher enables the "Restart Bot" button in the web UI to automatically restart the container when settings are changed.
### Environment Variables
All settings are configured via `.env` file:
- Drift wallet credentials

38
app/api/restart/route.ts Normal file
View File

@@ -0,0 +1,38 @@
/**
* Container Restart API Endpoint
*
* Creates a restart flag file that triggers container restart from the host
*/
import { NextResponse } from 'next/server'
import fs from 'fs'
import path from 'path'
const RESTART_FLAG = path.join(process.cwd(), 'logs', '.restart-requested')
export async function POST() {
try {
// Create logs directory if it doesn't exist
const logsDir = path.dirname(RESTART_FLAG)
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true })
}
// Create restart flag file
fs.writeFileSync(RESTART_FLAG, new Date().toISOString(), 'utf-8')
return NextResponse.json({
success: true,
message: 'Restart requested. Container will restart in a few seconds...'
})
} catch (error: any) {
console.error('Failed to create restart flag:', error)
return NextResponse.json(
{
error: 'Failed to request restart',
details: error.message
},
{ status: 500 }
)
}
}

View File

@@ -29,6 +29,7 @@ export default function SettingsPage() {
const [settings, setSettings] = useState<TradingSettings | null>(null)
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [restarting, setRestarting] = useState(false)
const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null)
useEffect(() => {
@@ -58,7 +59,7 @@ export default function SettingsPage() {
})
if (response.ok) {
setMessage({ type: 'success', text: 'Settings saved! Restart the bot to apply changes.' })
setMessage({ type: 'success', text: 'Settings saved! Click "Restart Bot" to apply changes.' })
} else {
setMessage({ type: 'error', text: 'Failed to save settings' })
}
@@ -68,6 +69,25 @@ export default function SettingsPage() {
setSaving(false)
}
const restartBot = async () => {
setRestarting(true)
setMessage(null)
try {
const response = await fetch('/api/restart', {
method: 'POST',
})
if (response.ok) {
setMessage({ type: 'success', text: 'Bot is restarting... Settings will be applied in ~10 seconds.' })
} else {
setMessage({ type: 'error', text: 'Failed to restart bot. Please restart manually with: docker restart trading-bot' })
}
} catch (error) {
setMessage({ type: 'error', text: 'Failed to restart bot. Please restart manually with: docker restart trading-bot' })
}
setRestarting(false)
}
const updateSetting = (key: keyof TradingSettings, value: any) => {
if (!settings) return
setSettings({ ...settings, [key]: value })
@@ -301,7 +321,7 @@ export default function SettingsPage() {
</Section>
</div>
{/* Save Button */}
{/* Action Buttons */}
<div className="mt-8 flex gap-4">
<button
onClick={saveSettings}
@@ -310,16 +330,23 @@ export default function SettingsPage() {
>
{saving ? '💾 Saving...' : '💾 Save Settings'}
</button>
<button
onClick={restartBot}
disabled={restarting}
className="flex-1 bg-gradient-to-r from-green-500 to-emerald-500 text-white font-bold py-4 px-6 rounded-lg hover:from-green-600 hover:to-emerald-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
>
{restarting ? '🔄 Restarting...' : '🔄 Restart Bot'}
</button>
<button
onClick={loadSettings}
className="bg-slate-700 text-white font-bold py-4 px-6 rounded-lg hover:bg-slate-600 transition-all"
>
🔄 Reset
Reset
</button>
</div>
<div className="mt-4 text-center text-slate-500 text-sm">
Changes require bot restart to take effect
<div className="mt-4 text-center text-slate-400 text-sm">
💡 Save settings first, then click Restart Bot to apply changes
</div>
</div>
</div>

View File

@@ -20,10 +20,10 @@ services:
# Load from .env file (create from .env.example)
DRIFT_WALLET_PRIVATE_KEY: ${DRIFT_WALLET_PRIVATE_KEY}
DRIFT_ENV: ${DRIFT_ENV:-mainnet-beta}
DRIFT_ENV: "${DRIFT_ENV:-mainnet-beta}"
API_SECRET_KEY: ${API_SECRET_KEY}
SOLANA_RPC_URL: ${SOLANA_RPC_URL}
PYTH_HERMES_URL: ${PYTH_HERMES_URL:-https://hermes.pyth.network}
PYTH_HERMES_URL: "${PYTH_HERMES_URL:-https://hermes.pyth.network}"
# Trading configuration
MAX_POSITION_SIZE_USD: ${MAX_POSITION_SIZE_USD:-50}
@@ -52,6 +52,9 @@ services:
# Mount logs directory
- ./logs:/app/logs
# Mount Docker socket for container restart capability
- /var/run/docker.sock:/var/run/docker.sock
# Mount for hot reload in development (comment out in production)
# - ./v4:/app/v4:ro

View File

@@ -0,0 +1,17 @@
[Unit]
Description=Trading Bot v4 Container Restart Watcher
After=docker.service
Requires=docker.service
[Service]
Type=simple
User=root
WorkingDirectory=/home/icke/traderv4
ExecStart=/home/icke/traderv4/watch-restart.sh
Restart=always
RestartSec=10
StandardOutput=append:/home/icke/traderv4/logs/restart-watcher.log
StandardError=append:/home/icke/traderv4/logs/restart-watcher.log
[Install]
WantedBy=multi-user.target

40
watch-restart.sh Executable file
View File

@@ -0,0 +1,40 @@
#!/bin/bash
#
# Container Restart Watcher
# Monitors for restart flag and restarts the trading-bot container
#
WATCH_DIR="/home/icke/traderv4/logs"
RESTART_FLAG="$WATCH_DIR/.restart-requested"
CONTAINER_NAME="trading-bot-v4"
echo "🔍 Watching for restart requests in: $WATCH_DIR"
echo "📦 Container: $CONTAINER_NAME"
echo ""
# Create logs directory if it doesn't exist
mkdir -p "$WATCH_DIR"
while true; do
if [ -f "$RESTART_FLAG" ]; then
echo "🔄 Restart requested at $(cat $RESTART_FLAG)"
echo "🔄 Restarting container: $CONTAINER_NAME"
# Remove flag before restart
rm "$RESTART_FLAG"
# Restart container
cd /home/icke/traderv4
docker compose restart $CONTAINER_NAME
if [ $? -eq 0 ]; then
echo "✅ Container restarted successfully"
else
echo "❌ Failed to restart container"
fi
echo ""
fi
# Check every 2 seconds
sleep 2
done