Initial commit
This commit is contained in:
		
							
								
								
									
										38
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  |  | ||||||
|  | # Build stage | ||||||
|  | FROM golang:1.21-alpine AS builder | ||||||
|  |  | ||||||
|  | WORKDIR /app | ||||||
|  |  | ||||||
|  | # Copy go mod files | ||||||
|  | COPY go.mod go.sum* ./ | ||||||
|  |  | ||||||
|  | # Download dependencies | ||||||
|  | RUN go mod download | ||||||
|  |  | ||||||
|  | # Copy source code | ||||||
|  | COPY main.go . | ||||||
|  |  | ||||||
|  | # Build the binary | ||||||
|  | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o middleware main.go | ||||||
|  |  | ||||||
|  | # Final stage | ||||||
|  | FROM alpine:latest | ||||||
|  |  | ||||||
|  | # Add ca-certificates for HTTPS requests | ||||||
|  | RUN apk --no-cache add ca-certificates tzdata | ||||||
|  |  | ||||||
|  | WORKDIR /root/ | ||||||
|  |  | ||||||
|  | # Copy the binary from builder stage | ||||||
|  | COPY --from=builder /app/middleware . | ||||||
|  |  | ||||||
|  | # Expose port | ||||||
|  | EXPOSE 8080 | ||||||
|  |  | ||||||
|  | # Health check | ||||||
|  | HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ | ||||||
|  |     CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 | ||||||
|  |  | ||||||
|  | # Run the binary | ||||||
|  | CMD ["./middleware"] | ||||||
							
								
								
									
										54
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | .PHONY: build run test clean docker-build docker-run help | ||||||
|  |  | ||||||
|  | BINARY_NAME=middleware | ||||||
|  | DOCKER_IMAGE=slack-to-ntfy | ||||||
|  |  | ||||||
|  | # Default target | ||||||
|  | help: | ||||||
|  | 	@echo "Available targets:" | ||||||
|  | 	@echo "  build        - Build the Go binary" | ||||||
|  | 	@echo "  run          - Run the application locally" | ||||||
|  | 	@echo "  test         - Run tests" | ||||||
|  | 	@echo "  clean        - Clean build artifacts" | ||||||
|  | 	@echo "  docker-build - Build Docker image" | ||||||
|  | 	@echo "  docker-run   - Run with Docker Compose" | ||||||
|  | 	@echo "  docker-stop  - Stop Docker Compose" | ||||||
|  | 	@echo "  logs         - Show Docker logs" | ||||||
|  |  | ||||||
|  | build: | ||||||
|  | 	go build -o $(BINARY_NAME) main.go | ||||||
|  |  | ||||||
|  | run: build | ||||||
|  | 	./$(BINARY_NAME) | ||||||
|  |  | ||||||
|  | test: | ||||||
|  | 	go test -v ./... | ||||||
|  |  | ||||||
|  | clean: | ||||||
|  | 	go clean | ||||||
|  | 	rm -f $(BINARY_NAME) | ||||||
|  |  | ||||||
|  | docker-build: | ||||||
|  | 	docker build -t $(DOCKER_IMAGE) . | ||||||
|  |  | ||||||
|  | docker-run: | ||||||
|  | 	docker compose up -d | ||||||
|  |  | ||||||
|  | docker-stop: | ||||||
|  | 	docker compose down | ||||||
|  |  | ||||||
|  | logs: | ||||||
|  | 	docker compose logs -f | ||||||
|  |  | ||||||
|  | # Development helpers | ||||||
|  | fmt: | ||||||
|  | 	go fmt ./... | ||||||
|  |  | ||||||
|  | vet: | ||||||
|  | 	go vet ./... | ||||||
|  |  | ||||||
|  | mod-tidy: | ||||||
|  | 	go mod tidy | ||||||
|  |  | ||||||
|  | deps: mod-tidy | ||||||
|  | 	go mod download | ||||||
							
								
								
									
										78
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | # Slack to ntfy Middleware | ||||||
|  |  | ||||||
|  | A lightweight Go service that acts as a middleware between Slack webhooks and ntfy servers, with Bearer token authentication and basic authentication support. | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  |  | ||||||
|  | - ✅ Parses Slack webhook format | ||||||
|  | - ✅ Forwards alerts to self-hosted ntfy servers | ||||||
|  | - ✅ Bearer token authentication support | ||||||
|  | - ✅ Health check endpoint | ||||||
|  | - ✅ Lightweight Docker container (~8MB) | ||||||
|  | - ✅ High performance and low resource usage | ||||||
|  |  | ||||||
|  | ## Quick Start | ||||||
|  |  | ||||||
|  | 1. **Configure the service**: | ||||||
|  |    ```bash | ||||||
|  |    # Edit docker-compose.yml and set: | ||||||
|  |    # - NTFY_BASE_URL=https://your-ntfy-server.com | ||||||
|  |    # - NTFY_TOKEN=tk_your_bearer_token | ||||||
|  |    # OR | ||||||
|  |    # - NTFY_USERNAME=your_username | ||||||
|  |    # - NTFY_PASSWORD=your_password | ||||||
|  |    ``` | ||||||
|  |  | ||||||
|  | 2. **Run the service**: | ||||||
|  |    ```bash | ||||||
|  |    docker compose up -d | ||||||
|  |    ``` | ||||||
|  |  | ||||||
|  | 3. **Configure Slack**: | ||||||
|  |    - Go to Slack Integrations → Incoming Webhooks | ||||||
|  |    - Add new webhook | ||||||
|  |    - Webhook URL: `http://your-server-ip:8080/your-topic-name` | ||||||
|  |  | ||||||
|  | 4. **Test the service**: | ||||||
|  |    ```bash | ||||||
|  |    # Test webhook | ||||||
|  |    curl -X POST http://localhost:8080/test-topic \ | ||||||
|  |         -H 'Content-Type: application/json' \ | ||||||
|  |         -d '{"text": "Test alert from Slack to ntfy"}' | ||||||
|  |     | ||||||
|  |    # Check health | ||||||
|  |    curl http://localhost:8080/health | ||||||
|  |    ``` | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  |  | ||||||
|  | | Environment Variable | Default | Description | | ||||||
|  | |---------------------|---------|-------------| | ||||||
|  | | `NTFY_BASE_URL` | `https://ntfy.sh` | Your ntfy server URL (without topic) | | ||||||
|  | | `NTFY_TOKEN` | `""` | Bearer token for ntfy authentication | | ||||||
|  | | `NTFY_USERNAME` | `""` | Username for ntfy basic authentication | | ||||||
|  | | `NTFY_PASSWORD` | `""` | Password for ntfy basic authentication | | ||||||
|  | | `BIND_ADDRESS` | `0.0.0.0` | Interface to bind to | | ||||||
|  | | `BIND_PORT` | `8080` | Port to listen on | | ||||||
|  |  | ||||||
|  | ## Development | ||||||
|  |  | ||||||
|  | ### Build locally | ||||||
|  | ```bash | ||||||
|  | go build -o middleware main.go | ||||||
|  | ./middleware | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Build Docker image | ||||||
|  | ```bash | ||||||
|  | docker build -t slack-to-ntfy . | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Run tests | ||||||
|  | ```bash | ||||||
|  | go test ./... | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## License | ||||||
|  |  | ||||||
|  | MIT License | ||||||
							
								
								
									
										46
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | version: '3.8' | ||||||
|  |  | ||||||
|  | services: | ||||||
|  |   slack-to-ntfy: | ||||||
|  |     build: . | ||||||
|  |     container_name: slack-to-ntfy | ||||||
|  |     ports: | ||||||
|  |       - "8080:8080" | ||||||
|  |     environment: | ||||||
|  |       # Replace with your ntfy server URL (without topic path) | ||||||
|  |       - NTFY_BASE_URL=https://ntfy.dangerzone.dev | ||||||
|  |        | ||||||
|  |       # For token-based authentication, uncomment and replace with your ntfy Bearer token (e.g., tk_xxxx) | ||||||
|  |       - NTFY_TOKEN=tk_7hdlipcf02hsaslgmav70ruzkurcp | ||||||
|  |        | ||||||
|  |       # For username/password authentication, uncomment and replace with your ntfy credentials | ||||||
|  |       # - NTFY_USERNAME=your_username | ||||||
|  |       # - NTFY_PASSWORD=your_password | ||||||
|  |        | ||||||
|  |       # Bind configuration | ||||||
|  |       - BIND_ADDRESS=0.0.0.0 | ||||||
|  |       - BIND_PORT=8080 | ||||||
|  |     restart: unless-stopped | ||||||
|  |      | ||||||
|  |     # Optional: Resource limits | ||||||
|  |     deploy: | ||||||
|  |       resources: | ||||||
|  |         limits: | ||||||
|  |           memory: 32M | ||||||
|  |           cpus: '0.1' | ||||||
|  |         reservations: | ||||||
|  |           memory: 16M | ||||||
|  |           cpus: '0.05' | ||||||
|  |      | ||||||
|  |     # Health check using the built-in endpoint | ||||||
|  |     healthcheck: | ||||||
|  |       test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] | ||||||
|  |       interval: 30s | ||||||
|  |       timeout: 10s | ||||||
|  |       retries: 3 | ||||||
|  |       start_period: 10s | ||||||
|  |  | ||||||
|  | # Optional: Create a custom network | ||||||
|  | networks: | ||||||
|  |   default: | ||||||
|  |     name: slack-ntfy-middleware | ||||||
							
								
								
									
										232
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,232 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 	"os" | ||||||
|  | 	"path" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // SlackWebhookPayload represents the incoming Slack webhook format | ||||||
|  | type SlackWebhookPayload struct { | ||||||
|  | 	Text        string `json:"text"` | ||||||
|  | 	Username    string `json:"username,omitempty"` | ||||||
|  | 	Channel     string `json:"channel,omitempty"` | ||||||
|  | 	IconEmoji   string `json:"icon_emoji,omitempty"` | ||||||
|  | 	Attachments []struct { | ||||||
|  | 		Color     string `json:"color,omitempty"` | ||||||
|  | 		Title     string `json:"title,omitempty"` | ||||||
|  | 		Text      string `json:"text,omitempty"` | ||||||
|  | 		Footer    string `json:"footer,omitempty"` | ||||||
|  | 		Timestamp int64  `json:"ts,omitempty"` | ||||||
|  | 	} `json:"attachments,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Config holds the middleware configuration | ||||||
|  | type Config struct { | ||||||
|  | 	NtfyBaseURL  string | ||||||
|  | 	NtfyToken    string | ||||||
|  | 	NtfyUsername string | ||||||
|  | 	NtfyPassword string | ||||||
|  | 	BindAddress  string | ||||||
|  | 	BindPort     string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Middleware handles the webhook forwarding | ||||||
|  | type Middleware struct { | ||||||
|  | 	config Config | ||||||
|  | 	client *http.Client | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewMiddleware(config Config) *Middleware { | ||||||
|  | 	return &Middleware{ | ||||||
|  | 		config: config, | ||||||
|  | 		client: &http.Client{ | ||||||
|  | 			Timeout: 10 * time.Second, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // extractMessage extracts the alert message from various Slack webhook formats | ||||||
|  | func (m *Middleware) extractMessage(payload *SlackWebhookPayload) string { | ||||||
|  | 	// Primary message text | ||||||
|  | 	if payload.Text != "" { | ||||||
|  | 		return payload.Text | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Check attachments for additional content | ||||||
|  | 	if len(payload.Attachments) > 0 { | ||||||
|  | 		var parts []string | ||||||
|  |  | ||||||
|  | 		for _, attachment := range payload.Attachments { | ||||||
|  | 			if attachment.Title != "" { | ||||||
|  | 				parts = append(parts, attachment.Title) | ||||||
|  | 			} | ||||||
|  | 			if attachment.Text != "" { | ||||||
|  | 				parts = append(parts, attachment.Text) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if len(parts) > 0 { | ||||||
|  | 			return strings.Join(parts, "\n") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return "Generic Message" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // forwardToNtfy sends the message to the ntfy server | ||||||
|  | func (m *Middleware) forwardToNtfy(topic, message string) error { | ||||||
|  | 	ntfyURL := fmt.Sprintf("%s/%s", strings.TrimRight(m.config.NtfyBaseURL, "/"), topic) | ||||||
|  |  | ||||||
|  | 	req, err := http.NewRequest("POST", ntfyURL, bytes.NewBufferString(message)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to create request: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req.Header.Set("Content-Type", "text/plain; charset=utf-8") | ||||||
|  | 	req.Header.Set("User-Agent", "slack-to-ntfy/1.0") | ||||||
|  |  | ||||||
|  | 	if m.config.NtfyToken != "" { | ||||||
|  | 		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", m.config.NtfyToken)) | ||||||
|  | 	} else if m.config.NtfyUsername != "" && m.config.NtfyPassword != "" { | ||||||
|  | 		req.SetBasicAuth(m.config.NtfyUsername, m.config.NtfyPassword) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req.Header.Set("Title", "Slack to ntfy Alert") | ||||||
|  | 	req.Header.Set("Priority", "high") | ||||||
|  | 	req.Header.Set("Tags", "warning,computer") | ||||||
|  |  | ||||||
|  | 	resp, err := m.client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to send request: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	if resp.StatusCode < 200 || resp.StatusCode >= 300 { | ||||||
|  | 		body, _ := io.ReadAll(resp.Body) | ||||||
|  | 		return fmt.Errorf("ntfy request failed with status %d: %s", resp.StatusCode, string(body)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	log.Printf("Successfully forwarded message to ntfy topic '%s'", topic) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // webhookHandler handles incoming Slack webhook requests | ||||||
|  | func (m *Middleware) webhookHandler(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	if r.Method != http.MethodPost { | ||||||
|  | 		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Extract topic from URL path | ||||||
|  | 	topic := strings.TrimPrefix(r.URL.Path, "/") | ||||||
|  | 	if topic == "" { | ||||||
|  | 		http.Error(w, "Topic is required in URL path", http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Clean topic (remove any path traversal attempts) | ||||||
|  | 	topic = path.Clean(topic) | ||||||
|  | 	if strings.Contains(topic, "..") { | ||||||
|  | 		http.Error(w, "Invalid topic name", http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Read request body | ||||||
|  | 	body, err := io.ReadAll(r.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("Failed to read request body: %v", err) | ||||||
|  | 		http.Error(w, "Failed to read request", http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var message string | ||||||
|  |  | ||||||
|  | 	// Try to parse as JSON (Slack webhook format) | ||||||
|  | 	var payload SlackWebhookPayload | ||||||
|  | 	if err := json.Unmarshal(body, &payload); err == nil { | ||||||
|  | 		message = m.extractMessage(&payload) | ||||||
|  | 		log.Printf("Received Slack webhook for topic '%s': %s", topic, message) | ||||||
|  | 	} else { | ||||||
|  | 		// Fallback to raw text | ||||||
|  | 		message = string(body) | ||||||
|  | 		if message == "" { | ||||||
|  | 			message = "Slack to ntfy Alert (no content)" | ||||||
|  | 		} | ||||||
|  | 		log.Printf("Received raw webhook for topic '%s': %s", topic, message) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Forward to ntfy | ||||||
|  | 	if err := m.forwardToNtfy(topic, message); err != nil { | ||||||
|  | 		log.Printf("Failed to forward to ntfy: %v", err) | ||||||
|  | 		http.Error(w, "Failed to forward alert", http.StatusInternalServerError) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Return success response | ||||||
|  | 	w.Header().Set("Content-Type", "application/json") | ||||||
|  | 	response := map[string]string{ | ||||||
|  | 		"status":  "success", | ||||||
|  | 		"message": "Alert forwarded to ntfy", | ||||||
|  | 		"topic":   topic, | ||||||
|  | 	} | ||||||
|  | 	json.NewEncoder(w).Encode(response) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // healthHandler provides a health check endpoint | ||||||
|  | func (m *Middleware) healthHandler(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	w.Header().Set("Content-Type", "application/json") | ||||||
|  | 	response := map[string]interface{}{ | ||||||
|  | 		"status":       "healthy", | ||||||
|  | 		"ntfy_url":     m.config.NtfyBaseURL, | ||||||
|  | 		"auth_enabled": m.config.NtfyToken != "" || m.config.NtfyUsername != "", | ||||||
|  | 		"timestamp":    time.Now().Unix(), | ||||||
|  | 	} | ||||||
|  | 	json.NewEncoder(w).Encode(response) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func loadConfig() Config { | ||||||
|  | 	return Config{ | ||||||
|  | 		NtfyBaseURL:  getEnv("NTFY_BASE_URL", "https://ntfy.sh"), | ||||||
|  | 		NtfyToken:    getEnv("NTFY_TOKEN", ""), | ||||||
|  | 		NtfyUsername: getEnv("NTFY_USERNAME", ""), | ||||||
|  | 		NtfyPassword: getEnv("NTFY_PASSWORD", ""), | ||||||
|  | 		BindAddress:  getEnv("BIND_ADDRESS", "0.0.0.0"), | ||||||
|  | 		BindPort:     getEnv("BIND_PORT", "8080"), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getEnv(key, defaultValue string) string { | ||||||
|  | 	if value := os.Getenv(key); value != "" { | ||||||
|  | 		return value | ||||||
|  | 	} | ||||||
|  | 	return defaultValue | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	config := loadConfig() | ||||||
|  | 	middleware := NewMiddleware(config) | ||||||
|  |  | ||||||
|  | 	// Setup routes | ||||||
|  | 	http.HandleFunc("/health", middleware.healthHandler) | ||||||
|  | 	http.HandleFunc("/", middleware.webhookHandler) | ||||||
|  |  | ||||||
|  | 	// Start server | ||||||
|  | 	bindAddr := fmt.Sprintf("%s:%s", config.BindAddress, config.BindPort) | ||||||
|  |  | ||||||
|  | 	log.Printf("Starting Slack to ntfy middleware server") | ||||||
|  | 	log.Printf("Listening on: %s", bindAddr) | ||||||
|  | 	log.Printf("ntfy URL: %s", config.NtfyBaseURL) | ||||||
|  | 	log.Printf("Authentication: %t", config.NtfyToken != "" || config.NtfyUsername != "") | ||||||
|  |  | ||||||
|  | 	if err := http.ListenAndServe(bindAddr, nil); err != nil { | ||||||
|  | 		log.Fatalf("Server failed to start: %v", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								scripts/build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										26
									
								
								scripts/build.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  |  | ||||||
|  | echo "🔨 Building Slack to ntfy middleware..." | ||||||
|  |  | ||||||
|  | # Build for different platforms | ||||||
|  | PLATFORMS="linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64" | ||||||
|  | OUTPUT_DIR="dist" | ||||||
|  |  | ||||||
|  | mkdir -p $OUTPUT_DIR | ||||||
|  |  | ||||||
|  | for platform in $PLATFORMS; do | ||||||
|  |     OS=$(echo $platform | cut -d'/' -f1) | ||||||
|  |     ARCH=$(echo $platform | cut -d'/' -f2) | ||||||
|  |      | ||||||
|  |     OUTPUT_NAME="middleware-$OS-$ARCH" | ||||||
|  |     if [ $OS = "windows" ]; then | ||||||
|  |         OUTPUT_NAME="$OUTPUT_NAME.exe" | ||||||
|  |     fi | ||||||
|  |      | ||||||
|  |     echo "Building for $OS/$ARCH..." | ||||||
|  |     CGO_ENABLED=0 GOOS=$OS GOARCH=$ARCH go build -a -installsuffix cgo -o $OUTPUT_DIR/$OUTPUT_NAME main.go | ||||||
|  | done | ||||||
|  |  | ||||||
|  | echo "✅ Build complete! Binaries available in $OUTPUT_DIR/" | ||||||
|  | ls -la $OUTPUT_DIR/ | ||||||
							
								
								
									
										56
									
								
								scripts/setup.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										56
									
								
								scripts/setup.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  |  | ||||||
|  | echo "⚙️  Setting up Slack to ntfy middleware..." | ||||||
|  |  | ||||||
|  | # Check if Docker is installed | ||||||
|  | if ! command -v docker &> /dev/null; then | ||||||
|  |     echo "❌ Docker is not installed. Please install Docker first." | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # Check if Docker Compose is available | ||||||
|  | if ! docker compose version &> /dev/null; then | ||||||
|  |     echo "❌ Docker Compose is not available. Please install Docker Compose." | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | echo "✅ Docker and Docker Compose are available" | ||||||
|  |  | ||||||
|  | # Prompt for configuration | ||||||
|  | echo | ||||||
|  | echo "📝 Please provide your ntfy server configuration:" | ||||||
|  | read -p "ntfy server URL (e.g., https://ntfy.example.com): " NTFY_URL | ||||||
|  | read -p "Bearer token (e.g., tk_xxxxx) [optional]: " NTFY_TOKEN | ||||||
|  | read -p "ntfy username (for basic auth) [optional]: " NTFY_USERNAME | ||||||
|  | read -p "ntfy password (for basic auth) [optional]: " NTFY_PASSWORD | ||||||
|  |  | ||||||
|  | # Update docker-compose.yml | ||||||
|  | if [ ! -z "$NTFY_URL" ]; then | ||||||
|  |     sed -i '' "s|https://your-ntfy-server.com|$NTFY_URL|g" docker-compose.yml | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # Reset all auth lines to commented placeholders first to ensure a clean state | ||||||
|  | sed -i '' "s|^      - NTFY_TOKEN=.*|      # - NTFY_TOKEN=tk_your_bearer_token_here|" docker-compose.yml | ||||||
|  | sed -i '' "s|^      - NTFY_USERNAME=.*|      # - NTFY_USERNAME=your_username|" docker-compose.yml | ||||||
|  | sed -i '' "s|^      - NTFY_PASSWORD=.*|      # - NTFY_PASSWORD=your_password|" docker-compose.yml | ||||||
|  |  | ||||||
|  | if [ ! -z "$NTFY_TOKEN" ]; then | ||||||
|  |     sed -i '' "s|^      # - NTFY_TOKEN=tk_your_bearer_token_here|      - NTFY_TOKEN=$NTFY_TOKEN|" docker-compose.yml | ||||||
|  | elif [ ! -z "$NTFY_USERNAME" ] && [ ! -z "$NTFY_PASSWORD" ]; then | ||||||
|  |     sed -i '' "s|^      # - NTFY_USERNAME=your_username|      - NTFY_USERNAME=$NTFY_USERNAME|" docker-compose.yml | ||||||
|  |     sed -i '' "s|^      # - NTFY_PASSWORD=your_password|      - NTFY_PASSWORD=$NTFY_PASSWORD|" docker-compose.yml | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | echo "✅ Configuration updated in docker-compose.yml" | ||||||
|  |  | ||||||
|  | # Build and start | ||||||
|  | echo "🐳 Building and starting the service..." | ||||||
|  | docker compose up -d --build | ||||||
|  |  | ||||||
|  | echo "✅ Service is starting up..." | ||||||
|  | echo | ||||||
|  | echo "Next steps:" | ||||||
|  | echo "1. Test the service: ./scripts/test.sh" | ||||||
|  | echo "2. Check logs: docker compose logs -f" | ||||||
|  | echo "3. Configure Slack webhook URL: http://your-server-ip:8080/your-topic-name" | ||||||
							
								
								
									
										39
									
								
								scripts/test.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										39
									
								
								scripts/test.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  |  | ||||||
|  | MIDDLEWARE_URL="http://localhost:8080" | ||||||
|  | TEST_TOPIC="test-topic" | ||||||
|  |  | ||||||
|  | echo "🧪 Testing Slack to ntfy middleware..." | ||||||
|  |  | ||||||
|  | # Check if service is running | ||||||
|  | echo "Checking if middleware is running..." | ||||||
|  | if ! curl -s "$MIDDLEWARE_URL/health" > /dev/null; then | ||||||
|  |     echo "❌ Middleware is not running on $MIDDLEWARE_URL" | ||||||
|  |     echo "Please start it with: docker compose up -d" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | echo "✅ Middleware is running" | ||||||
|  |  | ||||||
|  | # Test health endpoint | ||||||
|  | echo "Testing health endpoint..." | ||||||
|  | HEALTH_RESPONSE=$(curl -s "$MIDDLEWARE_URL/health") | ||||||
|  | echo "Health response: $HEALTH_RESPONSE" | ||||||
|  |  | ||||||
|  | # Test Slack webhook format | ||||||
|  | echo "Testing Slack webhook format..." | ||||||
|  | curl -X POST "$MIDDLEWARE_URL/$TEST_TOPIC" \ | ||||||
|  |      -H 'Content-Type: application/json' \ | ||||||
|  |      -d '{"text": "🧪 Test alert from Slack to ntfy middleware test script"}' \ | ||||||
|  |      -w "\nHTTP Status: %{http_code}\n" | ||||||
|  |  | ||||||
|  | # Test plain text format | ||||||
|  | echo "Testing plain text format..." | ||||||
|  | curl -X POST "$MIDDLEWARE_URL/$TEST_TOPIC" \ | ||||||
|  |      -H 'Content-Type: text/plain' \ | ||||||
|  |      -d 'Plain text test alert' \ | ||||||
|  |      -w "\nHTTP Status: %{http_code}\n" | ||||||
|  |  | ||||||
|  | echo "✅ Tests completed!" | ||||||
|  | echo "Check your ntfy client to see if messages were delivered to topic: $TEST_TOPIC" | ||||||
		Reference in New Issue
	
	Block a user