Files
apxtri/setup/init.sh
T

616 lines
18 KiB
Bash
Executable File

#!/bin/bash
# apXtri Installation Script - New Structure
# Target directory structure:
# /opt/apxtowns/{TOWN}.{NATION}/apxtri/ (code)
# /var/lib/apxtowns/{TOWN}.{NATION}/{TOWN}/ (data - first/default tribe)
#
# Usage:
# sudo ./init.sh --town <name> --nation <name> [--clean] [--help]
#
# Example:
# sudo ./init.sh --town farm --nation test
set -eEuo pipefail
trap 'err_handler $LINENO $?' ERR
# ==== CONFIGURATION ====
URL_REPO="https://gitea.ndda.fr/apxtri"
REPO_APXTRI="$URL_REPO/apxtri.git"
APXTRI_USER="apxtri"
NODE_VERSION="lts"
# Default paths (can be overridden)
DEFAULT_DATAPATH_BASE="/var/lib/apxtowns"
DEFAULT_NODEPATH_BASE="/opt/apxtowns"
LOGPATH="/var/log/apxtowns"
# User-provided values
NATION=""
TOWN=""
MODE=""
APIPORT=""
DOMAIN=""
CLEAN_INSTALL=false
# ==== LOGGING ====
sudo touch /var/log/apxtri_install.log
sudo chown "$USER":"$USER" /var/log/apxtri_install.log 2>/dev/null || true
exec > >(tee -i /var/log/apxtri_install.log)
exec 2>&1
# ==== ERROR HANDLER ====
err_handler() {
local lineno="$1"
local code="$2"
if [[ "$code" -eq 99 ]]; then
echo "[INFO] Installation voluntarily stopped at line $lineno (exit 99)."
exit 99
fi
echo "[ERROR] Failure at line $lineno (code $code)."
echo "[INFO] Check logs above for details."
exit "$code"
}
# ==== USAGE ====
usage() {
cat <<EOF
Usage: $0 --town <name> --nation <name> [options]
Options:
--town <name> Town name (required)
--nation <name> Nation name (required, use 'test' for dev mode)
--domain <domain> Public domain (required for prod, auto in dev)
--apiport <port> API port (default: auto-detect from 3000)
--datapath <path> Base data path (default: /var/lib/apxtowns)
--nodepath <path> Base node path (default: /opt/apxtowns)
--clean Remove existing installation before install
--help Show this help message
Example:
sudo ./init.sh --town farm --nation test
sudo ./init.sh --town nova --nation libera --domain nova.libera.maildigit.fr
sudo ./init.sh --town mytown --nation ants --apiport 3001
EOF
exit 1
}
# ==== ARGUMENT PARSING ====
while [[ $# -gt 0 ]]; do
case "$1" in
--town)
TOWN="$2"
shift 2
;;
--nation)
NATION="$2"
shift 2
;;
--apiport)
APIPORT="$2"
shift 2
;;
--datapath)
DEFAULT_DATAPATH_BASE="$2"
shift 2
;;
--nodepath)
DEFAULT_NODEPATH_BASE="$2"
shift 2
;;
--domain)
DOMAIN="$2"
shift 2
;;
--clean)
CLEAN_INSTALL=true
shift
;;
--help|-h)
usage
;;
*)
echo "Error: Unknown option $1"
usage
;;
esac
done
# ==== VALIDATION ====
if [[ -z "$TOWN" ]] || [[ -z "$NATION" ]]; then
echo "[ERROR] Both --town and --nation are required."
usage
fi
# Validate town/nation names (alphanumeric and hyphen only)
if ! [[ "$TOWN" =~ ^[a-z0-9]+$ ]]; then
echo "[ERROR] Town name must be lowercase alphanumeric only (a-z, 0-9)."
exit 1
fi
if ! [[ "$NATION" =~ ^[a-z0-9]+$ ]]; then
echo "[ERROR] Nation name must be lowercase alphanumeric only (a-z, 0-9)."
exit 1
fi
# ==== COMPUTED PATHS ====
NODEPATH="${DEFAULT_NODEPATH_BASE}/${TOWN}.${NATION}"
DATAPATH="${DEFAULT_DATAPATH_BASE}/${TOWN}.${NATION}"
PROJECT_DIR="${NODEPATH}/apxtri"
TRIBE_DATA_DIR="${DATAPATH}/${TOWN}"
TRIBE_CODE_DIR="${NODEPATH}/${TOWN}"
# ==== CLEANING ====
cleaning() {
echo "[CLEAN] Starting cleanup..."
# Kill processes
if [[ -n "${APIPORT}" ]]; then
sudo fuser -k "${APIPORT}/tcp" 2>/dev/null || true
fi
# Stop services
sudo systemctl stop "apxtri@${TOWN}.${NATION}" 2>/dev/null || true
sudo systemctl disable "apxtri@${TOWN}.${NATION}" 2>/dev/null || true
# Remove service
sudo rm -f "/etc/systemd/system/apxtri@.service"
sudo systemctl daemon-reload
# Remove directories
sudo rm -rf "$NODEPATH" "$DATAPATH"
# Clean hosts entries
sudo sed -i "/\.${TOWN}\.${NATION}/d" /etc/hosts 2>/dev/null || true
echo "[CLEAN] Cleanup complete."
}
if [[ "$CLEAN_INSTALL" == true ]]; then
echo "[WARN] Clean install requested. Removing existing installation..."
cleaning
fi
# ==== CHECK EXISTING INSTALLATION ====
if [[ -d "$NODEPATH" ]] || [[ -d "$DATAPATH" ]]; then
echo "[ERROR] Installation already exists at:"
echo " - $NODEPATH"
echo " - $DATAPATH"
echo ""
echo "To reinstall, run with --clean flag:"
echo " sudo $0 --town $TOWN --nation $NATION --clean"
exit 99
fi
# ==== MODE DETECTION ====
if [[ "$NATION" == "test" ]]; then
MODE="dev"
DOMAIN="admin.${TOWN}.${NATION}"
echo "[INFO] DEV mode (nation=test)"
else
MODE="prod"
if [[ -z "$DOMAIN" ]]; then
echo "[WARN] No domain provided. Use --domain <domain> for production."
echo "[WARN] You can set DOMAIN manually in .env later."
else
echo "[INFO] Using domain: $DOMAIN"
fi
echo "[INFO] PROD mode (nation=$NATION)"
fi
# ==== AUTO-DETECT FREE PORT ====
if [[ -z "$APIPORT" ]]; then
APIPORT=3000
while sudo lsof -Pi :$APIPORT -sTCP:LISTEN -t >/dev/null 2>&1; do
echo "[INFO] Port $APIPORT in use, trying next..."
APIPORT=$((APIPORT + 1))
done
fi
echo "[INFO] Using API port: $APIPORT"
# ==== CONFIRMATION ====
echo ""
echo "============================================"
echo "apXtri Installation Configuration"
echo "============================================"
echo " Mode: $MODE"
echo " Town: $TOWN"
echo " Nation: $NATION"
echo " Domain: $DOMAIN"
echo " API Port: $APIPORT"
echo " Data Path: $DATAPATH"
echo " Node Path: $NODEPATH"
echo " Project Dir: $PROJECT_DIR"
echo " Tribe Data: $TRIBE_DATA_DIR"
echo "============================================"
echo ""
read -p "Continue? [Y/n]: " confirm
if [[ "$confirm" =~ ^[Nn]$ ]]; then
echo "Installation cancelled."
exit 0
fi
# ==== STEP 1: SYSTEM DEPENDENCIES ====
echo "[STEP 1/11] Installing system dependencies..."
sudo apt update --fix-missing
sudo apt install -y curl git gnupg jq build-essential locales vim libcap2-bin p7zip-full unzip psmisc
# Ensure UTF-8 locale
if ! locale -a 2>/dev/null | grep -q '\.UTF-8'; then
echo "[INFO] Generating en_US.UTF-8 locale..."
sudo locale-gen en_US.UTF-8
sudo update-locale LANG=en_US.UTF-8
fi
# ==== STEP 2: CADDY ====
echo "[STEP 2/11] Installing Caddy..."
if ! command -v caddy &>/dev/null; then
sudo mkdir -p /usr/share/keyrings
sudo chmod 755 /usr/share/keyrings
sudo curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/gpg.key -o /tmp/caddy-key.gpg
sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg /tmp/caddy-key.gpg
sudo chmod 644 /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo rm -f /tmp/caddy-key.gpg
echo "deb [signed-by=/usr/share/keyrings/caddy-stable-archive-keyring.gpg] https://dl.cloudsmith.io/public/caddy/stable/deb/debian any-version main" | \
sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy
else
echo "[INFO] Caddy already installed."
fi
# ==== STEP 3: HOSTS CONFIGURATION ====
echo "[STEP 3/11] Configuring /etc/hosts..."
if [[ "$MODE" == "dev" ]]; then
if ! grep -q "$DOMAIN" /etc/hosts; then
echo "127.0.0.1 $DOMAIN" | sudo tee -a /etc/hosts
echo "[INFO] Added $DOMAIN to /etc/hosts"
fi
fi
# ==== STEP 4: CREATE USER ====
echo "[STEP 4/11] Creating system user..."
if ! id "$APXTRI_USER" &>/dev/null; then
sudo useradd --system --no-create-home --shell /usr/sbin/nologin "$APXTRI_USER"
echo "$APXTRI_USER:$APXTRI_USER" | sudo chpasswd
else
echo "[INFO] User $APXTRI_USER already exists."
fi
# ==== STEP 5: CREATE DIRECTORIES ====
echo "[STEP 5/11] Creating directories..."
sudo mkdir -p "$PROJECT_DIR"
sudo mkdir -p "$TRIBE_DATA_DIR"/{objects/{options,persons,wwws/{cdn/draft,cdn/optz,cdn/lib,townadmin}},logs,backup,tmp,blockchain}
sudo mkdir -p "$TRIBE_CODE_DIR"/{routes,models,schema,middlewares,wwws}
# Ownership
sudo chown -R "$APXTRI_USER:$APXTRI_USER" "$NODEPATH" "$DATAPATH"
sudo chmod -R g+rwX "$NODEPATH" "$DATAPATH"
echo "[INFO] Directory structure created:"
echo " $NODEPATH/"
echo " └── apxtri/ (core code)"
echo " └── $TOWN/ (tribe code)"
echo " $DATAPATH/"
echo " └── $TOWN/ (tribe data + blockchain)"
# ==== STEP 6: CLONE REPOSITORY ====
echo "[STEP 6/11] Cloning apxtri repository..."
if [[ -d "$PROJECT_DIR/.git" ]]; then
echo "[INFO] Removing existing repo for clean clone..."
sudo rm -rf "$PROJECT_DIR"
fi
sudo -u "$APXTRI_USER" HOME="$PROJECT_DIR" git clone "$REPO_APXTRI" "$PROJECT_DIR"
sudo -u "$APXTRI_USER" HOME="$PROJECT_DIR" git config --global --add safe.directory "$PROJECT_DIR"
# ==== STEP 7: COPY SKELETON ====
echo "[STEP 7/11] Setting up tribe skeleton..."
SKELETON_DATA="${PROJECT_DIR}/setup/skeletons/newtribe/data"
SKELETON_CODE="${PROJECT_DIR}/setup/skeletons/newtribe/code"
# Create essential object directories
for obj in nations towns tribes; do
sudo mkdir -p "$TRIBE_DATA_DIR/objects/$obj/itm"
done
# Copy code skeleton (if exists)
if [[ -d "$SKELETON_CODE" ]]; then
sudo cp -r "$SKELETON_CODE"/* "$TRIBE_CODE_DIR/" 2>/dev/null || true
echo "[INFO] Code skeleton copied."
fi
# Copy data skeleton (townadmin, etc.) to tribe data dir
if [[ -d "$SKELETON_DATA" ]]; then
sudo cp -r "$SKELETON_DATA"/* "$TRIBE_DATA_DIR/" 2>/dev/null || true
echo "[INFO] Data skeleton copied."
fi
# Create missing directories for apidoc
sudo mkdir -p "$TRIBE_CODE_DIR/routes"
sudo mkdir -p "$TRIBE_DATA_DIR/wwws/apidoc/dist"
sudo mkdir -p "$TRIBE_DATA_DIR/wwws/cdn/lib"
sudo mkdir -p "$TRIBE_DATA_DIR/wwws/cdn/draft"
sudo mkdir -p "$TRIBE_DATA_DIR/wwws/cdn/optz"
# Render Mustache templates in skeleton files
if [[ -f "$TRIBE_CODE_DIR/wwws/apidoc/conf.json" ]]; then
sed -i \
-e "s/{{TOWN}}/$TOWN/g" \
-e "s/{{NATION}}/$NATION/g" \
-e "s/{{tribe}}/$TOWN/g" \
-e "s|{{NODEPATH}}|$NODEPATH|g" \
-e "s|{{DATAPATH}}|$DATAPATH|g" \
"$TRIBE_CODE_DIR/wwws/apidoc/conf.json"
echo "[INFO] apidoc conf.json rendered."
fi
# Render Mustache templates in code townadmin appconf.json
if [[ -f "$TRIBE_CODE_DIR/wwws/townadmin/appconf.json" ]]; then
sed -i \
-e "s/{{TOWN}}/$TOWN/g" \
-e "s/{{NATION}}/$NATION/g" \
-e "s/{{tribe}}/$TOWN/g" \
-e "s|{{NODEPATH}}|$NODEPATH|g" \
-e "s|{{DATAPATH}}|$DATAPATH|g" \
"$TRIBE_CODE_DIR/wwws/townadmin/appconf.json"
echo "[INFO] townadmin appconf.json rendered."
fi
# Render variables {TOWN}, {NATION}, {tribe} in data skeleton files
if [[ -f "$TRIBE_DATA_DIR/wwws/townadmin/appconf.json" ]]; then
sudo sed -i \
-e "s/{TOWN}/$TOWN/g" \
-e "s/{NATION}/$NATION/g" \
-e "s/{tribe}/$TOWN/g" \
"$TRIBE_DATA_DIR/wwws/townadmin/appconf.json"
echo "[INFO] townadmin appconf.json rendered."
fi
if [[ -f "$TRIBE_DATA_DIR/wwws/townadmin/index.html" ]]; then
sudo sed -i \
-e "s/{TOWN}/$TOWN/g" \
-e "s/{NATION}/$NATION/g" \
-e "s/{tribe}/$TOWN/g" \
"$TRIBE_DATA_DIR/wwws/townadmin/index.html"
echo "[INFO] townadmin index.html rendered."
fi
# Create conf.json and initial items for essential objects
for obj in nations towns tribes; do
CONF_FILE="$TRIBE_DATA_DIR/objects/$obj/conf.json"
if [[ ! -f "$CONF_FILE" ]]; then
echo "{\"name\":\"$obj\",\"schema\":\"apxtri/schema/$obj.json\",\"lastupdate\":0}" | \
sudo tee "$CONF_FILE" > /dev/null
fi
done
# Create initial nation item
NATION_ITEM="$TRIBE_DATA_DIR/objects/nations/itm/${NATION}.json"
if [[ ! -f "$NATION_ITEM" ]]; then
echo "{\"nationId\":\"$NATION\",\"dtcreate\":\"$(date -Iseconds)\"}" | \
sudo tee "$NATION_ITEM" > /dev/null
echo "[INFO] Nation '$NATION' created."
fi
# Create initial town item
TOWN_ITEM="$TRIBE_DATA_DIR/objects/towns/itm/${TOWN}.json"
if [[ ! -f "$TOWN_ITEM" ]]; then
echo "{\"townId\":\"$TOWN\",\"nationId\":\"$NATION\",\"status\":\"unchain\",\"dns\":[\"admin.${TOWN}.${NATION}\"]}" | \
sudo tee "$TOWN_ITEM" > /dev/null
echo "[INFO] Town '$TOWN' created."
fi
# Create initial tribe item (same name as TOWN)
TRIBE_ITEM="$TRIBE_DATA_DIR/objects/tribes/itm/${TOWN}.json"
if [[ ! -f "$TRIBE_ITEM" ]]; then
cat <<TRIBE_EOF | sudo tee "$TRIBE_ITEM" > /dev/null
{
"tribeId": "$TOWN",
"api": {
"languages": ["en", "fr"],
"appset": { "trust proxy": true },
"json": { "limit": "10mb", "type": "application/json", "rawBody": true },
"bodyparse": {
"urlencoded": { "limit": "50mb", "extended": true },
"json": { "limit": "500mb" }
}
},
"emailcontact": "",
"smtp": {},
"sms": {}
}
TRIBE_EOF
echo "[INFO] Tribe '$TOWN' created."
fi
# Fix ownership after sudo cp/mkdir in step 7 (files created as root)
sudo chown -R "$APXTRI_USER:$APXTRI_USER" "$TRIBE_DATA_DIR" "$TRIBE_CODE_DIR"
# ==== STEP 8: CREATE .ENV FILE ====
echo "[STEP 8/11] Creating environment configuration..."
# Get IP addresses
LAN_IP=$(hostname -I | awk '{print $1}')
PUBLIC_IP=$(curl -s --connect-timeout 5 http://icanhazip.com || echo "$LAN_IP")
echo "[INFO] LAN IP: $LAN_IP"
echo "[INFO] Public IP: $PUBLIC_IP"
sudo tee "${PROJECT_DIR}/.env" > /dev/null <<EOL
URL_REPO="$URL_REPO"
VERSION="1.0.0"
MODE="$MODE"
TOWN="$TOWN"
NATION="$NATION"
DOMAIN="$DOMAIN"
UBUNTU="server"
ETCCONF="/etc/apxtowns/apxtowns"
DATAPATH="$DATAPATH"
NODEPATH="$NODEPATH"
LOGPATH="$LOGPATH"
APIPORT="$APIPORT"
ACTIVELOG=apxtri,isAuthenticated,checkHeaders,Odmdb,Pagans,Persons,Wwws,Trackings,Notifications
LOG_LEVEL=debug
EXPOSEDHEADERS="xdays,xalias,xlang,xtribe,xapp,xuuid"
SOCKETPORT="$((APIPORT + 1))"
BACKENDURL="localhost"
CADDYAPIURL="http://localhost:2019"
BLOCKCHAIN_CONNECT=false
PRIVKMAYOR=""
LOCALIP="$LAN_IP"
PUBLICIP="$PUBLIC_IP"
EOL
sudo chown "$APXTRI_USER:$APXTRI_USER" "${PROJECT_DIR}/.env"
echo "[INFO] .env file created."
# ==== STEP 9: NODE.JS & NPM ====
echo "[STEP 9/11] Installing Node.js..."
NVM_DIR="${PROJECT_DIR}/.nvm"
npm_config_cache="${DATAPATH}/.npm"
sudo mkdir -p "$npm_config_cache"
sudo chown -R "$APXTRI_USER:$APXTRI_USER" "$npm_config_cache"
sudo -u "$APXTRI_USER" HOME="$PROJECT_DIR" \
NVM_DIR="$NVM_DIR" \
PROJECT_DIR="$PROJECT_DIR" \
npm_config_cache="$npm_config_cache" \
bash <<'NVMEOF'
set -euo pipefail
if [ ! -d "$NVM_DIR" ]; then
git clone https://github.com/nvm-sh/nvm.git "$NVM_DIR"
cd "$NVM_DIR"
git checkout master
fi
chmod +x "$NVM_DIR/nvm.sh"
export NVM_DIR="$NVM_DIR"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install --lts
nvm alias default 'lts/*'
nvm use default
npm install -g npm@latest
cd "$PROJECT_DIR"
npm install
NVMEOF
echo "[INFO] Node.js installed."
echo "[INFO] npm dependencies installed."
# ==== STEP 10: SYSTEMD SERVICE ====
echo "[STEP 10/11] Creating systemd service..."
# Create template service
sudo tee "/etc/systemd/system/apxtri@.service" > /dev/null <<EOF
[Unit]
Description=apXtri node %i
After=network.target
[Service]
Type=simple
User=$APXTRI_USER
Group=$APXTRI_USER
WorkingDirectory=$PROJECT_DIR
EnvironmentFile=$PROJECT_DIR/.env
ExecStart=/bin/bash -lc "source $NVM_DIR/nvm.sh && node apxtri.js"
ExecStop=/bin/bash -lc "pkill -u $APXTRI_USER -f 'node.*apxtri.js'"
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable "apxtri@${TOWN}.${NATION}"
sudo systemctl start "apxtri@${TOWN}.${NATION}"
# ==== HELPER SCRIPTS (usr/local/bin) ====
echo "[STEP 12] Configuring automatic updates (cron)..."
CRON_FILE="/etc/cron.d/apxtri-${TOWN}.${NATION}"
UPDATE_LOG="${TRIBE_DATA_DIR}/logs/update.log"
CRON_TEMP=$(mktemp)
mkdir -p "$(dirname "$UPDATE_LOG")"
cat > "$CRON_TEMP" << CRONEOF
# apxtri ${TOWN}.${NATION} - automatic updates
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Update check daily at 3:00 AM
0 3 * * * root /opt/apxtowns/${TOWN}.${NATION}/apxtri/setup/update.sh >> ${UPDATE_LOG} 2>&1
CRONEOF
if [[ -f "$CRON_FILE" ]]; then
echo "[INFO] Cron file already exists: $CRON_FILE"
else
sudo cp "$CRON_TEMP" "$CRON_FILE" && echo "[INFO] Cron file created: $CRON_FILE"
fi
rm -f "$CRON_TEMP"
echo "[STEP 13] Configuring automatic backups (cron)..."
BACKUP_LOG="${TRIBE_DATA_DIR}/logs/backup.log"
mkdir -p "$(dirname "$BACKUP_LOG")"
cat >> "$CRON_FILE" << CRONEOF
# Backup: daily at 4:00 AM (dayname = day of week)
0 4 * * * root /opt/apxtowns/${TOWN}.${NATION}/apxtri/setup/backup.sh dayname >> ${BACKUP_LOG} 2>&1
CRONEOF
echo "[INFO] Backup cron entry added to $CRON_FILE"
echo "[STEP 11] Installing helper scripts to /usr/local/bin..."
USRLOCALBIN_DIR="${PROJECT_DIR}/setup/usrlocalbin"
if [[ -d "$USRLOCALBIN_DIR" ]]; then
for script in "$USRLOCALBIN_DIR"/*; do
if [[ -f "$script" && -x "$script" ]]; then
script_name=$(basename "$script")
sudo cp "$script" "/usr/local/bin/$script_name"
sudo chmod +x "/usr/local/bin/$script_name"
echo "[INFO] Installed: $script_name"
fi
done
else
echo "[INFO] No scripts in $USRLOCALBIN_DIR"
fi
# ==== FINAL MESSAGE ====
echo ""
echo "============================================"
echo "INSTALLATION COMPLETE"
echo "============================================"
echo ""
echo "Configuration:"
echo " Instance: $TOWN.$NATION"
echo " Mode: $MODE"
echo " API Port: $APIPORT"
echo ""
echo "Directories:"
echo " Code: $PROJECT_DIR"
echo " Data: $DATAPATH"
echo " Tribe Data: $TRIBE_DATA_DIR"
echo " Tribe Code: $TRIBE_CODE_DIR"
echo ""
echo "Service:"
echo " Start: sudo systemctl start apxtri@$TOWN.$NATION"
echo " Stop: sudo systemctl stop apxtri@$TOWN.$NATION"
echo " Status: sudo systemctl status apxtri@$TOWN.$NATION"
echo " Logs: apxdev $TOWN.$NATION logs"
echo ""
echo "Helper:"
echo " apxdev - Manage apxtri instances"
echo ""
if [[ "$MODE" == "dev" ]]; then
echo "Access: http://$DOMAIN:$APIPORT"
fi
echo ""
echo "============================================"