mirror of
https://oauth2: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@github.com/m1ngsama/automa.git
synced 2026-05-10 19:11:07 +08:00
polish: CLI UX overhaul and rich .env.example metadata
CLI improvements: - Unicode status indicators (✔ ✘ ▶ ● ○ ⚠) and braille spinners - Animated spinner for docker pull/up operations - Project metadata parsed from .env.example (@name, @desc, @url, @port, @note) - Descriptions shown in list, deploy selection, and status views - Auto-generate passwords for secret fields (PASSWORD/TOKEN/AUTHKEY) - Confirmation prompt before deploy with project summary - Post-deploy access URL hint based on @port metadata - Divider lines for visual section separation - Helpful error messages with suggested commands - Command aliases: ls, st, ps, down, log, configure - Bash 3.2 compatible (no associative arrays) .env.example enrichment: - All projects now have @name, @desc, @url, @port metadata headers - Inline field descriptions shown as context during interactive config - Tailscale: @note hints for profile-based DERP deployment - Structured comments group related settings visually Installer: - Prerequisite check with per-tool status (✔/✘) - Quieter git operations - Cleaner post-install instructions
This commit is contained in:
parent
adcf0b1884
commit
e5542de818
9 changed files with 522 additions and 245 deletions
607
automa
607
automa
|
|
@ -1,55 +1,101 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# automa - interactive Docker Compose project deployer
|
# automa - interactive Docker Compose project deployer
|
||||||
#
|
#
|
||||||
# Install & run:
|
# Quick start:
|
||||||
# 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
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# Constants
|
|
||||||
# ============================================================================
|
|
||||||
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 (disabled when not a terminal)
|
# ============================================================================
|
||||||
|
# Terminal
|
||||||
|
# ============================================================================
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m'
|
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'
|
BLUE='\033[0;34m' CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m' DIM='\033[2m' NC='\033[0m'
|
||||||
|
COLS=$(tput cols 2>/dev/null || echo 80)
|
||||||
else
|
else
|
||||||
RED='' GREEN='' YELLOW='' CYAN='' BOLD='' DIM='' NC=''
|
RED='' GREEN='' YELLOW='' BLUE='' CYAN=''
|
||||||
|
BOLD='' DIM='' NC=''
|
||||||
|
COLS=80
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Helpers
|
# Output helpers
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
info() { echo -e "${GREEN}[+]${NC} $*"; }
|
info() { echo -e " ${GREEN}\xe2\x9c\x94${NC} $*"; }
|
||||||
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
warn() { echo -e " ${YELLOW}\xe2\x9a\xa0${NC} $*"; }
|
||||||
error() { echo -e "${RED}[-]${NC} $*" >&2; }
|
error() { echo -e " ${RED}\xe2\x9c\x98${NC} $*" >&2; }
|
||||||
|
step() { echo -e " ${CYAN}\xe2\x96\xb6${NC} ${BOLD}$*${NC}"; }
|
||||||
|
dim() { echo -e " ${DIM}$*${NC}"; }
|
||||||
|
|
||||||
banner() {
|
divider() {
|
||||||
echo ""
|
printf " ${DIM}"
|
||||||
echo -e "${CYAN}${BOLD} automa${NC} ${DIM}v${AUTOMA_VERSION}${NC}"
|
printf '%.0s\xe2\x94\x80' $(seq 1 $(( (COLS - 4) / 3 + 1 )) )
|
||||||
echo -e " ${DIM}docker compose project deployer${NC}"
|
printf "${NC}\n"
|
||||||
echo ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Spinner for long operations
|
||||||
|
spinner() {
|
||||||
|
local pid=$1 msg="${2:-Working...}"
|
||||||
|
local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
|
||||||
|
local i=0
|
||||||
|
|
||||||
|
while kill -0 "$pid" 2>/dev/null; do
|
||||||
|
printf "\r ${CYAN}%s${NC} %s" "${frames[$i]}" "$msg"
|
||||||
|
i=$(( (i + 1) % ${#frames[@]} ))
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
wait "$pid"
|
||||||
|
local rc=$?
|
||||||
|
printf "\r\033[2K" # clear line
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
run_with_spinner() {
|
||||||
|
local msg="$1"; shift
|
||||||
|
"$@" &>/dev/null &
|
||||||
|
local pid=$!
|
||||||
|
if spinner "$pid" "$msg"; then
|
||||||
|
info "$msg"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
error "$msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Prerequisites
|
||||||
|
# ============================================================================
|
||||||
check_docker() {
|
check_docker() {
|
||||||
if ! command -v docker &>/dev/null; then
|
if ! command -v docker &>/dev/null; then
|
||||||
error "docker is required but not installed."
|
echo ""
|
||||||
echo -e " ${DIM}Install: https://docs.docker.com/engine/install/${NC}"
|
error "Docker is not installed"
|
||||||
|
echo ""
|
||||||
|
dim "Install Docker:"
|
||||||
|
dim " curl -fsSL https://get.docker.com | sh"
|
||||||
|
dim ""
|
||||||
|
dim "Or visit: https://docs.docker.com/engine/install/"
|
||||||
|
echo ""
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if ! docker compose version &>/dev/null 2>&1; then
|
if ! docker compose version &>/dev/null 2>&1; then
|
||||||
error "docker compose plugin is required."
|
echo ""
|
||||||
echo -e " ${DIM}Install: https://docs.docker.com/compose/install/${NC}"
|
error "Docker Compose plugin is not installed"
|
||||||
|
dim "Install: https://docs.docker.com/compose/install/"
|
||||||
|
echo ""
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Project discovery
|
# Project helpers
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
PROJECTS=()
|
PROJECTS=()
|
||||||
|
|
||||||
|
|
@ -60,120 +106,205 @@ discover_projects() {
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
project_exists() {
|
project_exists() { [[ -f "$SCRIPT_DIR/$1/compose.yaml" ]]; }
|
||||||
local name="$1"
|
|
||||||
[[ -f "$SCRIPT_DIR/$name/compose.yaml" ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
project_dir() {
|
# Parse @key from .env.example header
|
||||||
echo "$SCRIPT_DIR/$1"
|
project_meta() {
|
||||||
}
|
local slug="$1" key="$2"
|
||||||
|
local env_example="$SCRIPT_DIR/$slug/.env.example"
|
||||||
# compose wrapper that auto-adds --env-file if .env exists
|
[[ -f "$env_example" ]] || return
|
||||||
compose() {
|
while IFS= read -r line; do
|
||||||
local name="$1"; shift
|
if [[ "$line" =~ ^#\ @${key}\ (.+) ]]; then
|
||||||
local dir="$SCRIPT_DIR/$name"
|
echo "${BASH_REMATCH[1]}"
|
||||||
local args=(-f "$dir/compose.yaml")
|
return
|
||||||
|
|
||||||
if [[ -f "$dir/.env" ]]; then
|
|
||||||
args+=(--env-file "$dir/.env")
|
|
||||||
fi
|
fi
|
||||||
|
[[ ! "$line" =~ ^# ]] && return # stop at first non-comment
|
||||||
|
done < "$env_example"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collect all @note lines
|
||||||
|
project_notes() {
|
||||||
|
local slug="$1"
|
||||||
|
local env_example="$SCRIPT_DIR/$slug/.env.example"
|
||||||
|
[[ -f "$env_example" ]] || return
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ "$line" =~ ^#\ @note\ (.+) ]] && echo "${BASH_REMATCH[1]}"
|
||||||
|
[[ ! "$line" =~ ^# ]] && return
|
||||||
|
done < "$env_example"
|
||||||
|
}
|
||||||
|
|
||||||
|
project_status() {
|
||||||
|
local slug="$1"
|
||||||
|
if [[ ! -f "$SCRIPT_DIR/$slug/.env" ]]; then
|
||||||
|
echo "not_configured"
|
||||||
|
elif compose "$slug" ps --status running 2>/dev/null | grep -q .; then
|
||||||
|
echo "running"
|
||||||
|
else
|
||||||
|
echo "stopped"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
status_badge() {
|
||||||
|
case "$1" in
|
||||||
|
running) echo -e "${GREEN}\xe2\x97\x8f running${NC}" ;;
|
||||||
|
stopped) echo -e "${YELLOW}\xe2\x97\x8f stopped${NC}" ;;
|
||||||
|
not_configured) echo -e "${DIM}\xe2\x97\x8b not configured${NC}" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# compose wrapper
|
||||||
|
compose() {
|
||||||
|
local slug="$1"; shift
|
||||||
|
local dir="$SCRIPT_DIR/$slug"
|
||||||
|
local args=(-f "$dir/compose.yaml")
|
||||||
|
[[ -f "$dir/.env" ]] && args+=(--env-file "$dir/.env")
|
||||||
docker compose "${args[@]}" "$@"
|
docker compose "${args[@]}" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Get access URL after deploy
|
||||||
|
access_hint() {
|
||||||
|
local slug="$1"
|
||||||
|
local port_var
|
||||||
|
port_var=$(project_meta "$slug" "port")
|
||||||
|
[[ -z "$port_var" ]] && return
|
||||||
|
|
||||||
|
local env_file="$SCRIPT_DIR/$slug/.env"
|
||||||
|
[[ -f "$env_file" ]] || return
|
||||||
|
|
||||||
|
local port_val
|
||||||
|
port_val=$(grep "^${port_var}=" "$env_file" 2>/dev/null | cut -d= -f2)
|
||||||
|
[[ -z "$port_val" ]] && return
|
||||||
|
|
||||||
|
if [[ "$port_val" == *:* ]]; then
|
||||||
|
echo "http://${port_val}"
|
||||||
|
elif [[ "$port_val" == */* ]]; then
|
||||||
|
echo "$port_val"
|
||||||
|
else
|
||||||
|
echo "http://localhost:${port_val}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Interactive .env configuration
|
# Interactive .env configuration
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
generate_password() {
|
||||||
|
LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom 2>/dev/null | head -c 24 || openssl rand -hex 12
|
||||||
|
}
|
||||||
|
|
||||||
configure_env() {
|
configure_env() {
|
||||||
local name="$1"
|
local slug="$1"
|
||||||
local dir="$SCRIPT_DIR/$name"
|
local dir="$SCRIPT_DIR/$slug"
|
||||||
local env_example="$dir/.env.example"
|
local env_example="$dir/.env.example"
|
||||||
local env_file="$dir/.env"
|
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"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If .env already exists, ask what to do
|
local name
|
||||||
|
name=$(project_meta "$slug" "name")
|
||||||
|
name="${name:-$slug}"
|
||||||
|
|
||||||
|
# Handle existing .env
|
||||||
if [[ -f "$env_file" ]]; then
|
if [[ -f "$env_file" ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${YELLOW}.env already exists for ${BOLD}${name}${NC}"
|
dim ".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
|
|
||||||
k|keep|"") info "Keeping existing .env"; return 0 ;;
|
|
||||||
r|reconfigure) break ;;
|
|
||||||
v|view)
|
|
||||||
echo ""
|
echo ""
|
||||||
|
echo -e " ${BOLD}k${NC} Keep current configuration"
|
||||||
|
echo -e " ${BOLD}r${NC} Reconfigure from scratch"
|
||||||
|
echo -e " ${BOLD}v${NC} View current values"
|
||||||
|
echo ""
|
||||||
|
while true; do
|
||||||
|
read -rp " Choose [k]: " choice
|
||||||
|
case "${choice:-k}" in
|
||||||
|
k) info "Keeping existing .env"; return 0 ;;
|
||||||
|
r) break ;;
|
||||||
|
v)
|
||||||
|
echo ""
|
||||||
|
divider
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
echo -e " ${DIM}${line}${NC}"
|
echo -e " ${DIM}${line}${NC}"
|
||||||
done < "$env_file"
|
done < "$env_file"
|
||||||
|
divider
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${DIM}[k]eep [r]econfigure${NC}"
|
|
||||||
;;
|
;;
|
||||||
*) echo -e " ${DIM}Enter k, r, or v${NC}" ;;
|
*) dim "Enter k, r, or v" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${BOLD}Configure: ${CYAN}${name}${NC}"
|
step "Configure ${name}"
|
||||||
echo ""
|
local desc
|
||||||
|
desc=$(project_meta "$slug" "desc")
|
||||||
|
local url
|
||||||
|
url=$(project_meta "$slug" "url")
|
||||||
|
[[ -n "$desc" ]] && dim "$desc"
|
||||||
|
[[ -n "$url" ]] && dim "Docs: ${url}"
|
||||||
|
|
||||||
|
# Show notes
|
||||||
|
local has_notes=0
|
||||||
|
while IFS= read -r note; do
|
||||||
|
[[ -z "$note" ]] && continue
|
||||||
|
[[ $has_notes -eq 0 ]] && echo ""
|
||||||
|
echo -e " ${YELLOW}!${NC} ${DIM}${note}${NC}"
|
||||||
|
has_notes=1
|
||||||
|
done < <(project_notes "$slug")
|
||||||
|
|
||||||
# Show header comments from .env.example
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ "$line" =~ ^#.* ]] && echo -e " ${DIM}${line}${NC}" || break
|
|
||||||
done < "$env_example"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${DIM}Enter values (blank = accept default in brackets)${NC}"
|
divider
|
||||||
|
dim "Press ${BOLD}Enter${NC}${DIM} to accept [default] values${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
|
||||||
# Blank line
|
# Blank line
|
||||||
if [[ -z "$line" ]]; then
|
[[ -z "$line" ]] && continue
|
||||||
[[ -n "$pending_comment" ]] && { echo "" ; pending_comment=""; }
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Comment line — show as hint for next variable
|
# Skip @metadata
|
||||||
|
[[ "$line" =~ ^#\ @(name|desc|url|port|note) ]] && continue
|
||||||
|
|
||||||
|
# Comment → show as hint
|
||||||
if [[ "$line" =~ ^#.* ]]; then
|
if [[ "$line" =~ ^#.* ]]; then
|
||||||
echo -e " ${DIM}${line}${NC}"
|
echo -e " ${DIM}${line#\# }${NC}"
|
||||||
pending_comment="$line"
|
|
||||||
continue
|
continue
|
||||||
fi
|
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 " ${BOLD}${key}${NC} [${DIM}${default}${NC}]: " val
|
||||||
echo "${key}=${val:-$default}" >> "$tmp_env"
|
echo "${key}=${val:-$default}" >> "$tmp_env"
|
||||||
else
|
else
|
||||||
# Required field — no default
|
# Required — check if it's a secret
|
||||||
while true; do
|
if [[ "$key" =~ PASSWORD|SECRET|TOKEN|AUTHKEY ]]; then
|
||||||
read -rp " ${key} (required): " val
|
echo -e " ${DIM}Leave blank to auto-generate${NC}"
|
||||||
if [[ -n "$val" ]]; then
|
read -rp " ${BOLD}${key}${NC}: " val
|
||||||
break
|
if [[ -z "$val" ]]; then
|
||||||
|
val=$(generate_password)
|
||||||
|
echo -e " ${DIM}Generated: ${val}${NC}"
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
while true; do
|
||||||
|
read -rp " ${BOLD}${key}${NC} ${RED}(required)${NC}: " val
|
||||||
|
[[ -n "$val" ]] && break
|
||||||
echo -e " ${RED}This field cannot be empty${NC}"
|
echo -e " ${RED}This field cannot be empty${NC}"
|
||||||
done
|
done
|
||||||
|
fi
|
||||||
echo "${key}=${val}" >> "$tmp_env"
|
echo "${key}=${val}" >> "$tmp_env"
|
||||||
fi
|
fi
|
||||||
done < "$env_example"
|
done < "$env_example"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
divider
|
||||||
|
|
||||||
mv "$tmp_env" "$env_file"
|
mv "$tmp_env" "$env_file"
|
||||||
chmod 600 "$env_file"
|
chmod 600 "$env_file"
|
||||||
echo ""
|
info "Configuration saved"
|
||||||
info ".env saved (chmod 600)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
@ -182,6 +313,7 @@ configure_env() {
|
||||||
|
|
||||||
cmd_list() {
|
cmd_list() {
|
||||||
banner
|
banner
|
||||||
|
check_docker
|
||||||
discover_projects
|
discover_projects
|
||||||
|
|
||||||
if [[ ${#PROJECTS[@]} -eq 0 ]]; then
|
if [[ ${#PROJECTS[@]} -eq 0 ]]; then
|
||||||
|
|
@ -189,20 +321,18 @@ cmd_list() {
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e " ${BOLD}Available projects:${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
local i=1
|
local i=1
|
||||||
for p in "${PROJECTS[@]}"; do
|
for slug in "${PROJECTS[@]}"; do
|
||||||
local status="${DIM}not configured${NC}"
|
local st
|
||||||
if [[ -f "$SCRIPT_DIR/$p/.env" ]]; then
|
st=$(project_status "$slug")
|
||||||
if compose "$p" ps --status running 2>/dev/null | grep -q .; then
|
local badge
|
||||||
status="${GREEN}running${NC}"
|
badge=$(status_badge "$st")
|
||||||
else
|
local desc
|
||||||
status="${YELLOW}stopped${NC}"
|
desc=$(project_meta "$slug" "desc")
|
||||||
fi
|
|
||||||
fi
|
printf " ${BOLD}%2d${NC} %-20s %b\n" "$i" "$slug" "$badge"
|
||||||
printf " ${BOLD}%2d${NC} %-24s %b\n" "$i" "$p" "$status"
|
[[ -n "$desc" ]] && echo -e " ${DIM}${desc}${NC}"
|
||||||
|
|
||||||
((i++))
|
((i++))
|
||||||
done
|
done
|
||||||
echo ""
|
echo ""
|
||||||
|
|
@ -218,27 +348,42 @@ cmd_deploy() {
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If arguments provided, deploy those directly
|
# Direct deploy
|
||||||
if [[ $# -gt 0 ]]; then
|
if [[ $# -gt 0 ]]; then
|
||||||
|
local ok=0 fail=0
|
||||||
for name in "$@"; do
|
for name in "$@"; do
|
||||||
deploy_project "$name"
|
echo ""
|
||||||
|
if deploy_project "$name"; then ((ok++)); else ((fail++)); fi
|
||||||
done
|
done
|
||||||
|
deploy_summary $ok $fail
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Interactive selection
|
# Interactive
|
||||||
echo -e " ${BOLD}Select projects to deploy:${NC}"
|
step "Select projects to deploy"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
local i=1
|
local i=1
|
||||||
for p in "${PROJECTS[@]}"; do
|
for slug in "${PROJECTS[@]}"; do
|
||||||
printf " ${BOLD}%2d${NC} %s\n" "$i" "$p"
|
local st
|
||||||
|
st=$(project_status "$slug")
|
||||||
|
local badge
|
||||||
|
badge=$(status_badge "$st")
|
||||||
|
local desc
|
||||||
|
desc=$(project_meta "$slug" "desc")
|
||||||
|
|
||||||
|
printf " ${BOLD}%2d${NC} %-20s %b\n" "$i" "$slug" "$badge"
|
||||||
|
[[ -n "$desc" ]] && echo -e " ${DIM}${desc}${NC}"
|
||||||
((i++))
|
((i++))
|
||||||
done
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
dim "Enter numbers separated by spaces, e.g. ${BOLD}1 3 5${NC}"
|
||||||
|
dim "Type ${BOLD}all${NC} to deploy everything, or ${BOLD}q${NC} to quit"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${DIM}Enter numbers (space-separated), or 'q' to quit${NC}"
|
|
||||||
read -rp " > " selection
|
read -rp " > " selection
|
||||||
|
|
||||||
[[ "$selection" == "q" || -z "$selection" ]] && return 0
|
[[ -z "$selection" || "$selection" == "q" ]] && return 0
|
||||||
|
|
||||||
local selected=()
|
local selected=()
|
||||||
if [[ "$selection" == "all" ]]; then
|
if [[ "$selection" == "all" ]]; then
|
||||||
|
|
@ -248,83 +393,122 @@ cmd_deploy() {
|
||||||
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: $num (skipped)"
|
warn "Skipping invalid: $num"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${#selected[@]} -eq 0 ]]; then
|
[[ ${#selected[@]} -eq 0 ]] && return 0
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
# Confirmation
|
||||||
echo ""
|
echo ""
|
||||||
info "Will deploy: ${selected[*]}"
|
divider
|
||||||
|
step "Will deploy:"
|
||||||
|
for s in "${selected[@]}"; do
|
||||||
|
local desc
|
||||||
|
desc=$(project_meta "$s" "desc")
|
||||||
|
echo -e " ${CYAN}\xe2\x96\xb6${NC} ${s} ${DIM}${desc:-}${NC}"
|
||||||
|
done
|
||||||
echo ""
|
echo ""
|
||||||
|
read -rp " Proceed? [Y/n] " confirm
|
||||||
|
[[ "${confirm:-y}" =~ ^[Nn] ]] && { echo ""; dim "Cancelled."; return 0; }
|
||||||
|
|
||||||
|
divider
|
||||||
|
|
||||||
local ok=0 fail=0
|
local ok=0 fail=0
|
||||||
for name in "${selected[@]}"; do
|
for name in "${selected[@]}"; do
|
||||||
if deploy_project "$name"; then
|
|
||||||
((ok++))
|
|
||||||
else
|
|
||||||
((fail++))
|
|
||||||
fi
|
|
||||||
echo ""
|
echo ""
|
||||||
|
if deploy_project "$name"; then ((ok++)); else ((fail++)); fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo -e " ${BOLD}Done.${NC} ${GREEN}${ok} deployed${NC}" \
|
deploy_summary $ok $fail
|
||||||
"$( ((fail > 0)) && echo -e ", ${RED}${fail} failed${NC}" )"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deploy_project() {
|
deploy_project() {
|
||||||
local name="$1"
|
local slug="$1"
|
||||||
|
|
||||||
if ! project_exists "$name"; then
|
if ! project_exists "$slug"; then
|
||||||
error "Project not found: $name"
|
error "Project not found: ${slug}"
|
||||||
|
dim "Run ${BOLD}automa list${NC}${DIM} to see available projects${NC}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e " ${CYAN}── ${BOLD}${name}${NC} ${CYAN}──${NC}"
|
local name
|
||||||
|
name=$(project_meta "$slug" "name")
|
||||||
|
name="${name:-$slug}"
|
||||||
|
local desc
|
||||||
|
desc=$(project_meta "$slug" "desc")
|
||||||
|
|
||||||
configure_env "$name"
|
step "${name}"
|
||||||
|
[[ -n "$desc" ]] && dim "${desc}"
|
||||||
|
|
||||||
if [[ ! -f "$SCRIPT_DIR/$name/.env" ]]; then
|
configure_env "$slug"
|
||||||
error "No .env file — run: automa config $name"
|
|
||||||
|
if [[ ! -f "$SCRIPT_DIR/$slug/.env" ]]; then
|
||||||
|
error "No .env — run: ${BOLD}automa config $slug${NC}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Starting containers..."
|
echo ""
|
||||||
if compose "$name" up -d 2>&1; then
|
if run_with_spinner "Pulling images..." compose "$slug" pull; then
|
||||||
info "${name} is up"
|
if run_with_spinner "Starting containers..." compose "$slug" up -d; then
|
||||||
|
local url
|
||||||
|
url=$(access_hint "$slug")
|
||||||
|
[[ -n "$url" ]] && dim "Access: ${BOLD}${url}${NC}"
|
||||||
return 0
|
return 0
|
||||||
else
|
|
||||||
error "${name} failed to start"
|
|
||||||
return 1
|
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy_summary() {
|
||||||
|
local ok=$1 fail=$2
|
||||||
|
echo ""
|
||||||
|
divider
|
||||||
|
echo ""
|
||||||
|
if [[ $fail -eq 0 ]]; then
|
||||||
|
info "${BOLD}All done!${NC} ${ok} project(s) deployed"
|
||||||
|
else
|
||||||
|
warn "${BOLD}Done.${NC} ${GREEN}${ok} deployed${NC}, ${RED}${fail} failed${NC}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
dim "Useful commands:"
|
||||||
|
dim " ${BOLD}automa status${NC}${DIM} — check running state${NC}"
|
||||||
|
dim " ${BOLD}automa logs${NC}${DIM} <project> — view logs${NC}"
|
||||||
|
dim " ${BOLD}automa update${NC}${DIM} <project> — pull & restart${NC}"
|
||||||
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_stop() {
|
cmd_stop() {
|
||||||
local name="${1:?Usage: automa stop <project>}"
|
local slug="${1:-}"
|
||||||
|
if [[ -z "$slug" ]]; then
|
||||||
if ! project_exists "$name"; then
|
error "Usage: ${BOLD}automa stop <project>${NC}"
|
||||||
error "Project not found: $name"
|
dim "Run ${BOLD}automa list${NC}${DIM} to see available projects${NC}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Stopping ${name}..."
|
if ! project_exists "$slug"; then
|
||||||
compose "$name" down
|
error "Project not found: ${slug}"
|
||||||
info "${name} stopped"
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_with_spinner "Stopping ${slug}..." compose "$slug" down
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_logs() {
|
cmd_logs() {
|
||||||
local name="${1:?Usage: automa logs <project>}"
|
local slug="${1:-}"
|
||||||
|
if [[ -z "$slug" ]]; then
|
||||||
|
error "Usage: ${BOLD}automa logs <project>${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
shift
|
shift
|
||||||
|
|
||||||
if ! project_exists "$name"; then
|
if ! project_exists "$slug"; then
|
||||||
error "Project not found: $name"
|
error "Project not found: ${slug}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
compose "$name" logs -f "$@"
|
compose "$slug" logs -f "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_status() {
|
cmd_status() {
|
||||||
|
|
@ -332,86 +516,120 @@ cmd_status() {
|
||||||
check_docker
|
check_docker
|
||||||
discover_projects
|
discover_projects
|
||||||
|
|
||||||
for p in "${PROJECTS[@]}"; do
|
if [[ ${#PROJECTS[@]} -eq 0 ]]; then
|
||||||
echo -e " ${BOLD}${p}${NC}"
|
warn "No projects found"
|
||||||
local output
|
return 1
|
||||||
output=$(compose "$p" ps --format "table {{.Name}}\t{{.Status}}" 2>/dev/null | tail -n +2) || true
|
fi
|
||||||
if [[ -n "$output" ]]; then
|
|
||||||
while IFS= read -r line; do
|
for slug in "${PROJECTS[@]}"; do
|
||||||
echo -e " ${line}"
|
local st name
|
||||||
done <<< "$output"
|
st=$(project_status "$slug")
|
||||||
else
|
name=$(project_meta "$slug" "name")
|
||||||
echo -e " ${DIM}not running${NC}"
|
name="${name:-$slug}"
|
||||||
|
|
||||||
|
local badge
|
||||||
|
badge=$(status_badge "$st")
|
||||||
|
echo -e " ${BOLD}${name}${NC} ${badge}"
|
||||||
|
|
||||||
|
if [[ "$st" == "running" ]]; then
|
||||||
|
compose "$slug" ps --format "table {{.Name}}\t{{.Status}}" 2>/dev/null \
|
||||||
|
| tail -n +2 \
|
||||||
|
| while IFS= read -r line; do
|
||||||
|
echo -e " ${DIM}${line}${NC}"
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_restart() {
|
cmd_restart() {
|
||||||
local name="${1:?Usage: automa restart <project>}"
|
local slug="${1:-}"
|
||||||
|
if [[ -z "$slug" ]]; then
|
||||||
if ! project_exists "$name"; then
|
error "Usage: ${BOLD}automa restart <project>${NC}"
|
||||||
error "Project not found: $name"
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Restarting ${name}..."
|
if ! project_exists "$slug"; then
|
||||||
compose "$name" restart
|
error "Project not found: ${slug}"
|
||||||
info "${name} restarted"
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_with_spinner "Restarting ${slug}..." compose "$slug" restart
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_config() {
|
cmd_config() {
|
||||||
local name="${1:?Usage: automa config <project>}"
|
local slug="${1:-}"
|
||||||
|
if [[ -z "$slug" ]]; then
|
||||||
if ! project_exists "$name"; then
|
error "Usage: ${BOLD}automa config <project>${NC}"
|
||||||
error "Project not found: $name"
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
configure_env "$name"
|
if ! project_exists "$slug"; then
|
||||||
|
error "Project not found: ${slug}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
configure_env "$slug"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_update() {
|
cmd_update() {
|
||||||
local name="${1:?Usage: automa update <project>}"
|
local slug="${1:-}"
|
||||||
|
if [[ -z "$slug" ]]; then
|
||||||
if ! project_exists "$name"; then
|
error "Usage: ${BOLD}automa update <project>${NC}"
|
||||||
error "Project not found: $name"
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Pulling latest images for ${name}..."
|
if ! project_exists "$slug"; then
|
||||||
compose "$name" pull
|
error "Project not found: ${slug}"
|
||||||
info "Recreating containers..."
|
return 1
|
||||||
compose "$name" up -d
|
fi
|
||||||
info "${name} updated"
|
|
||||||
|
local name
|
||||||
|
name=$(project_meta "$slug" "name")
|
||||||
|
name="${name:-$slug}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
step "Updating ${name}"
|
||||||
|
run_with_spinner "Pulling latest images..." compose "$slug" pull
|
||||||
|
run_with_spinner "Recreating containers..." compose "$slug" up -d
|
||||||
|
echo ""
|
||||||
|
info "Update complete"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
banner() {
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}${CYAN}automa${NC} ${DIM}v${AUTOMA_VERSION}${NC}"
|
||||||
|
dim "Self-hosted Docker Compose deployer"
|
||||||
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_help() {
|
cmd_help() {
|
||||||
banner
|
banner
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
${BOLD}Usage:${NC} automa <command> [args]
|
${BOLD}Usage${NC}
|
||||||
|
automa <command> [options]
|
||||||
|
|
||||||
${BOLD}Commands:${NC}
|
${BOLD}Commands${NC}
|
||||||
deploy [project...] Interactive deploy (or specify project names)
|
${BOLD}deploy${NC} [project...] Deploy projects interactively or by name
|
||||||
list List all available projects and status
|
${BOLD}list${NC} List all projects and their status
|
||||||
status Show running containers per project
|
${BOLD}status${NC} Show running containers
|
||||||
stop <project> Stop a project (docker compose down)
|
${BOLD}config${NC} <project> Configure environment variables
|
||||||
restart <project> Restart a project
|
${BOLD}stop${NC} <project> Stop a running project
|
||||||
logs <project> Follow logs of a project
|
${BOLD}restart${NC} <project> Restart a project
|
||||||
config <project> (Re)configure .env for a project
|
${BOLD}update${NC} <project> Pull latest images and recreate
|
||||||
update <project> Pull latest images and recreate
|
${BOLD}logs${NC} <project> Follow container logs
|
||||||
help Show this help
|
${BOLD}help${NC} Show this help message
|
||||||
|
|
||||||
${BOLD}Examples:${NC}
|
${BOLD}Examples${NC}
|
||||||
automa deploy # interactive project selection
|
${DIM}\$${NC} automa deploy ${DIM}# interactive selection${NC}
|
||||||
automa deploy forgejo filesuite # deploy specific projects
|
${DIM}\$${NC} automa deploy forgejo nextcloud ${DIM}# deploy by name${NC}
|
||||||
automa status # check all project status
|
${DIM}\$${NC} automa status ${DIM}# overview dashboard${NC}
|
||||||
automa logs forgejo # follow forgejo logs
|
${DIM}\$${NC} automa logs minecraft ${DIM}# follow logs${NC}
|
||||||
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
|
${DIM}\$${NC} curl -fsSL https://raw.githubusercontent.com/m1ngsama/automa/main/install.sh | bash
|
||||||
cd ~/automa && ./automa deploy
|
${DIM}\$${NC} cd ~/automa && ./automa deploy
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
@ -420,21 +638,26 @@ EOF
|
||||||
# Main
|
# Main
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
main() {
|
main() {
|
||||||
local cmd="${1:-help}"
|
local cmd="${1:-}"
|
||||||
shift 2>/dev/null || true
|
[[ -z "$cmd" ]] && { cmd_help; return; }
|
||||||
|
shift
|
||||||
|
|
||||||
case "$cmd" in
|
case "$cmd" in
|
||||||
deploy) cmd_deploy "$@" ;;
|
deploy) cmd_deploy "$@" ;;
|
||||||
list|ls) cmd_list ;;
|
list|ls) cmd_list ;;
|
||||||
status) cmd_status ;;
|
status|st|ps) cmd_status ;;
|
||||||
stop) cmd_stop "$@" ;;
|
stop|down) cmd_stop "$@" ;;
|
||||||
restart) cmd_restart "$@" ;;
|
restart) cmd_restart "$@" ;;
|
||||||
logs) cmd_logs "$@" ;;
|
logs|log) cmd_logs "$@" ;;
|
||||||
config) cmd_config "$@" ;;
|
config|configure) cmd_config "$@" ;;
|
||||||
update) cmd_update "$@" ;;
|
update|upgrade) cmd_update "$@" ;;
|
||||||
help|-h|--help) cmd_help ;;
|
help|-h|--help) cmd_help ;;
|
||||||
version|-v|--version) echo "automa v${AUTOMA_VERSION}" ;;
|
version|-v|--version) echo "automa v${AUTOMA_VERSION}" ;;
|
||||||
*) error "Unknown command: $cmd"; cmd_help; exit 1 ;;
|
*)
|
||||||
|
error "Unknown command: ${cmd}"
|
||||||
|
dim "Run ${BOLD}automa help${NC}${DIM} for usage${NC}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
# Filesuite - Cloudreve cloud storage + qBittorrent
|
# @name Filesuite
|
||||||
# Both services share the same downloads directory
|
# @desc Cloudreve cloud storage + qBittorrent downloader
|
||||||
|
# @url https://cloudreve.org
|
||||||
|
# @port CLOUDREVE_PORT
|
||||||
|
|
||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
PUID=1000
|
PUID=1000
|
||||||
PGID=1000
|
PGID=1000
|
||||||
|
|
||||||
# Shared downloads path (absolute path recommended)
|
# Shared download directory — both services read/write here
|
||||||
|
# Use an absolute path for external drives (e.g. /mnt/data/downloads)
|
||||||
DOWNLOADS_DIR=./downloads
|
DOWNLOADS_DIR=./downloads
|
||||||
|
|
||||||
# Cloudreve
|
# Cloudreve — web file manager
|
||||||
CLOUDREVE_PORT=5212
|
CLOUDREVE_PORT=5212
|
||||||
CR_ENABLE_ARIA2=0
|
|
||||||
|
|
||||||
# qBittorrent
|
# qBittorrent — torrent client
|
||||||
QB_WEBUI_PORT=8090
|
QB_WEBUI_PORT=8090
|
||||||
|
# BT listen port — must be forwarded in your router/firewall
|
||||||
QB_BT_PORT=44773
|
QB_BT_PORT=44773
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
# Forgejo - self-hosted Git service
|
# @name Forgejo
|
||||||
# Docs: https://forgejo.org/docs/latest/admin/config-cheat-sheet/
|
# @desc Self-hosted Git service (Gitea fork)
|
||||||
|
# @url https://forgejo.org
|
||||||
|
# @port FORGEJO_HTTP_PORT
|
||||||
|
|
||||||
|
# Web and SSH access ports
|
||||||
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
|
# Public URL — set this to your domain when behind a reverse proxy
|
||||||
FORGEJO_ROOT_URL=http://localhost:3000
|
FORGEJO_ROOT_URL=http://localhost:3000
|
||||||
|
|
|
||||||
58
install.sh
58
install.sh
|
|
@ -6,49 +6,61 @@ set -euo pipefail
|
||||||
REPO="https://github.com/m1ngsama/automa.git"
|
REPO="https://github.com/m1ngsama/automa.git"
|
||||||
INSTALL_DIR="${AUTOMA_DIR:-$HOME/automa}"
|
INSTALL_DIR="${AUTOMA_DIR:-$HOME/automa}"
|
||||||
|
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m' GREEN='\033[0;32m' CYAN='\033[0;36m'
|
||||||
GREEN='\033[0;32m'
|
BOLD='\033[1m' DIM='\033[2m' NC='\033[0m'
|
||||||
CYAN='\033[0;36m'
|
|
||||||
BOLD='\033[1m'
|
|
||||||
DIM='\033[2m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
info() { echo -e "${GREEN}[+]${NC} $*"; }
|
info() { echo -e " ${GREEN}\xe2\x9c\x94${NC} $*"; }
|
||||||
error() { echo -e "${RED}[-]${NC} $*" >&2; }
|
error() { echo -e " ${RED}\xe2\x9c\x98${NC} $*" >&2; }
|
||||||
|
step() { echo -e " ${CYAN}\xe2\x96\xb6${NC} ${BOLD}$*${NC}"; }
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}${BOLD} automa installer${NC}"
|
echo -e " ${BOLD}${CYAN}automa${NC}${BOLD} installer${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Check prerequisites
|
# Check prerequisites
|
||||||
|
missing=0
|
||||||
for cmd in git docker; do
|
for cmd in git docker; do
|
||||||
if ! command -v "$cmd" &>/dev/null; then
|
if command -v "$cmd" &>/dev/null; then
|
||||||
error "$cmd is required but not installed."
|
info "$cmd found"
|
||||||
exit 1
|
else
|
||||||
|
error "$cmd is not installed"
|
||||||
|
missing=1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if ! docker compose version &>/dev/null 2>&1; then
|
if docker compose version &>/dev/null 2>&1; then
|
||||||
error "docker compose plugin is required."
|
info "docker compose plugin found"
|
||||||
error "Install: https://docs.docker.com/compose/install/"
|
else
|
||||||
|
error "docker compose plugin is not installed"
|
||||||
|
echo -e " ${DIM}Install: https://docs.docker.com/compose/install/${NC}"
|
||||||
|
missing=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $missing -eq 1 ]]; then
|
||||||
|
echo ""
|
||||||
|
error "Please install missing dependencies and try again"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
# Clone or update
|
# Clone or update
|
||||||
if [[ -d "$INSTALL_DIR/.git" ]]; then
|
if [[ -d "$INSTALL_DIR/.git" ]]; then
|
||||||
info "Updating existing installation..."
|
step "Updating existing installation..."
|
||||||
git -C "$INSTALL_DIR" pull --ff-only
|
git -C "$INSTALL_DIR" pull --ff-only --quiet
|
||||||
|
info "Updated"
|
||||||
else
|
else
|
||||||
info "Cloning automa to ${INSTALL_DIR}..."
|
step "Installing to ${INSTALL_DIR}..."
|
||||||
git clone "$REPO" "$INSTALL_DIR"
|
git clone --quiet "$REPO" "$INSTALL_DIR"
|
||||||
|
info "Cloned"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chmod +x "$INSTALL_DIR/automa"
|
chmod +x "$INSTALL_DIR/automa"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
info "Installed to ${INSTALL_DIR}"
|
echo -e " ${GREEN}${BOLD}Ready!${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${BOLD}Next steps:${NC}"
|
echo -e " ${DIM}Get started:${NC}"
|
||||||
echo -e " cd ${INSTALL_DIR}"
|
echo -e " ${BOLD}cd ${INSTALL_DIR}${NC}"
|
||||||
echo -e " ./automa deploy"
|
echo -e " ${BOLD}./automa deploy${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,23 @@
|
||||||
# Minecraft server (itzg/minecraft-server)
|
# @name Minecraft
|
||||||
# Docs: https://docker-minecraft-server.readthedocs.io/
|
# @desc Fabric Minecraft server (itzg/minecraft-server)
|
||||||
|
# @url https://docker-minecraft-server.readthedocs.io
|
||||||
|
# @port MC_PORT
|
||||||
|
|
||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
# Server type and version
|
||||||
MC_TYPE=FABRIC
|
MC_TYPE=FABRIC
|
||||||
MC_VERSION=1.21.1
|
MC_VERSION=1.21.1
|
||||||
|
|
||||||
|
# Memory allocation — adjust based on player count and mods
|
||||||
MC_MEMORY=4G
|
MC_MEMORY=4G
|
||||||
|
|
||||||
|
# Set to true for Mojang account verification
|
||||||
MC_ONLINE_MODE=false
|
MC_ONLINE_MODE=false
|
||||||
|
|
||||||
|
# Ports
|
||||||
MC_PORT=25565
|
MC_PORT=25565
|
||||||
RCON_PORT=25575
|
RCON_PORT=25575
|
||||||
|
|
||||||
|
# RCON password for remote console access
|
||||||
RCON_PASSWORD=
|
RCON_PASSWORD=
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,25 @@
|
||||||
# Nextcloud with MariaDB + Redis
|
# @name Nextcloud
|
||||||
# Docs: https://hub.docker.com/_/nextcloud
|
# @desc Nextcloud private cloud with MariaDB + Redis
|
||||||
|
# @url https://nextcloud.com
|
||||||
|
# @port NC_PORT
|
||||||
|
|
||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
# Web interface
|
||||||
NC_PORT=8080
|
NC_PORT=8080
|
||||||
|
|
||||||
|
# Admin account — created on first startup
|
||||||
NC_ADMIN_USER=admin
|
NC_ADMIN_USER=admin
|
||||||
NC_ADMIN_PASSWORD=
|
NC_ADMIN_PASSWORD=
|
||||||
|
|
||||||
|
# Trusted domains — space-separated list of domains/IPs that can access Nextcloud
|
||||||
NC_TRUSTED_DOMAINS=localhost
|
NC_TRUSTED_DOMAINS=localhost
|
||||||
|
|
||||||
# MariaDB
|
# MariaDB database
|
||||||
MYSQL_DATABASE=nextcloud
|
MYSQL_DATABASE=nextcloud
|
||||||
MYSQL_USER=nextcloud
|
MYSQL_USER=nextcloud
|
||||||
MYSQL_PASSWORD=
|
MYSQL_PASSWORD=
|
||||||
MYSQL_ROOT_PASSWORD=
|
MYSQL_ROOT_PASSWORD=
|
||||||
|
|
||||||
# Redis
|
# Redis cache
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,28 @@
|
||||||
# Tailscale + DERP relay server
|
# @name Tailscale + DERP
|
||||||
#
|
# @desc Tailscale mesh VPN client with optional DERP relay
|
||||||
# Deploy tailscale only: docker compose --profile tailscale up -d
|
# @url https://tailscale.com/kb/1282/docker
|
||||||
# Deploy with DERP: docker compose --profile derp up -d
|
# @note Deploy tailscale only: docker compose --profile tailscale up -d
|
||||||
|
# @note Deploy with DERP relay: docker compose --profile derp up -d
|
||||||
|
|
||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
# Hostname shown in the Tailscale admin console
|
||||||
TS_HOSTNAME=
|
TS_HOSTNAME=
|
||||||
|
|
||||||
|
# Auth key — generate at https://login.tailscale.com/admin/settings/keys
|
||||||
|
# For headscale: generate via headscale CLI
|
||||||
TS_AUTHKEY=
|
TS_AUTHKEY=
|
||||||
|
|
||||||
# For headscale: --advertise-tags=tag:container --login-server=https://your.headscale.host
|
# Extra arguments passed to tailscaled
|
||||||
|
# For headscale users, add: --login-server=https://your.headscale.host
|
||||||
TS_EXTRA_ARGS=--advertise-tags=tag:container
|
TS_EXTRA_ARGS=--advertise-tags=tag:container
|
||||||
|
|
||||||
|
# Networking mode: false = kernel (better performance), true = userspace
|
||||||
TS_USERSPACE=false
|
TS_USERSPACE=false
|
||||||
TS_FIREWALL_MODE=nftables
|
TS_FIREWALL_MODE=nftables
|
||||||
|
|
||||||
# DERP relay (only needed with --profile derp)
|
# DERP relay settings (only used with --profile derp)
|
||||||
|
# Public IP of this server — clients connect to this address
|
||||||
DERP_HOST=
|
DERP_HOST=
|
||||||
DERP_PORT=443
|
DERP_PORT=443
|
||||||
STUN_PORT=3478
|
STUN_PORT=3478
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
# TeamSpeak voice server
|
# @name TeamSpeak
|
||||||
|
# @desc TeamSpeak voice communication server
|
||||||
|
# @url https://teamspeak.com
|
||||||
|
# @port 9987/udp
|
||||||
|
|
||||||
|
# Server admin password — set on first run, used for ServerQuery
|
||||||
TS3_ADMIN_PASSWORD=
|
TS3_ADMIN_PASSWORD=
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
# Uptime Kuma - uptime monitoring
|
# @name Uptime Kuma
|
||||||
# Default binds to localhost only; change to 0.0.0.0:3001 for external access
|
# @desc Uptime monitoring dashboard
|
||||||
|
# @url https://github.com/louislam/uptime-kuma
|
||||||
|
# @port KUMA_PORT
|
||||||
|
|
||||||
|
# Bind address — default localhost only; use 0.0.0.0:3001 for external access
|
||||||
KUMA_PORT=127.0.0.1:3001
|
KUMA_PORT=127.0.0.1:3001
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue