mirror of
https://oauth2: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@github.com/m1ngsama/automa.git
synced 2026-05-10 19:11:07 +08:00
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:
parent
48b32d46b2
commit
1ef24b3be8
16 changed files with 267 additions and 124 deletions
12
README.md
12
README.md
|
|
@ -14,11 +14,11 @@ cd ~/automa
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./automa deploy # interactive project selection
|
./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 status # check all project status
|
||||||
./automa logs forgejo # follow logs
|
./automa logs forgejo # follow logs
|
||||||
./automa stop forgejo # stop a project
|
./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 config tailscale # reconfigure .env
|
||||||
./automa list # list available projects
|
./automa list # list available projects
|
||||||
```
|
```
|
||||||
|
|
@ -29,15 +29,11 @@ cd ~/automa
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `forgejo` | Self-hosted Git (Gitea fork) |
|
| `forgejo` | Self-hosted Git (Gitea fork) |
|
||||||
| `uptime-kuma` | Uptime monitoring dashboard |
|
| `uptime-kuma` | Uptime monitoring dashboard |
|
||||||
| `tailscale` | Tailscale client + DERP relay server |
|
| `tailscale` | Tailscale client + DERP relay server (profiles) |
|
||||||
| `monitoring` | Prometheus + Grafana + Blackbox + Node Exporter |
|
|
||||||
| `filesuite` | Cloudreve cloud storage + qBittorrent |
|
| `filesuite` | Cloudreve cloud storage + qBittorrent |
|
||||||
| `minecraft` | Fabric Minecraft server |
|
| `minecraft` | Fabric Minecraft server |
|
||||||
| `teamspeak` | TeamSpeak voice server |
|
| `teamspeak` | TeamSpeak voice server |
|
||||||
| `nextcloud` | Nextcloud with MariaDB + Redis |
|
| `nextcloud` | Nextcloud with MariaDB + Redis |
|
||||||
| `huajibot` | HuaJi Bot |
|
|
||||||
| `dockge` | Docker Compose stack manager |
|
|
||||||
| `notification-center` | Webhook notification service |
|
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
|
|
@ -46,7 +42,7 @@ Each project is a self-contained directory:
|
||||||
```
|
```
|
||||||
project-name/
|
project-name/
|
||||||
├── compose.yaml # Docker Compose definition
|
├── 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)
|
└── .env # Your config (gitignored, created by CLI)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
248
automa
248
automa
|
|
@ -3,11 +3,7 @@
|
||||||
#
|
#
|
||||||
# Install & run:
|
# Install & run:
|
||||||
# curl -fsSL https://raw.githubusercontent.com/m1ngsama/automa/main/install.sh | bash
|
# curl -fsSL https://raw.githubusercontent.com/m1ngsama/automa/main/install.sh | bash
|
||||||
# automa deploy
|
# cd ~/automa && ./automa deploy
|
||||||
#
|
|
||||||
# Or clone and run directly:
|
|
||||||
# git clone https://github.com/m1ngsama/automa.git && cd automa
|
|
||||||
# ./automa deploy
|
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -17,15 +13,13 @@ set -euo pipefail
|
||||||
AUTOMA_VERSION="1.0.0"
|
AUTOMA_VERSION="1.0.0"
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
# Colors
|
# Colors (disabled when not a terminal)
|
||||||
RED='\033[0;31m'
|
if [[ -t 1 ]]; then
|
||||||
GREEN='\033[0;32m'
|
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m'
|
||||||
YELLOW='\033[1;33m'
|
CYAN='\033[0;36m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m'
|
||||||
BLUE='\033[0;34m'
|
else
|
||||||
CYAN='\033[0;36m'
|
RED='' GREEN='' YELLOW='' CYAN='' BOLD='' DIM='' NC=''
|
||||||
BOLD='\033[1m'
|
fi
|
||||||
DIM='\033[2m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Helpers
|
# Helpers
|
||||||
|
|
@ -33,7 +27,6 @@ NC='\033[0m'
|
||||||
info() { echo -e "${GREEN}[+]${NC} $*"; }
|
info() { echo -e "${GREEN}[+]${NC} $*"; }
|
||||||
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
||||||
error() { echo -e "${RED}[-]${NC} $*" >&2; }
|
error() { echo -e "${RED}[-]${NC} $*" >&2; }
|
||||||
dim() { echo -e "${DIM}$*${NC}"; }
|
|
||||||
|
|
||||||
banner() {
|
banner() {
|
||||||
echo ""
|
echo ""
|
||||||
|
|
@ -42,47 +35,75 @@ banner() {
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
require_cmd() {
|
check_docker() {
|
||||||
if ! command -v "$1" &>/dev/null; then
|
if ! command -v docker &>/dev/null; then
|
||||||
error "$1 is required but not installed."
|
error "docker is required but not installed."
|
||||||
[[ -n "${2:-}" ]] && dim " Install: $2"
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Project discovery
|
# Project discovery
|
||||||
# Scans for top-level dirs containing compose.yaml
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
PROJECTS=()
|
||||||
|
|
||||||
discover_projects() {
|
discover_projects() {
|
||||||
local projects=()
|
PROJECTS=()
|
||||||
for dir in "$SCRIPT_DIR"/*/; do
|
for dir in "$SCRIPT_DIR"/*/; do
|
||||||
[[ -f "$dir/compose.yaml" ]] && projects+=("$(basename "$dir")")
|
[[ -f "$dir/compose.yaml" ]] && PROJECTS+=("$(basename "$dir")")
|
||||||
done
|
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
|
# Interactive .env configuration
|
||||||
# Reads .env.example, prompts user for each value, writes .env
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
configure_env() {
|
configure_env() {
|
||||||
local project_dir="$1"
|
local name="$1"
|
||||||
local env_example="$project_dir/.env.example"
|
local dir="$SCRIPT_DIR/$name"
|
||||||
local env_file="$project_dir/.env"
|
local env_example="$dir/.env.example"
|
||||||
|
local env_file="$dir/.env"
|
||||||
|
|
||||||
if [[ ! -f "$env_example" ]]; then
|
if [[ ! -f "$env_example" ]]; then
|
||||||
warn "No .env.example found, skipping configuration"
|
warn "No .env.example found, skipping configuration"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# If .env already exists, ask what to do
|
||||||
if [[ -f "$env_file" ]]; then
|
if [[ -f "$env_file" ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${YELLOW}.env already exists.${NC}"
|
echo -e " ${YELLOW}.env already exists for ${BOLD}${name}${NC}"
|
||||||
echo -e " ${DIM}[k]eep [r]econfigure [v]iew${NC}"
|
echo -e " ${DIM}[k]eep current [r]econfigure [v]iew${NC}"
|
||||||
while true; do
|
while true; do
|
||||||
read -rp " > " choice
|
read -rp " > " choice
|
||||||
case "$choice" in
|
case "${choice,,}" in
|
||||||
k|keep|"") info "Keeping existing .env"; return 0 ;;
|
k|keep|"") info "Keeping existing .env"; return 0 ;;
|
||||||
r|reconfigure) break ;;
|
r|reconfigure) break ;;
|
||||||
v|view)
|
v|view)
|
||||||
|
|
@ -93,39 +114,66 @@ configure_env() {
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${DIM}[k]eep [r]econfigure${NC}"
|
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
|
esac
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
info "Configure ${BOLD}$(basename "$project_dir")${NC}"
|
echo -e " ${BOLD}Configure: ${CYAN}${name}${NC}"
|
||||||
dim " Enter values (blank = use default shown in brackets)"
|
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 ""
|
echo ""
|
||||||
|
|
||||||
local tmp_env
|
local tmp_env
|
||||||
tmp_env="$(mktemp)"
|
tmp_env="$(mktemp)"
|
||||||
|
local pending_comment=""
|
||||||
|
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
# Skip comments and empty lines
|
# Blank line
|
||||||
[[ "$line" =~ ^#.* ]] && continue
|
if [[ -z "$line" ]]; then
|
||||||
[[ -z "$line" ]] && continue
|
[[ -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 key="${line%%=*}"
|
||||||
local default="${line#*=}"
|
local default="${line#*=}"
|
||||||
|
pending_comment=""
|
||||||
|
|
||||||
|
local val
|
||||||
if [[ -n "$default" ]]; then
|
if [[ -n "$default" ]]; then
|
||||||
read -rp " ${key} [${default}]: " val
|
read -rp " ${key} [${default}]: " val
|
||||||
echo "${key}=${val:-$default}" >> "$tmp_env"
|
echo "${key}=${val:-$default}" >> "$tmp_env"
|
||||||
else
|
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"
|
echo "${key}=${val}" >> "$tmp_env"
|
||||||
fi
|
fi
|
||||||
done < "$env_example"
|
done < "$env_example"
|
||||||
|
|
||||||
mv "$tmp_env" "$env_file"
|
mv "$tmp_env" "$env_file"
|
||||||
chmod 600 "$env_file"
|
chmod 600 "$env_file"
|
||||||
info ".env written"
|
echo ""
|
||||||
|
info ".env saved (chmod 600)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
@ -134,25 +182,24 @@ configure_env() {
|
||||||
|
|
||||||
cmd_list() {
|
cmd_list() {
|
||||||
banner
|
banner
|
||||||
local projects
|
discover_projects
|
||||||
read -ra projects <<< "$(discover_projects)"
|
|
||||||
|
|
||||||
if [[ ${#projects[@]} -eq 0 ]]; then
|
if [[ ${#PROJECTS[@]} -eq 0 ]]; then
|
||||||
warn "No projects found"
|
warn "No projects found"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e " ${BOLD}Available projects:${NC}"
|
echo -e " ${BOLD}Available projects:${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
local i=1
|
local i=1
|
||||||
for p in "${projects[@]}"; do
|
for p in "${PROJECTS[@]}"; do
|
||||||
local status="${DIM}not deployed${NC}"
|
local status="${DIM}not configured${NC}"
|
||||||
if [[ -f "$SCRIPT_DIR/$p/.env" ]]; then
|
if [[ -f "$SCRIPT_DIR/$p/.env" ]]; then
|
||||||
# Check if compose is running
|
if compose "$p" ps --status running 2>/dev/null | grep -q .; then
|
||||||
if docker compose -f "$SCRIPT_DIR/$p/compose.yaml" ps --status running 2>/dev/null | grep -q .; then
|
|
||||||
status="${GREEN}running${NC}"
|
status="${GREEN}running${NC}"
|
||||||
else
|
else
|
||||||
status="${YELLOW}configured${NC}"
|
status="${YELLOW}stopped${NC}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
printf " ${BOLD}%2d${NC} %-24s %b\n" "$i" "$p" "$status"
|
printf " ${BOLD}%2d${NC} %-24s %b\n" "$i" "$p" "$status"
|
||||||
|
|
@ -163,13 +210,10 @@ cmd_list() {
|
||||||
|
|
||||||
cmd_deploy() {
|
cmd_deploy() {
|
||||||
banner
|
banner
|
||||||
require_cmd docker "https://docs.docker.com/engine/install/"
|
check_docker
|
||||||
require_cmd "docker compose" || require_cmd docker-compose
|
discover_projects
|
||||||
|
|
||||||
local projects
|
if [[ ${#PROJECTS[@]} -eq 0 ]]; then
|
||||||
read -ra projects <<< "$(discover_projects)"
|
|
||||||
|
|
||||||
if [[ ${#projects[@]} -eq 0 ]]; then
|
|
||||||
error "No projects found"
|
error "No projects found"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
@ -186,36 +230,35 @@ cmd_deploy() {
|
||||||
echo -e " ${BOLD}Select projects to deploy:${NC}"
|
echo -e " ${BOLD}Select projects to deploy:${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
local i=1
|
local i=1
|
||||||
for p in "${projects[@]}"; do
|
for p in "${PROJECTS[@]}"; do
|
||||||
printf " ${BOLD}%2d${NC} %s\n" "$i" "$p"
|
printf " ${BOLD}%2d${NC} %s\n" "$i" "$p"
|
||||||
((i++))
|
((i++))
|
||||||
done
|
done
|
||||||
echo ""
|
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
|
read -rp " > " selection
|
||||||
|
|
||||||
[[ "$selection" == "q" ]] && return 0
|
[[ "$selection" == "q" || -z "$selection" ]] && return 0
|
||||||
|
|
||||||
local selected=()
|
local selected=()
|
||||||
if [[ "$selection" == "all" || -z "$selection" ]]; then
|
if [[ "$selection" == "all" ]]; then
|
||||||
selected=("${projects[@]}")
|
selected=("${PROJECTS[@]}")
|
||||||
else
|
else
|
||||||
for num in $selection; do
|
for num in $selection; do
|
||||||
if [[ "$num" =~ ^[0-9]+$ ]] && ((num > 0 && num <= ${#projects[@]})); then
|
if [[ "$num" =~ ^[0-9]+$ ]] && ((num > 0 && num <= ${#PROJECTS[@]})); then
|
||||||
selected+=("${projects[$((num-1))]}")
|
selected+=("${PROJECTS[$((num-1))]}")
|
||||||
else
|
else
|
||||||
warn "Invalid selection: $num"
|
warn "Invalid: $num (skipped)"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${#selected[@]} -eq 0 ]]; then
|
if [[ ${#selected[@]} -eq 0 ]]; then
|
||||||
error "No projects selected"
|
return 0
|
||||||
return 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
info "Deploying: ${selected[*]}"
|
info "Will deploy: ${selected[*]}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
local ok=0 fail=0
|
local ok=0 fail=0
|
||||||
|
|
@ -225,76 +268,78 @@ cmd_deploy() {
|
||||||
else
|
else
|
||||||
((fail++))
|
((fail++))
|
||||||
fi
|
fi
|
||||||
|
echo ""
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo -e " ${BOLD}Done.${NC} ${GREEN}${ok} deployed${NC}" \
|
||||||
echo -e " ${BOLD}Summary${NC}"
|
"$( ((fail > 0)) && echo -e ", ${RED}${fail} failed${NC}" )"
|
||||||
[[ $ok -gt 0 ]] && info "Deployed: $ok"
|
|
||||||
[[ $fail -gt 0 ]] && error "Failed: $fail"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deploy_project() {
|
deploy_project() {
|
||||||
local name="$1"
|
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"
|
error "Project not found: $name"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e " ${CYAN}── ${BOLD}${name}${NC} ${CYAN}──${NC}"
|
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..."
|
info "Starting containers..."
|
||||||
if docker compose -f "$project_dir/compose.yaml" --env-file "$project_dir/.env" up -d 2>&1; then
|
if compose "$name" up -d 2>&1; then
|
||||||
info "${name} deployed"
|
info "${name} is up"
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
error "${name} deployment failed"
|
error "${name} failed to start"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_stop() {
|
cmd_stop() {
|
||||||
local name="${1:?Usage: automa stop <project>}"
|
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"
|
error "Project not found: $name"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Stopping ${name}..."
|
info "Stopping ${name}..."
|
||||||
docker compose -f "$project_dir/compose.yaml" down
|
compose "$name" down
|
||||||
info "${name} stopped"
|
info "${name} stopped"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_logs() {
|
cmd_logs() {
|
||||||
local name="${1:?Usage: automa logs <project>}"
|
local name="${1:?Usage: automa logs <project>}"
|
||||||
shift
|
shift
|
||||||
local project_dir="$SCRIPT_DIR/$name"
|
|
||||||
|
|
||||||
if [[ ! -f "$project_dir/compose.yaml" ]]; then
|
if ! project_exists "$name"; then
|
||||||
error "Project not found: $name"
|
error "Project not found: $name"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker compose -f "$project_dir/compose.yaml" logs -f "$@"
|
compose "$name" logs -f "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_status() {
|
cmd_status() {
|
||||||
banner
|
banner
|
||||||
local projects
|
check_docker
|
||||||
read -ra projects <<< "$(discover_projects)"
|
discover_projects
|
||||||
|
|
||||||
for p in "${projects[@]}"; do
|
for p in "${PROJECTS[@]}"; do
|
||||||
local dir="$SCRIPT_DIR/$p"
|
|
||||||
echo -e " ${BOLD}${p}${NC}"
|
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
|
local output
|
||||||
docker compose -f "$dir/compose.yaml" ps --format "table {{.Name}}\t{{.Status}}" 2>/dev/null | tail -n +2 | while IFS= read -r line; do
|
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}"
|
echo -e " ${line}"
|
||||||
done
|
done <<< "$output"
|
||||||
else
|
else
|
||||||
echo -e " ${DIM}not running${NC}"
|
echo -e " ${DIM}not running${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
@ -304,43 +349,40 @@ cmd_status() {
|
||||||
|
|
||||||
cmd_restart() {
|
cmd_restart() {
|
||||||
local name="${1:?Usage: automa restart <project>}"
|
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"
|
error "Project not found: $name"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Restarting ${name}..."
|
info "Restarting ${name}..."
|
||||||
docker compose -f "$project_dir/compose.yaml" restart
|
compose "$name" restart
|
||||||
info "${name} restarted"
|
info "${name} restarted"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_config() {
|
cmd_config() {
|
||||||
local name="${1:?Usage: automa config <project>}"
|
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"
|
error "Project not found: $name"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
configure_env "$project_dir"
|
configure_env "$name"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_update() {
|
cmd_update() {
|
||||||
local name="${1:?Usage: automa update <project>}"
|
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"
|
error "Project not found: $name"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Pulling latest images for ${name}..."
|
info "Pulling latest images for ${name}..."
|
||||||
docker compose -f "$project_dir/compose.yaml" pull
|
compose "$name" pull
|
||||||
info "Recreating containers..."
|
info "Recreating containers..."
|
||||||
docker compose -f "$project_dir/compose.yaml" --env-file "$project_dir/.env" up -d
|
compose "$name" up -d
|
||||||
info "${name} updated"
|
info "${name} updated"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,25 +393,25 @@ cmd_help() {
|
||||||
|
|
||||||
${BOLD}Commands:${NC}
|
${BOLD}Commands:${NC}
|
||||||
deploy [project...] Interactive deploy (or specify project names)
|
deploy [project...] Interactive deploy (or specify project names)
|
||||||
list List all available projects
|
list List all available projects and status
|
||||||
status Show running status of all projects
|
status Show running containers per project
|
||||||
stop <project> Stop a project
|
stop <project> Stop a project (docker compose down)
|
||||||
restart <project> Restart a project
|
restart <project> Restart a project
|
||||||
logs <project> Follow logs of 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
|
update <project> Pull latest images and recreate
|
||||||
help Show this help
|
help Show this help
|
||||||
|
|
||||||
${BOLD}Examples:${NC}
|
${BOLD}Examples:${NC}
|
||||||
automa deploy # interactive project selection
|
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 status # check all project status
|
||||||
automa logs forgejo # follow forgejo logs
|
automa logs forgejo # follow forgejo logs
|
||||||
automa update monitoring # pull & restart monitoring
|
automa update nextcloud # pull & restart nextcloud
|
||||||
|
|
||||||
${BOLD}Quick start:${NC}
|
${BOLD}Quick start:${NC}
|
||||||
curl -fsSL https://raw.githubusercontent.com/m1ngsama/automa/main/install.sh | bash
|
curl -fsSL https://raw.githubusercontent.com/m1ngsama/automa/main/install.sh | bash
|
||||||
cd automa && ./automa deploy
|
cd ~/automa && ./automa deploy
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,17 @@
|
||||||
|
# Filesuite - Cloudreve cloud storage + qBittorrent
|
||||||
|
# Both services share the same downloads directory
|
||||||
|
|
||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
PUID=1000
|
PUID=1000
|
||||||
PGID=1000
|
PGID=1000
|
||||||
|
|
||||||
|
# Shared downloads path (absolute path recommended)
|
||||||
DOWNLOADS_DIR=./downloads
|
DOWNLOADS_DIR=./downloads
|
||||||
|
|
||||||
|
# Cloudreve
|
||||||
CLOUDREVE_PORT=5212
|
CLOUDREVE_PORT=5212
|
||||||
|
CR_ENABLE_ARIA2=0
|
||||||
|
|
||||||
|
# qBittorrent
|
||||||
QB_WEBUI_PORT=8090
|
QB_WEBUI_PORT=8090
|
||||||
QB_BT_PORT=44773
|
QB_BT_PORT=44773
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,18 @@ services:
|
||||||
container_name: cloudreve
|
container_name: cloudreve
|
||||||
environment:
|
environment:
|
||||||
TZ: "${TZ:-Asia/Shanghai}"
|
TZ: "${TZ:-Asia/Shanghai}"
|
||||||
|
CR_ENABLE_ARIA2: "${CR_ENABLE_ARIA2:-0}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./cloudreve-data:/cloudreve/data
|
- ./cloudreve-data:/cloudreve/data
|
||||||
- ${DOWNLOADS_DIR:-./downloads}:/data/downloads
|
- ${DOWNLOADS_DIR:-./downloads}:/data/downloads
|
||||||
ports:
|
ports:
|
||||||
- "${CLOUDREVE_PORT:-5212}:5212"
|
- "${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
|
restart: unless-stopped
|
||||||
|
|
||||||
qbittorrent:
|
qbittorrent:
|
||||||
|
|
@ -26,4 +33,10 @@ services:
|
||||||
- "${QB_WEBUI_PORT:-8090}:${QB_WEBUI_PORT:-8090}"
|
- "${QB_WEBUI_PORT:-8090}:${QB_WEBUI_PORT:-8090}"
|
||||||
- "${QB_BT_PORT:-44773}:${QB_BT_PORT:-44773}"
|
- "${QB_BT_PORT:-44773}:${QB_BT_PORT:-44773}"
|
||||||
- "${QB_BT_PORT:-44773}:${QB_BT_PORT:-44773}/udp"
|
- "${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
|
restart: unless-stopped
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,8 @@
|
||||||
|
# Forgejo - self-hosted Git service
|
||||||
|
# Docs: https://forgejo.org/docs/latest/admin/config-cheat-sheet/
|
||||||
|
|
||||||
FORGEJO_HTTP_PORT=3000
|
FORGEJO_HTTP_PORT=3000
|
||||||
FORGEJO_SSH_PORT=2223
|
FORGEJO_SSH_PORT=2223
|
||||||
|
|
||||||
|
# Set this to your public URL when behind a reverse proxy
|
||||||
|
FORGEJO_ROOT_URL=http://localhost:3000
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,11 @@ services:
|
||||||
image: codeberg.org/forgejo/forgejo:9
|
image: codeberg.org/forgejo/forgejo:9
|
||||||
container_name: forgejo
|
container_name: forgejo
|
||||||
environment:
|
environment:
|
||||||
USER_UID: 1000
|
USER_UID: "${FORGEJO_UID:-1000}"
|
||||||
USER_GID: 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:
|
ports:
|
||||||
- "${FORGEJO_HTTP_PORT:-3000}:3000"
|
- "${FORGEJO_HTTP_PORT:-3000}:3000"
|
||||||
- "${FORGEJO_SSH_PORT:-2223}:22"
|
- "${FORGEJO_SSH_PORT:-2223}:22"
|
||||||
|
|
@ -12,4 +15,10 @@ services:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
- /etc/timezone:/etc/timezone:ro
|
- /etc/timezone:/etc/timezone:ro
|
||||||
- /etc/localtime:/etc/localtime: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
|
restart: unless-stopped
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
# Minecraft server (itzg/minecraft-server)
|
||||||
|
# Docs: https://docker-minecraft-server.readthedocs.io/
|
||||||
|
|
||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
MC_TYPE=FABRIC
|
MC_TYPE=FABRIC
|
||||||
MC_VERSION=1.21.1
|
MC_VERSION=1.21.1
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,12 @@ services:
|
||||||
TZ: "${TZ:-Asia/Shanghai}"
|
TZ: "${TZ:-Asia/Shanghai}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
- ./mods:/data/mods
|
healthcheck:
|
||||||
- ./configs:/configs:ro
|
test: mc-health
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 120s
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
tty: true
|
tty: true
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
|
# Nextcloud with MariaDB + Redis
|
||||||
|
# Docs: https://hub.docker.com/_/nextcloud
|
||||||
|
|
||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
NC_PORT=8080
|
NC_PORT=8080
|
||||||
NC_ADMIN_USER=admin
|
NC_ADMIN_USER=admin
|
||||||
NC_ADMIN_PASSWORD=
|
NC_ADMIN_PASSWORD=
|
||||||
NC_TRUSTED_DOMAINS=localhost
|
NC_TRUSTED_DOMAINS=localhost
|
||||||
|
|
||||||
|
# MariaDB
|
||||||
MYSQL_DATABASE=nextcloud
|
MYSQL_DATABASE=nextcloud
|
||||||
MYSQL_USER=nextcloud
|
MYSQL_USER=nextcloud
|
||||||
MYSQL_PASSWORD=
|
MYSQL_PASSWORD=
|
||||||
MYSQL_ROOT_PASSWORD=
|
MYSQL_ROOT_PASSWORD=
|
||||||
|
|
||||||
|
# Redis
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ services:
|
||||||
image: nextcloud:stable-apache
|
image: nextcloud:stable-apache
|
||||||
container_name: nextcloud
|
container_name: nextcloud
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
db:
|
||||||
- redis
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
ports:
|
ports:
|
||||||
- "${NC_PORT:-8080}:80"
|
- "${NC_PORT:-8080}:80"
|
||||||
environment:
|
environment:
|
||||||
|
|
@ -23,6 +25,12 @@ services:
|
||||||
- nc-data:/var/www/html/data
|
- nc-data:/var/www/html/data
|
||||||
- nc-config:/var/www/html/config
|
- nc-config:/var/www/html/config
|
||||||
- nc-apps:/var/www/html/custom_apps
|
- 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
|
restart: unless-stopped
|
||||||
|
|
||||||
db:
|
db:
|
||||||
|
|
@ -37,6 +45,12 @@ services:
|
||||||
MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
|
MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
|
||||||
volumes:
|
volumes:
|
||||||
- nc-db:/var/lib/mysql
|
- 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
|
restart: unless-stopped
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
|
|
@ -45,6 +59,11 @@ services:
|
||||||
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
|
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
|
||||||
volumes:
|
volumes:
|
||||||
- nc-redis:/data
|
- nc-redis:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
||||||
|
|
@ -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
|
TZ=Asia/Shanghai
|
||||||
TS_HOSTNAME=
|
TS_HOSTNAME=
|
||||||
TS_AUTHKEY=
|
TS_AUTHKEY=
|
||||||
|
|
||||||
|
# For headscale: --advertise-tags=tag:container --login-server=https://your.headscale.host
|
||||||
TS_EXTRA_ARGS=--advertise-tags=tag:container
|
TS_EXTRA_ARGS=--advertise-tags=tag:container
|
||||||
|
|
||||||
TS_USERSPACE=false
|
TS_USERSPACE=false
|
||||||
TS_FIREWALL_MODE=nftables
|
TS_FIREWALL_MODE=nftables
|
||||||
|
|
||||||
|
# DERP relay (only needed with --profile derp)
|
||||||
DERP_HOST=
|
DERP_HOST=
|
||||||
DERP_PORT=443
|
DERP_PORT=443
|
||||||
STUN_PORT=3478
|
STUN_PORT=3478
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,13 @@ services:
|
||||||
image: tailscale/tailscale:latest
|
image: tailscale/tailscale:latest
|
||||||
container_name: tailscale
|
container_name: tailscale
|
||||||
hostname: "${TS_HOSTNAME}"
|
hostname: "${TS_HOSTNAME}"
|
||||||
volumes:
|
profiles: ["tailscale", "derp"]
|
||||||
- ./tailscale-data:/var/lib/tailscale
|
|
||||||
- /dev/net/tun:/dev/net/tun
|
|
||||||
- /var/run/tailscale:/var/run/tailscale
|
|
||||||
privileged: true
|
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
- SYS_MODULE
|
- SYS_MODULE
|
||||||
- NET_RAW
|
- NET_RAW
|
||||||
|
devices:
|
||||||
|
- /dev/net/tun:/dev/net/tun
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
TS_AUTHKEY: "${TS_AUTHKEY}"
|
TS_AUTHKEY: "${TS_AUTHKEY}"
|
||||||
|
|
@ -22,13 +20,22 @@ services:
|
||||||
TS_DEBUG_FIREWALL_MODE: "${TS_FIREWALL_MODE:-nftables}"
|
TS_DEBUG_FIREWALL_MODE: "${TS_FIREWALL_MODE:-nftables}"
|
||||||
TS_HOSTNAME: "${TS_HOSTNAME}"
|
TS_HOSTNAME: "${TS_HOSTNAME}"
|
||||||
TZ: "${TZ:-Asia/Shanghai}"
|
TZ: "${TZ:-Asia/Shanghai}"
|
||||||
|
volumes:
|
||||||
|
- ./tailscale-data:/var/lib/tailscale
|
||||||
|
- /var/run/tailscale:/var/run/tailscale
|
||||||
|
- /lib/modules:/lib/modules:ro
|
||||||
healthcheck:
|
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
|
restart: unless-stopped
|
||||||
|
|
||||||
derp-server:
|
derp-server:
|
||||||
image: ghcr.io/nbtca/tailscale-derp:edge
|
image: ghcr.io/nbtca/tailscale-derp:edge
|
||||||
container_name: tailscale-derp
|
container_name: tailscale-derp
|
||||||
|
profiles: ["derp"]
|
||||||
network_mode: host
|
network_mode: host
|
||||||
depends_on:
|
depends_on:
|
||||||
tailscale:
|
tailscale:
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
|
# TeamSpeak voice server
|
||||||
|
|
||||||
TS3_ADMIN_PASSWORD=
|
TS3_ADMIN_PASSWORD=
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,12 @@ services:
|
||||||
TS3SERVER_SERVERADMIN_PASSWORD: "${TS3_ADMIN_PASSWORD}"
|
TS3SERVER_SERVERADMIN_PASSWORD: "${TS3_ADMIN_PASSWORD}"
|
||||||
volumes:
|
volumes:
|
||||||
- ts-data:/var/ts3server/
|
- 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
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
||||||
|
|
@ -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
|
KUMA_PORT=127.0.0.1:3001
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,10 @@ services:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
ports:
|
ports:
|
||||||
- "${KUMA_PORT:-127.0.0.1:3001}:3001"
|
- "${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
|
restart: unless-stopped
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue