feat: add infra service deploy scripts

Add services/ directory with deploy scripts for system-level infrastructure
services. Each script reads INFRA_DIR pointing to the corresponding infra
module, sources its .env, substitutes config templates via envsubst, and
installs/enables systemd services. Zero hardcoded values — public-safe.

New scripts:
- services/email/deploy.sh      (Postfix + Dovecot + OpenDKIM + SpamAssassin)
- services/nginx/deploy.sh      (Nginx vhosts via envsubst)
- services/shadowsocks/server/deploy.sh  (shadowsocks-rust server)
- services/shadowsocks/client/deploy.sh  (sslocal + privoxy chain)
- services/frp/server/deploy.sh (frps)
- services/frp/client/deploy.sh (frpc)

README: add "Relationship with infra" section explaining the two-repo workflow
Makefile: add deploy-email, deploy-nginx, deploy-ss-{server,client},
          deploy-frp-{server,client} targets

Closes #6
This commit is contained in:
m1ngsama 2026-02-28 01:09:36 +08:00
parent 9b709b25b4
commit f82cd2d956
8 changed files with 333 additions and 8 deletions

View file

@ -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..."

View file

@ -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/<name>/.env → automa/services/<name>/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.
@ -73,11 +136,20 @@ 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
│ └── 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

37
services/email/deploy.sh Executable file
View file

@ -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)"

34
services/frp/client/deploy.sh Executable file
View file

@ -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}"

34
services/frp/server/deploy.sh Executable file
View file

@ -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}"

36
services/nginx/deploy.sh Executable file
View file

@ -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"

View file

@ -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"

View file

@ -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}"