improve: best-practice configs for all projects, CLI UX overhaul

Compose improvements:
- forgejo: add healthcheck (/api/healthz), ROOT_URL + SSH_PORT env, LFS
- tailscale: drop redundant privileged (use cap_add only), use devices
  for /dev/net/tun, mount /lib/modules, reliable healthcheck (tailscale
  status), profiles for opt-in DERP, headscale comment in .env.example
- uptime-kuma: add built-in healthcheck (extra/healthcheck)
- filesuite: add healthchecks for both cloudreve and qbittorrent
- minecraft: add mc-health check (built into itzg image), simplify volumes
- teamspeak: add healthcheck via ServerQuery (nc localhost 10011)
- nextcloud: add healthchecks for all 3 services, depends_on with
  service_healthy conditions so startup order is correct

CLI improvements:
- Fix docker compose detection (was broken with space in arg)
- Use global array for project discovery (no word-splitting bugs)
- Empty selection no longer defaults to "all" (safety)
- Show .env.example comments as hints during interactive configure
- Required fields (empty default) loop until user provides a value
- Disable colors when stdout is not a terminal
- compose() wrapper auto-adds --env-file
- Deduplicate project_exists / project_dir helpers
This commit is contained in:
m1ngsama 2026-04-15 10:02:41 +08:00
parent 48b32d46b2
commit 1ef24b3be8
16 changed files with 267 additions and 124 deletions

View file

@ -14,11 +14,11 @@ cd ~/automa
```bash
./automa deploy # interactive project selection
./automa deploy forgejo monitoring # deploy specific projects
./automa deploy forgejo filesuite # deploy specific projects
./automa status # check all project status
./automa logs forgejo # follow logs
./automa stop forgejo # stop a project
./automa update monitoring # pull latest images & recreate
./automa update nextcloud # pull latest images & recreate
./automa config tailscale # reconfigure .env
./automa list # list available projects
```
@ -29,15 +29,11 @@ cd ~/automa
|---------|-------------|
| `forgejo` | Self-hosted Git (Gitea fork) |
| `uptime-kuma` | Uptime monitoring dashboard |
| `tailscale` | Tailscale client + DERP relay server |
| `monitoring` | Prometheus + Grafana + Blackbox + Node Exporter |
| `tailscale` | Tailscale client + DERP relay server (profiles) |
| `filesuite` | Cloudreve cloud storage + qBittorrent |
| `minecraft` | Fabric Minecraft server |
| `teamspeak` | TeamSpeak voice server |
| `nextcloud` | Nextcloud with MariaDB + Redis |
| `huajibot` | HuaJi Bot |
| `dockge` | Docker Compose stack manager |
| `notification-center` | Webhook notification service |
## Structure
@ -46,7 +42,7 @@ Each project is a self-contained directory:
```
project-name/
├── compose.yaml # Docker Compose definition
├── .env.example # Template with default values
├── .env.example # Template — comments shown during setup
└── .env # Your config (gitignored, created by CLI)
```

250
automa
View file

@ -3,11 +3,7 @@
#
# Install & run:
# curl -fsSL https://raw.githubusercontent.com/m1ngsama/automa/main/install.sh | bash
# automa deploy
#
# Or clone and run directly:
# git clone https://github.com/m1ngsama/automa.git && cd automa
# ./automa deploy
# cd ~/automa && ./automa deploy
set -euo pipefail
@ -17,15 +13,13 @@ set -euo pipefail
AUTOMA_VERSION="1.0.0"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'
# Colors (disabled when not a terminal)
if [[ -t 1 ]]; then
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m'
CYAN='\033[0;36m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m'
else
RED='' GREEN='' YELLOW='' CYAN='' BOLD='' DIM='' NC=''
fi
# ============================================================================
# Helpers
@ -33,7 +27,6 @@ NC='\033[0m'
info() { echo -e "${GREEN}[+]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
error() { echo -e "${RED}[-]${NC} $*" >&2; }
dim() { echo -e "${DIM}$*${NC}"; }
banner() {
echo ""
@ -42,90 +35,145 @@ banner() {
echo ""
}
require_cmd() {
if ! command -v "$1" &>/dev/null; then
error "$1 is required but not installed."
[[ -n "${2:-}" ]] && dim " Install: $2"
check_docker() {
if ! command -v docker &>/dev/null; then
error "docker is required but not installed."
echo -e " ${DIM}Install: https://docs.docker.com/engine/install/${NC}"
exit 1
fi
if ! docker compose version &>/dev/null 2>&1; then
error "docker compose plugin is required."
echo -e " ${DIM}Install: https://docs.docker.com/compose/install/${NC}"
exit 1
fi
}
# ============================================================================
# Project discovery
# Scans for top-level dirs containing compose.yaml
# ============================================================================
PROJECTS=()
discover_projects() {
local projects=()
PROJECTS=()
for dir in "$SCRIPT_DIR"/*/; do
[[ -f "$dir/compose.yaml" ]] && projects+=("$(basename "$dir")")
[[ -f "$dir/compose.yaml" ]] && PROJECTS+=("$(basename "$dir")")
done
echo "${projects[@]}"
}
project_exists() {
local name="$1"
[[ -f "$SCRIPT_DIR/$name/compose.yaml" ]]
}
project_dir() {
echo "$SCRIPT_DIR/$1"
}
# compose wrapper that auto-adds --env-file if .env exists
compose() {
local name="$1"; shift
local dir="$SCRIPT_DIR/$name"
local args=(-f "$dir/compose.yaml")
if [[ -f "$dir/.env" ]]; then
args+=(--env-file "$dir/.env")
fi
docker compose "${args[@]}" "$@"
}
# ============================================================================
# Interactive .env configuration
# Reads .env.example, prompts user for each value, writes .env
# ============================================================================
configure_env() {
local project_dir="$1"
local env_example="$project_dir/.env.example"
local env_file="$project_dir/.env"
local name="$1"
local dir="$SCRIPT_DIR/$name"
local env_example="$dir/.env.example"
local env_file="$dir/.env"
if [[ ! -f "$env_example" ]]; then
warn "No .env.example found, skipping configuration"
return 0
fi
# If .env already exists, ask what to do
if [[ -f "$env_file" ]]; then
echo ""
echo -e " ${YELLOW}.env already exists.${NC}"
echo -e " ${DIM}[k]eep [r]econfigure [v]iew${NC}"
echo -e " ${YELLOW}.env already exists for ${BOLD}${name}${NC}"
echo -e " ${DIM}[k]eep current [r]econfigure [v]iew${NC}"
while true; do
read -rp " > " choice
case "$choice" in
case "${choice,,}" in
k|keep|"") info "Keeping existing .env"; return 0 ;;
r|reconfigure) break ;;
v|view)
echo ""
while IFS= read -r line; do
echo -e " ${DIM}${line}${NC}"
echo -e " ${DIM}${line}${NC}"
done < "$env_file"
echo ""
echo -e " ${DIM}[k]eep [r]econfigure${NC}"
;;
*) echo -e " ${DIM}k/r/v${NC}" ;;
*) echo -e " ${DIM}Enter k, r, or v${NC}" ;;
esac
done
fi
echo ""
info "Configure ${BOLD}$(basename "$project_dir")${NC}"
dim " Enter values (blank = use default shown in brackets)"
echo -e " ${BOLD}Configure: ${CYAN}${name}${NC}"
echo ""
# Show header comments from .env.example
while IFS= read -r line; do
[[ "$line" =~ ^#.* ]] && echo -e " ${DIM}${line}${NC}" || break
done < "$env_example"
echo ""
echo -e " ${DIM}Enter values (blank = accept default in brackets)${NC}"
echo ""
local tmp_env
tmp_env="$(mktemp)"
local pending_comment=""
while IFS= read -r line; do
# Skip comments and empty lines
[[ "$line" =~ ^#.* ]] && continue
[[ -z "$line" ]] && continue
# Blank line
if [[ -z "$line" ]]; then
[[ -n "$pending_comment" ]] && { echo "" ; pending_comment=""; }
continue
fi
# Comment line — show as hint for next variable
if [[ "$line" =~ ^#.* ]]; then
echo -e " ${DIM}${line}${NC}"
pending_comment="$line"
continue
fi
local key="${line%%=*}"
local default="${line#*=}"
pending_comment=""
local val
if [[ -n "$default" ]]; then
read -rp " ${key} [${default}]: " val
echo "${key}=${val:-$default}" >> "$tmp_env"
else
read -rp " ${key}: " val
# Required field — no default
while true; do
read -rp " ${key} (required): " val
if [[ -n "$val" ]]; then
break
fi
echo -e " ${RED}This field cannot be empty${NC}"
done
echo "${key}=${val}" >> "$tmp_env"
fi
done < "$env_example"
mv "$tmp_env" "$env_file"
chmod 600 "$env_file"
info ".env written"
echo ""
info ".env saved (chmod 600)"
}
# ============================================================================
@ -134,25 +182,24 @@ configure_env() {
cmd_list() {
banner
local projects
read -ra projects <<< "$(discover_projects)"
discover_projects
if [[ ${#projects[@]} -eq 0 ]]; then
if [[ ${#PROJECTS[@]} -eq 0 ]]; then
warn "No projects found"
return 1
fi
echo -e " ${BOLD}Available projects:${NC}"
echo ""
local i=1
for p in "${projects[@]}"; do
local status="${DIM}not deployed${NC}"
for p in "${PROJECTS[@]}"; do
local status="${DIM}not configured${NC}"
if [[ -f "$SCRIPT_DIR/$p/.env" ]]; then
# Check if compose is running
if docker compose -f "$SCRIPT_DIR/$p/compose.yaml" ps --status running 2>/dev/null | grep -q .; then
if compose "$p" ps --status running 2>/dev/null | grep -q .; then
status="${GREEN}running${NC}"
else
status="${YELLOW}configured${NC}"
status="${YELLOW}stopped${NC}"
fi
fi
printf " ${BOLD}%2d${NC} %-24s %b\n" "$i" "$p" "$status"
@ -163,13 +210,10 @@ cmd_list() {
cmd_deploy() {
banner
require_cmd docker "https://docs.docker.com/engine/install/"
require_cmd "docker compose" || require_cmd docker-compose
check_docker
discover_projects
local projects
read -ra projects <<< "$(discover_projects)"
if [[ ${#projects[@]} -eq 0 ]]; then
if [[ ${#PROJECTS[@]} -eq 0 ]]; then
error "No projects found"
return 1
fi
@ -186,36 +230,35 @@ cmd_deploy() {
echo -e " ${BOLD}Select projects to deploy:${NC}"
echo ""
local i=1
for p in "${projects[@]}"; do
for p in "${PROJECTS[@]}"; do
printf " ${BOLD}%2d${NC} %s\n" "$i" "$p"
((i++))
done
echo ""
echo -e " ${DIM}Enter numbers (space-separated), 'all', or 'q' to quit${NC}"
echo -e " ${DIM}Enter numbers (space-separated), or 'q' to quit${NC}"
read -rp " > " selection
[[ "$selection" == "q" ]] && return 0
[[ "$selection" == "q" || -z "$selection" ]] && return 0
local selected=()
if [[ "$selection" == "all" || -z "$selection" ]]; then
selected=("${projects[@]}")
if [[ "$selection" == "all" ]]; then
selected=("${PROJECTS[@]}")
else
for num in $selection; do
if [[ "$num" =~ ^[0-9]+$ ]] && ((num > 0 && num <= ${#projects[@]})); then
selected+=("${projects[$((num-1))]}")
if [[ "$num" =~ ^[0-9]+$ ]] && ((num > 0 && num <= ${#PROJECTS[@]})); then
selected+=("${PROJECTS[$((num-1))]}")
else
warn "Invalid selection: $num"
warn "Invalid: $num (skipped)"
fi
done
fi
if [[ ${#selected[@]} -eq 0 ]]; then
error "No projects selected"
return 1
return 0
fi
echo ""
info "Deploying: ${selected[*]}"
info "Will deploy: ${selected[*]}"
echo ""
local ok=0 fail=0
@ -225,76 +268,78 @@ cmd_deploy() {
else
((fail++))
fi
echo ""
done
echo ""
echo -e " ${BOLD}Summary${NC}"
[[ $ok -gt 0 ]] && info "Deployed: $ok"
[[ $fail -gt 0 ]] && error "Failed: $fail"
echo -e " ${BOLD}Done.${NC} ${GREEN}${ok} deployed${NC}" \
"$( ((fail > 0)) && echo -e ", ${RED}${fail} failed${NC}" )"
}
deploy_project() {
local name="$1"
local project_dir="$SCRIPT_DIR/$name"
if [[ ! -f "$project_dir/compose.yaml" ]]; then
if ! project_exists "$name"; then
error "Project not found: $name"
return 1
fi
echo -e " ${CYAN}── ${BOLD}${name}${NC} ${CYAN}──${NC}"
configure_env "$project_dir"
configure_env "$name"
if [[ ! -f "$SCRIPT_DIR/$name/.env" ]]; then
error "No .env file — run: automa config $name"
return 1
fi
info "Starting containers..."
if docker compose -f "$project_dir/compose.yaml" --env-file "$project_dir/.env" up -d 2>&1; then
info "${name} deployed"
if compose "$name" up -d 2>&1; then
info "${name} is up"
return 0
else
error "${name} deployment failed"
error "${name} failed to start"
return 1
fi
}
cmd_stop() {
local name="${1:?Usage: automa stop <project>}"
local project_dir="$SCRIPT_DIR/$name"
if [[ ! -f "$project_dir/compose.yaml" ]]; then
if ! project_exists "$name"; then
error "Project not found: $name"
return 1
fi
info "Stopping ${name}..."
docker compose -f "$project_dir/compose.yaml" down
compose "$name" down
info "${name} stopped"
}
cmd_logs() {
local name="${1:?Usage: automa logs <project>}"
shift
local project_dir="$SCRIPT_DIR/$name"
if [[ ! -f "$project_dir/compose.yaml" ]]; then
if ! project_exists "$name"; then
error "Project not found: $name"
return 1
fi
docker compose -f "$project_dir/compose.yaml" logs -f "$@"
compose "$name" logs -f "$@"
}
cmd_status() {
banner
local projects
read -ra projects <<< "$(discover_projects)"
check_docker
discover_projects
for p in "${projects[@]}"; do
local dir="$SCRIPT_DIR/$p"
for p in "${PROJECTS[@]}"; do
echo -e " ${BOLD}${p}${NC}"
if docker compose -f "$dir/compose.yaml" ps --format "table {{.Name}}\t{{.Status}}" 2>/dev/null | tail -n +2 | grep -q .; then
docker compose -f "$dir/compose.yaml" ps --format "table {{.Name}}\t{{.Status}}" 2>/dev/null | tail -n +2 | while IFS= read -r line; do
local output
output=$(compose "$p" ps --format "table {{.Name}}\t{{.Status}}" 2>/dev/null | tail -n +2) || true
if [[ -n "$output" ]]; then
while IFS= read -r line; do
echo -e " ${line}"
done
done <<< "$output"
else
echo -e " ${DIM}not running${NC}"
fi
@ -304,43 +349,40 @@ cmd_status() {
cmd_restart() {
local name="${1:?Usage: automa restart <project>}"
local project_dir="$SCRIPT_DIR/$name"
if [[ ! -f "$project_dir/compose.yaml" ]]; then
if ! project_exists "$name"; then
error "Project not found: $name"
return 1
fi
info "Restarting ${name}..."
docker compose -f "$project_dir/compose.yaml" restart
compose "$name" restart
info "${name} restarted"
}
cmd_config() {
local name="${1:?Usage: automa config <project>}"
local project_dir="$SCRIPT_DIR/$name"
if [[ ! -f "$project_dir/compose.yaml" ]]; then
if ! project_exists "$name"; then
error "Project not found: $name"
return 1
fi
configure_env "$project_dir"
configure_env "$name"
}
cmd_update() {
local name="${1:?Usage: automa update <project>}"
local project_dir="$SCRIPT_DIR/$name"
if [[ ! -f "$project_dir/compose.yaml" ]]; then
if ! project_exists "$name"; then
error "Project not found: $name"
return 1
fi
info "Pulling latest images for ${name}..."
docker compose -f "$project_dir/compose.yaml" pull
compose "$name" pull
info "Recreating containers..."
docker compose -f "$project_dir/compose.yaml" --env-file "$project_dir/.env" up -d
compose "$name" up -d
info "${name} updated"
}
@ -351,25 +393,25 @@ cmd_help() {
${BOLD}Commands:${NC}
deploy [project...] Interactive deploy (or specify project names)
list List all available projects
status Show running status of all projects
stop <project> Stop a project
list List all available projects and status
status Show running containers per project
stop <project> Stop a project (docker compose down)
restart <project> Restart a project
logs <project> Follow logs of a project
config <project> Configure .env for a project
config <project> (Re)configure .env for a project
update <project> Pull latest images and recreate
help Show this help
${BOLD}Examples:${NC}
automa deploy # interactive project selection
automa deploy forgejo monitoring # deploy specific projects
automa deploy forgejo filesuite # deploy specific projects
automa status # check all project status
automa logs forgejo # follow forgejo logs
automa update monitoring # pull & restart monitoring
automa update nextcloud # pull & restart nextcloud
${BOLD}Quick start:${NC}
curl -fsSL https://raw.githubusercontent.com/m1ngsama/automa/main/install.sh | bash
cd automa && ./automa deploy
cd ~/automa && ./automa deploy
EOF
}

View file

@ -1,7 +1,17 @@
# Filesuite - Cloudreve cloud storage + qBittorrent
# Both services share the same downloads directory
TZ=Asia/Shanghai
PUID=1000
PGID=1000
# Shared downloads path (absolute path recommended)
DOWNLOADS_DIR=./downloads
# Cloudreve
CLOUDREVE_PORT=5212
CR_ENABLE_ARIA2=0
# qBittorrent
QB_WEBUI_PORT=8090
QB_BT_PORT=44773

View file

@ -4,11 +4,18 @@ services:
container_name: cloudreve
environment:
TZ: "${TZ:-Asia/Shanghai}"
CR_ENABLE_ARIA2: "${CR_ENABLE_ARIA2:-0}"
volumes:
- ./cloudreve-data:/cloudreve/data
- ${DOWNLOADS_DIR:-./downloads}:/data/downloads
ports:
- "${CLOUDREVE_PORT:-5212}:5212"
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:5212/ || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
restart: unless-stopped
qbittorrent:
@ -26,4 +33,10 @@ services:
- "${QB_WEBUI_PORT:-8090}:${QB_WEBUI_PORT:-8090}"
- "${QB_BT_PORT:-44773}:${QB_BT_PORT:-44773}"
- "${QB_BT_PORT:-44773}:${QB_BT_PORT:-44773}/udp"
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:${QB_WEBUI_PORT:-8090}/ || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
restart: unless-stopped

View file

@ -1,2 +1,8 @@
# Forgejo - self-hosted Git service
# Docs: https://forgejo.org/docs/latest/admin/config-cheat-sheet/
FORGEJO_HTTP_PORT=3000
FORGEJO_SSH_PORT=2223
# Set this to your public URL when behind a reverse proxy
FORGEJO_ROOT_URL=http://localhost:3000

View file

@ -3,8 +3,11 @@ services:
image: codeberg.org/forgejo/forgejo:9
container_name: forgejo
environment:
USER_UID: 1000
USER_GID: 1000
USER_UID: "${FORGEJO_UID:-1000}"
USER_GID: "${FORGEJO_GID:-1000}"
FORGEJO__server__ROOT_URL: "${FORGEJO_ROOT_URL:-http://localhost:3000}"
FORGEJO__server__SSH_PORT: "${FORGEJO_SSH_PORT:-2223}"
FORGEJO__server__LFS_START_SERVER: "true"
ports:
- "${FORGEJO_HTTP_PORT:-3000}:3000"
- "${FORGEJO_SSH_PORT:-2223}:22"
@ -12,4 +15,10 @@ services:
- ./data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
healthcheck:
test: ["CMD", "curl", "-fSs", "http://localhost:3000/api/healthz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
restart: unless-stopped

View file

@ -1,3 +1,6 @@
# Minecraft server (itzg/minecraft-server)
# Docs: https://docker-minecraft-server.readthedocs.io/
TZ=Asia/Shanghai
MC_TYPE=FABRIC
MC_VERSION=1.21.1

View file

@ -17,8 +17,12 @@ services:
TZ: "${TZ:-Asia/Shanghai}"
volumes:
- ./data:/data
- ./mods:/data/mods
- ./configs:/configs:ro
healthcheck:
test: mc-health
interval: 30s
timeout: 10s
retries: 5
start_period: 120s
restart: unless-stopped
tty: true
stdin_open: true

View file

@ -1,10 +1,17 @@
# Nextcloud with MariaDB + Redis
# Docs: https://hub.docker.com/_/nextcloud
TZ=Asia/Shanghai
NC_PORT=8080
NC_ADMIN_USER=admin
NC_ADMIN_PASSWORD=
NC_TRUSTED_DOMAINS=localhost
# MariaDB
MYSQL_DATABASE=nextcloud
MYSQL_USER=nextcloud
MYSQL_PASSWORD=
MYSQL_ROOT_PASSWORD=
# Redis
REDIS_PASSWORD=

View file

@ -3,8 +3,10 @@ services:
image: nextcloud:stable-apache
container_name: nextcloud
depends_on:
- db
- redis
db:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "${NC_PORT:-8080}:80"
environment:
@ -23,6 +25,12 @@ services:
- nc-data:/var/www/html/data
- nc-config:/var/www/html/config
- nc-apps:/var/www/html/custom_apps
healthcheck:
test: ["CMD", "curl", "-fSs", "http://localhost/status.php"]
interval: 30s
timeout: 5s
retries: 5
start_period: 60s
restart: unless-stopped
db:
@ -37,6 +45,12 @@ services:
MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
volumes:
- nc-db:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
redis:
@ -45,6 +59,11 @@ services:
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
volumes:
- nc-redis:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 3
restart: unless-stopped
volumes:

View file

@ -1,9 +1,19 @@
# Tailscale + DERP relay server
#
# Deploy tailscale only: docker compose --profile tailscale up -d
# Deploy with DERP: docker compose --profile derp up -d
TZ=Asia/Shanghai
TS_HOSTNAME=
TS_AUTHKEY=
# For headscale: --advertise-tags=tag:container --login-server=https://your.headscale.host
TS_EXTRA_ARGS=--advertise-tags=tag:container
TS_USERSPACE=false
TS_FIREWALL_MODE=nftables
# DERP relay (only needed with --profile derp)
DERP_HOST=
DERP_PORT=443
STUN_PORT=3478

View file

@ -3,15 +3,13 @@ services:
image: tailscale/tailscale:latest
container_name: tailscale
hostname: "${TS_HOSTNAME}"
volumes:
- ./tailscale-data:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
- /var/run/tailscale:/var/run/tailscale
privileged: true
profiles: ["tailscale", "derp"]
cap_add:
- NET_ADMIN
- SYS_MODULE
- NET_RAW
devices:
- /dev/net/tun:/dev/net/tun
network_mode: host
environment:
TS_AUTHKEY: "${TS_AUTHKEY}"
@ -22,13 +20,22 @@ services:
TS_DEBUG_FIREWALL_MODE: "${TS_FIREWALL_MODE:-nftables}"
TS_HOSTNAME: "${TS_HOSTNAME}"
TZ: "${TZ:-Asia/Shanghai}"
volumes:
- ./tailscale-data:/var/lib/tailscale
- /var/run/tailscale:/var/run/tailscale
- /lib/modules:/lib/modules:ro
healthcheck:
test: ["CMD-SHELL", "tailscale status --json | grep -q '\"BackendState\": \"Running\"'"]
test: ["CMD-SHELL", "tailscale status"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
restart: unless-stopped
derp-server:
image: ghcr.io/nbtca/tailscale-derp:edge
container_name: tailscale-derp
profiles: ["derp"]
network_mode: host
depends_on:
tailscale:

View file

@ -1 +1,3 @@
# TeamSpeak voice server
TS3_ADMIN_PASSWORD=

View file

@ -11,6 +11,12 @@ services:
TS3SERVER_SERVERADMIN_PASSWORD: "${TS3_ADMIN_PASSWORD}"
volumes:
- ts-data:/var/ts3server/
healthcheck:
test: ["CMD-SHELL", "echo quit | nc localhost 10011 | grep -q TS3"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
restart: unless-stopped
volumes:

View file

@ -1 +1,4 @@
# Uptime Kuma - uptime monitoring
# Default binds to localhost only; change to 0.0.0.0:3001 for external access
KUMA_PORT=127.0.0.1:3001

View file

@ -6,4 +6,10 @@ services:
- ./data:/app/data
ports:
- "${KUMA_PORT:-127.0.0.1:3001}:3001"
healthcheck:
test: ["CMD-SHELL", "extra/healthcheck"]
interval: 60s
timeout: 30s
retries: 5
start_period: 180s
restart: unless-stopped