Skip to content

Conversation

@pandafy
Copy link
Member

@pandafy pandafy commented Dec 30, 2025

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • I have updated the documentation.

Reference to Existing Issue

Fixes #490

TODOS

  • Requires extensive manual testing

Summary by CodeRabbit

  • New Features

    • VPN functionality can now be optionally disabled during installation.
    • Celery service network configuration is now environment-driven for greater flexibility.
  • Improvements

    • OpenVPN is now an optional dependency rather than required.
    • VPN-related resources initialize only when VPN is enabled, reducing unnecessary setup.
    • Updated OpenVPN service restart behavior for better reliability.

✏️ Tip: You can customize this high-level summary in your review settings.

@pandafy pandafy force-pushed the issues/490-openvpn-not-required branch from 92a1eaf to eb16b2e Compare December 30, 2025 19:44
@nemesifier
Copy link
Member

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

📝 Walkthrough

Walkthrough

The changes implement conditional VPN activation in the deployment configuration. When OpenVPN is disabled (selecting 'n' during installation), VPN-related environment variables are cleared, service dependencies are made optional, and data initialization logic is gated behind VPN-enabled checks to prevent unnecessary service startup.

Changes

Cohort / File(s) Summary
Environment & Installation Configuration
.env, deploy/auto-install.sh
Added CELERY_SERVICE_NETWORK_MODE environment variable. Modified installation script to clear both VPN_DOMAIN and CELERY_SERVICE_NETWORK_MODE when VPN is disabled (input 'n'), replacing previous fallback to "example.com".
Docker Service Composition
docker-compose.yml
Made openvpn dependency optional (required: false) in x-celery-depends-on. Replaced hard-coded network_mode: "service:openvpn" with environment-driven CELERY_SERVICE_NETWORK_MODE for celery and celery-monitoring services. Changed openvpn restart policy from always to on-failure.
Data Initialization
images/openwisp_dashboard/load_init_data.py
Introduced conditional VPN initialization gated by is_vpn_enabled check based on VPN_DOMAIN. Default CA, Certificate, VPN, and VPN template creation are now skipped when VPN is disabled. Topology creation similarly depends on both VPN enablement and topology module flag.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 VPN now bends to our command,
When 'n' is pressed, we understand—
Tun device shall rest in peace,
No more container turbulence!
Optional dependencies we embrace,
Configuration wins the race! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The PR description is incomplete: manual testing, test cases, and documentation updates remain unchecked despite being required by the template. Complete all checklist items or clearly document why they cannot be completed. Update documentation and add test cases for the VPN enablement logic.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective of the PR: making the OpenVPN container optional when disabled via the install script.
Linked Issues check ✅ Passed Code changes directly address issue #490: environment-driven CELERY_SERVICE_NETWORK_MODE, conditional VPN initialization, and proper VPN disabling logic when vpn_domain is 'n'.
Out of Scope Changes check ✅ Passed All changes are properly scoped to make OpenVPN optional: environment variables, install script logic, docker-compose configuration, and conditional VPN initialization.
✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
images/openwisp_dashboard/load_init_data.py (1)

245-259: Consider refactoring for clarity.

While the logic is technically correct (short-circuit evaluation ensures default_vpn is only referenced when defined), the code structure could be clearer. Static analyzers and developers might flag the reference to default_vpn on line 259 when it's defined conditionally on line 248.

🔎 Suggested refactors for better clarity

Option 1: Nest topology creation inside VPN initialization block

 is_vpn_enabled = os.environ.get("VPN_DOMAIN", "") != ""
 if is_vpn_enabled:
     default_ca = create_default_ca()
     default_cert = create_default_cert(default_ca)
     default_vpn = create_default_vpn(
         default_ca,
         default_cert,
     )
     create_default_vpn_template(default_vpn)
+    
+    if env_bool(os.environ.get("USE_OPENWISP_TOPOLOGY")):
+        Topology = load_model("topology", "Topology")
+        create_default_topology(default_vpn)

 create_default_credentials()
 create_ssh_key_template()

-if is_vpn_enabled and env_bool(os.environ.get("USE_OPENWISP_TOPOLOGY")):
-    Topology = load_model("topology", "Topology")
-    create_default_topology(default_vpn)

