Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions bin/entrypoint
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ fi

# Execute user startup script if it exists
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.

fi

# Redirect output to /dev/null unless in debug mode
Expand Down