From 29e8fe8b7f20739f5be3dcab6220e3aaae37e1d8 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Sun, 21 Apr 2024 22:05:05 -0700 Subject: [PATCH] This commit refactors the Cloudflare UFW Updater script, which automatically updates UFW (Uncomplicated Firewall) rules to allow incoming HTTP and HTTPS traffic only from Cloudflare IP addresses. The script includes the following features and enhancements: - Fetches the latest Cloudflare IP addresses (IPv4 and IPv6) from the official Cloudflare API endpoints. - Updates UFW rules to allow incoming traffic on specified ports (80 and 443) only from the fetched Cloudflare IP addresses. - Supports customization through a configuration file or environment variables, allowing users to specify custom ports, rule labels, and log file paths. - Implements logging functionality to track script execution and any errors encountered, with timestamps and log levels. - Performs error handling and dependency checks to ensure smooth operation and provide informative error messages. - Includes a backup and restore mechanism for UFW rules, allowing easy rollback to the previous state if needed. - Follows the Google Shell Style Guide for consistent and maintainable code style, and passes ShellCheck for static analysis. - Incorporates a MIT license header to clearly define the terms of use and distribution. - Provides a comprehensive README.md file with installation instructions, usage guidelines, configuration options, and troubleshooting tips. - Includes a Dockerfile for containerization and easy deployment of the script. - Adds a dependabot configuration file to enable automatic dependency updates for GitHub Actions and Docker. - Implements a GitHub Actions workflow for automated testing using BATS (Bash Automated Testing System) and running tests in a containerized environment. This commit lays the foundation for a robust and maintainable solution to manage UFW rules for Cloudflare IP addresses, promoting security and automation. --- .github/.dependabot.yml | 28 ++++- .github/workflows/test.yml | 24 +++-- .gitignore | 2 + Dockerfile | 23 ++-- Dockerfile.test | 18 ---- README.md | 100 ++++++++++++----- cf_ufw.sh | 72 ------------- cloudflare-ufw-updater.conf | 20 ++++ cloudflare-ufw-updater.sh | 174 ++++++++++++++++++++++++++++++ docker-compose.yml | 7 -- test_cf_ufw.bats | 23 ---- tests/cloudflare_ufw_updater.bats | 32 ++++++ tests/docker-compose.yml | 9 ++ 13 files changed, 367 insertions(+), 165 deletions(-) delete mode 100644 Dockerfile.test delete mode 100644 cf_ufw.sh create mode 100644 cloudflare-ufw-updater.conf create mode 100644 cloudflare-ufw-updater.sh delete mode 100644 docker-compose.yml delete mode 100644 test_cf_ufw.bats create mode 100755 tests/cloudflare_ufw_updater.bats create mode 100644 tests/docker-compose.yml diff --git a/.github/.dependabot.yml b/.github/.dependabot.yml index 2f27b1c..5c943b0 100644 --- a/.github/.dependabot.yml +++ b/.github/.dependabot.yml @@ -1,9 +1,35 @@ version: 2 updates: + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "chore:" + include: "scope" + labels: + - "dependencies" + - "docker" + - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" + commit-message: + prefix: "chore:" + include: "scope" + labels: + - "dependencies" + - "github-actions" + + - package-ecosystem: "github-actions" + directory: "/.github/workflows" + schedule: + interval: "weekly" commit-message: prefix: "chore:" include: "scope" + labels: + - "dependencies" + - "github-actions" + - "workflows" \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f1f0b80..6d13c01 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,15 +13,19 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v3 - - name: Set up BATS - run: | - git clone https://github.com/bats-core/bats-core.git - cd bats-core - sudo ./install.sh /usr/local + - name: Set up BATS + run: | + sudo apt-get update + sudo apt-get install -y bats - - name: Run tests - run: | - bats test_cf_ufw.bats + - name: Set up test environment + run: | + sudo apt-get install -y ufw curl + + - name: Run tests + run: | + cd tests + sudo bats cloudflare_ufw_updater.bats \ No newline at end of file diff --git a/.gitignore b/.gitignore index b6e4761..d77b176 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ __pycache__/ # C extensions *.so +.idea +.vscode # Distribution / packaging .Python diff --git a/Dockerfile b/Dockerfile index a1c9530..12c55eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,17 @@ -# Use a lightweight base image with shell capabilities -FROM alpine:latest +FROM ubuntu:latest -# Install curl and ufw (Uncomplicated Firewall) -RUN apk add --no-cache curl ufw +# Install dependencies +RUN apt-get update && \ + apt-get install -y ufw curl -# Copy the script into the container -COPY cf_ufw.sh /cf_ufw.sh +# Create a directory for the script +RUN mkdir /app -# Set the script as executable -RUN chmod +x /cf_ufw.sh +# Copy the script to the container +COPY cloudflare-ufw-updater.sh /app/ -# Set the entrypoint to run the script -ENTRYPOINT ["/cf_ufw.sh"] +# Make the script executable +RUN chmod +x /app/cloudflare-ufw-updater.sh + +# Set the entrypoint to the script +ENTRYPOINT ["/app/cloudflare-ufw-updater.sh"] \ No newline at end of file diff --git a/Dockerfile.test b/Dockerfile.test deleted file mode 100644 index 8087924..0000000 --- a/Dockerfile.test +++ /dev/null @@ -1,18 +0,0 @@ -# Use a base image with shell capabilities -FROM ubuntu:latest - -# Install curl, ufw, git, and other dependencies -RUN apt-get update && \ - apt-get install -y curl ufw git - -# Install BATS -RUN git clone https://github.com/bats-core/bats-core.git && \ - cd bats-core && \ - ./install.sh /usr/local - -# Copy the script and the test file into the container -COPY cf_ufw.sh /cf_ufw.sh -COPY test_cf_ufw.bats /test_cf_ufw.bats - -# Set the entrypoint to run BATS with the test file -ENTRYPOINT ["bats", "/test_cf_ufw.bats"] diff --git a/README.md b/README.md index 13a8662..cb2e7e8 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,105 @@ # Cloudflare UFW Updater -This repository contains a script `cf_ufw.sh` that automatically updates UFW rules to allow only HTTP and HTTPS traffic from Cloudflare IP addresses, ensuring a secure and up-to-date firewall. +A Bash script to automatically update UFW (Uncomplicated Firewall) rules to allow incoming HTTP and HTTPS traffic only from Cloudflare IP addresses. + +## Features + +- Fetches the latest Cloudflare IP addresses (IPv4 and IPv6) from the official Cloudflare API. +- Updates UFW rules to allow incoming traffic on specified ports only from Cloudflare IP addresses. +- Supports customization through a configuration file or environment variables. +- Provides logging functionality to track script execution and any errors encountered. +- Includes error handling and dependency checks to ensure smooth operation. +- Supports backup and restore of UFW rules for easy rollback if needed. ## Prerequisites -- UFW (Uncomplicated Firewall) installed and enabled -- `curl` command-line tool installed +- UFW (Uncomplicated Firewall) installed and enabled on your system. +- Bash shell environment. +- `curl` command-line tool for fetching Cloudflare IP addresses. ## Installation -1. Clone the repository: - -`git clone https://github.com/yourusername/cloudflare-ufw-updater.git` +1. Clone the repository or download the script file: -2. Change to the repository directory: + ```bash + git clone https://github.com/yourusername/cloudflare-ufw-updater.git + ``` -`cd cloudflare-ufw-updater` +2. Make the script executable: -3. Make the script executable: + ```bash + chmod +x cloudflare-ufw-updater.sh + ``` -`chmod +x cf_ufw.sh` +3. (Optional) Create a configuration file at `/etc/cloudflare-ufw-updater.conf` to customize the script's behavior. See the "Configuration" section for more details. ## Usage -You can run the script manually: +Run the script with root privileges: -`./cf_ufw.sh` +```bash +sudo ./cloudflare-ufw-updater.sh +``` -To schedule the script to run automatically every day, follow these steps: +The script will fetch the latest Cloudflare IP addresses, update the UFW rules, and reload UFW to apply the changes. -1. Open the root user's crontab: +## Configuration -`sudo crontab -e` +The script can be customized through a configuration file or environment variables. -2. Add the following line to the end of the file, replacing `/path/to/script` with the actual path to the `cf_ufw.sh` script: +### Configuration File -`@daily /path/to/script/cf_ufw.sh &> /dev/null` +Create a configuration file at `/etc/cloudflare-ufw-updater.conf` with the following variables: -3. Save and exit the editor. The script will now run once a day, updating your UFW rules to the latest Cloudflare IP ranges. +```bash +# Cloudflare IP address URLs +CLOUDFLARE_IPV4_URL="https://www.cloudflare.com/ips-v4" +CLOUDFLARE_IPV6_URL="https://www.cloudflare.com/ips-v6" -## Inspired by -[https://github.com/Paul-Reed/cloudflare-ufw/blob/master/cloudflare-ufw.sh](https://github.com/Paul-Reed/cloudflare-ufw/blob/master/cloudflare-ufw.sh) +# Allowed HTTP/HTTPS ports +ALLOWED_HTTP_PORTS="80,443,8080" -[https://github.com/jakejarvis/cloudflare-ufw-updater/](https://github.com/jakejarvis/cloudflare-ufw-updater/) +# Cloudflare UFW rule label +CLOUDFLARE_RULE_LABEL="Cloudflare" -## Contributing +# Log file path +LOG_FILE="/var/log/cloudflare-ufw-updater.log" + +# Backup file path +BACKUP_FILE="/etc/ufw/cloudflare-ufw-updater.backup" +``` + +Adjust the values according to your requirements. + +### Environment Variables + +You can also set configuration values using environment variables. The script will prioritize environment variables over the values in the configuration file. + +Example: -If you'd like to contribute to this project, please submit a pull request with your changes or open an issue to discuss your ideas. +```bash +LOG_FILE="/path/to/custom/log.txt" ./cloudflare-ufw-updater.sh +``` + +## Logging + +The script logs its execution details and any errors encountered to the specified log file (default: `/var/log/cloudflare-ufw-updater.log`). You can customize the log file path in the configuration file or through the `LOG_FILE` environment variable. + +## Backup and Restore + +The script automatically creates a backup of the current UFW rules before making any changes. The backup file is stored at the path specified by the `BACKUP_FILE` variable (default: `/etc/ufw/cloudflare-ufw-updater.backup`). + +To restore the UFW rules from the backup file, run the script with the `--restore` flag: + +```bash +sudo ./cloudflare-ufw-updater.sh --restore +``` ## License -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information. +This script is released under the [MIT License](LICENSE). + +## Contributing + +Contributions are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request on the [GitHub repository](https://github.com/yourusername/cloudflare-ufw-updater). +```` \ No newline at end of file diff --git a/cf_ufw.sh b/cf_ufw.sh deleted file mode 100644 index 58e32b6..0000000 --- a/cf_ufw.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/sh - -# This script updates the UFW rules to permit only HTTP and HTTPS traffic originating from Cloudflare IP addresses. -# For further information and documentation, visit: -# https://github.com/thomasvincent/cloudflare-ufw-updater/blob/master/README.md - -set -eu - -# Define variables -CLOUDFLARE_IP_FILE=$(mktemp) -CLOUDFLARE_IPV4_URL="https://www.cloudflare.com/ips-v4" -CLOUDFLARE_IPV6_URL="https://www.cloudflare.com/ips-v6" -ALLOWED_HTTP_PORTS="80,443" -CLOUDFLARE_RULE_LABEL="Cloudflare" - -# Check dependencies -check_dependencies() { - for cmd in ufw curl; do - if ! command -v "$cmd" > /dev/null; then - printf "Command not found in PATH: %s\n" "$cmd" - exit 1 - fi - done -} - -# Check permissions -check_permissions() { - if [ "$(id -u)" -ne 0 ]; then - printf "This script must be run as root. Aborting.\n" - exit 1 - fi -} - -# Fetch latest IPv4 addresses -fetch_ipv4_addresses() { - if ! curl -s --retry 3 --retry-delay 5 "${CLOUDFLARE_IPV4_URL}" > "$CLOUDFLARE_IP_FILE"; then - printf "Failed to fetch IPv4 addresses: %s\n" "$(curl -s -o /dev/null -w %{http_code})" - exit 1 - fi -} - -# Fetch latest IPv6 addresses -fetch_ipv6_addresses() { - if ! curl -s --retry 3 --retry-delay 5 "${CLOUDFLARE_IPV6_URL}" >> "$CLOUDFLARE_IP_FILE"; then - printf "Failed to fetch IPv6 addresses: %s\n" "$(curl -s -o /dev/null -w %{http_code})" - exit 1 - fi -} - -# Update UFW rules -update_ufw_rules() { - while IFS= read -r ip; do - ufw allow from "$ip" to any port "$ALLOWED_HTTP_PORTS" proto tcp comment "$CLOUDFLARE_RULE_LABEL" - done < "$CLOUDFLARE_IP_FILE" -} - -# Main function -main() { - check_dependencies - check_permissions - fetch_ipv4_addresses - fetch_ipv6_addresses - - # Optional: Close temporary file explicitly - # cat "$CLOUDFLARE_IP_FILE" | update_ufw_rules - - update_ufw_rules - rm -f "$CLOUDFLARE_IP_FILE" # Ensure removal even on errors - ufw reload -} - -main diff --git a/cloudflare-ufw-updater.conf b/cloudflare-ufw-updater.conf new file mode 100644 index 0000000..dced00a --- /dev/null +++ b/cloudflare-ufw-updater.conf @@ -0,0 +1,20 @@ +# Cloudflare UFW Updater Configuration File + +# URLs to fetch the Cloudflare IP addresses +CLOUDFLARE_IPV4_URL="https://www.cloudflare.com/ips-v4" +CLOUDFLARE_IPV6_URL="https://www.cloudflare.com/ips-v6" + +# Ports to allow HTTP and HTTPS traffic +ALLOWED_HTTP_PORTS="80,443" + +# Label used for UFW rules +CLOUDFLARE_RULE_LABEL="Cloudflare" + +# Path to the log file +LOG_FILE="/var/log/cloudflare-ufw-updater.log" + +# Path to the backup file for UFW rules +BACKUP_FILE="/etc/ufw/cloudflare-ufw-updater.backup" + +# Optional: Define the minimum required version of UFW +MIN_UFW_VERSION="0.36" diff --git a/cloudflare-ufw-updater.sh b/cloudflare-ufw-updater.sh new file mode 100644 index 0000000..2f63b8d --- /dev/null +++ b/cloudflare-ufw-updater.sh @@ -0,0 +1,174 @@ +#!/bin/bash + +# MIT License +# +# Copyright (c) 2022-2024 Thomas Vincent +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This script updates the UFW rules to permit only HTTP and HTTPS traffic +# originating from Cloudflare IP addresses. For further information and +# documentation, visit: +# https://github.com/thomasvincent/cloudflare-ufw-updater/blob/master/README.md + +set -euo pipefail + +# Check Bash version compatibility - Mac OS X ships with 3.2.* for several releases. Use brew bash. =( +if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then + echo "This script requires Bash version 4.0 or higher. You are using Bash version ${BASH_VERSION}. Please upgrade your Bash." >&2 + exit 1 +fi + +readonly CLOUDFLARE_IP_FILE="$(mktemp)" +readonly CLOUDFLARE_IPV4_URL="https://www.cloudflare.com/ips-v4" +readonly CLOUDFLARE_IPV6_URL="https://www.cloudflare.com/ips-v6" +readonly ALLOWED_HTTP_PORTS="80,443" +readonly CLOUDFLARE_RULE_LABEL="Cloudflare" +readonly LOG_FILE="/var/log/cloudflare-ufw-updater.log" +readonly CONFIG_FILE="/etc/cloudflare-ufw-updater.conf" +readonly BACKUP_FILE="/etc/ufw/cloudflare-ufw-updater.backup" +readonly MIN_UFW_VERSION="0.36" + +# Clean up temporary files on exit +trap 'rm -f "$CLOUDFLARE_IP_FILE"' EXIT + +check_dependencies() { + local missing=0 + for cmd in ufw curl; do + if ! command -v "$cmd" &>/dev/null; then + log_error "Command not found in PATH: $cmd" + missing=1 + fi + done + if (( missing )); then + exit 1 + fi +} + +check_permissions() { + if (( EUID != 0 )); then + log_error "This script must be run as root. Aborting." + exit 1 + fi +} + +check_ufw_version() { + local ufw_version + ufw_version="$(ufw --version | awk '{print $2}')" + if ! version_greater_equal "$ufw_version" "$MIN_UFW_VERSION"; then + log_error "UFW version $ufw_version is not compatible. Minimum required version is $MIN_UFW_VERSION." + exit 1 + fi +} + +version_greater_equal() { + printf '%s\n%s' "$1" "$2" | sort -C -V +} + +fetch_addresses() { + local url="$1" + if ! curl -s --retry 3 --retry-delay 5 "$url" >> "$CLOUDFLARE_IP_FILE"; then + log_error "Failed to fetch addresses from $url" + exit 1 + fi +} + +update_ufw_rules() { + # Delete existing Cloudflare rules + ufw delete allow from any to any port "$ALLOWED_HTTP_PORTS" proto tcp comment "$CLOUDFLARE_RULE_LABEL" + + while IFS= read -r ip; do + ufw allow from "$ip" to any port "$ALLOWED_HTTP_PORTS" proto tcp comment "$CLOUDFLARE_RULE_LABEL" + log_message "Allowing traffic from $ip to ports $ALLOWED_HTTP_PORTS" + done < "$CLOUDFLARE_IP_FILE" +} + +log_message() { + printf "%s - %s\n" "$(date +"%Y-%m-%d %H:%M:%S")" "$1" | tee -a "$LOG_FILE" +} + +log_error() { + printf "%s - [ERROR] %s\n" "$(date +"%Y-%m-%d %H:%M:%S")" "$1" | tee -a "$LOG_FILE" >&2 +} + +load_config() { + if [[ -f "$CONFIG_FILE" ]]; then + # shellcheck source=/etc/cloudflare-ufw-updater.conf + source "$CONFIG_FILE" + fi + + # Override config values with environment variables if set + CLOUDFLARE_IPV4_URL="${CLOUDFLARE_IPV4_URL:-$CLOUDFLARE_IPV4_URL}" + CLOUDFLARE_IPV6_URL="${CLOUDFLARE_IPV6_URL:-$CLOUDFLARE_IPV6_URL}" + ALLOWED_HTTP_PORTS="${ALLOWED_HTTP_PORTS:-$ALLOWED_HTTP_PORTS}" + CLOUDFLARE_RULE_LABEL="${CLOUDFLARE_RULE_LABEL:-$CLOUDFLARE_RULE_LABEL}" + LOG_FILE="${LOG_FILE:-$LOG_FILE}" + BACKUP_FILE="${BACKUP_FILE:-$BACKUP_FILE}" +} + +backup_ufw_rules() { + ufw status numbered | tee "$BACKUP_FILE" + log_message "Backed up UFW rules to $BACKUP_FILE" +} + +restore_ufw_rules() { + if [[ -f "$BACKUP_FILE" ]]; then + ufw reset 1>/dev/null + while read -r rule; do + # Skip comment lines + if [[ $rule =~ ^\s*# ]]; then + continue + fi + ufw "$rule" + done < "$BACKUP_FILE" + log_message "Restored UFW rules from $BACKUP_FILE" + else + log_error "Backup file not found: $BACKUP_FILE" + fi +} + +main() { + check_dependencies + check_permissions + check_ufw_version + load_config + + if [[ "$1" == "--restore" ]]; then + restore_ufw_rules + exit 0 + fi + + log_message "Starting Cloudflare UFW Updater" + + log_message "Fetching Cloudflare IP addresses..." + fetch_addresses "$CLOUDFLARE_IPV4_URL" + fetch_addresses "$CLOUDFLARE_IPV6_URL" + + backup_ufw_rules + + log_message "Updating UFW rules..." + update_ufw_rules + + log_message "Reloading UFW..." + ufw reload + + log_message "UFW rules updated successfully." +} + +main "$@" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index c4a825f..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: "3.9" - -services: - bats_test: - build: - context: . - dockerfile: Dockerfile.test diff --git a/test_cf_ufw.bats b/test_cf_ufw.bats deleted file mode 100644 index 82cb291..0000000 --- a/test_cf_ufw.bats +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bats - -@test "Check if the script fetches IP ranges and updates UFW rules" { - # Set up a temporary UFW rules file for testing - export UFW_RULES_FILE="$(mktemp)" - echo "# temporary UFW rules file for testing" > "$UFW_RULES_FILE" - - # Replace the `ufw` command with a mock that appends the rule to the temporary file - function ufw() { - echo "$@" >> "$UFW_RULES_FILE" - } - - # Run the script - ./cf_ufw.sh - - # Check if the temporary UFW rules file contains the expected rules - grep -q "allow from" "$UFW_RULES_FILE" - [ "$?" -eq 0 ] - - # Clean up - unset UFW_RULES_FILE - unalias ufw -} diff --git a/tests/cloudflare_ufw_updater.bats b/tests/cloudflare_ufw_updater.bats new file mode 100755 index 0000000..79014d3 --- /dev/null +++ b/tests/cloudflare_ufw_updater.bats @@ -0,0 +1,32 @@ +#!/usr/bin/env bats + +setup() { + # Set up the test environment + docker-compose up -d + docker cp ../cloudflare-ufw-updater.sh cloudflare-ufw-updater:/app/ + docker exec cloudflare-ufw-updater chmod +x /app/cloudflare-ufw-updater.sh +} + +teardown() { + # Clean up the test environment + docker-compose down +} + +@test "Cloudflare UFW Updater script updates UFW rules" { + run docker exec cloudflare-ufw-updater /app/cloudflare-ufw-updater.sh + assert_success + assert_output --partial "UFW rules updated successfully" +} + +@test "Cloudflare UFW Updater script backs up UFW rules" { + run docker exec cloudflare-ufw-updater /app/cloudflare-ufw-updater.sh + assert_success + run docker exec cloudflare-ufw-updater ls /etc/ufw/cloudflare-ufw-updater.backup + assert_success +} + +@test "Cloudflare UFW Updater script restores UFW rules from backup" { + run docker exec cloudflare-ufw-updater /app/cloudflare-ufw-updater.sh --restore + assert_success + assert_output --partial "Restored UFW rules from backup" +} \ No newline at end of file diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml new file mode 100644 index 0000000..9b84c26 --- /dev/null +++ b/tests/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3' +services: + cloudflare-ufw-updater: + image: ubuntu:latest + container_name: cloudflare-ufw-updater + tty: true + volumes: + - ./:/app + command: tail -f /dev/null \ No newline at end of file