Skip to content

Conversation

@wolf31o2
Copy link
Member

@wolf31o2 wolf31o2 commented Nov 26, 2025

Summary by cubic

Run the user startup script with bash instead of executing it directly. This prevents failures when the script isn’t executable or lacks a shebang and ensures consistent shell behavior.

Written for commit 6fcc967. Summary will update automatically on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced application startup process by refining how the initialization script is executed to improve reliability.

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

Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
@wolf31o2 wolf31o2 requested review from a team as code owners November 26, 2025 21:07
@coderabbitai
Copy link

coderabbitai bot commented Nov 26, 2025

📝 Walkthrough

Walkthrough

The bin/entrypoint script was modified to change how the startup script is invoked. Previously, the script made the startup executable via chmod and then executed it directly. The change replaces this approach by invoking the startup script through bash without modifying file permissions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

  • Verify whether removing the chmod step affects intended file permissions or security posture
  • Confirm that bash invocation produces identical behavior compared to direct execution of the startup script
  • Ensure this change is compatible with all expected startup script formats and shebangs
  • Consider whether containerized environments may have specific permission or execution requirements that this change impacts

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(entrypoint): bash the user script' clearly describes the main change - switching from direct script execution to bash invocation of the user startup script.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/bash-script

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 1 file

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: 1

🧹 Nitpick comments (1)
bin/entrypoint (1)

28-30: Consider adding error handling for the user startup script.

The entrypoint continues to launch OpenVPN regardless of whether the user startup script succeeds or fails. This could lead to unexpected behavior if the user script performs critical setup steps.

Consider checking the exit code and optionally failing fast on errors:

 # Execute user startup script if it exists
 if [[ -e ${USER_STARTUP_SCRIPT} ]]; then
 	bash ${USER_STARTUP_SCRIPT}
+	USER_SCRIPT_EXIT=$?
+	if [[ ${USER_SCRIPT_EXIT} -ne 0 ]]; then
+		echo "ERROR: User startup script failed with exit code ${USER_SCRIPT_EXIT}" >&2
+		exit ${USER_SCRIPT_EXIT}
+	fi
 fi

Alternatively, if you want to log errors but continue anyway:

 # Execute user startup script if it exists
 if [[ -e ${USER_STARTUP_SCRIPT} ]]; then
 	bash ${USER_STARTUP_SCRIPT}
+	if [[ $? -ne 0 ]]; then
+		echo "WARNING: User startup script exited with error code $?" >&2
+	fi
 fi
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a35bbee and 6fcc967.

