diff --git a/pylons-grpc-relay/Dockerfile b/pylons-grpc-relay/Dockerfile new file mode 100644 index 0000000000..e5e9a66fc1 --- /dev/null +++ b/pylons-grpc-relay/Dockerfile @@ -0,0 +1,17 @@ +# Use the official Go image as the base image +FROM golang:1.21-alpine + +# Set the working directory +WORKDIR /app + +# Copy go mod and sum files +COPY go.mod go.sum ./ + +# Download dependencies +RUN go mod download + +# Copy the source code +COPY . . + +# Build and run the application +CMD ["go", "run", "."] \ No newline at end of file diff --git a/pylons-grpc-relay/README.md b/pylons-grpc-relay/README.md index 13d4318025..b42a19cbe1 100644 --- a/pylons-grpc-relay/README.md +++ b/pylons-grpc-relay/README.md @@ -1,6 +1,181 @@ -# Pylons gRPC Relay Server +# gRPC Relay Service -A relay server that handles message wrapping for the Pylons mobile app's gRPC communication. +A gRPC relay service that forwards gRPC requests to a target server while providing comprehensive logging and monitoring. + +## Features + +- gRPC request forwarding +- Comprehensive logging +- Docker support +- Local development with ngrok +- Systemd service support for production deployment + +## Logging + +The service provides detailed logging for all operations. Logs are written to both stdout and a log file (`grpc_relay.log`). + +### Log Categories + +- `[REQUEST]`: Initial request received +- `[METADATA]`: Request metadata and headers +- `[CONNECTING]`: Connection attempt to target server +- `[CONNECTED]`: Successful connection to target server +- `[ERROR]`: Any errors that occur +- `[SUCCESS]`: Successful request completion + +### Log Format + +``` +[REQUEST] 2024-03-14T10:00:00Z /service/method +[METADATA] /service/method - Headers: map[key:value] +[CONNECTING] /service/method - Attempting to connect to target.address:443 +[CONNECTED] /service/method - Successfully connected to target.address:443 +[SUCCESS] /service/method (150ms) +``` + +Error logs include detailed error information: +``` +[ERROR] /service/method - Connection failed: connection refused +[ERROR] /service/method (50ms) - client recv error: connection reset +``` + +## Configuration + +### Environment Variables + +- `TARGET_ADDRESS`: Target gRPC server address (default: "pylons.api.m.stavr.tech:443") +- `PORT`: Port to listen on (default: "50051") + +## Local Development + +### Prerequisites + +- Docker and Docker Compose +- ngrok (for exposing local service) + +### Running Locally + +1. Start the service: +```bash +docker-compose up --build +``` + +2. In a separate terminal, start ngrok: +```bash +ngrok tcp 50051 +``` + +3. Use the ngrok URL (e.g., `0.tcp.ngrok.io:12345`) to connect to your local service. + +## Production Deployment + +### Docker Deployment + +1. Build the image: +```bash +docker build -t grpc-relay . +``` + +2. Run the container: +```bash +docker run -d \ + -p 50051:50051 \ + -e TARGET_ADDRESS=your.target.address:443 \ + -e PORT=50051 \ + --name grpc-relay \ + grpc-relay +``` + +### Systemd Deployment + +1. Copy the service file: +```bash +sudo cp grpc-relay.service /etc/systemd/system/ +``` + +2. Copy the binary: +```bash +sudo cp relay /opt/grpc-relay/ +``` + +3. Enable and start the service: +```bash +sudo systemctl enable grpc-relay +sudo systemctl start grpc-relay +``` + +## Monitoring + +### Log Files + +- Container logs: `docker logs grpc-relay` +- File logs: `/app/grpc_relay.log` (in container) or `/opt/grpc-relay/grpc_relay.log` (systemd) + +### Service Status + +- Docker: `docker ps` and `docker logs` +- Systemd: `systemctl status grpc-relay` + +## Troubleshooting + +### Common Issues + +1. **Connection Refused** + - Check if target server is accessible + - Verify TARGET_ADDRESS is correct + - Check firewall settings + +2. **Port Already in Use** + - Change PORT environment variable + - Check if another instance is running + +3. **Log File Issues** + - Service falls back to stdout-only logging if file writing fails + - Check directory permissions + +## Security Considerations + +- The service uses TLS for target connections +- Consider implementing authentication if needed +- Review and adjust firewall rules +- Monitor log files for unusual patterns + +## Maintenance + +### Log Rotation + +Consider implementing log rotation for the log file. Example logrotate configuration: + +``` +/opt/grpc-relay/grpc_relay.log { + daily + rotate 7 + compress + delaycompress + missingok + notifempty + create 0640 grpc-relay grpc-relay +} +``` + +### Updates + +1. Stop the service +2. Update the binary +3. Restart the service + +For Docker: +```bash +docker-compose down +docker-compose up --build -d +``` + +For Systemd: +```bash +sudo systemctl stop grpc-relay +sudo cp new-relay /opt/grpc-relay/relay +sudo systemctl start grpc-relay +``` ## Purpose The relay server acts as a man-in-the-middle to properly wrap messages in the `Any` type format required by the Pylons node. This fixes the HTTP 501 errors encountered by the mobile app. diff --git a/pylons-grpc-relay/deploy.sh b/pylons-grpc-relay/deploy.sh new file mode 100644 index 0000000000..53a00b0507 --- /dev/null +++ b/pylons-grpc-relay/deploy.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Exit on error +set -e + +# Create service user +sudo useradd -r -s /bin/false grpc-relay + +# Create application directory +sudo mkdir -p /opt/grpc-relay +sudo chown grpc-relay:grpc-relay /opt/grpc-relay + +# Copy binary and service file +sudo cp relay /opt/grpc-relay/ +sudo cp grpc-relay.service /etc/systemd/system/ + +# Set permissions +sudo chown grpc-relay:grpc-relay /opt/grpc-relay/relay +sudo chmod +x /opt/grpc-relay/relay + +# Reload systemd and enable service +sudo systemctl daemon-reload +sudo systemctl enable grpc-relay +sudo systemctl start grpc-relay + +# Check status +sudo systemctl status grpc-relay \ No newline at end of file diff --git a/pylons-grpc-relay/docker-compose.yml b/pylons-grpc-relay/docker-compose.yml new file mode 100644 index 0000000000..1c6a6df190 --- /dev/null +++ b/pylons-grpc-relay/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3' +services: + grpc-relay: + build: . + ports: + - "50051:50051" + environment: + - TARGET_ADDRESS=pylons.api.m.stavr.tech:443 + volumes: + - .:/app + command: go run . \ No newline at end of file diff --git a/pylons-grpc-relay/grpc-relay.service b/pylons-grpc-relay/grpc-relay.service new file mode 100644 index 0000000000..65139e4925 --- /dev/null +++ b/pylons-grpc-relay/grpc-relay.service @@ -0,0 +1,16 @@ +[Unit] +Description=gRPC Relay Service +After=network.target + +[Service] +Type=simple +User=grpc-relay +Group=grpc-relay +WorkingDirectory=/opt/grpc-relay +ExecStart=/opt/grpc-relay/relay +Restart=always +RestartSec=5 +Environment=TARGET_ADDRESS=pylons.api.m.stavr.tech:443 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/pylons-grpc-relay/relay.go b/pylons-grpc-relay/relay.go index 6ae069e5ba..b38d09a697 100644 --- a/pylons-grpc-relay/relay.go +++ b/pylons-grpc-relay/relay.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "cloud.google.com/go/run" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" @@ -18,44 +19,56 @@ import ( "google.golang.org/protobuf/types/known/anypb" ) -func main() { - // Get target address from command line or use default - targetAddr := "pylons.api.m.stavr.tech:443" - if len(os.Args) > 1 { - targetAddr = os.Args[1] - // Remove any protocol prefix if present - targetAddr = strings.TrimPrefix(targetAddr, "https://") - targetAddr = strings.TrimPrefix(targetAddr, "http://") +var ( + targetAddr string + port string + logger *log.Logger +) + +func init() { + // Get target address from environment variable or use default + targetAddr = os.Getenv("TARGET_ADDRESS") + if targetAddr == "" { + targetAddr = "pylons.api.m.stavr.tech:443" } - - listenAddr := ":50051" - if len(os.Args) > 2 { - listenAddr = os.Args[2] + // Remove any protocol prefix if present + targetAddr = strings.TrimPrefix(targetAddr, "https://") + targetAddr = strings.TrimPrefix(targetAddr, "http://") + + // Get port from environment variable or use default + port = os.Getenv("PORT") + if port == "" { + port = "50051" } - - // Create log file + + // Set up logging to both file and stdout for local development logFile, err := os.OpenFile("grpc_relay.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - log.Fatalf("Failed to open log file: %v", err) + log.Printf("Warning: Failed to open log file: %v. Logging to stdout only.", err) + logger = log.New(os.Stdout, "", log.LstdFlags) + } else { + // Create a multi-writer to log to both file and stdout + multiWriter := io.MultiWriter(os.Stdout, logFile) + logger = log.New(multiWriter, "", log.LstdFlags) } - defer logFile.Close() - logger := log.New(logFile, "", log.LstdFlags) - +} + +func main() { // Set up server - lis, err := net.Listen("tcp", listenAddr) + lis, err := net.Listen("tcp", ":"+port) if err != nil { - log.Fatalf("Failed to listen: %v", err) + logger.Fatalf("Failed to listen: %v", err) } - + // Create a router that passes all requests through to the target server := grpc.NewServer( grpc.UnknownServiceHandler(proxyHandler(targetAddr, logger)), ) reflection.Register(server) - - fmt.Printf("Starting gRPC relay on %s -> %s\n", listenAddr, targetAddr) - fmt.Printf("Logging to grpc_relay.log\n") - log.Fatal(server.Serve(lis)) + + logger.Printf("Starting gRPC relay on port %s -> %s\n", port, targetAddr) + logger.Printf("Logging to both stdout and grpc_relay.log\n") + logger.Fatal(server.Serve(lis)) } func proxyHandler(targetAddr string, logger *log.Logger) grpc.StreamHandler {