diff --git a/docker-compose.yml b/docker-compose.yml index 3e7f4aa..2017cf4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,108 +1,60 @@ volumes: - ssl-data: - name: ssl - wireguard-data: - name: wireguard - portainer-data: - name: portainer - gitea-mirror-data: - name: gitea_mirror + ssl-data: { name: ssl } + wireguard-data: { name: wireguard } + portainer-data: { name: portainer } + gitea-mirror-data: { name: gitea_mirror } + networks: - # Specific network for reverse proxy communication - socket-ro-bridge: - name: socket_ro_bridge - internal: true - socket-rw-bridge: - name: socket_rw_bridge - internal: true + # Secured internal bridge for read-only and read-write + # Docker socket access + socket-ro-bridge: { name: socket_ro_bridge, internal: true } + socket-rw-bridge: { name: socket_rw_bridge, internal: true } + # Public-facing network for Nginx Proxy and web services web-network: name: web_network - internal: false - external: false enable_ipv6: true services: + # Read-Only Proxy: Restricts access to metadata only (GET requests) socket-ro: container_name: socket-ro image: lscr.io/linuxserver/socket-proxy:latest - # Only grant read-only access to container metadata environment: - - ALLOW_START=0 - - ALLOW_STOP=0 - - ALLOW_RESTARTS=0 - - AUTH=0 - - BUILD=0 - - COMMIT=0 - - CONFIGS=0 - - CONTAINERS=1 - - DISABLE_IPV6=0 - - DISTRIBUTION=0 - - EVENTS=1 - - EXEC=0 - - IMAGES=1 - - INFO=1 + - CONTAINERS=1 # Monitor container status + - EVENTS=1 # Real-time discovery + - IMAGES=1 # View image info + - INFO=1 # Engine info + - NETWORKS=1 # Network mapping + - PING=1 # Connectivity check + - SYSTEM=1 # System metadata + - VOLUMES=1 # Volume inspection - LOG_LEVEL=info - - NETWORKS=1 - - NODES=0 - - PING=1 - - PLUGINS=0 - - POST=0 - - SECRETS=0 - - SERVICES=0 - - SESSION=0 - - SWARM=0 - - SYSTEM=1 - - TASKS=0 - - TZ=Etc/UTC - - VERSION=1 - - VOLUMES=1 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro networks: - socket-ro-bridge + # Read-Write Proxy: Allows modifications (POST/EXEC) for administrative tasks socket-rw: container_name: socket-rw image: lscr.io/linuxserver/socket-proxy:latest - # Only grant read-only access to container metadata environment: - - ALLOW_START=0 - - ALLOW_STOP=0 - - ALLOW_RESTARTS=0 - - AUTH=0 - - BUILD=0 - - COMMIT=0 - - CONFIGS=0 - CONTAINERS=1 - - DISABLE_IPV6=0 - - DISTRIBUTION=0 - EVENTS=1 - - EXEC=1 + - EXEC=1 # Required for triggering tasks inside containers - IMAGES=1 - INFO=1 - - LOG_LEVEL=info - NETWORKS=1 - - NODES=0 - PING=1 - - PLUGINS=0 - - POST=1 - - SECRETS=0 - - SERVICES=0 - - SESSION=0 - - SWARM=0 + - POST=1 # Allows container start/stop/restart - SYSTEM=1 - - TASKS=0 - - TZ=Etc/UTC - - VERSION=1 - VOLUMES=1 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro networks: - socket-rw-bridge - # -------------------------------- - # Auto backup through S3 - # -------------------------------- + # Automated Volume Backups: Daily S3 sync with container pausing backup: container_name: backup image: offen/docker-volume-backup @@ -114,23 +66,17 @@ services: AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} BACKUP_CRON_EXPRESSION: "0 0 * * *" BACKUP_RETENTION_DAYS: 3 - # Mounting docker socket to stop/pause containers - # to prevent volume corruption. - DOCKER_HOST: tcp://socket-rw:2375 + DOCKER_HOST: tcp://socket-rw:2375 # Uses RW proxy to pause containers during backup volumes: - # Include container volumes in the backup process. - wireguard-data:/backup/wireguard:ro - ./synapse:/backup/synapse:ro - ssl-data:/backup/ssl:ro - # Local directory for backup archives. - ./backup:/archive networks: - socket-rw-bridge - web-network - # -------------------------------- - # Reverse Proxy - # -------------------------------- + # Automated Nginx Reverse Proxy: Routes traffic based on VIRTUAL_HOST labels nginx-proxy: image: nginxproxy/nginx-proxy:alpine container_name: nginx-proxy @@ -139,28 +85,21 @@ services: - "80:80" - "443:443" environment: - # Grant access to Docker socket enables automated - # proxy configuration based on container events. - - DOCKER_HOST=tcp://socket-ro:2375 + - DOCKER_HOST=tcp://socket-ro:2375 # Discovers containers via RO proxy - ENABLE_IPV6=true volumes: - # Grant access to certification volume allow to - # nginx to read and send SSL keys for security. - - ssl-data:/etc/nginx/certs + - ssl-data:/etc/nginx/certs:ro - ./nginx/default_html:/usr/share/nginx/html - # - ./nginx/default.conf:/etc/nginx/conf.d/default.conf - ./nginx/vhost.d:/etc/nginx/vhost.d labels: - - "docker-volume-backup.stop-during-backup=true" + - "docker-volume-backup.stop-during-backup=true" # Ensure consistency during backup depends_on: - socket-ro networks: - socket-ro-bridge - web-network - # -------------------------------- - # ACME Companion for SSL certs - # -------------------------------- + # ACME Companion: Automated Let's Encrypt certificate issuance/renewal acme-companion: image: nginxproxy/acme-companion container_name: acme-companion @@ -168,18 +107,11 @@ services: environment: - DEFAULT_EMAIL=${EMAIL} - NGINX_PROXY_CONTAINER=nginx-proxy - # Grant access to Docker socket enables automated - # SSL certificate issuance. - - DOCKER_HOST=tcp://socket-rw:2375 + - DOCKER_HOST=tcp://socket-rw:2375 # Needs RW to restart Nginx after renewal volumes: - # Store SSL certifications into ssl-data volume. - ssl-data:/etc/nginx/certs - # Required for ACME HTTP-01 challenges and domain validation. - ./nginx/vhost.d:/etc/nginx/vhost.d - # Shared web root for serving ACME challenge files. - ./nginx/default_html:/usr/share/nginx/html - # Prevent from recreate a Let's encrypt account - # each restart. - ./nginx/acme_config:/etc/acme.sh labels: - "docker-volume-backup.stop-during-backup=true" @@ -189,21 +121,22 @@ services: - socket-rw-bridge - web-network - web: - container_name: web - build: ./guezoloic/website # using guezoloic website repo - restart: unless-stopped - environment: - - VIRTUAL_HOST=${HOSTNAME}, www.${HOSTNAME} - - LETSENCRYPT_HOST=${HOSTNAME}, www.${HOSTNAME} - - LETSENCRYPT_EMAIL=${EMAIL} - volumes: - - ./data:/usr/share/nginx/html/data - depends_on: - - nginx-proxy - networks: - - web-network + # Main Portfolio/Website: Built from local repository + # website: + # container_name: web + # # Reference to your internal Gitea registry + # image: // self website repo + # restart: unless-stopped + # environment: + # - VIRTUAL_HOST=${HOSTNAME}, www.${HOSTNAME} + # - LETSENCRYPT_HOST=${HOSTNAME}, www.${HOSTNAME} + # - LETSENCRYPT_EMAIL=${EMAIL} + # volumes: + # - ./data:/usr/share/nginx/html/data + # networks: + # - web-network + # Portainer: Web UI for container and stack management portainer: container_name: portainer image: portainer/portainer-ce:lts @@ -213,18 +146,14 @@ services: - LETSENCRYPT_HOST=mtr.${HOSTNAME} - LETSENCRYPT_EMAIL=${EMAIL} - VIRTUAL_PORT=9000 - - DOCKER_HOST=socket-ro:2375 + - DOCKER_HOST=socket-ro:2375 # Securely monitor engine via RO proxy volumes: - portainer-data:/data - ports: - - 9000:9000 - # - 8000:8000 - depends_on: - - nginx-proxy networks: - web-network - socket-ro-bridge + # WireGuard VPN: Secure remote access with Web UI (wg-easy) wg-easy: image: ghcr.io/wg-easy/wg-easy:15 container_name: wg-easy @@ -234,10 +163,7 @@ services: - SYS_MODULE sysctls: - net.ipv4.ip_forward=1 - - net.ipv4.conf.all.src_valid_mark=1 - net.ipv6.conf.all.disable_ipv6=0 - - net.ipv6.conf.all.forwarding=1 - - net.ipv6.conf.default.forwarding=1 environment: - TZ=Europe/Paris - VIRTUAL_HOST=vpn.${HOSTNAME} @@ -251,13 +177,11 @@ services: - "51820:51820/udp" labels: - "docker-volume-backup.stop-during-backup=true" - depends_on: - - nginx-proxy networks: - web-network + # Synapse: Matrix homeserver for decentralized communication synapse: - # private chat server (useful for notifications) image: matrixdotorg/synapse:latest container_name: synapse restart: unless-stopped @@ -269,41 +193,31 @@ services: - LETSENCRYPT_HOST=msg.${HOSTNAME} - LETSENCRYPT_EMAIL=${EMAIL} - VIRTUAL_PORT=8008 - expose: - - "8008" - depends_on: - - nginx-proxy networks: - web-network + # Gitea: Self-hosted Git forge (Lightweight alternative to GitHub) gitea: image: gitea/gitea:latest container_name: gitea + restart: unless-stopped environment: - - USER_UID=1000 - - USER_GID=1000 - VIRTUAL_HOST=git.${HOSTNAME} - LETSENCRYPT_HOST=git.${HOSTNAME} - LETSENCRYPT_EMAIL=${EMAIL} - VIRTUAL_PORT=3000 - - DISABLE_REGISTRATION=true - GITEA__server__DOMAIN=git.${HOSTNAME} - - GITEA__server__SSH_DOMAIN=git.${HOSTNAME} - - GITEA__server__SSH_PORT=222 - GITEA__server__ROOT_URL=https://git.${HOSTNAME}/ - - GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION=false - - GITEA__service__DISABLE_REGISTRATION=true - - GITEA__service__SHOW_REGISTRATION_BUTTON=false - restart: unless-stopped - networks: - - web-network + - DISABLE_REGISTRATION=true # Private instance security volumes: - ./gitea:/data - - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro ports: - - "222:22" + - "222:22" # SSH port mapping for Git operations + networks: + - web-network + # Gitea Mirror: Repository synchronization and backup tool gitea-mirror: image: ghcr.io/raylabshq/gitea-mirror:latest container_name: gitea-mirror @@ -314,9 +228,6 @@ services: - LETSENCRYPT_EMAIL=${EMAIL} - VIRTUAL_PORT=4321 - BETTER_AUTH_SECRET=${MIRROR_AUTH_SECRET} - - SCHEDULE_ENABLED=true - - SCHEDULE_INTERVAL=3600 - volumes: - gitea-mirror-data:/app/data networks: diff --git a/install.sh b/install.sh index 90efb89..3267d34 100644 --- a/install.sh +++ b/install.sh @@ -1,105 +1,76 @@ #!/bin/bash -source ./libs/common.sh +set -euo pipefail -mkdir -p $ETC_DIR -rm -f $LOG +readonly PROJECT_NAME="serverconfig" +readonly PROJECT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -ISERROR=false -INSTALLED_DEP=( $(grep -v '^#' $(pwd)/requirements.txt) ) +readonly REQ=("curl" "docker") +readonly ENV_FILE="${PROJECT_DIR}/.env" -if [[ $EUID -ne 0 ]]; then - echo "The script needs to run as root." - exit 1 -fi +readonly GREEN='\033[0;32m' +readonly RED='\033[0;31m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' -touch "$LOG" || ISERROR=true -if $ISERROR; then - info_print "Failed to Create $LOG" $ERROR_FLAG false; exit 1 -fi +DATETIME_FORMAT="%d-%m-%Y %H:%M:%S" -chmod 644 "$LOG" +function log() { + local type="${1}" + local color="${2}" + local message="${3}" + echo -e "${color}[$(date +"$DATETIME_FORMAT")] [${type}]${NC} ${message}" +} -info_print "\n\ -==================================================\n\ - ServerConfig Installation v1.0.0\n\ ---------------------------------------------------" -info_print "License : MIT" -info_print "Repository : https://github.com/guezoloic/serverconfig" -info_print "Date : Installation $(date '+%Y-%m-%d %H:%M:%S')" +function log_info() { log "INFO" "$BLUE" "$1"; } +function log_success() { log "OK " "$GREEN" "$1"; } +function log_error() { log "ERR " "$RED" "$1" >&2; } - -if $ISERROR; then - info_print "Failed to move some scripts to $SCRIPT_FILE, See log $LOG" $ERROR_FLAG false; exit 1 -fi - -info_print "\n\ -==================================================\n\ - Installing config files to $ETC_DIR\n\ ---------------------------------------------------" -- false - -for config in config/*; do - filename=$(basename "$config") - info_print "Moving $filename to $SCRIPT_FILE" - - if [ -d "$config" ]; then - cp -r "$config" "$ETC_DIR/" \ - && { info_print "$ETC_DIR/$filename installed (directory)." $SUCCESS_FLAG; } \ - || { info_print "$ETC_DIR/$filename failed (directory)." $ERROR_FLAG; ISERROR=true; } - else - install -Dm755 "$config" "$ETC_DIR/$filename" \ - && { info_print "$ETC_DIR/$filename installed." $SUCCESS_FLAG; } \ - || { info_print "$ETC_DIR/$filename failed." $ERROR_FLAG; ISERROR=true; } +function check_root() { + if [[ $EUID -ne 0 ]]; then + log_error "The script needs to run as root." + exit 1 fi -done +} -if $ISERROR; then - info_print "Failed to move some scripts to $ENV_FILE, See log $LOG" 3 false; exit 1 -fi +function check_dependencies() { + log_info "Checking system dependencies..." + for cmd in "${REQ[@]}"; do + if ! command -pv "$cmd" &>/dev/null; then + log_error "${cmd} is not installed." + exit 1 + else + log_success "${cmd} is installed." + fi + done +} -info_print "\n\ -==================================================\n\ - Checking dependencies \n\ ---------------------------------------------------" -- false +function install_scripts() { + log_info "Installing scripts..." + for script in "$SCRIPT_FILE"/*.sh; do + [ -e "$script" ] || continue + log_info "Configuring $(basename "$script")..." + if ! bash "$script" --install; then + log_error "Hook failed for $script" + fi + done +} -for dep in ${INSTALLED_DEP[@]}; do - if command -v "$dep" &>/dev/null; then - info_print "$dep is installed." $SUCCESS_FLAG - else - info_print "$dep is not installed." $ERROR_FLAG - ISERROR=true - fi -done +function main() { + clear + echo -e "${YELLOW}${PROJECT_NAME} Installation${NC}" -if $ISERROR; then - info_print "Some Dependencies are missing. Please check requirements.txt." $ERROR_FLAG false; exit 1 -fi + check_root + check_dependencies + + log_info "Creating directories and files..." + touch "$ENV_FILE" -info_print "\n\ -==================================================\n\ - Installing scripts to $SCRIPT_FILE \n\ ---------------------------------------------------" -- false + install_scripts -for scripts in libs/*.sh scripts/*.sh; do - info_print "Moving $scripts to $SCRIPT_FILE" - output="$SCRIPT_FILE/$scripts" + log_success "Installation Complete" +} - install $argument "$scripts" $output -Dm755 \ - && { info_print "$output installed." $SUCCESS_FLAG; } \ - || { info_print "$output failed." $ERROR_FLAG; ISERROR=true; } -done - -touch $ENV_FILE -for element in $SCRIPT_FILE/*/*.sh; do - bash "$element" --install -done - -info_print "\n\ -==================================================\n\ - Installation Complete\n\ ---------------------------------------------------" -info_print "All config files are in $ETC_DIR" -info_print "All scripts are in $SCRIPT_FILE" - -echo "Log file written at: $LOG" \ No newline at end of file +main "$@" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index d7096dc..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -curl -aws -docker \ No newline at end of file diff --git a/scripts/aws-backup.sh b/scripts/aws-backup.sh deleted file mode 100644 index d65e85f..0000000 --- a/scripts/aws-backup.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/bin/bash - -source /usr/local/bin/libs/common.sh -source /etc/serverconfig/.env - -DIR="$(cd "$(dirname "$0")" && pwd)" -BACKUP="$ETC_DIR/aws-backup.bak" - -INSTALLED=$1 -if [[ "--install" == $INSTALLED ]]; then - info_print "\n\ -==================================================\n\ - AWS-backup Installation\n\ ---------------------------------------------------" - - read -p "Enter aws server: " AWS_client - create_env_variable "AWS" "$AWS_client" - - read -p "Enter endpoint server (leave empty to not define it): " ENDPOINT_server - [[ -n $ENDPOINT_server ]] && create_env_variable "ENDPOINT" "$ENDPOINT_server" - - info_print "AWS configuration." - aws configure - - create_env_variable AWS_ACCESS_KEY_ID $(aws configure get aws_access_key_id) -- false - create_env_variable AWS_SECRET_ACCESS_KEY $(aws configure get aws_secret_access_key) -- false - - touch "$BACKUP" - info_print "$BACKUP created." - - while true; do - read -p "Add backup directory or file name (leave empty to quit): " key - [[ -z "$key" ]] && break - create_raw_line_variable "$key" $BACKUP - done - info_print "You can add more names later by editing $BACKUP." - - if ! command -v crontab >/dev/null 2>&1; then - info_print "Error: crontab not found." $ERROR_FLAG - exit 1 - fi - - CRON_JOB="0 0 * * * $SCRIPT_FILE/scripts/aws-backup.sh" - crontab -l | grep -F "$CRON_JOB" > /dev/null 2>&1 - if ! crontab -l | grep -Fq "$CRON_JOB"; then - (crontab -l 2>/dev/null; echo "$CRON_JOB") | crontab - - info_print "Cron job added." $SUCCESS_FLAG - fi - - exit 0 -fi - -if [[ "$1" == "clean" ]]; then - info_print "Purge aws-bak files." - rm -f $BACKUP - exit 0 -fi - -if [[ ! -s "BACKUP" ]]; then - exit 0 -fi - -while IFS= read -r SOURCE_PATH || [ -n "$SOURCE_PATH" ]; do - if [[ -z "$SOURCE_PATH" || "$SOURCE_PATH" =~ ^[[:space:]]*$ ]]; then - continue - fi - - if [[ -d "$SOURCE_PATH" || -f "$SOURCE_PATH" ]]; then - DEST="s3://$AWS/$(basename "$SOURCE_PATH")" - - if [[ -d "$SOURCE_PATH" ]]; then - info_print "Syncing directory: $SOURCE_PATH → $DEST" - aws_cmd=(aws s3 sync "$SOURCE_PATH" "$DEST" --delete) - elif [[ -f "$SOURCE_PATH" ]]; then - info_print "Uploading file: $SOURCE_PATH → $DEST" - aws_cmd=(aws s3 cp "$SOURCE_PATH" "$DEST") - fi - - if [[ -n "$ENDPOINT" ]]; then - info_print "Using custom endpoint: $ENDPOINT" - aws_cmd+=("--endpoint-url" "$ENDPOINT") - fi - - - "${aws_cmd[@]}" - - if [ $? -ne 0 ]; then - info_print "Error while syncing $SOURCE_PATH to the AWS server." $ERROR_FLAG - BACKUPSUCCESSED=false - exit 1 - - else - info_print "Successfully synced $SOURCE_PATH" $SUCCESS_FLAG - BACKUPSUCCESSED=true - fi - else - info_print "$SOURCE_PATH not found or inaccessible." $ERROR_FLAG - BACKUPSUCCESSED=false - exit 1 - fi -done < "$BACKUP" - -source /usr/local/bin/libs/notifications.sh - -if [[ "$BACKUP_SUCCESS" == true ]]; then - send_notification "💿 AWS Backup 💿 : All files successfully backed up." -else - send_notification "💿 AWS Backup 💿 : One or more files failed to back up. Check the log for details." -fi