feat: Docker support and improve connection handling in UptimeKumaService.

Added DockerHub instructions to README, implemented automatic reconnection with retry logic, and introduced force reconnect functionality.
Updated authentication process to handle retries and added logging for better error tracking.
This commit is contained in:
Shrev Dev
2025-10-21 09:55:48 -05:00
parent 1b5e053261
commit dcd2e24159
4 changed files with 157 additions and 5 deletions
+70
View File
@@ -0,0 +1,70 @@
name: Build and Push to DockerHub
on:
push:
branches: [ main, develop ]
tags: [ 'v*' ]
pull_request:
branches: [ main ]
env:
REGISTRY: docker.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
type=sha,prefix={{branch}}-
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Image digest
run: echo ${{ steps.build.outputs.digest }}
- name: Output image info
if: github.event_name != 'pull_request'
run: |
echo "Image pushed successfully!"
echo "Tags: ${{ steps.meta.outputs.tags }}"
echo "Digest: ${{ steps.build.outputs.digest }}"
+19
View File
@@ -67,6 +67,25 @@ See **[QUICKSTART.md](QUICKSTART.md)** for detailed setup instructions.
3. Run: `docker-compose up -d` or `npm start`
4. Use `/set-channel` and `/track-all` in Discord
### DockerHub
Pre-built images are automatically available on DockerHub:
```bash
# Pull the latest image
docker pull boker02/uptime-kuma-discord-bot:latest
# Run with environment variables
docker run -d \
--name uptime-kuma-discord-bot \
-e DISCORD_BOT_TOKEN=your_token \
-e UPTIME_KUMA_URL=your_url \
-e UPTIME_KUMA_USERNAME=your_username \
-e UPTIME_KUMA_PASSWORD=your_password \
-v bot-data:/app/data \
boker02/uptime-kuma-discord-bot:latest
```
## Usage
### Basic Setup
+22 -1
View File
@@ -83,12 +83,33 @@ class UptimeKumaDiscordBot {
}
private startUpdateInterval(): void {
const updateFn = () => {
let consecutiveDisconnections = 0;
const maxConsecutiveDisconnections = 5;
const updateFn = async () => {
if (!this.uptimeKuma.isConnected()) {
this.logger.warn('Uptime Kuma is not connected, skipping update');
consecutiveDisconnections++;
// If we've been disconnected for too long, try a force reconnect
if (consecutiveDisconnections >= maxConsecutiveDisconnections) {
this.logger.warn(`Uptime Kuma has been disconnected for ${consecutiveDisconnections} consecutive checks, attempting force reconnect...`);
try {
await this.uptimeKuma.forceReconnect();
consecutiveDisconnections = 0;
this.logger.info('Force reconnect successful');
} catch (error: any) {
this.logger.error(`Force reconnect failed: ${error.message}`);
}
}
return;
}
// Reset counter on successful connection
if (consecutiveDisconnections > 0) {
consecutiveDisconnections = 0;
}
const monitors = this.uptimeKuma.getMonitorStats();
this.discord.updateMonitorStatus(monitors).catch(error => {
this.logger.error(`Failed to update Discord status: ${error.message}`);
+46 -4
View File
@@ -12,6 +12,9 @@ export class UptimeKumaService extends EventEmitter {
private isAuthenticated = false;
private logger: Logger;
private manualReconnectTimeout: NodeJS.Timeout | null = null;
private authRetryAttempts = 0;
private maxAuthRetries = 5;
private authRetryDelay = 10000;
constructor() {
super();
@@ -68,6 +71,11 @@ export class UptimeKumaService extends EventEmitter {
if (!this.isConnected() && this.socket) {
this.logger.info('Attempting manual reconnection...');
this.socket.connect();
} else if (!this.isConnected() && !this.socket) {
this.logger.info('Socket is null, attempting full reconnection...');
this.connect().catch(err => {
this.logger.error(`Manual reconnection failed: ${err.message}`);
});
}
}, 30000);
});
@@ -79,11 +87,11 @@ export class UptimeKumaService extends EventEmitter {
this.manualReconnectTimeout = null;
}
if (this.reconnectAttempts > 0) {
this.logger.info('Reconnected to Uptime Kuma, re-authenticating...');
if (this.reconnectAttempts > 0 || !this.isAuthenticated) {
this.logger.info('Connected to Uptime Kuma, authenticating...');
this.reconnectAttempts = 0;
this.authenticate().catch(err => {
this.logger.error(`Re-authentication failed: ${err.message}`);
this.authenticateWithRetry().catch(err => {
this.logger.error(`Authentication failed after retries: ${err.message}`);
});
}
});
@@ -139,6 +147,7 @@ export class UptimeKumaService extends EventEmitter {
if (response.ok) {
this.isAuthenticated = true;
this.authRetryAttempts = 0;
this.logger.info('Successfully authenticated with Uptime Kuma');
resolve();
} else {
@@ -151,6 +160,31 @@ export class UptimeKumaService extends EventEmitter {
});
}
private async authenticateWithRetry(): Promise<void> {
if (this.authRetryAttempts >= this.maxAuthRetries) {
throw new Error(`Authentication failed after ${this.maxAuthRetries} attempts`);
}
try {
await this.authenticate();
} catch (error: any) {
this.authRetryAttempts++;
this.logger.warn(`Authentication attempt ${this.authRetryAttempts}/${this.maxAuthRetries} failed: ${error.message}`);
if (this.authRetryAttempts < this.maxAuthRetries) {
this.logger.info(`Retrying authentication in ${this.authRetryDelay / 1000} seconds...`);
setTimeout(() => {
this.authenticateWithRetry().catch(err => {
this.logger.error(`Authentication retry failed: ${err.message}`);
});
}, this.authRetryDelay);
} else {
this.logger.error(`Authentication failed after ${this.maxAuthRetries} attempts. Manual intervention required.`);
throw error;
}
}
}
public getAllMonitors(): Map<number, Monitor> {
const allMonitors = new Map<number, Monitor>();
for (const [id, stats] of this.monitors.entries()) {
@@ -261,6 +295,12 @@ export class UptimeKumaService extends EventEmitter {
return this.socket !== null && this.socket.connected && this.isAuthenticated;
}
public async forceReconnect(): Promise<void> {
this.logger.info('Force reconnecting to Uptime Kuma...');
this.disconnect();
await this.connect();
}
public disconnect(): void {
if (this.manualReconnectTimeout) {
clearTimeout(this.manualReconnectTimeout);
@@ -272,6 +312,8 @@ export class UptimeKumaService extends EventEmitter {
this.socket.disconnect();
this.socket = null;
this.isAuthenticated = false;
this.authRetryAttempts = 0;
this.reconnectAttempts = 0;
}
}
}