📒 Files selected for processing (1)
  • bin/entrypoint (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer

if [[ -e ${USER_STARTUP_SCRIPT} ]]; then
chmod a+x ${USER_STARTUP_SCRIPT}
${USER_STARTUP_SCRIPT}
bash ${USER_STARTUP_SCRIPT}
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:

#!/bin/bash
# Explore repository structure and find relevant files
echo "=== Repository structure ==="
git ls-files | head -30

echo -e "\n=== Searching for entrypoint-related files ==="
fd -t f "entrypoint" 2>/dev/null

echo -e "\n=== Checking bin/entrypoint file size ==="
wc -l bin/entrypoint 2>/dev/null || echo "File not found in expected location"

Repository: blinklabs-io/docker-openvpn


<shell_result>
Repository: blinklabs-io/docker-openvpn
Exit code: 0

stdout:

=== Repository structure ===
.github/CODEOWNERS
.github/dependabot.yml
.github/workflows/ci-docker.yml
.github/workflows/conventional-commits.yml
.github/workflows/publish.yml
Dockerfile
LICENSE
README.md
bin/entrypoint

=== Searching for entrypoint-related files ===
bin/entrypoint

=== Checking bin/entrypoint file size ===
37 bin/entrypoint

</shell_result>


Script executed:

#!/bin/bash
# Read the complete bin/entrypoint file
echo "=== Contents of bin/entrypoint ==="
cat -n bin/entrypoint

echo -e "\n=== Searching for USER_STARTUP_SCRIPT documentation ==="
rg -n "USER_STARTUP_SCRIPT" --type md

echo -e "\n=== Searching for entrypoint-user mentions ==="
rg -n "entrypoint-user" 

echo -e "\n=== README.md content ==="
cat README.md

Repository: blinklabs-io/docker-openvpn


<shell_result>
Repository: blinklabs-io/docker-openvpn
Exit code: 0

stdout:

=== Contents of bin/entrypoint ===
     1	#!/bin/bash
     2	
     3	CONFIG_DIR=/etc/openvpn
     4	
     5	USER_STARTUP_SCRIPT=${USER_STARTUP_SCRIPT:-/usr/local/bin/entrypoint-user.sh}
     6	
     7	ENABLE_NAT=${ENABLE_NAT:-1}
     8	NAT_SOURCE=${NAT_SOURCE:-10.8.0.0/24}
     9	NAT_DEVICE=${NAT_DEVICE:-eth0}
    10	
    11	DEBUG=${DEBUG:-0}
    12	
    13	# Create device for tun interfaces
    14	mkdir -p /dev/net
    15	if [ ! -c /dev/net/tun ]; then
    16		mknod /dev/net/tun c 10 200
    17	fi
    18	
    19	if [[ $ENABLE_NAT = 1 ]]; then
    20		iptables -t nat -A POSTROUTING -s ${NAT_SOURCE} -o ${NAT_DEVICE} -j MASQUERADE
    21		# Basic kill switch: Drop forwarded traffic by default, allow only VPN-related
    22		iptables -P FORWARD DROP
    23		iptables -A FORWARD -i tun+ -o ${NAT_DEVICE} -j ACCEPT
    24		iptables -A FORWARD -i ${NAT_DEVICE} -o tun+ -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    25	fi
    26	
    27	# Execute user startup script if it exists
    28	if [[ -e ${USER_STARTUP_SCRIPT} ]]; then
    29		bash ${USER_STARTUP_SCRIPT}
    30	fi
    31	
    32	# Redirect output to /dev/null unless in debug mode
    33	if [[ $DEBUG = 0 ]]; then
    34		exec &>/dev/null
    35	fi
    36	
    37	exec openvpn --config ${CONFIG_DIR}/openvpn.conf --cd ${CONFIG_DIR} $@

=== Searching for USER_STARTUP_SCRIPT documentation ===
README.md:14:but the location can be overridden with the `USER_STARTUP_SCRIPT` environment variable

=== Searching for entrypoint-user mentions ===
README.md:13:The image provides for the ability to provide a custom startup script. It looks for a user script at `/usr/local/bin/entrypoint-user.sh` by default,
bin/entrypoint:5:USER_STARTUP_SCRIPT=${USER_STARTUP_SCRIPT:-/usr/local/bin/entrypoint-user.sh}

=== README.md content ===
# docker-openvpn

Simple OpenVPN image with updated version, optimized for privacy-focused VPN services.

## Using the image

There is no default config provided by the image, so you'll need to provide your own.

```
docker run -d -n openvpn -v /path/to/openvpn.conf:/etc/openvpn/openvpn.conf ghcr.io/blinklabs-io/openvpn --config /etc/openvpn/openvpn.conf
```

The image provides for the ability to provide a custom startup script. It looks for a user script at `/usr/local/bin/entrypoint-user.sh` by default,
but the location can be overridden with the `USER_STARTUP_SCRIPT` environment variable

## Privacy and Security Recommendations

For a no-log, privacy-focused setup:

### OpenVPN Configuration
Use these options in your `openvpn.conf` for strong encryption and no logging:
```conf
# Disable logging
log /dev/null
verb 0

# Strong ciphers
cipher AES-256-GCM
auth SHA256
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384
tls-version-min 1.2

# Privilege drop
user nobody
group nogroup

# Other privacy settings
persist-key
persist-tun
```

### Docker Run Options
- Use Docker secrets for certificates/keys: `--secret mykey=/path/to/key`
- Run with necessary capabilities only: `--cap-add=NET_ADMIN` (avoid `--privileged`)
- For Kubernetes (Helm), use `securityContext` to limit privileges.

### Maintenance
- Rebuild images regularly to pull security updates from Debian repos.
- Scan for vulnerabilities with tools like Trivy (automated via GitHub Actions).
- Test for leaks using services like ipleak.net while connected.

**Warning**: Running in privileged mode increases security risks—use only when necessary.

</shell_result>


Script executed:

#!/bin/bash
# Check git history for bin/entrypoint
echo "=== Recent commits touching bin/entrypoint ==="
git log --oneline -20 -- bin/entrypoint 2>/dev/null || echo "No git history available"

echo -e "\n=== Show changes to line 29 ==="
git log -p -S "bash.*USER_STARTUP_SCRIPT" -- bin/entrypoint 2>/dev/null | head -100 || echo "No detailed history available"

echo -e "\n=== Git blame for line 29 ==="
git blame -L 29,29 bin/entrypoint 2>/dev/null || echo "No blame info available"

echo -e "\n=== Check current branch ==="
git branch -a 2>/dev/null | head -10
git log --oneline -5 2>/dev/null

Repository: blinklabs-io/docker-openvpn


<shell_result>
Repository: blinklabs-io/docker-openvpn
Exit code: 0

stdout:

=== Recent commits touching bin/entrypoint ===
6fcc967 fix(entrypoint): bash the user script

=== Show changes to line 29 ===

=== Git blame for line 29 ===
^6fcc967 (Chris Gianelloni 2025-11-26 16:07:13 -0500 29) 	bash ${USER_STARTUP_SCRIPT}

=== Check current branch ===
* (HEAD detached at FETCH_HEAD)
  coderabbit_19
  main
  remotes/origin/HEAD -> origin/main
  remotes/origin/main
6fcc967 fix(entrypoint): bash the user script

</shell_result>


Script executed:

#!/bin/bash
# Show the full diff of this commit
echo "=== Full diff of commit 6fcc967 ==="
git show 6fcc967 --no-patch

echo -e "\n=== Full diff content ==="
git show 6fcc967 -- bin/entrypoint

Repository: blinklabs-io/docker-openvpn


<shell_result>
Repository: blinklabs-io/docker-openvpn
Exit code: 0

stdout:

=== Full diff of commit 6fcc967 ===
commit 6fcc967b511b2ae36d609814261776f0c6b4f4b9
Author: Chris Gianelloni <wolf31o2@blinklabs.io>
Date:   Wed Nov 26 16:07:13 2025 -0500

    fix(entrypoint): bash the user script
    
    Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>

=== Full diff content ===
commit 6fcc967b511b2ae36d609814261776f0c6b4f4b9
Author: Chris Gianelloni <wolf31o2@blinklabs.io>
Date:   Wed Nov 26 16:07:13 2025 -0500

    fix(entrypoint): bash the user script
    
    Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>

diff --git a/bin/entrypoint b/bin/entrypoint
new file mode 100644
index 0000000..a9773e3
--- /dev/null
+++ b/bin/entrypoint
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+CONFIG_DIR=/etc/openvpn
+
+USER_STARTUP_SCRIPT=${USER_STARTUP_SCRIPT:-/usr/local/bin/entrypoint-user.sh}
+
+ENABLE_NAT=${ENABLE_NAT:-1}
+NAT_SOURCE=${NAT_SOURCE:-10.8.0.0/24}
+NAT_DEVICE=${NAT_DEVICE:-eth0}
+
+DEBUG=${DEBUG:-0}
+
+# Create device for tun interfaces
+mkdir -p /dev/net
+if [ ! -c /dev/net/tun ]; then
+	mknod /dev/net/tun c 10 200
+fi
+
+if [[ $ENABLE_NAT = 1 ]]; then
+	iptables -t nat -A POSTROUTING -s ${NAT_SOURCE} -o ${NAT_DEVICE} -j MASQUERADE
+	# Basic kill switch: Drop forwarded traffic by default, allow only VPN-related
+	iptables -P FORWARD DROP
+	iptables -A FORWARD -i tun+ -o ${NAT_DEVICE} -j ACCEPT
+	iptables -A FORWARD -i ${NAT_DEVICE} -o tun+ -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+fi
+
+# Execute user startup script if it exists
+if [[ -e ${USER_STARTUP_SCRIPT} ]]; then
+	bash ${USER_STARTUP_SCRIPT}
+fi
+
+# Redirect output to /dev/null unless in debug mode
+if [[ $DEBUG = 0 ]]; then
+	exec &>/dev/null
+fi
+
+exec openvpn --config ${CONFIG_DIR}/openvpn.conf --cd ${CONFIG_DIR} $@

</shell_result>


Script executed:

#!/bin/bash
# Check if entrypoint exists in any previous commits
echo "=== Check if bin/entrypoint exists in parent commits ==="
git log --all --full-history -- bin/entrypoint | grep -c "commit" || echo "Only 1 commit (this is the first)"

echo -e "\n=== Show parent commit ==="
git log --oneline -5

echo -e "\n=== Verify this is the initial commit for this file ==="
git log --follow --all -- bin/entrypoint | tail -20

Repository: blinklabs-io/docker-openvpn


<shell_result>
Repository: blinklabs-io/docker-openvpn
Exit code: 0

stdout:

=== Check if bin/entrypoint exists in parent commits ===
6

=== Show parent commit ===
6fcc967 fix(entrypoint): bash the user script

=== Verify this is the initial commit for this file ===

    feat: run optional user startup script (#3)
    
    Signed-off-by: Aurora Gaffney <aurora@blinklabs.io>

commit 579e0f7554398789439380b8c5d1cb26356ce05e
Author: Aurora Gaffney <aurora@blinklabs.io>
Date:   Sat May 10 08:45:38 2025 -0400

    feat: support for configuring iptables masquerading for NAT (#2)
    
    Signed-off-by: Aurora Gaffney <aurora@blinklabs.io>

commit 363b9ca7d2882ee55ac26927c6e405b1423d0f6b
Author: Aurora Gaffney <aurora@blinklabs.io>
Date:   Fri May 2 14:17:56 2025 -0400

    feat: initial image (#1)
    
    Signed-off-by: Aurora Gaffney <aurora@blinklabs.io>

</shell_result>


Script executed:

#!/bin/bash
# Get the previous version before the bash change
echo "=== Show previous version of bin/entrypoint ==="
git show 6fcc967^:bin/entrypoint 2>/dev/null || echo "Could not get previous version"

echo -e "\n=== Show diff between previous and current ==="
git diff 6fcc967^ 6fcc967 -- bin/entrypoint

Repository: blinklabs-io/docker-openvpn


<shell_result>
Repository: blinklabs-io/docker-openvpn
Exit code: 128

stdout:

=== Show previous version of bin/entrypoint ===
Could not get previous version

=== Show diff between previous and current ===

stderr:

fatal: bad revision '6fcc967^'

</shell_result>


Script executed:

#!/bin/bash
# Check full history of changes to bin/entrypoint
echo "=== Full git log with patches for bin/entrypoint ==="
git log -p --all -- bin/entrypoint | head -200

echo -e "\n=== Check origin/main to see full history ==="
git log --oneline origin/main -- bin/entrypoint 2>/dev/null | head -10

Repository: blinklabs-io/docker-openvpn


<shell_result>
Repository: blinklabs-io/docker-openvpn
Exit code: 0

stdout:

=== Full git log with patches for bin/entrypoint ===

=== Check origin/main to see full history ===
b9144cb fix: enhance OpenVPN image security and privacy (#16)
8e4b3c1 feat: hide logs by default (#4)
533bc7c feat: run optional user startup script (#3)
579e0f7 feat: support for configuring iptables masquerading for NAT (#2)
363b9ca feat: initial image (#1)

</shell_result>


Script executed:

#!/bin/bash
# Get the version from origin/main (before this PR)
echo "=== Version from origin/main HEAD ==="
git show origin/main:bin/entrypoint 2>/dev/null | cat -n

echo -e "\n=== Compare: lines 27-30 from origin/main ==="
git show origin/main:bin/entrypoint 2>/dev/null | sed -n '27,30p'

echo -e "\n=== Compare: current version lines 27-30 ==="
sed -n '27,30p' bin/entrypoint

Repository: blinklabs-io/docker-openvpn


<shell_result>
Repository: blinklabs-io/docker-openvpn
Exit code: 0

stdout:

=== Version from origin/main HEAD ===
     1	#!/bin/bash
     2	
     3	CONFIG_DIR=/etc/openvpn
     4	
     5	USER_STARTUP_SCRIPT=${USER_STARTUP_SCRIPT:-/usr/local/bin/entrypoint-user.sh}
     6	
     7	ENABLE_NAT=${ENABLE_NAT:-1}
     8	NAT_SOURCE=${NAT_SOURCE:-10.8.0.0/24}
     9	NAT_DEVICE=${NAT_DEVICE:-eth0}
    10	
    11	DEBUG=${DEBUG:-0}
    12	
    13	# Create device for tun interfaces
    14	mkdir -p /dev/net
    15	if [ ! -c /dev/net/tun ]; then
    16		mknod /dev/net/tun c 10 200
    17	fi
    18	
    19	if [[ $ENABLE_NAT = 1 ]]; then
    20		iptables -t nat -A POSTROUTING -s ${NAT_SOURCE} -o ${NAT_DEVICE} -j MASQUERADE
    21		# Basic kill switch: Drop forwarded traffic by default, allow only VPN-related
    22		iptables -P FORWARD DROP
    23		iptables -A FORWARD -i tun+ -o ${NAT_DEVICE} -j ACCEPT
    24		iptables -A FORWARD -i ${NAT_DEVICE} -o tun+ -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    25	fi
    26	
    27	# Execute user startup script if it exists
    28	if [[ -e ${USER_STARTUP_SCRIPT} ]]; then
    29		chmod a+x ${USER_STARTUP_SCRIPT}
    30		${USER_STARTUP_SCRIPT}
    31	fi
    32	
    33	# Redirect output to /dev/null unless in debug mode
    34	if [[ $DEBUG = 0 ]]; then
    35		exec &>/dev/null
    36	fi
    37	
    38	exec openvpn --config ${CONFIG_DIR}/openvpn.conf --cd ${CONFIG_DIR} $@

=== Compare: lines 27-30 from origin/main ===
# Execute user startup script if it exists
if [[ -e ${USER_STARTUP_SCRIPT} ]]; then
	chmod a+x ${USER_STARTUP_SCRIPT}
	${USER_STARTUP_SCRIPT}

=== Compare: current version lines 27-30 ===
# Execute user startup script if it exists
if [[ -e ${USER_STARTUP_SCRIPT} ]]; then
	bash ${USER_STARTUP_SCRIPT}
fi

</shell_result>


Script executed:

#!/bin/bash
# Check the PR/commit message more closely and look for related issues
echo "=== Full commit message ==="
git show 6fcc967

echo -e "\n=== Check if there are any related PRs or issues mentioned ==="
git log --all --grep="entrypoint\|startup" --oneline 2>/dev/null | head -20

Repository: blinklabs-io/docker-openvpn


<shell_result>
Repository: blinklabs-io/docker-openvpn
Exit code: 0

stdout:

=== Full commit message ===
commit 6fcc967b511b2ae36d609814261776f0c6b4f4b9
Author: Chris Gianelloni <wolf31o2@blinklabs.io>
Date:   Wed Nov 26 16:07:13 2025 -0500

    fix(entrypoint): bash the user script
    
    Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>

diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..300b511
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,5 @@
+# Blink Labs
+#
+* @blinklabs-io/core @blinklabs-io/ops
+*.md @blinklabs-io/core @blinklabs-io/docs @blinklabs-io/pms
+LICENSE @blinklabs-io/core @blinklabs-io/pms
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..4fcaebb
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,15 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+  - package-ecosystem: "docker"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml
new file mode 100644
index 0000000..cc312fc
--- /dev/null
+++ b/.github/workflows/ci-docker.yml
@@ -0,0 +1,34 @@
+name: Docker CI
+
+on:
+  pull_request:
+    branches: ['main']
+    paths: ['Dockerfile','.github/workflows/ci-docker.yml']
+
+env:
+  REGISTRY: ghcr.io
+  IMAGE_NAME: blinklabs-io/openvpn
+
+permissions:
+  contents: read
+
+jobs:
+  docker:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 https://github.com/actions/checkout/releases/tag/v6.0.0
+      - name: qemu
+        uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 https://github.com/docker/setup-qemu-action/releases/tag/v3.7.0
+      - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 https://github.com/docker/setup-buildx-action/releases/tag/v3.11.1
+      - id: meta
+        uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0 https://github.com/docker/metadata-action/releases/tag/v5.9.0
+        with:
+          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+      - name: build
+        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 https://github.com/docker/build-push-action/releases/tag/v6.18.0
+        with:
+          context: .
+          push: false
+          platforms: linux/amd64,linux/arm64
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml
new file mode 100644
index 0000000..829821f
--- /dev/null
+++ b/.github/workflows/conventional-commits.yml
@@ -0,0 +1,17 @@
+# The below is pulled from upstream and slightly modified
+# https://github.com/webiny/action-conventional-commits/blob/master/README.md#usage
+
+name: Conventional Commits
+
+on:
+  pull_request:
+
+jobs:
+  build:
+    name: Conventional Commits
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+    steps:
+      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 https://github.com/actions/checkout/releases/tag/v6.0.0
+      - uses: webiny/action-conventional-commits@8bc41ff4e7d423d56fa4905f6ff79209a78776c7 # v1.3.0 https://github.com/webiny/action-conventional-commits/releases/tag/v1.3.0
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..093bb14
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,108 @@
+name: publish
+
+on:
+  push:
+    branches: ['main']
+    tags:
+      - 'v*.*.*'
+  schedule:
+    # Weekly on Mondays at 00:00 UTC
+    - cron: '0 0 * * 1'
+
+concurrency: ${{ github.ref }}
+
+jobs:
+  build-and-push-image:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+      security-events: write
+    steps:
+      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 https://github.com/actions/checkout/releases/tag/v6.0.0
+      - name: qemu
+        uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 https://github.com/docker/setup-qemu-action/releases/tag/v3.7.0
+      - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 https://github.com/docker/setup-buildx-action/releases/tag/v3.11.1
+      - name: Login to Docker Hub
+        uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 https://github.com/docker/login-action/releases/tag/v3.6.0
+        with:
+          username: blinklabs
+          password: ${{ secrets.DOCKER_PASSWORD }} # uses token
+      - name: Login to GHCR
+        uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 https://github.com/docker/login-action/releases/tag/v3.6.0
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      - id: meta
+        uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0 https://github.com/docker/metadata-action/releases/tag/v5.9.0
+        with:
+          images: |
+            blinklabs/openvpn
+            ghcr.io/blinklabs-io/openvpn
+          tags: |
+            # version
+            type=match,pattern=v(.*),group=1
+            # branch
+            type=ref,event=branch
+      - name: Extract first tag
+        run: |
+          FIRST_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -n1 | rev | cut -d: -f1 | rev)
+          echo "FIRST_TAG=$FIRST_TAG" >> $GITHUB_ENV
+      - name: push
+        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 https://github.com/docker/build-push-action/releases/tag/v6.18.0
+        with:
+          context: .
+          push: true
+          platforms: linux/amd64,linux/arm64
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+      - name: Run Trivy vulnerability scanner
+        uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # v0.33.1
+        with:
+          scan-type: 'image'
+          scan-ref: 'ghcr.io/blinklabs-io/openvpn:${{ env.FIRST_TAG }}'
+          format: 'sarif'
+          output: 'trivy-results-${{ env.FIRST_TAG }}.sarif'
+          ignore-unfixed: true
+          severity: 'HIGH,CRITICAL'
+      - name: Upload Trivy scan results to GitHub Security tab
+        uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
+        if: always()
+        with:
+          sarif_file: 'trivy-results-${{ env.FIRST_TAG }}.sarif'
+      # Update Docker Hub from README
+      - name: Docker Hub Description
+        uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0 https://github.com/peter-evans/dockerhub-description/releases/tag/v5.0.0
+        with:
+          username: blinklabs
+          password: ${{ secrets.DOCKER_PASSWORD }}
+          repository: blinklabs/openvpn
+          readme-filepath: ./README.md
+          short-description: "Simple OpenVPN image"
+
+  github-release:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+    needs: [build-and-push-image]
+    steps:
+      - run: "echo \"RELEASE_TAG=${GITHUB_REF#refs/tags/}\" >> $GITHUB_ENV"
+      - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 https://github.com/actions/github-script/releases/tag/v8.0.0
+        if: startsWith(github.ref, 'refs/tags/')
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            try {
+              await github.rest.repos.createRelease({
+                draft: false,
+                generate_release_notes: true,
+                name: process.env.RELEASE_TAG,
+                owner: context.repo.owner,
+                prerelease: false,
+                repo: context.repo.repo,
+                tag_name: process.env.RELEASE_TAG,
+              });
+            } catch (error) {
+              core.setFailed(error.message);
+            }
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e117be9
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+FROM debian:bookworm-slim AS base
+
+COPY bin/ /usr/local/bin
+
+RUN apt-get update \
+ && apt-get dist-upgrade -y \
+# Install OpenVPN (latest 2.6.x from Debian repos; rebuild image periodically for security updates)
+ && apt-get install -y --no-install-recommends openvpn iptables \
+ && apt-get purge -y --auto-remove cron rsyslog \
+ && rm -rf /var/log/* \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* \
+ && chmod +x /usr/local/bin/*
+
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD pgrep -f openvpn || exit 1
+
+EXPOSE 1194/udp
+
+ENTRYPOINT ["/usr/local/bin/entrypoint"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9b37ae8
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Blink Labs Software
+
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..adce85c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+# docker-openvpn
+
+Simple OpenVPN image with updated version, optimized for privacy-focused VPN services.
+
+## Using the image
+
+There is no default config provided by the image, so you'll need to provide your own.
+
+```
+docker run -d -n openvpn -v /path/to/openvpn.conf:/etc/openvpn/openvpn.conf ghcr.io/blinklabs-io/openvpn --config /etc/openvpn/openvpn.conf
+```
+
+The image provides for the ability to provide a custom startup script. It looks for a user script at `/usr/local/bin/entrypoint-user.sh` by default,
+but the location can be overridden with the `USER_STARTUP_SCRIPT` environment variable
+
+## Privacy and Security Recommendations
+
+For a no-log, privacy-focused setup:
+
+### OpenVPN Configuration
+Use these options in your `openvpn.conf` for strong encryption and no logging:
+```conf
+# Disable logging
+log /dev/null
+verb 0
+
+# Strong ciphers
+cipher AES-256-GCM
+auth SHA256
+tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384
+tls-version-min 1.2
+
+# Privilege drop
+user nobody
+group nogroup
+
+# Other privacy settings
+persist-key
+persist-tun
+```
+
+### Docker Run Options
+- Use Docker secrets for certificates/keys: `--secret mykey=/path/to/key`
+- Run with necessary capabilities only: `--cap-add=NET_ADMIN` (avoid `--privileged`)
+- For Kubernetes (Helm), use `securityContext` to limit privileges.
+
+### Maintenance
+- Rebuild images regularly to pull security updates from Debian repos.
+- Scan for vulnerabilities with tools like Trivy (automated via GitHub Actions).
+- Test for leaks using services like ipleak.net while connected.
+
+**Warning**: Running in privileged mode increases security risks—use only when necessary.
diff --git a/bin/entrypoint b/bin/entrypoint
new file mode 100644
index 0000000..a9773e3
--- /dev/null
+++ b/bin/entrypoint
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+CONFIG_DIR=/etc/openvpn
+
+USER_STARTUP_SCRIPT=${USER_STARTUP_SCRIPT:-/usr/local/bin/entrypoint-user.sh}
+
+ENABLE_NAT=${ENABLE_NAT:-1}
+NAT_SOURCE=${NAT_SOURCE:-10.8.0.0/24}
+NAT_DEVICE=${NAT_DEVICE:-eth0}
+
+DEBUG=${DEBUG:-0}
+
+# Create device for tun interfaces
+mkdir -p /dev/net
+if [ ! -c /dev/net/tun ]; then
+	mknod /dev/net/tun c 10 200
+fi
+
+if [[ $ENABLE_NAT = 1 ]]; then
+	iptables -t nat -A POSTROUTING -s ${NAT_SOURCE} -o ${NAT_DEVICE} -j MASQUERADE
+	# Basic kill switch: Drop forwarded traffic by default, allow only VPN-related
+	iptables -P FORWARD DROP
+	iptables -A FORWARD -i tun+ -o ${NAT_DEVICE} -j ACCEPT
+	iptables -A FORWARD -i ${NAT_DEVICE} -o tun+ -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+fi
+
+# Execute user startup script if it exists
+if [[ -e ${USER_STARTUP_SCRIPT} ]]; then
+	bash ${USER_STARTUP_SCRIPT}
+fi
+
+# Redirect output to /dev/null unless in debug mode
+if [[ $DEBUG = 0 ]]; then
+	exec &>/dev/null
+fi
+
+exec openvpn --config ${CONFIG_DIR}/openvpn.conf --cd ${CONFIG_DIR} $@

=== Check if there are any related PRs or issues mentioned ===
6fcc967 fix(entrypoint): bash the user script
533bc7c feat: run optional user startup script (#3)

</shell_result>


Now I have complete visibility. Let me generate the final rewritten review comment based on verification:


Breaking change confirmed: Forcing bash now ignores script shebangs and removes permission handling.

The change from chmod a+x && ${USER_STARTUP_SCRIPT} to bash ${USER_STARTUP_SCRIPT} breaks existing deployments where the user startup script uses a non-bash interpreter (e.g., #!/bin/sh, #!/usr/bin/env python). Direct execution respects the shebang line; bash invocation ignores it and forces bash interpretation.

While this removes the chmod dependency (helpful in some containerized environments), it silently breaks non-bash scripts. Additionally, the commit message ("fix(entrypoint): bash the user script") suggests this was intentional, but:

  1. Documentation doesn't specify the bash requirement — README.md mentions the feature but doesn't state scripts must be bash-compatible.
  2. No error handling — User script failures are silently ignored; execution continues to openvpn regardless of success/failure.

Recommendations:

  • Update README.md to clearly document that USER_STARTUP_SCRIPT must be bash-compatible or use bash syntax.
  • Add error handling to fail fast if the user script exits with an error:
    bash ${USER_STARTUP_SCRIPT} || exit 1
  • Consider if the original approach (direct execution with chmod) could be improved with fallback logic instead of forcing bash.

@wolf31o2 wolf31o2 merged commit 5ae0a16 into main Nov 26, 2025
6 checks passed
@wolf31o2 wolf31o2 deleted the fix/bash-script branch November 26, 2025 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants