Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions pylons-grpc-relay/Dockerfile
Original file line number Diff line number Diff line change
@@ -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", "."]
179 changes: 177 additions & 2 deletions pylons-grpc-relay/README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
27 changes: 27 additions & 0 deletions pylons-grpc-relay/deploy.sh
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions pylons-grpc-relay/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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 .
16 changes: 16 additions & 0 deletions pylons-grpc-relay/grpc-relay.service
Original file line number Diff line number Diff line change
@@ -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
63 changes: 38 additions & 25 deletions pylons-grpc-relay/relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,51 +11,64 @@ import (
"strings"
"time"

"cloud.google.com/go/run"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/metadata"
"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 {
Expand Down
Loading