diff --git a/Makefile b/Makefile index b2010bd..92c2db6 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ .PHONY: help all status up down logs restart clean minecraft teamspeak nextcloud .PHONY: health health-minecraft health-teamspeak health-nextcloud .PHONY: backup backup-minecraft backup-teamspeak backup-nextcloud backup-list backup-cleanup +.PHONY: deploy-email deploy-nginx deploy-ss-server deploy-ss-client deploy-frp-server deploy-frp-client # Default target help: @@ -21,6 +22,14 @@ help: @echo " backup-list List available backups" @echo " backup-cleanup Remove old backups" @echo "" + @echo "Infrastructure Deploy (set INFRA_DIR first):" + @echo " deploy-email Deploy Postfix+Dovecot+OpenDKIM+SpamAssassin" + @echo " deploy-nginx Deploy Nginx vhosts" + @echo " deploy-ss-server Deploy Shadowsocks server" + @echo " deploy-ss-client Deploy Shadowsocks client + privoxy" + @echo " deploy-frp-server Deploy FRP server (frps)" + @echo " deploy-frp-client Deploy FRP client (frpc)" + @echo "" @echo "Service-specific Commands:" @echo " Minecraft:" @echo " minecraft-up Start Minecraft server" @@ -55,6 +64,36 @@ help: @echo " check Check prerequisites" @echo " clean Remove stopped containers and unused volumes" +# ============================================================================ +# Infrastructure Service Targets +# Requires INFRA_DIR pointing to the corresponding infra module directory. +# ============================================================================ + +# deploy-email: INFRA_DIR=/path/to/infra/services/email make deploy-email +deploy-email: + @[ -n "$(INFRA_DIR)" ] || { echo "Set INFRA_DIR=/path/to/infra/services/email"; exit 1; } + INFRA_DIR=$(INFRA_DIR) ./services/email/deploy.sh + +deploy-nginx: + @[ -n "$(INFRA_DIR)" ] || { echo "Set INFRA_DIR=/path/to/infra/services/nginx"; exit 1; } + INFRA_DIR=$(INFRA_DIR) ./services/nginx/deploy.sh + +deploy-ss-server: + @[ -n "$(INFRA_DIR)" ] || { echo "Set INFRA_DIR=/path/to/infra/services/shadowsocks/server"; exit 1; } + INFRA_DIR=$(INFRA_DIR) ./services/shadowsocks/server/deploy.sh + +deploy-ss-client: + @[ -n "$(INFRA_DIR)" ] || { echo "Set INFRA_DIR=/path/to/infra/services/shadowsocks/client"; exit 1; } + INFRA_DIR=$(INFRA_DIR) ./services/shadowsocks/client/deploy.sh + +deploy-frp-server: + @[ -n "$(INFRA_DIR)" ] || { echo "Set INFRA_DIR=/path/to/infra/services/frp/server"; exit 1; } + INFRA_DIR=$(INFRA_DIR) ./services/frp/server/deploy.sh + +deploy-frp-client: + @[ -n "$(INFRA_DIR)" ] || { echo "Set INFRA_DIR=/path/to/infra/services/frp/client"; exit 1; } + INFRA_DIR=$(INFRA_DIR) ./services/frp/client/deploy.sh + # Check prerequisites check: @echo "Checking prerequisites..." diff --git a/README.md b/README.md index 2391e63..827b8d9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,27 @@ # Automa -A collection of self-hosted service automation tools following the Unix philosophy: do one thing well, be composable, and stay simple. +Deployment scripts for self-hosted infrastructure. Pairs with [infra](https://github.com/m1ngsama/infra) (private) for configuration. + +``` +infra/services//.env → automa/services//deploy.sh +``` + +## Relationship with infra + +**infra** (private) holds config templates and `.env.example` files — the "what" and "how to configure". +**automa** (public) holds deployment scripts — the "how to deploy". Zero hardcoded values, zero domain names. + +Workflow: +1. Clone infra (private), fill in `.env` files for each service you want +2. Clone automa (public), run the matching deploy script +3. Each script reads `INFRA_DIR` to locate the corresponding `.env` + +```bash +# Example +cd infra/services/email && cp .env.example .env && $EDITOR .env +cd automa/services/email +INFRA_DIR=../../infra/services/email ./deploy.sh +``` ## Philosophy @@ -10,7 +31,49 @@ This project embraces Unix principles: - **Composability**: Tools work together through standard interfaces - **Transparency**: Plain text configuration, readable scripts -## Services +## Infrastructure Services + +System services deployed from infra module configs. + +### Email +Postfix + Dovecot + OpenDKIM + SpamAssassin. + +```bash +INFRA_DIR=/path/to/infra/services/email ./services/email/deploy.sh +``` + +### Nginx +Web server and reverse proxy vhosts. + +```bash +INFRA_DIR=/path/to/infra/services/nginx ./services/nginx/deploy.sh +``` + +### Shadowsocks +GFW-resistant proxy. + +```bash +# Server (VPS) +INFRA_DIR=/path/to/infra/services/shadowsocks/server ./services/shadowsocks/server/deploy.sh + +# Client (home machine) +INFRA_DIR=/path/to/infra/services/shadowsocks/client ./services/shadowsocks/client/deploy.sh +``` + +### FRP +Reverse tunnel — expose home services through VPS. + +```bash +# Server (VPS) +INFRA_DIR=/path/to/infra/services/frp/server ./services/frp/server/deploy.sh + +# Client (home machine) +INFRA_DIR=/path/to/infra/services/frp/client ./services/frp/client/deploy.sh +``` + +## Home Services + +Docker-based services with their own config. ### Minecraft Server Automated Minecraft Fabric server deployment with mod management. @@ -72,12 +135,21 @@ Batch clone all repositories from a GitHub organization. ``` automa/ -├── bin/ # Utility scripts -│ └── org-clone.sh # GitHub org repo cloner -├── minecraft/ # Minecraft server setup -├── teamspeak/ # TeamSpeak server setup -├── nextcloud/ # Nextcloud setup -└── README.md # This file +├── bin/ # Utility scripts +│ └── lib/common.sh # Shared logging + env helpers +├── services/ # Infrastructure deploy scripts (reads infra .env) +│ ├── email/deploy.sh +│ ├── nginx/deploy.sh +│ ├── shadowsocks/ +│ │ ├── server/deploy.sh +│ │ └── client/deploy.sh +│ └── frp/ +│ ├── server/deploy.sh +│ └── client/deploy.sh +├── minecraft/ # Minecraft server (Docker) +├── teamspeak/ # TeamSpeak server (Docker) +├── nextcloud/ # Nextcloud (Docker) +└── README.md ``` ## Common Operations diff --git a/services/email/deploy.sh b/services/email/deploy.sh new file mode 100755 index 0000000..dcd0ca0 --- /dev/null +++ b/services/email/deploy.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Deploys Postfix + Dovecot + OpenDKIM + SpamAssassin email stack. +# Usage: INFRA_DIR=/path/to/infra/services/email ./deploy.sh + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../../bin/lib/common.sh" + +ENV_FILE="${INFRA_DIR:-.}/.env" +[ -f "$ENV_FILE" ] || { log_error "No .env found at $ENV_FILE"; exit 1; } +source "$ENV_FILE" + +require_env DOMAIN MAIL_HOST MAIL_USER + +log_info "Installing packages..." +apt-get install -y postfix dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd \ + dovecot-sieve opendkim opendkim-tools spamassassin spamc + +log_info "Deploying Postfix config..." +envsubst < "${INFRA_DIR}/postfix/main.cf" > /etc/postfix/main.cf +cp "${INFRA_DIR}/postfix/aliases" /etc/aliases +newaliases + +log_info "Deploying Dovecot config..." +cp "${INFRA_DIR}/dovecot/dovecot.conf" /etc/dovecot/dovecot.conf +cp "${INFRA_DIR}/dovecot/99-stats-fix.conf" /etc/dovecot/conf.d/99-stats-fix.conf + +log_info "Adding postfix to dovecot group..." +usermod -aG dovecot postfix + +log_info "Enabling services..." +systemctl enable --now postfix dovecot opendkim spamassassin + +log_info "Email stack deployed. Remaining manual steps:" +echo " 1. Run certbot for mail.${DOMAIN}" +echo " 2. Generate DKIM key: opendkim-genkey -b 2048 -d ${DOMAIN} -s mail -D /etc/opendkim/keys/${DOMAIN}/" +echo " 3. Add DNS records (see services/email/README.md)" diff --git a/services/frp/client/deploy.sh b/services/frp/client/deploy.sh new file mode 100755 index 0000000..4dd9f4b --- /dev/null +++ b/services/frp/client/deploy.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Deploys frpc (FRP client) on home machine. +# Usage: INFRA_DIR=/path/to/infra/services/frp/client ./deploy.sh + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../../../bin/lib/common.sh" + +ENV_FILE="${INFRA_DIR:-.}/.env" +[ -f "$ENV_FILE" ] || { log_error "No .env found at $ENV_FILE"; exit 1; } +source "$ENV_FILE" + +require_env FRP_SERVER_ADDR FRP_SERVER_PORT FRP_TOKEN + +log_info "Downloading FRP..." +VERSION=$(curl -s https://api.github.com/repos/fatedier/frp/releases/latest \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['tag_name'][1:])") +ARCHIVE="frp_${VERSION}_linux_amd64.tar.gz" +wget -q "https://github.com/fatedier/frp/releases/download/v${VERSION}/${ARCHIVE}" +tar -xf "$ARCHIVE" +mkdir -p /opt/frp +cp "frp_${VERSION}_linux_amd64/frpc" /opt/frp/ +chmod +x /opt/frp/frpc +rm -rf "$ARCHIVE" "frp_${VERSION}_linux_amd64" + +log_info "Deploying config..." +envsubst < "${INFRA_DIR}/frpc.toml.example" > /opt/frp/frpc.toml + +log_info "Installing service..." +cp "${INFRA_DIR}/frpc.service" /etc/systemd/system/ +systemctl daemon-reload +systemctl enable --now frpc + +log_info "FRP client deployed, connecting to ${FRP_SERVER_ADDR}:${FRP_SERVER_PORT}" diff --git a/services/frp/server/deploy.sh b/services/frp/server/deploy.sh new file mode 100755 index 0000000..377b9a9 --- /dev/null +++ b/services/frp/server/deploy.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Deploys frps (FRP server) on VPS. +# Usage: INFRA_DIR=/path/to/infra/services/frp/server ./deploy.sh + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../../../bin/lib/common.sh" + +ENV_FILE="${INFRA_DIR:-.}/.env" +[ -f "$ENV_FILE" ] || { log_error "No .env found at $ENV_FILE"; exit 1; } +source "$ENV_FILE" + +require_env FRP_TOKEN FRP_WEB_PASSWORD FRP_BIND_PORT + +log_info "Downloading FRP..." +VERSION=$(curl -s https://api.github.com/repos/fatedier/frp/releases/latest \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['tag_name'][1:])") +ARCHIVE="frp_${VERSION}_linux_amd64.tar.gz" +wget -q "https://github.com/fatedier/frp/releases/download/v${VERSION}/${ARCHIVE}" +tar -xf "$ARCHIVE" +mkdir -p /opt/frp +cp "frp_${VERSION}_linux_amd64/frps" /opt/frp/ +chmod +x /opt/frp/frps +rm -rf "$ARCHIVE" "frp_${VERSION}_linux_amd64" + +log_info "Deploying config..." +envsubst < "${INFRA_DIR}/frps.toml.example" > /opt/frp/frps.toml + +log_info "Installing service..." +cp "${INFRA_DIR}/frps.service" /etc/systemd/system/ +systemctl daemon-reload +systemctl enable --now frps + +log_info "FRP server deployed on port ${FRP_BIND_PORT}" diff --git a/services/nginx/deploy.sh b/services/nginx/deploy.sh new file mode 100755 index 0000000..5812945 --- /dev/null +++ b/services/nginx/deploy.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Deploys Nginx web server and vhost configs. +# Usage: INFRA_DIR=/path/to/infra/services/nginx ./deploy.sh + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../../bin/lib/common.sh" + +ENV_FILE="${INFRA_DIR:-.}/.env" +[ -f "$ENV_FILE" ] || { log_error "No .env found at $ENV_FILE"; exit 1; } +source "$ENV_FILE" + +require_env DOMAIN + +log_info "Installing nginx..." +apt-get install -y nginx certbot python3-certbot-nginx + +log_info "Deploying nginx.conf..." +cp "${INFRA_DIR}/nginx.conf" /etc/nginx/nginx.conf + +log_info "Deploying vhost configs..." +mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled + +for conf in "${INFRA_DIR}/sites/"*.conf; do + name="$(basename "$conf" .conf)" + envsubst < "$conf" > "/etc/nginx/sites-available/${name}" + ln -sf "/etc/nginx/sites-available/${name}" "/etc/nginx/sites-enabled/${name}" + log_info " Deployed ${name}" +done + +log_info "Testing nginx config..." +nginx -t + +log_info "Nginx deployed. Remaining manual steps:" +echo " 1. Get TLS certs: certbot --nginx -d ${DOMAIN} -d ${CHAN_DOMAIN:-chan.${DOMAIN}} -d ${BLOG_DOMAIN:-blog.${DOMAIN}}" +echo " 2. systemctl reload nginx" diff --git a/services/shadowsocks/client/deploy.sh b/services/shadowsocks/client/deploy.sh new file mode 100755 index 0000000..aab663b --- /dev/null +++ b/services/shadowsocks/client/deploy.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Installs shadowsocks-rust client (sslocal) + privoxy proxy chain. +# Usage: INFRA_DIR=/path/to/infra/services/shadowsocks/client ./deploy.sh + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../../../bin/lib/common.sh" + +ENV_FILE="${INFRA_DIR:-.}/.env" +[ -f "$ENV_FILE" ] || { log_error "No .env found at $ENV_FILE"; exit 1; } +source "$ENV_FILE" + +require_env SS_SERVER SS_PORT SS_PASSWORD SS_METHOD + +log_info "Downloading shadowsocks-rust client..." +VERSION=$(curl -s https://api.github.com/repos/shadowsocks/shadowsocks-rust/releases/latest \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['tag_name'])") +ARCHIVE="shadowsocks-${VERSION}.x86_64-unknown-linux-gnu.tar.xz" +wget -q "https://github.com/shadowsocks/shadowsocks-rust/releases/download/${VERSION}/${ARCHIVE}" +tar -xf "$ARCHIVE" +cp sslocal /usr/local/bin/sslocal +chmod +x /usr/local/bin/sslocal +rm -f "$ARCHIVE" ssserver sslocal ssurl ssmanager redir tunnel + +log_info "Installing privoxy..." +apt-get install -y privoxy + +log_info "Deploying configs..." +mkdir -p /etc/shadowsocks-rust +envsubst < "${INFRA_DIR}/config.json.example" > /etc/shadowsocks-rust/client.json +cp "${INFRA_DIR}/privoxy.conf.example" /etc/privoxy/config + +log_info "Installing service..." +cp "${INFRA_DIR}/shadowsocks-client.service" /etc/systemd/system/ +systemctl daemon-reload +systemctl enable --now shadowsocks-client +systemctl enable --now privoxy + +log_info "Proxy chain ready: SOCKS5 at 127.0.0.1:1080, HTTP at 127.0.0.1:8118" diff --git a/services/shadowsocks/server/deploy.sh b/services/shadowsocks/server/deploy.sh new file mode 100755 index 0000000..88cf951 --- /dev/null +++ b/services/shadowsocks/server/deploy.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Installs shadowsocks-rust server and configures systemd service. +# Usage: INFRA_DIR=/path/to/infra/services/shadowsocks/server ./deploy.sh + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../../../bin/lib/common.sh" + +ENV_FILE="${INFRA_DIR:-.}/.env" +[ -f "$ENV_FILE" ] || { log_error "No .env found at $ENV_FILE"; exit 1; } +source "$ENV_FILE" + +require_env SS_PORT SS_PASSWORD SS_METHOD + +log_info "Downloading shadowsocks-rust..." +VERSION=$(curl -s https://api.github.com/repos/shadowsocks/shadowsocks-rust/releases/latest \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['tag_name'])") +ARCHIVE="shadowsocks-${VERSION}.x86_64-unknown-linux-gnu.tar.xz" +wget -q "https://github.com/shadowsocks/shadowsocks-rust/releases/download/${VERSION}/${ARCHIVE}" +tar -xf "$ARCHIVE" +cp ssserver /usr/local/bin/ssserver-rust +chmod +x /usr/local/bin/ssserver-rust +rm -f "$ARCHIVE" ssserver sslocal ssurl ssmanager redir tunnel + +log_info "Deploying config..." +mkdir -p /etc/shadowsocks-rust +envsubst < "${INFRA_DIR}/config.json.example" > /etc/shadowsocks-rust/config.json + +log_info "Installing service..." +cp "${INFRA_DIR}/shadowsocks-rust.service" /etc/systemd/system/ +systemctl daemon-reload +systemctl enable --now shadowsocks-rust + +log_info "Shadowsocks server deployed on port ${SS_PORT}"