Add turn server
This commit is contained in:
parent
42fe8e2067
commit
d38aeb2f44
6 changed files with 163 additions and 9 deletions
19
.env.example
19
.env.example
|
|
@ -49,3 +49,22 @@ LOG_REQUESTS=false
|
||||||
# Get a key at: https://aistudio.google.com/apikey
|
# Get a key at: https://aistudio.google.com/apikey
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
GEMINI_API_KEY=
|
GEMINI_API_KEY=
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# OPTIONAL - TURN Server (REQUIRED for cross-network multiplayer)
|
||||||
|
# Without TURN, players behind restrictive NATs/firewalls cannot connect.
|
||||||
|
#
|
||||||
|
# Option A: Self-hosted coturn (see docker-compose.turn.yml)
|
||||||
|
# 1. Edit turnserver.conf (set external-ip, realm, user password)
|
||||||
|
# 2. docker compose -f docker-compose.turn.yml up -d
|
||||||
|
# 3. Set values below to match turnserver.conf
|
||||||
|
# VITE_TURN_URL=turn:your-server-ip:3478
|
||||||
|
# VITE_TURN_USERNAME=kaboot
|
||||||
|
# VITE_TURN_CREDENTIAL=your-password-from-turnserver-conf
|
||||||
|
#
|
||||||
|
# Option B: Metered.ca free tier (500GB/mo)
|
||||||
|
# Get credentials at: https://www.metered.ca/tools/openrelay/
|
||||||
|
# ==============================================================================
|
||||||
|
VITE_TURN_URL=
|
||||||
|
VITE_TURN_USERNAME=
|
||||||
|
VITE_TURN_CREDENTIAL=
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,19 @@ ARG VITE_AUTHENTIK_URL
|
||||||
ARG VITE_OIDC_CLIENT_ID=kaboot-spa
|
ARG VITE_OIDC_CLIENT_ID=kaboot-spa
|
||||||
ARG VITE_OIDC_APP_SLUG=kaboot
|
ARG VITE_OIDC_APP_SLUG=kaboot
|
||||||
ARG GEMINI_API_KEY
|
ARG GEMINI_API_KEY
|
||||||
|
ARG VITE_TURN_URL
|
||||||
|
ARG VITE_TURN_USERNAME
|
||||||
|
ARG VITE_TURN_CREDENTIAL
|
||||||
|
|
||||||
ENV VITE_API_URL=$VITE_API_URL \
|
ENV VITE_API_URL=$VITE_API_URL \
|
||||||
VITE_BACKEND_URL=$VITE_BACKEND_URL \
|
VITE_BACKEND_URL=$VITE_BACKEND_URL \
|
||||||
VITE_AUTHENTIK_URL=$VITE_AUTHENTIK_URL \
|
VITE_AUTHENTIK_URL=$VITE_AUTHENTIK_URL \
|
||||||
VITE_OIDC_CLIENT_ID=$VITE_OIDC_CLIENT_ID \
|
VITE_OIDC_CLIENT_ID=$VITE_OIDC_CLIENT_ID \
|
||||||
VITE_OIDC_APP_SLUG=$VITE_OIDC_APP_SLUG \
|
VITE_OIDC_APP_SLUG=$VITE_OIDC_APP_SLUG \
|
||||||
GEMINI_API_KEY=$GEMINI_API_KEY
|
GEMINI_API_KEY=$GEMINI_API_KEY \
|
||||||
|
VITE_TURN_URL=$VITE_TURN_URL \
|
||||||
|
VITE_TURN_USERNAME=$VITE_TURN_USERNAME \
|
||||||
|
VITE_TURN_CREDENTIAL=$VITE_TURN_CREDENTIAL
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ services:
|
||||||
VITE_OIDC_CLIENT_ID: kaboot-spa
|
VITE_OIDC_CLIENT_ID: kaboot-spa
|
||||||
VITE_OIDC_APP_SLUG: kaboot
|
VITE_OIDC_APP_SLUG: kaboot
|
||||||
GEMINI_API_KEY: ${GEMINI_API_KEY:-}
|
GEMINI_API_KEY: ${GEMINI_API_KEY:-}
|
||||||
|
VITE_TURN_URL: ${VITE_TURN_URL:-}
|
||||||
|
VITE_TURN_USERNAME: ${VITE_TURN_USERNAME:-}
|
||||||
|
VITE_TURN_CREDENTIAL: ${VITE_TURN_CREDENTIAL:-}
|
||||||
container_name: kaboot-frontend
|
container_name: kaboot-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|
|
||||||
9
docker-compose.turn.yml
Normal file
9
docker-compose.turn.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
services:
|
||||||
|
coturn:
|
||||||
|
image: coturn/coturn:4.6
|
||||||
|
container_name: kaboot-turn
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: host
|
||||||
|
volumes:
|
||||||
|
- ./turnserver.conf:/etc/coturn/turnserver.conf:ro
|
||||||
|
command: ["-c", "/etc/coturn/turnserver.conf"]
|
||||||
|
|
@ -4,7 +4,7 @@ import { useAuth } from 'react-oidc-context';
|
||||||
import { Quiz, Player, GameState, GameRole, NetworkMessage, AnswerOption, Question, GenerateQuizOptions, ProcessedDocument, GameConfig, DEFAULT_GAME_CONFIG, PointsBreakdown } from '../types';
|
import { Quiz, Player, GameState, GameRole, NetworkMessage, AnswerOption, Question, GenerateQuizOptions, ProcessedDocument, GameConfig, DEFAULT_GAME_CONFIG, PointsBreakdown } from '../types';
|
||||||
import { generateQuiz } from '../services/geminiService';
|
import { generateQuiz } from '../services/geminiService';
|
||||||
import { QUESTION_TIME, QUESTION_TIME_MS, PLAYER_COLORS, calculatePointsWithBreakdown, getPlayerRank } from '../constants';
|
import { QUESTION_TIME, QUESTION_TIME_MS, PLAYER_COLORS, calculatePointsWithBreakdown, getPlayerRank } from '../constants';
|
||||||
import { Peer, DataConnection } from 'peerjs';
|
import { Peer, DataConnection, PeerOptions } from 'peerjs';
|
||||||
import { uniqueNamesGenerator, adjectives, animals } from 'unique-names-generator';
|
import { uniqueNamesGenerator, adjectives, animals } from 'unique-names-generator';
|
||||||
|
|
||||||
const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3001';
|
const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3001';
|
||||||
|
|
@ -12,6 +12,42 @@ const SESSION_STORAGE_KEY = 'kaboot_session';
|
||||||
const DRAFT_QUIZ_KEY = 'kaboot_draft_quiz';
|
const DRAFT_QUIZ_KEY = 'kaboot_draft_quiz';
|
||||||
const STATE_SYNC_INTERVAL = 5000;
|
const STATE_SYNC_INTERVAL = 5000;
|
||||||
|
|
||||||
|
// ICE server configuration for WebRTC NAT traversal
|
||||||
|
// TURN servers are required for peers behind restrictive NATs/firewalls
|
||||||
|
const getIceServers = (): RTCIceServer[] => {
|
||||||
|
const servers: RTCIceServer[] = [
|
||||||
|
// Google's public STUN servers (free, for NAT discovery)
|
||||||
|
{ urls: 'stun:stun.l.google.com:19302' },
|
||||||
|
{ urls: 'stun:stun1.l.google.com:19302' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add TURN server if configured (required for restrictive NATs)
|
||||||
|
const turnUrl = import.meta.env.VITE_TURN_URL;
|
||||||
|
const turnUsername = import.meta.env.VITE_TURN_USERNAME;
|
||||||
|
const turnCredential = import.meta.env.VITE_TURN_CREDENTIAL;
|
||||||
|
|
||||||
|
if (turnUrl) {
|
||||||
|
servers.push({
|
||||||
|
urls: turnUrl,
|
||||||
|
username: turnUsername || '',
|
||||||
|
credential: turnCredential || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPeerOptions = (id?: string): PeerOptions => {
|
||||||
|
const options: PeerOptions = {
|
||||||
|
config: {
|
||||||
|
iceServers: getIceServers(),
|
||||||
|
iceCandidatePoolSize: 10,
|
||||||
|
},
|
||||||
|
debug: import.meta.env.DEV ? 2 : 0, // Errors + warnings in dev
|
||||||
|
};
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
interface StoredSession {
|
interface StoredSession {
|
||||||
pin: string;
|
pin: string;
|
||||||
role: GameRole;
|
role: GameRole;
|
||||||
|
|
@ -313,7 +349,7 @@ export const useGame = () => {
|
||||||
const handleClientDataRef = useRef<(data: NetworkMessage) => void>(() => {});
|
const handleClientDataRef = useRef<(data: NetworkMessage) => void>(() => {});
|
||||||
|
|
||||||
const setupHostPeer = (pin: string, onReady: (peerId: string) => void) => {
|
const setupHostPeer = (pin: string, onReady: (peerId: string) => void) => {
|
||||||
const peer = new Peer(`kaboot-${pin}`);
|
const peer = new Peer(`kaboot-${pin}`, getPeerOptions());
|
||||||
peerRef.current = peer;
|
peerRef.current = peer;
|
||||||
|
|
||||||
peer.on('open', (id) => {
|
peer.on('open', (id) => {
|
||||||
|
|
@ -332,7 +368,7 @@ export const useGame = () => {
|
||||||
peer.on('error', (err) => {
|
peer.on('error', (err) => {
|
||||||
if (err.type === 'unavailable-id') {
|
if (err.type === 'unavailable-id') {
|
||||||
peer.destroy();
|
peer.destroy();
|
||||||
const newPeer = new Peer();
|
const newPeer = new Peer(getPeerOptions());
|
||||||
peerRef.current = newPeer;
|
peerRef.current = newPeer;
|
||||||
|
|
||||||
newPeer.on('open', (id) => {
|
newPeer.on('open', (id) => {
|
||||||
|
|
@ -499,7 +535,7 @@ export const useGame = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const peer = new Peer();
|
const peer = new Peer(getPeerOptions());
|
||||||
peerRef.current = peer;
|
peerRef.current = peer;
|
||||||
|
|
||||||
peer.on('open', (id) => {
|
peer.on('open', (id) => {
|
||||||
|
|
@ -1126,7 +1162,7 @@ export const useGame = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const peer = new Peer();
|
const peer = new Peer(getPeerOptions());
|
||||||
peerRef.current = peer;
|
peerRef.current = peer;
|
||||||
|
|
||||||
peer.on('open', (id) => {
|
peer.on('open', (id) => {
|
||||||
|
|
@ -1173,7 +1209,7 @@ export const useGame = () => {
|
||||||
peerRef.current.destroy();
|
peerRef.current.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
const peer = new Peer();
|
const peer = new Peer(getPeerOptions());
|
||||||
peerRef.current = peer;
|
peerRef.current = peer;
|
||||||
|
|
||||||
peer.on('open', (id) => {
|
peer.on('open', (id) => {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ print_header
|
||||||
KABOOT_DOMAIN=""
|
KABOOT_DOMAIN=""
|
||||||
AUTH_DOMAIN=""
|
AUTH_DOMAIN=""
|
||||||
GEMINI_API_KEY=""
|
GEMINI_API_KEY=""
|
||||||
|
TURN_IP=""
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
|
|
@ -52,6 +53,10 @@ while [[ $# -gt 0 ]]; do
|
||||||
GEMINI_API_KEY="$2"
|
GEMINI_API_KEY="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
--turn-ip)
|
||||||
|
TURN_IP="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
--help|-h)
|
--help|-h)
|
||||||
echo "Usage: $0 [OPTIONS]"
|
echo "Usage: $0 [OPTIONS]"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
@ -59,6 +64,7 @@ while [[ $# -gt 0 ]]; do
|
||||||
echo " --domain DOMAIN Main application domain (e.g., kaboot.example.com)"
|
echo " --domain DOMAIN Main application domain (e.g., kaboot.example.com)"
|
||||||
echo " --auth-domain DOMAIN Authentication domain (e.g., auth.example.com)"
|
echo " --auth-domain DOMAIN Authentication domain (e.g., auth.example.com)"
|
||||||
echo " --gemini-key KEY Gemini API key for system AI (optional)"
|
echo " --gemini-key KEY Gemini API key for system AI (optional)"
|
||||||
|
echo " --turn-ip IP Public IP for TURN server (required for cross-network play)"
|
||||||
echo " --help, -h Show this help message"
|
echo " --help, -h Show this help message"
|
||||||
echo ""
|
echo ""
|
||||||
echo "If options are not provided, you will be prompted for them."
|
echo "If options are not provided, you will be prompted for them."
|
||||||
|
|
@ -116,6 +122,17 @@ if [ -z "$GEMINI_API_KEY" ]; then
|
||||||
read -p "Enter Gemini API key (or press Enter to skip): " GEMINI_API_KEY
|
read -p "Enter Gemini API key (or press Enter to skip): " GEMINI_API_KEY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}TURN Server Configuration (Cross-Network Play)${NC}"
|
||||||
|
echo "────────────────────────────────────────────────────────────"
|
||||||
|
echo "A TURN server is required for players on different networks"
|
||||||
|
echo "(behind NAT/firewalls) to connect to each other."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -z "$TURN_IP" ]; then
|
||||||
|
read -p "Enter your server's public IP for TURN (or press Enter to skip): " TURN_IP
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
print_step "Generating secrets..."
|
print_step "Generating secrets..."
|
||||||
|
|
||||||
|
|
@ -124,6 +141,7 @@ AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60 | tr -d '\n' | tr -d '/')
|
||||||
AUTHENTIK_BOOTSTRAP_PASSWORD=$(openssl rand -base64 24 | tr -d '\n' | tr -d '/')
|
AUTHENTIK_BOOTSTRAP_PASSWORD=$(openssl rand -base64 24 | tr -d '\n' | tr -d '/')
|
||||||
AUTHENTIK_BOOTSTRAP_TOKEN=$(openssl rand -hex 32)
|
AUTHENTIK_BOOTSTRAP_TOKEN=$(openssl rand -hex 32)
|
||||||
ENCRYPTION_KEY=$(openssl rand -base64 36 | tr -d '\n' | tr -d '/')
|
ENCRYPTION_KEY=$(openssl rand -base64 36 | tr -d '\n' | tr -d '/')
|
||||||
|
TURN_PASSWORD=$(openssl rand -base64 24 | tr -d '\n' | tr -d '/' | tr -d '+')
|
||||||
|
|
||||||
print_success "Secrets generated"
|
print_success "Secrets generated"
|
||||||
|
|
||||||
|
|
@ -162,6 +180,11 @@ LOG_REQUESTS=true
|
||||||
|
|
||||||
# System AI (optional - server-side quiz generation)
|
# System AI (optional - server-side quiz generation)
|
||||||
GEMINI_API_KEY=${GEMINI_API_KEY}
|
GEMINI_API_KEY=${GEMINI_API_KEY}
|
||||||
|
|
||||||
|
# TURN Server (for cross-network multiplayer)
|
||||||
|
VITE_TURN_URL=${TURN_IP:+turn:${TURN_IP}:3478}
|
||||||
|
VITE_TURN_USERNAME=${TURN_IP:+kaboot}
|
||||||
|
VITE_TURN_CREDENTIAL=${TURN_IP:+${TURN_PASSWORD}}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
print_success "Created .env"
|
print_success "Created .env"
|
||||||
|
|
@ -216,6 +239,32 @@ EOF
|
||||||
|
|
||||||
print_success "Created Caddyfile"
|
print_success "Created Caddyfile"
|
||||||
|
|
||||||
|
if [ -n "$TURN_IP" ]; then
|
||||||
|
print_step "Creating turnserver.conf..."
|
||||||
|
|
||||||
|
cat > turnserver.conf << EOF
|
||||||
|
# Coturn TURN Server Configuration
|
||||||
|
# Generated by setup-prod.sh on $(date)
|
||||||
|
|
||||||
|
listening-port=3478
|
||||||
|
tls-listening-port=5349
|
||||||
|
external-ip=${TURN_IP}
|
||||||
|
realm=${KABOOT_DOMAIN}
|
||||||
|
lt-cred-mech
|
||||||
|
user=kaboot:${TURN_PASSWORD}
|
||||||
|
fingerprint
|
||||||
|
no-multicast-peers
|
||||||
|
no-cli
|
||||||
|
log-file=/var/log/turnserver.log
|
||||||
|
total-quota=100
|
||||||
|
stale-nonce=600
|
||||||
|
min-port=49152
|
||||||
|
max-port=65535
|
||||||
|
EOF
|
||||||
|
|
||||||
|
print_success "Created turnserver.conf"
|
||||||
|
fi
|
||||||
|
|
||||||
print_step "Creating production Authentik blueprint..."
|
print_step "Creating production Authentik blueprint..."
|
||||||
|
|
||||||
BLUEPRINT_DIR="authentik/blueprints"
|
BLUEPRINT_DIR="authentik/blueprints"
|
||||||
|
|
@ -241,6 +290,12 @@ else
|
||||||
print_warning "No Gemini API key provided - users must configure their own"
|
print_warning "No Gemini API key provided - users must configure their own"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -n "$TURN_IP" ]; then
|
||||||
|
print_success "TURN server configured for cross-network play"
|
||||||
|
else
|
||||||
|
print_warning "No TURN IP provided - cross-network play may not work"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${GREEN}${BOLD}════════════════════════════════════════════════════════════${NC}"
|
echo -e "${GREEN}${BOLD}════════════════════════════════════════════════════════════${NC}"
|
||||||
echo -e "${GREEN}${BOLD} Setup Complete!${NC}"
|
echo -e "${GREEN}${BOLD} Setup Complete!${NC}"
|
||||||
|
|
@ -255,6 +310,11 @@ if [ -n "$GEMINI_API_KEY" ]; then
|
||||||
else
|
else
|
||||||
echo -e " System AI: ${YELLOW}Disabled (users need own API key)${NC}"
|
echo -e " System AI: ${YELLOW}Disabled (users need own API key)${NC}"
|
||||||
fi
|
fi
|
||||||
|
if [ -n "$TURN_IP" ]; then
|
||||||
|
echo -e " TURN Server: ${GREEN}${TURN_IP}:3478${NC}"
|
||||||
|
else
|
||||||
|
echo -e " TURN Server: ${YELLOW}Not configured${NC}"
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BOLD}Authentik Admin${NC}"
|
echo -e "${BOLD}Authentik Admin${NC}"
|
||||||
echo "────────────────────────────────────────────────────────────"
|
echo "────────────────────────────────────────────────────────────"
|
||||||
|
|
@ -268,6 +328,9 @@ echo -e "${BOLD}Files Created${NC}"
|
||||||
echo "────────────────────────────────────────────────────────────"
|
echo "────────────────────────────────────────────────────────────"
|
||||||
echo " .env - Environment variables"
|
echo " .env - Environment variables"
|
||||||
echo " Caddyfile - Reverse proxy config"
|
echo " Caddyfile - Reverse proxy config"
|
||||||
|
if [ -n "$TURN_IP" ]; then
|
||||||
|
echo " turnserver.conf - TURN server config"
|
||||||
|
fi
|
||||||
echo " authentik/blueprints/kaboot-setup-production.yaml"
|
echo " authentik/blueprints/kaboot-setup-production.yaml"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BOLD}Next Steps${NC}"
|
echo -e "${BOLD}Next Steps${NC}"
|
||||||
|
|
@ -277,6 +340,23 @@ echo " 1. Ensure DNS records point to this server:"
|
||||||
echo -e " ${KABOOT_DOMAIN} → ${BLUE}<your-server-ip>${NC}"
|
echo -e " ${KABOOT_DOMAIN} → ${BLUE}<your-server-ip>${NC}"
|
||||||
echo -e " ${AUTH_DOMAIN} → ${BLUE}<your-server-ip>${NC}"
|
echo -e " ${AUTH_DOMAIN} → ${BLUE}<your-server-ip>${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
if [ -n "$TURN_IP" ]; then
|
||||||
|
echo " 2. Open firewall ports for TURN server:"
|
||||||
|
echo -e " ${YELLOW}sudo ufw allow 3478/tcp && sudo ufw allow 3478/udp${NC}"
|
||||||
|
echo -e " ${YELLOW}sudo ufw allow 5349/tcp && sudo ufw allow 49152:65535/udp${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " 3. Start the TURN server:"
|
||||||
|
echo -e " ${YELLOW}docker compose -f docker-compose.turn.yml up -d${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " 4. Start the production stack:"
|
||||||
|
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml up -d${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " 5. Wait for services to start (~60 seconds for Authentik)"
|
||||||
|
echo ""
|
||||||
|
echo " 6. Verify all services are running:"
|
||||||
|
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml ps${NC}"
|
||||||
|
echo -e " ${YELLOW}docker compose -f docker-compose.turn.yml ps${NC}"
|
||||||
|
else
|
||||||
echo " 2. Start the production stack:"
|
echo " 2. Start the production stack:"
|
||||||
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml up -d${NC}"
|
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml up -d${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
@ -284,10 +364,11 @@ echo " 3. Wait for services to start (~60 seconds for Authentik)"
|
||||||
echo ""
|
echo ""
|
||||||
echo " 4. Verify all services are running:"
|
echo " 4. Verify all services are running:"
|
||||||
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml ps${NC}"
|
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml ps${NC}"
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
echo " 5. Check Authentik blueprint was applied:"
|
echo " Check Authentik blueprint was applied:"
|
||||||
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml logs authentik-server | grep -i blueprint${NC}"
|
echo -e " ${YELLOW}docker compose -f docker-compose.prod.yml logs authentik-server | grep -i blueprint${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo " 6. Access your app at:"
|
echo " Access your app at:"
|
||||||
echo -e " ${BLUE}https://${KABOOT_DOMAIN}${NC}"
|
echo -e " ${BLUE}https://${KABOOT_DOMAIN}${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue