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:
@@ -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 ./
|
||||
|
||||
38
README.md
38
README.md
@@ -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
38
app/api/restart/route.ts
Normal 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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
17
trading-bot-restart-watcher.service
Normal file
17
trading-bot-restart-watcher.service
Normal 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
40
watch-restart.sh
Executable 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
|
||||
Reference in New Issue
Block a user