Option 2: Initialize default_vpn = None for clarity

 is_vpn_enabled = os.environ.get("VPN_DOMAIN", "") != ""
+default_vpn = None
 if is_vpn_enabled:
     default_ca = create_default_ca()
     default_cert = create_default_cert(default_ca)
     default_vpn = create_default_vpn(
         default_ca,
         default_cert,
     )
     create_default_vpn_template(default_vpn)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85f3ce1 and eb16b2e.

📒 Files selected for processing (4)
  • .env
  • deploy/auto-install.sh
  • docker-compose.yml
  • images/openwisp_dashboard/load_init_data.py
🧰 Additional context used
🧬 Code graph analysis (1)
images/openwisp_dashboard/load_init_data.py (1)
images/common/openwisp/utils.py (1)
  • env_bool (29-30)
🪛 dotenv-linter (4.0.0)
.env

[warning] 61-61: [UnorderedKey] The CELERY_SERVICE_NETWORK_MODE key should go before the CERT_ADMIN_EMAIL key

(UnorderedKey)

🔇 Additional comments (4)
.env (1)

61-61: LGTM! Environment-driven network mode configuration.

The new CELERY_SERVICE_NETWORK_MODE variable correctly defaults to service:openvpn for backward compatibility and will be cleared when VPN is disabled during installation.

Note: The static analysis warning about key ordering is cosmetic and can be safely ignored.

docker-compose.yml (1)

10-10: Good! Makes OpenVPN dependency optional.

This correctly allows dependent services to start even when OpenVPN is disabled.

images/openwisp_dashboard/load_init_data.py (1)

244-244: Good! Clear VPN enablement check.

The is_vpn_enabled check correctly determines VPN status based on the VPN_DOMAIN environment variable, aligning with the installation script behavior.

deploy/auto-install.sh (1)

152-153: Logic is correct; minor style inconsistency noted.

The case-insensitive check and variable clearing correctly implement VPN disabling. However, there's minor inconsistency in quoting style (line 152 uses quotes around the variable name, line 153 doesn't).

More importantly, this relates to the concern raised in docker-compose.yml: setting CELERY_SERVICE_NETWORK_MODE to an empty string may cause issues when Docker Compose evaluates network_mode: "${CELERY_SERVICE_NETWORK_MODE}". Please verify the behavior or consider using an explicit fallback value like "bridge".

- .env
<<: *celery-depends-on
network_mode: "service:openvpn"
network_mode: "${CELERY_SERVICE_NETWORK_MODE}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "docker-compose.yml" -o -name "docker-compose.yaml" | head -5

Repository: openwisp/docker-openwisp

Length of output: 88


🏁 Script executed:

# Also search for auto-install.sh to understand when CELERY_SERVICE_NETWORK_MODE is set
find . -name "auto-install.sh" | head -5

Repository: openwisp/docker-openwisp

Length of output: 92


🏁 Script executed:

# Check if docker-compose or docker compose is available for testing
which docker docker-compose 2>/dev/null || echo "Docker not available in sandbox"

Repository: openwisp/docker-openwisp

Length of output: 99


🏁 Script executed:

cat -n ./docker-compose.yml | sed -n '80,105p'

Repository: openwisp/docker-openwisp

Length of output: 1055


🏁 Script executed:

grep -n "CELERY_SERVICE_NETWORK_MODE" ./deploy/auto-install.sh -A 2 -B 2

Repository: openwisp/docker-openwisp

Length of output: 245


🏁 Script executed:

# Also search for how CELERY_SERVICE_NETWORK_MODE is initialized/used
grep -rn "CELERY_SERVICE_NETWORK_MODE" . --include="*.sh" --include="*.yml"

Repository: openwisp/docker-openwisp

Length of output: 288


🏁 Script executed:

# Let's see more context around the VPN configuration to understand the full picture
cat -n ./deploy/auto-install.sh | sed -n '140,165p'

Repository: openwisp/docker-openwisp

Length of output: 1091


🌐 Web query:

Docker Compose empty environment variable network_mode behavior

💡 Result:

Short answer: Docker Compose variable interpolation replaces an unset variable with an empty string. For options like network_mode that are set in an earlier file, an empty-string substitution does NOT clear the key — Compose/YAML either treats the empty value as no effective override (so the previous value remains) or the empty value is invalid. In practice this means using ${VAR} where VAR is unset will not reliably "unset" network_mode. See Docker docs on variable interpolation and discussion/workarounds on Stack Overflow/GitHub. [1][2]

