Fix Caddy port configuration and improve connectivity testing
This commit is contained in:
parent
12b486ace3
commit
1c34047f75
467
start.sh
467
start.sh
@ -228,7 +228,173 @@ window.ENTE_CONFIG = {
|
||||
console.log("Ente config loaded, API_URL =", window.ENTE_CONFIG.API_URL);
|
||||
EOT
|
||||
|
||||
# Set up Caddy server
|
||||
# Runtime configuration
|
||||
echo "==> Setting up direct runtime configuration"
|
||||
|
||||
# Global API endpoint
|
||||
API_ENDPOINT="${CLOUDRON_APP_ORIGIN}/api"
|
||||
echo "==> Setting API endpoint to $API_ENDPOINT"
|
||||
|
||||
# Create runtime configuration files
|
||||
mkdir -p /app/data/runtime
|
||||
echo "==> Creating runtime configuration files"
|
||||
|
||||
# Create a config script
|
||||
cat > /app/data/runtime/config.js <<EOT
|
||||
// Direct configuration for Ente
|
||||
window.ENTE_CONFIG = {
|
||||
API_URL: "${API_ENDPOINT}"
|
||||
};
|
||||
|
||||
// Next.js environment variables
|
||||
window.process = window.process || {};
|
||||
window.process.env = window.process.env || {};
|
||||
window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = "${API_ENDPOINT}";
|
||||
window.process.env.REACT_APP_ENTE_ENDPOINT = "${API_ENDPOINT}";
|
||||
window.process.env.VUE_APP_ENTE_ENDPOINT = "${API_ENDPOINT}";
|
||||
|
||||
console.log("Ente config loaded - API_URL:", window.ENTE_CONFIG.API_URL);
|
||||
EOT
|
||||
|
||||
# First approach: Try to modify index.html files directly
|
||||
echo "==> Attempting to directly modify web app HTML files"
|
||||
WEB_APPS=("/app/web/photos" "/app/web/accounts" "/app/web/auth" "/app/web/cast")
|
||||
|
||||
for app_dir in "${WEB_APPS[@]}"; do
|
||||
if [ -f "$app_dir/index.html" ]; then
|
||||
echo "==> Processing $app_dir/index.html"
|
||||
|
||||
# Create a backup copy in our data directory
|
||||
mkdir -p "/app/data/original/$(basename $app_dir)"
|
||||
cp "$app_dir/index.html" "/app/data/original/$(basename $app_dir)/index.html"
|
||||
|
||||
# Create a modified version of the file in our data directory
|
||||
mkdir -p "/app/data/modified/$(basename $app_dir)"
|
||||
cp "$app_dir/index.html" "/app/data/modified/$(basename $app_dir)/index.html"
|
||||
|
||||
# Insert our configuration script into the head section
|
||||
sed -i 's|</head>|<script src="/config.js"></script></head>|' "/app/data/modified/$(basename $app_dir)/index.html"
|
||||
|
||||
# Try to replace the original file (may fail if read-only)
|
||||
if cp "/app/data/modified/$(basename $app_dir)/index.html" "$app_dir/index.html" 2>/dev/null; then
|
||||
echo "==> Successfully modified $app_dir/index.html"
|
||||
else
|
||||
echo "==> Could not modify $app_dir/index.html (read-only filesystem)"
|
||||
fi
|
||||
else
|
||||
echo "==> Skipping $app_dir - index.html not found"
|
||||
fi
|
||||
done
|
||||
|
||||
# Second approach: Create a modified copy that Caddy will serve
|
||||
mkdir -p /app/data/public
|
||||
cp /app/data/runtime/config.js /app/data/public/
|
||||
echo "==> Created public configuration script"
|
||||
|
||||
# Create a loading page for each app with configuration
|
||||
for app_name in "photos" "accounts" "auth" "cast"; do
|
||||
cat > "/app/data/public/${app_name}-config.html" <<EOT
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Loading ${app_name}</title>
|
||||
<script src="/debug.js"></script>
|
||||
<script>
|
||||
// Set the global configuration
|
||||
window.ENTE_CONFIG = {
|
||||
API_URL: "${API_ENDPOINT}"
|
||||
};
|
||||
|
||||
// Set Next.js environment variables
|
||||
window.process = window.process || {};
|
||||
window.process.env = window.process.env || {};
|
||||
window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = "${API_ENDPOINT}";
|
||||
|
||||
// Store in localStorage as a fallback mechanism
|
||||
localStorage.setItem('ENTE_CONFIG', JSON.stringify(window.ENTE_CONFIG));
|
||||
localStorage.setItem('NEXT_PUBLIC_ENTE_ENDPOINT', "${API_ENDPOINT}");
|
||||
|
||||
// Log the configuration
|
||||
console.log("Ente ${app_name} config loaded:", window.ENTE_CONFIG);
|
||||
|
||||
// Redirect to the main app after a brief delay
|
||||
setTimeout(function() {
|
||||
window.location.href = "/${app_name}/";
|
||||
}, 100);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Loading ${app_name}...</h1>
|
||||
<p>If you are not redirected automatically, <a href="/${app_name}/">click here</a>.</p>
|
||||
<p><a href="/debug">Debug Information</a></p>
|
||||
</body>
|
||||
</html>
|
||||
EOT
|
||||
done
|
||||
|
||||
# Create root index.html that loads config and redirects
|
||||
cat > /app/data/public/index.html <<EOT
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Ente</title>
|
||||
<script src="/debug.js"></script>
|
||||
<script>
|
||||
// Define configuration globally
|
||||
window.ENTE_CONFIG = {
|
||||
API_URL: "${API_ENDPOINT}"
|
||||
};
|
||||
|
||||
// Set environment variables for Next.js apps
|
||||
window.process = window.process || {};
|
||||
window.process.env = window.process.env || {};
|
||||
window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = "${API_ENDPOINT}";
|
||||
|
||||
// Store in localStorage as a fallback mechanism
|
||||
localStorage.setItem('ENTE_CONFIG', JSON.stringify(window.ENTE_CONFIG));
|
||||
localStorage.setItem('NEXT_PUBLIC_ENTE_ENDPOINT', "${API_ENDPOINT}");
|
||||
|
||||
// Redirect to photos app after a small delay to let the configuration load
|
||||
setTimeout(function() {
|
||||
window.location.href = "/photos/";
|
||||
}, 100);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Loading Ente...</h1>
|
||||
<p>You will be redirected automatically.</p>
|
||||
<p><a href="/debug">Debug Information</a></p>
|
||||
</body>
|
||||
</html>
|
||||
EOT
|
||||
|
||||
echo "==> Created special root index.html with configuration"
|
||||
|
||||
# Check port availability before starting services
|
||||
echo "==> Checking port availability"
|
||||
CADDY_PORT=3080
|
||||
API_PORT=8080
|
||||
|
||||
# Check if ports are already in use
|
||||
if lsof -i:$CADDY_PORT > /dev/null 2>&1; then
|
||||
echo "==> WARNING: Port $CADDY_PORT is already in use"
|
||||
echo "===> Process using port $CADDY_PORT:"
|
||||
lsof -i:$CADDY_PORT
|
||||
else
|
||||
echo "==> Port $CADDY_PORT is available for Caddy"
|
||||
fi
|
||||
|
||||
if lsof -i:$API_PORT > /dev/null 2>&1; then
|
||||
echo "==> WARNING: Port $API_PORT is already in use"
|
||||
echo "===> Process using port $API_PORT:"
|
||||
lsof -i:$API_PORT
|
||||
else
|
||||
echo "==> Port $API_PORT is available for API server"
|
||||
fi
|
||||
|
||||
# Set up Caddy
|
||||
echo "==> Setting up Caddy server for web apps"
|
||||
mkdir -p /app/data/caddy/public
|
||||
cat > /app/data/caddy/Caddyfile <<EOT
|
||||
@ -246,7 +412,7 @@ cat > /app/data/caddy/Caddyfile <<EOT
|
||||
}
|
||||
}
|
||||
|
||||
:8000 {
|
||||
:3080 {
|
||||
log {
|
||||
output file /app/data/caddy/access.log {
|
||||
roll_size 10MB
|
||||
@ -284,6 +450,11 @@ cat > /app/data/caddy/Caddyfile <<EOT
|
||||
respond "OK" 200
|
||||
}
|
||||
|
||||
# Cloudron health check endpoint
|
||||
handle /healthcheck {
|
||||
respond "OK" 200
|
||||
}
|
||||
|
||||
# Serve our custom config-injected landing pages
|
||||
handle /photos-config {
|
||||
root * /app/data/public
|
||||
@ -356,285 +527,45 @@ EOT
|
||||
|
||||
echo "==> Caddy configuration created at /app/data/caddy/Caddyfile"
|
||||
|
||||
# Create HTML transformer script to modify all index.html files on load
|
||||
mkdir -p /app/data/scripts
|
||||
cat > /app/data/scripts/insert-config.sh <<EOT
|
||||
#!/bin/bash
|
||||
# This script injects our configuration into all index.html files
|
||||
|
||||
# List of web app directories
|
||||
WEB_APPS=("/app/web/photos" "/app/web/accounts" "/app/web/auth" "/app/web/cast")
|
||||
|
||||
for app_dir in "\${WEB_APPS[@]}"; do
|
||||
if [ -f "\$app_dir/index.html" ]; then
|
||||
echo "Processing \$app_dir/index.html"
|
||||
|
||||
# Create a writable copy
|
||||
cp "\$app_dir/index.html" "/app/data/temp.html"
|
||||
|
||||
# Insert our config script right before the closing head tag
|
||||
sed -i 's|</head>|<script>window.ENTE_CONFIG = { API_URL: "${API_ENDPOINT}" };</script></head>|' "/app/data/temp.html"
|
||||
|
||||
# Create a symlink from the modified file to the original
|
||||
# Only if the temp file was created and modified successfully
|
||||
if [ -f "/app/data/temp.html" ]; then
|
||||
mkdir -p "/app/data/transformed/\$(basename \$app_dir)"
|
||||
cp "/app/data/temp.html" "/app/data/transformed/\$(basename \$app_dir)/index.html"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All index.html files processed"
|
||||
EOT
|
||||
chmod +x /app/data/scripts/insert-config.sh
|
||||
|
||||
# Run the transformer script to create modified index.html files
|
||||
mkdir -p /app/data/transformed
|
||||
echo "==> Creating modified index.html files with injected configuration"
|
||||
/app/data/scripts/insert-config.sh
|
||||
|
||||
# Create direct configuration files for different frameworks
|
||||
mkdir -p /app/data/public
|
||||
echo "==> Creating framework-specific configuration files"
|
||||
|
||||
# Create a Next.js public runtime configuration
|
||||
cat > /app/data/public/env.js <<EOT
|
||||
// Next.js runtime configuration
|
||||
window.ENV = {
|
||||
NEXT_PUBLIC_ENTE_ENDPOINT: "${API_ENDPOINT}"
|
||||
};
|
||||
window.process = window.process || {};
|
||||
window.process.env = window.process.env || {};
|
||||
window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = "${API_ENDPOINT}";
|
||||
EOT
|
||||
|
||||
# Create a simple JSON config
|
||||
cat > /app/data/public/config.json <<EOT
|
||||
{
|
||||
"API_URL": "${API_ENDPOINT}"
|
||||
}
|
||||
EOT
|
||||
|
||||
# Create additional HTML files with the config directly embedded
|
||||
for app_name in "photos" "accounts" "auth" "cast"; do
|
||||
cat > "/app/data/public/${app_name}-config.html" <<EOT
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Loading ${app_name}</title>
|
||||
<script src="/debug.js"></script>
|
||||
<script>
|
||||
// Set the global configuration
|
||||
window.ENTE_CONFIG = {
|
||||
API_URL: "${API_ENDPOINT}"
|
||||
};
|
||||
|
||||
// Set Next.js environment variables
|
||||
window.process = window.process || {};
|
||||
window.process.env = window.process.env || {};
|
||||
window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = "${API_ENDPOINT}";
|
||||
|
||||
// Store in localStorage as a fallback mechanism
|
||||
localStorage.setItem('ENTE_CONFIG', JSON.stringify(window.ENTE_CONFIG));
|
||||
localStorage.setItem('NEXT_PUBLIC_ENTE_ENDPOINT', "${API_ENDPOINT}");
|
||||
|
||||
// Log the configuration
|
||||
console.log("Ente ${app_name} config loaded:", window.ENTE_CONFIG);
|
||||
|
||||
// Redirect to the main app after a brief delay
|
||||
setTimeout(function() {
|
||||
window.location.href = "/${app_name}/";
|
||||
}, 100);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Loading ${app_name}...</h1>
|
||||
<p>If you are not redirected automatically, <a href="/${app_name}/">click here</a>.</p>
|
||||
<p><a href="/debug">Debug Information</a></p>
|
||||
</body>
|
||||
</html>
|
||||
EOT
|
||||
done
|
||||
|
||||
echo "==> Created app-specific config loading pages with debug info"
|
||||
|
||||
# Looking for Museum (Go server component)
|
||||
echo "==> Looking for Museum (Go server component)"
|
||||
|
||||
# Find the server directory - expanded search
|
||||
SERVER_DIR=""
|
||||
|
||||
# Look in all the most likely places
|
||||
if [ -d "/app/code/server" ]; then
|
||||
echo "==> Found server directory at /app/code/server"
|
||||
SERVER_DIR="/app/code/server"
|
||||
elif [ -d "/app/code/museum" ]; then
|
||||
echo "==> Found server directory at /app/code/museum"
|
||||
SERVER_DIR="/app/code/museum"
|
||||
fi
|
||||
|
||||
# If not found yet, try to find it based on Go files
|
||||
if [ -z "$SERVER_DIR" ]; then
|
||||
echo "==> Searching for Go server files in the repository"
|
||||
|
||||
# Look for cmd/museum path which is mentioned in docs
|
||||
if find /app/code -path "*/cmd/museum" -type d | grep -q .; then
|
||||
MUSEUM_DIR=$(find /app/code -path "*/cmd/museum" -type d | head -1)
|
||||
echo "==> Found Museum command directory at $MUSEUM_DIR"
|
||||
SERVER_DIR=$(dirname $(dirname "$MUSEUM_DIR"))
|
||||
echo "==> Setting server directory to $SERVER_DIR"
|
||||
# Look for main.go files in areas that might be server-related
|
||||
elif find /app/code -name "main.go" -path "*/server*" | grep -q .; then
|
||||
MAIN_GO=$(find /app/code -name "main.go" -path "*/server*" | head -1)
|
||||
SERVER_DIR=$(dirname "$MAIN_GO")
|
||||
echo "==> Found main.go in $SERVER_DIR"
|
||||
# Look for any Go files with "museum" in the path
|
||||
elif find /app/code -name "*.go" -path "*/museum*" | grep -q .; then
|
||||
MUSEUM_GO=$(find /app/code -name "*.go" -path "*/museum*" | head -1)
|
||||
SERVER_DIR=$(dirname "$MUSEUM_GO")
|
||||
echo "==> Found Go file in museum directory: $SERVER_DIR"
|
||||
# Last resort - look for any main.go file
|
||||
elif find /app/code -name "main.go" | grep -v "test" | grep -q .; then
|
||||
MAIN_GO=$(find /app/code -name "main.go" | grep -v "test" | head -1)
|
||||
SERVER_DIR=$(dirname "$MAIN_GO")
|
||||
echo "==> Found main.go as fallback: $SERVER_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
# If still not found, check the 'cli' directory as it might be a client for the Museum
|
||||
if [ -z "$SERVER_DIR" ] && [ -d "/app/code/cli" ]; then
|
||||
echo "==> Checking CLI directory for Museum server code"
|
||||
if find /app/code/cli -name "*.go" | grep -q .; then
|
||||
SERVER_DIR="/app/code/cli"
|
||||
echo "==> Using CLI directory as fallback for server: $SERVER_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
# If all else fails, just use the root
|
||||
if [ -z "$SERVER_DIR" ]; then
|
||||
echo "==> WARNING: Could not find server directory with Go files"
|
||||
echo "==> Using repository root as fallback"
|
||||
SERVER_DIR="/app/code"
|
||||
fi
|
||||
|
||||
echo "==> Selected server directory: $SERVER_DIR"
|
||||
echo "==> Contents of $SERVER_DIR:"
|
||||
ls -la "$SERVER_DIR"
|
||||
|
||||
# Check for server dependencies
|
||||
echo "==> Installing Go dependencies"
|
||||
if [ -x "$(command -v go)" ]; then
|
||||
echo "==> Go is installed, version: $(go version)"
|
||||
# Check go.mod version requirement and modify if needed
|
||||
GO_VERSION=$(go version | cut -d " " -f 3 | sed 's/go//')
|
||||
echo "==> Current Go version: $GO_VERSION"
|
||||
if [ -f "$SERVER_DIR/go.mod" ]; then
|
||||
REQUIRED_VERSION=$(grep -o "go [0-9]\+\.[0-9]\+" "$SERVER_DIR/go.mod" | cut -d " " -f 2)
|
||||
echo "==> Required Go version: $REQUIRED_VERSION"
|
||||
|
||||
# Don't try to modify the go.mod file since filesystem is read-only
|
||||
# Instead, use environment variables to override version requirements
|
||||
echo "==> Setting Go flags to override version requirements"
|
||||
export GOFLAGS="-modfile=/app/data/go/go.mod -mod=mod"
|
||||
export GO111MODULE=on
|
||||
export GOTOOLCHAIN=local
|
||||
fi
|
||||
else
|
||||
echo "==> Installing Go"
|
||||
apt-get update && apt-get install -y golang
|
||||
fi
|
||||
|
||||
# Skip trying to install specific Go versions since we can't modify /app/code
|
||||
echo "==> Using existing Go toolchain with compatibility flags"
|
||||
|
||||
# Check for libsodium
|
||||
if dpkg -l | grep -q libsodium; then
|
||||
echo "==> libsodium is installed"
|
||||
else
|
||||
echo "==> Installing libsodium"
|
||||
apt-get update && apt-get install -y libsodium23 libsodium-dev
|
||||
fi
|
||||
|
||||
# Check for pkg-config
|
||||
if [ -x "$(command -v pkg-config)" ]; then
|
||||
echo "==> pkg-config is installed"
|
||||
else
|
||||
echo "==> Installing pkg-config"
|
||||
apt-get update && apt-get install -y pkg-config
|
||||
fi
|
||||
|
||||
# Change to server directory
|
||||
cd "$SERVER_DIR"
|
||||
|
||||
# Set Go module cache to a writable location
|
||||
export GOPATH=/app/data/go
|
||||
export GO111MODULE=on
|
||||
# Use local toolchain to avoid downloading required version
|
||||
export GOTOOLCHAIN=local
|
||||
|
||||
# Ensure go.mod is in the writable directory
|
||||
if [ ! -f "/app/data/go/go.mod" ] && [ -f "$SERVER_DIR/go.mod" ]; then
|
||||
echo "==> Copying go.mod and go.sum to writable location"
|
||||
mkdir -p /app/data/go
|
||||
cp -f "$SERVER_DIR/go.mod" "$SERVER_DIR/go.sum" /app/data/go/ || echo "==> Warning: Could not copy go.mod/go.sum"
|
||||
chmod 666 /app/data/go/go.mod /app/data/go/go.sum || echo "==> Warning: Could not set permissions"
|
||||
fi
|
||||
|
||||
# Create proper directories with correct permissions
|
||||
mkdir -p /app/data/go/cache /app/data/go/pkg/mod /app/data/go/bin
|
||||
chmod -R 777 /app/data/go
|
||||
chown -R cloudron:cloudron /app/data/go
|
||||
|
||||
# Override version requirements and force module mode
|
||||
export GOFLAGS="-modfile=/app/data/go/go.mod -mod=mod -modcacherw"
|
||||
|
||||
# Create a temporary directory for Go module cache and build in writable area
|
||||
export GOCACHE=/app/data/go/cache
|
||||
export GOMODCACHE=/app/data/go/pkg/mod
|
||||
mkdir -p $GOCACHE $GOMODCACHE
|
||||
echo "==> Set up Go environment in writable directories"
|
||||
|
||||
# Set up more verbose logging for API debugging
|
||||
export ENTE_LOG_LEVEL=debug
|
||||
|
||||
# Start Caddy first before the Museum server to ensure port 3080 is available for health checks
|
||||
echo "==> Setting up Caddy before starting the Museum server"
|
||||
echo "==> Caddy will be listening on port 3080"
|
||||
echo "==> Testing if port 3080 is already in use"
|
||||
if netstat -tuln | grep -q ":3080 "; then
|
||||
echo "==> WARNING: Port 3080 is already in use, Caddy may fail to start"
|
||||
netstat -tuln | grep ":3080 "
|
||||
else
|
||||
echo "==> Port 3080 is available"
|
||||
fi
|
||||
echo "==> Caddy will be listening on port $CADDY_PORT"
|
||||
|
||||
# Only set permissions on the Caddy directory, not the web directory which is read-only
|
||||
mkdir -p /app/data/caddy/logs
|
||||
chmod -R 755 /app/data/caddy
|
||||
mkdir -p /app/data/caddy
|
||||
chmod -R 777 /app/data/caddy
|
||||
chown -R cloudron:cloudron /app/data/caddy
|
||||
|
||||
# Start Caddy
|
||||
echo "==> Starting Caddy"
|
||||
# Start Caddy server
|
||||
echo "==> Starting Caddy server..."
|
||||
caddy run --config /app/data/caddy/Caddyfile --adapter caddyfile &
|
||||
CADDY_PID=$!
|
||||
echo "==> Caddy started with PID $CADDY_PID"
|
||||
|
||||
# Check if Caddy started successfully
|
||||
# Wait a moment for Caddy to start
|
||||
sleep 2
|
||||
if ! ps -p $CADDY_PID > /dev/null; then
|
||||
echo "==> ERROR: Caddy failed to start"
|
||||
echo "==> Any logs available:"
|
||||
if [ -f "/app/data/caddy/caddy.log" ]; then
|
||||
cat /app/data/caddy/caddy.log
|
||||
|
||||
# Test Caddy connectivity
|
||||
echo "==> Testing Caddy connectivity on port $CADDY_PORT"
|
||||
for i in {1..5}; do
|
||||
if curl -s --max-time 2 --head --fail http://localhost:$CADDY_PORT/health > /dev/null; then
|
||||
echo "==> Caddy is running properly on port $CADDY_PORT"
|
||||
break
|
||||
else
|
||||
echo "No Caddy logs available yet"
|
||||
if [ $i -eq 5 ]; then
|
||||
echo "==> Failed to connect to Caddy on port $CADDY_PORT after multiple attempts"
|
||||
echo "==> Last 20 lines of Caddy log:"
|
||||
tail -20 /app/data/caddy/caddy.log || echo "==> No Caddy log available"
|
||||
echo "==> Network ports in use:"
|
||||
netstat -tuln || echo "==> netstat command not available"
|
||||
echo "==> Processes listening on ports:"
|
||||
lsof -i -P -n | grep LISTEN || echo "==> lsof command not available"
|
||||
else
|
||||
echo "==> Attempt $i: Waiting for Caddy to start... (1 second)"
|
||||
sleep 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "==> Caddy is running properly on port 3080"
|
||||
echo "==> Testing Caddy connectivity"
|
||||
curl -v http://localhost:3080/healthcheck || echo "==> Failed to connect to Caddy"
|
||||
fi
|
||||
done
|
||||
|
||||
# Determine available memory and set limits accordingly
|
||||
if [[ -f /sys/fs/cgroup/cgroup.controllers ]]; then # cgroup v2
|
||||
|
Loading…
x
Reference in New Issue
Block a user