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