Practical options:

  • Provide a real value (e.g. bridge) or explicitly set networks: [ default ] in your override to restore default networking. [2][3]
  • Use a sentinel string in the override (and special-case it) — many recommend setting network_mode to a non-host dummy value or explicitly configure networks instead. [3]
  • Remove the key in the override file (YAML/Compose can’t remove a scalar set in a base file by assigning an empty interpolated value); instead override by specifying networks or a valid network_mode. [3]

Sources:

  1. Docker Compose interpolation docs (variable substitution behavior). [https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/] [1]
  2. Docker docs — environment variables / set behavior. [https://docs.docker.com/compose/how-tos/environment-variables/set-environment-variables/] [2]
  3. Community/StackOverflow discussion on unsetting network_mode and override workarounds. [https://stackoverflow.com/questions/69584848/how-to-unset-network-mode-in-an-overriding-docker-compose-file] [3]

🌐 Web query:

Docker Compose network_mode empty string value validation

💡 Result:

Short answer: Docker Compose requires network_mode to be a string (one of the allowed values like "bridge", "host", "none", "service:NAME", "container:ID"). An empty string is not treated as a valid string by the YAML/Compose parser (empty scalar → null), so Compose will reject it. Use either remove/omit network_mode (so Compose uses the default network), set networks: [default], or set a valid network_mode value (for no networking use "none"). [1][2]

Sources:

  • Compose specification — network_mode rules and allowed values. [1]
  • StackOverflow / user reports showing Compose rejects non-string/empty list and that network_mode must be a string. [2]

Fix invalid network_mode configuration when VPN is disabled.

When VPN is disabled, CELERY_SERVICE_NETWORK_MODE is set to an empty string at lines 85 and 99 in docker-compose.yml, resulting in network_mode: "". Docker Compose treats empty YAML scalars as null and rejects empty string values for network_mode, which will cause container startup failures.

Use Docker Compose conditional syntax to omit the network_mode key entirely when VPN is disabled, or explicitly set it to a valid value like "bridge".

🤖 Prompt for AI Agents
In docker-compose.yml around line 85 (and similarly line 99), network_mode is
set to the variable CELERY_SERVICE_NETWORK_MODE which can be an empty string
when VPN is disabled; update these entries to avoid emitting network_mode: "" by
using Docker Compose conditional interpolation to omit the key when empty or
default to a valid value (e.g., bridge). Concretely, replace the current
assignment with an expression that either expands to nothing (so the
network_mode key is omitted) or uses a default like
"${CELERY_SERVICE_NETWORK_MODE:-bridge}", ensuring docker-compose receives
either a valid mode or no network_mode key at all.

openvpn:
image: openwisp/openwisp-openvpn:latest
restart: always
restart: on-failure
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Restart policy change doesn't prevent container from starting.

Changing the restart policy from always to on-failure reduces restart attempts when OpenVPN fails, but doesn't prevent the container from starting initially when VPN is disabled. The container will still attempt to start and likely fail due to missing configuration or /dev/net/tun in LXC environments.

Consider using Docker Compose profiles to conditionally include/exclude the openvpn service entirely when VPN is disabled:

🔎 Proposed approach using profiles

In docker-compose.yml:

 openvpn:
   image: openwisp/openwisp-openvpn:latest
-  restart: on-failure
+  restart: always
+  profiles:
+    - vpn

Then conditionally enable the profile in the startup command based on whether VPN is enabled.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In docker-compose.yml at line 171, changing the restart policy to on-failure
does not prevent the openvpn service from attempting to start when VPN is
disabled; instead add a Docker Compose profile to the openvpn service (e.g.,
service key "profiles: ['openvpn']") so the service is excluded by default,
update any documentation/startup scripts to include the profile when VPN is
enabled (docker-compose --profile openvpn up) and ensure any
orchestration/launch scripts set that flag based on the VPN-enabled config; this
removes the service from the default composition and prevents container start
when VPN is disabled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

Status: To do (general)

Development

Successfully merging this pull request may close these issues.

[bug] If I select ‘n’ in the install script for OpenVPN, the OpenVPN container is still started.

3 participants