Add super-linter (markdown/yaml/json/dockerfile/shell); fix lint findings

This commit is contained in:
Weston Blieden
2026-07-08 09:40:14 +02:00
parent e2daee6519
commit 216ae1ebf9
13 changed files with 170 additions and 122 deletions
+10
View File
@@ -0,0 +1,10 @@
DEFAULT_BRANCH=origin/main
LINTER_RULES_PATH=/
VALIDATE_ALL_CODEBASE=true
IGNORE_GITIGNORED_FILES=true
YAML_CONFIG_FILE=.yamllint.yaml
VALIDATE_DOCKERFILE_HADOLINT=true
VALIDATE_JSON=true
VALIDATE_MARKDOWN=true
VALIDATE_SHELL_SHFMT=true
VALIDATE_YAML=true
+22
View File
@@ -0,0 +1,22 @@
---
name: Lint
on: push
jobs:
Build:
name: Lint code base
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Environment
run: cat .github/super-linter.env >> "$GITHUB_ENV"
- name: Lint code base
uses: super-linter/super-linter/slim@v8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+8
View File
@@ -0,0 +1,8 @@
---
# Enforce error-level Dockerfile correctness. Warnings/info are advisory: the
# ACAP cross-compile Dockerfiles use accepted patterns (cd in RUN, ARG-templated
# FROM tags hadolint cannot resolve, optional pipefail).
failure-threshold: error
ignored:
# Pin versions in 'apt-get install' - the SDK base image is already pinned.
- DL3008
+4 -3
View File
@@ -1,7 +1,8 @@
---
# Line length
MD013:
line_length: 132
# Line length (disabled: long lines in tables, URLs and prose are acceptable)
MD013: false
# Allow inline HTML (e.g. <img> logos and badges in READMEs)
MD033: false
# Allow blank lines inside blockquotes
MD028: false
# First line in a file should be a top-level heading
+2
View File
@@ -0,0 +1,2 @@
rules:
line-length: disable
+14 -14
View File
@@ -13,13 +13,13 @@ All types of contributions are encouraged and valued. See the [Table of contents
- [I have a question](#i-have-a-question)
- [I want to contribute](#i-want-to-contribute)
- [Reporting bugs](#reporting-bugs)
- [Before submitting a bug report](#before-submitting-a-bug-report)
- [How do I submit a good bug report?](#how-do-i-submit-a-good-bug-report)
- [Suggesting enhancements](#suggesting-enhancements)
- [Before Submitting an Enhancement](#before-submitting-an-enhancement)
- [How do I submit a good enhancement suggestion?](#how-do-i-submit-a-good-enhancement-suggestion)
- [Your first code contribution](#your-first-code-contribution)
- [Reporting bugs](#reporting-bugs)
- [Before submitting a bug report](#before-submitting-a-bug-report)
- [How do I submit a good bug report?](#how-do-i-submit-a-good-bug-report)
- [Suggesting enhancements](#suggesting-enhancements)
- [Before Submitting an Enhancement](#before-submitting-an-enhancement)
- [How do I submit a good enhancement suggestion?](#how-do-i-submit-a-good-enhancement-suggestion)
- [Your first code contribution](#your-first-code-contribution)
## I have a question
@@ -46,13 +46,13 @@ A good bug report shouldn't leave others needing to chase you up for more inform
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker][issues_bugs].
- Also make sure to search the internet to see if users outside of the GitHub community have discussed the issue.
- Collect information about the bug:
- Axis device model
- Axis device firmware version
- Stack trace
- OS and version (Windows, Linux, macOS, x86, ARM)
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant
- Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
- Axis device model
- Axis device firmware version
- Stack trace
- OS and version (Windows, Linux, macOS, x86, ARM)
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant
- Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
#### How do I submit a good bug report?
+7 -2
View File
@@ -16,7 +16,7 @@ This repository provides an **ACAP package** that installs the [Tailscale VPN cl
[![Sponsor](https://img.shields.io/badge/sponsor-%E2%9D%A4-lightgrey?logo=github)](https://github.com/sponsors/Mo3he)
[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-orange?style=flat&logo=buy-me-a-coffee)](https://www.buymeacoffee.com/mo3he)
> **Disclaimer:** This is an independent, community-developed ACAP package and is not an official Axis Communications product. It is not affiliated with, endorsed by, or supported by Axis Communications AB. Use it at your own risk. For official Axis software, visit axis.com
> **Disclaimer:** This is an independent, community-developed ACAP package and is not an official Axis Communications product. It is not affiliated with, endorsed by, or supported by Axis Communications AB. Use it at your own risk. For official Axis software, visit axis.com
> **Tailscale Notice:** Tailscale is a product of Tailscale Inc. This package independently redistributes the Tailscale binaries under the [BSD 3-Clause License](LICENSE) and is not affiliated with, endorsed by, or supported by Tailscale Inc. For the official Tailscale client, visit [tailscale.com](https://tailscale.com).
---
@@ -47,7 +47,8 @@ Get the **prebuilt `.eap` file** from the [Releases page](https://github.com/Mo3
3. Upload the `.eap` file.
Once installed:
- Start the app.
- Start the app.
- Click **Open** to view logs and get your Tailscale authentication URL.
- On uninstall, all changes/files are removed.
@@ -80,6 +81,7 @@ All parameters are configurable via the web UI (**Open → Settings** card) and
---
### Proxy Support
All non-ROOT variants expose two local proxy endpoints that route outbound traffic through the Tailscale tunnel. The ports are configurable via **Settings → HTTP Proxy Port / SOCKS5 Proxy Port** in the web UI.
@@ -118,6 +120,7 @@ There is an important asymmetry to understand. Making the camera **reachable fro
In short: on non-root builds the proxies cover apps that know how to use a proxy, but a system feature like "add network share" opens a raw socket that never touches the tunnel. The ROOT build is the clean way to let the camera *consume* tailnet services.
### Plan B: reverse-SSH tunnel
>
> **Requires root on the camera.** Port 445 is privileged, so binding it needs a root-capable build (e.g. developer certificates installed).
If you cannot use the ROOT build but still need the camera to mount a share on a machine that is on your tailnet, you can make the remote share appear **local** to the camera with a reverse SSH tunnel. Because the destination becomes `127.0.0.1`, the proxy-unaware SMB client never has to route over the tailnet.
@@ -141,6 +144,7 @@ Then, in the camera's **System → Storage → Add network share** dialog, use `
### Manual update (advanced)
Replace the binaries in `common/app/lib/` (shared by `aarch64`, `armv7hf`, and their ROOT variants) or `arm_acap3/app/lib/` (legacy variant, kept separate):
- `tailscale`
- `tailscaled`
@@ -178,6 +182,7 @@ An **ACAP 3** variant (`armv7hf_acap3`) is available for older cameras that do n
## Purpose
Adding a VPN client directly to the camera enables:
- Secure remote access without additional hardware or complex network configuration.
- Easy setup through Tailscales lightweight WireGuard-based tunnel.
+2 -2
View File
@@ -1,5 +1,5 @@
To build, from main directory
docker build --tag aarch64 .
docker build --tag aarch64 .
docker cp $(docker create aarch64):/opt/app ./build
docker cp $(docker create aarch64):/opt/app ./build
+2 -2
View File
@@ -1,5 +1,5 @@
To build, from main directory
docker build --tag aarch64 .
docker build --tag aarch64 .
docker cp $(docker create aarch64):/opt/app ./build
docker cp $(docker create aarch64):/opt/app ./build
+2 -2
View File
@@ -1,5 +1,5 @@
To build from main directory
docker build --tag arm .
docker build --tag arm .
docker cp $(docker create arm):/opt/app ./build
docker cp $(docker create arm):/opt/app ./build
+2 -2
View File
@@ -1,5 +1,5 @@
To build from main directory
docker build --tag arm .
docker build --tag arm .
docker cp $(docker create arm):/opt/app ./build
docker cp $(docker create arm):/opt/app ./build
+23 -23
View File
@@ -16,19 +16,19 @@ logger -t "Tailscale_VPN" "Starting tailscaled daemon"
# Log to file (not piped through logger) -- avoids extra logger process holding
# tailscaled stdout open, which prevents our wait loop from detecting exit
"$APP_DIR/lib/tailscaled" \
--state="$STATE_DIR/tailscaled.state" \
--socket="$STATE_DIR/tailscaled.sock" \
--socks5-server=localhost:1055 \
--outbound-http-proxy-listen=localhost:8080 \
--tun=userspace-networking \
>> "$STATE_DIR/tailscaled.log" 2>&1 &
--state="$STATE_DIR/tailscaled.state" \
--socket="$STATE_DIR/tailscaled.sock" \
--socks5-server=localhost:1055 \
--outbound-http-proxy-listen=localhost:8080 \
--tun=userspace-networking \
>>"$STATE_DIR/tailscaled.log" 2>&1 &
TAILSCALED_PID=$!
# Wait for socket to appear (up to 15 seconds)
i=0
while [ $i -lt 15 ] && [ ! -S "$STATE_DIR/tailscaled.sock" ]; do
sleep 1
i=$((i + 1))
sleep 1
i=$((i + 1))
done
logger -t "Tailscale_VPN" "Connecting to Tailscale network"
@@ -37,9 +37,9 @@ logger -t "Tailscale_VPN" "Connecting to Tailscale network"
# cameras with limited RAM (e.g. 222 MB).
# Capture output so we can extract auth URL and log it to syslog for the web UI.
UP_OUT=$("$APP_DIR/lib/tailscale" \
--socket="$STATE_DIR/tailscaled.sock" \
up --hostname="$(hostname)" --timeout=10s 2>&1) || true
echo "$UP_OUT" >> "$STATE_DIR/tailscaled.log"
--socket="$STATE_DIR/tailscaled.sock" \
up --hostname="$(hostname)" --timeout=10s 2>&1) || true
echo "$UP_OUT" >>"$STATE_DIR/tailscaled.log"
# If an auth URL was returned, log it so the web UI can show it
AUTH_URL=$(echo "$UP_OUT" | grep -o 'https://login\.tailscale\.com/[^ ]*' | head -1)
@@ -48,7 +48,7 @@ AUTH_URL=$(echo "$UP_OUT" | grep -o 'https://login\.tailscale\.com/[^ ]*' | head
# Log IP and version into syslog so the web UI details panel can populate
TS_IP=$("$APP_DIR/lib/tailscale" --socket="$STATE_DIR/tailscaled.sock" ip -4 2>/dev/null | head -1)
TS_VER=$("$APP_DIR/lib/tailscale" --socket="$STATE_DIR/tailscaled.sock" version 2>/dev/null | head -1)
[ -n "$TS_IP" ] && logger -t "Tailscale_VPN" "Tailscale IP: $TS_IP"
[ -n "$TS_IP" ] && logger -t "Tailscale_VPN" "Tailscale IP: $TS_IP"
[ -n "$TS_VER" ] && logger -t "Tailscale_VPN" "Tailscale version: $TS_VER"
logger -t "Tailscale_VPN" "Tailscale VPN is running"
@@ -63,19 +63,19 @@ logger -t "Tailscale_VPN" "SOCKS5 proxy: 127.0.0.1:1055"
STATUS_FILE="$STATE_DIR/status.json"
publish_status() {
if "$APP_DIR/lib/tailscale" --socket="$STATE_DIR/tailscaled.sock" status --json > "$STATUS_FILE.tmp" 2>/dev/null; then
mv "$STATUS_FILE.tmp" "$STATUS_FILE" 2>/dev/null
chmod 644 "$STATUS_FILE" 2>/dev/null
else
rm -f "$STATUS_FILE.tmp" 2>/dev/null
fi
if "$APP_DIR/lib/tailscale" --socket="$STATE_DIR/tailscaled.sock" status --json >"$STATUS_FILE.tmp" 2>/dev/null; then
mv "$STATUS_FILE.tmp" "$STATUS_FILE" 2>/dev/null
chmod 644 "$STATUS_FILE" 2>/dev/null
else
rm -f "$STATUS_FILE.tmp" 2>/dev/null
fi
}
# Remove stale status on stop so the UI does not show a connected node after exit.
cleanup() {
rm -f "$STATUS_FILE" 2>/dev/null
[ -n "$TAILSCALED_PID" ] && kill "$TAILSCALED_PID" 2>/dev/null
exit 0
rm -f "$STATUS_FILE" 2>/dev/null
[ -n "$TAILSCALED_PID" ] && kill "$TAILSCALED_PID" 2>/dev/null
exit 0
}
trap cleanup TERM INT
@@ -83,8 +83,8 @@ trap cleanup TERM INT
# status fresh. This keeps the parent Tailscale_VPN (C launcher) in the process
# table so pidof finds it and the camera web UI shows "Running" instead of "Stopped".
while kill -0 "$TAILSCALED_PID" 2>/dev/null; do
publish_status
sleep 5
publish_status
sleep 5
done
rm -f "$STATUS_FILE" 2>/dev/null
+72 -72
View File
@@ -27,48 +27,48 @@ ACCEPT_ROUTES="false"
ADVERTISE_ROUTES=""
if [ -f "$STATE_DIR/params.conf" ]; then
. "$STATE_DIR/params.conf"
. "$STATE_DIR/params.conf"
fi
if [ "$VARIANT" = "root" ]; then
logger -t "Tailscale_VPN" "Starting (root mode): custom_server=${CUSTOM_SERVER:-(default)}"
logger -t "Tailscale_VPN" "Starting (root mode): custom_server=${CUSTOM_SERVER:-(default)}"
else
logger -t "Tailscale_VPN" "Starting: http_port=$CONF_HTTP socks_port=$CONF_SOCKS custom_server=${CUSTOM_SERVER:-(default)}"
logger -t "Tailscale_VPN" "Starting: http_port=$CONF_HTTP socks_port=$CONF_SOCKS custom_server=${CUSTOM_SERVER:-(default)}"
# Check whether a TCP port is already bound
is_port_in_use() {
local port=$1
local hex_port
hex_port=$(printf '%04X' "$port")
grep -q ":${hex_port} " /proc/net/tcp 2>/dev/null && return 0
grep -q ":${hex_port} " /proc/net/tcp6 2>/dev/null && return 0
return 1
}
# Check whether a TCP port is already bound
is_port_in_use() {
local port=$1
local hex_port
hex_port=$(printf '%04X' "$port")
grep -q ":${hex_port} " /proc/net/tcp 2>/dev/null && return 0
grep -q ":${hex_port} " /proc/net/tcp6 2>/dev/null && return 0
return 1
}
if is_port_in_use "$CONF_HTTP"; then
logger -t "Tailscale_VPN" "ERROR: HTTP proxy port $CONF_HTTP is already in use. Change it in Settings."
exit 1
fi
if is_port_in_use "$CONF_SOCKS"; then
logger -t "Tailscale_VPN" "ERROR: SOCKS5 port $CONF_SOCKS is already in use. Change it in Settings."
exit 1
fi
if is_port_in_use "$CONF_HTTP"; then
logger -t "Tailscale_VPN" "ERROR: HTTP proxy port $CONF_HTTP is already in use. Change it in Settings."
exit 1
fi
if is_port_in_use "$CONF_SOCKS"; then
logger -t "Tailscale_VPN" "ERROR: SOCKS5 port $CONF_SOCKS is already in use. Change it in Settings."
exit 1
fi
fi
logger -t "Tailscale_VPN" "Starting tailscaled daemon"
if [ "$VARIANT" = "root" ]; then
$TAILSCALED_PATH \
--state="$STATE_DIR/tailscaled.state" \
--socket=$SOCKET_PATH \
>/dev/null 2>&1 &
$TAILSCALED_PATH \
--state="$STATE_DIR/tailscaled.state" \
--socket=$SOCKET_PATH \
>/dev/null 2>&1 &
else
$TAILSCALED_PATH \
--state="$STATE_DIR/tailscaled.state" \
--socket=$SOCKET_PATH \
--socks5-server=localhost:$CONF_SOCKS \
--outbound-http-proxy-listen=localhost:$CONF_HTTP \
--tun=userspace-networking \
>/dev/null 2>&1 &
$TAILSCALED_PATH \
--state="$STATE_DIR/tailscaled.state" \
--socket=$SOCKET_PATH \
--socks5-server=localhost:$CONF_SOCKS \
--outbound-http-proxy-listen=localhost:$CONF_HTTP \
--tun=userspace-networking \
>/dev/null 2>&1 &
fi
TAILSCALED_PID=$!
@@ -77,19 +77,19 @@ sleep 2
TAILSCALE_CMD="$TAILSCALE_PATH --socket=$SOCKET_PATH up --reset --hostname=$(hostname)"
if [ -n "$CUSTOM_SERVER" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --login-server $CUSTOM_SERVER"
TAILSCALE_CMD="$TAILSCALE_CMD --login-server $CUSTOM_SERVER"
fi
if [ -n "$AUTH_KEY" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --authkey $AUTH_KEY"
TAILSCALE_CMD="$TAILSCALE_CMD --authkey $AUTH_KEY"
fi
if [ "$ACCEPT_DNS" = "true" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --accept-dns=true"
TAILSCALE_CMD="$TAILSCALE_CMD --accept-dns=true"
fi
if [ "$ACCEPT_ROUTES" = "true" ]; then
TAILSCALE_CMD="$TAILSCALE_CMD --accept-routes=true"
TAILSCALE_CMD="$TAILSCALE_CMD --accept-routes=true"
fi
# Advertise LAN subnets so this camera acts as a subnet router. Comma-separated
@@ -99,11 +99,11 @@ fi
# forward packets between the tailnet and the LAN, so enable IP forwarding.
# Routes must still be approved in the Tailscale admin console either way.
if [ -n "$ADVERTISE_ROUTES" ]; then
if [ "$VARIANT" = "root" ]; then
echo 1 > /proc/sys/net/ipv4/ip_forward 2>/dev/null || true
echo 1 > /proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || true
fi
TAILSCALE_CMD="$TAILSCALE_CMD --advertise-routes=$ADVERTISE_ROUTES"
if [ "$VARIANT" = "root" ]; then
echo 1 >/proc/sys/net/ipv4/ip_forward 2>/dev/null || true
echo 1 >/proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || true
fi
TAILSCALE_CMD="$TAILSCALE_CMD --advertise-routes=$ADVERTISE_ROUTES"
fi
# Run `tailscale up` in the background and act on its outcome. If the node needs
@@ -115,27 +115,27 @@ fi
# for it from here, because in POSIX sh `wait` only works on children of the
# current shell — a subshell waiting on the parent's child returns 127.
{
eval "$TAILSCALE_CMD"
up_exit=$?
if [ "$up_exit" -eq 0 ]; then
if [ "$VARIANT" = "root" ]; then
logger -t "Tailscale_VPN" "Tailscale VPN is running (root mode)"
else
logger -t "Tailscale_VPN" "Tailscale VPN is running"
fi
# Auth succeeded with a one-time auth key — signal param_bridge to clear it
if [ -n "$AUTH_KEY" ]; then
: > "$STATE_DIR/authkey_clear"
fi
else
logger -t "Tailscale_VPN" "ERROR: tailscale up failed (exit $up_exit)"
fi
eval "$TAILSCALE_CMD"
up_exit=$?
if [ "$up_exit" -eq 0 ]; then
if [ "$VARIANT" = "root" ]; then
logger -t "Tailscale_VPN" "Tailscale VPN is running (root mode)"
else
logger -t "Tailscale_VPN" "Tailscale VPN is running"
fi
# Auth succeeded with a one-time auth key — signal param_bridge to clear it
if [ -n "$AUTH_KEY" ]; then
: >"$STATE_DIR/authkey_clear"
fi
else
logger -t "Tailscale_VPN" "ERROR: tailscale up failed (exit $up_exit)"
fi
} &
TAILSCALE_UP_PID=$!
if [ "$VARIANT" != "root" ]; then
logger -t "Tailscale_VPN" "HTTP/HTTPS proxy: http://127.0.0.1:$CONF_HTTP"
logger -t "Tailscale_VPN" "SOCKS5 proxy: 127.0.0.1:$CONF_SOCKS"
logger -t "Tailscale_VPN" "HTTP/HTTPS proxy: http://127.0.0.1:$CONF_HTTP"
logger -t "Tailscale_VPN" "SOCKS5 proxy: 127.0.0.1:$CONF_SOCKS"
fi
# Publish tailscale's real backend state as JSON for the web UI to consume.
@@ -145,19 +145,19 @@ fi
STATUS_FILE="$APP_DIR/html/status.json"
publish_status() {
if "$TAILSCALE_PATH" --socket="$SOCKET_PATH" status --json > "$STATUS_FILE.tmp" 2>/dev/null; then
mv "$STATUS_FILE.tmp" "$STATUS_FILE" 2>/dev/null
chmod 644 "$STATUS_FILE" 2>/dev/null
else
rm -f "$STATUS_FILE.tmp" 2>/dev/null
fi
if "$TAILSCALE_PATH" --socket="$SOCKET_PATH" status --json >"$STATUS_FILE.tmp" 2>/dev/null; then
mv "$STATUS_FILE.tmp" "$STATUS_FILE" 2>/dev/null
chmod 644 "$STATUS_FILE" 2>/dev/null
else
rm -f "$STATUS_FILE.tmp" 2>/dev/null
fi
}
status_loop() {
while true; do
publish_status
sleep 5
done
while true; do
publish_status
sleep 5
done
}
status_loop &
STATUS_LOOP_PID=$!
@@ -166,11 +166,11 @@ STATUS_LOOP_PID=$!
# stop/restart so param_bridge (which signals this script) leaves no orphans or
# stale state.
cleanup() {
[ -n "$STATUS_LOOP_PID" ] && kill "$STATUS_LOOP_PID" 2>/dev/null
[ -n "$TAILSCALE_UP_PID" ] && kill "$TAILSCALE_UP_PID" 2>/dev/null
[ -n "$TAILSCALED_PID" ] && kill "$TAILSCALED_PID" 2>/dev/null
rm -f "$STATUS_FILE" 2>/dev/null
exit 0
[ -n "$STATUS_LOOP_PID" ] && kill "$STATUS_LOOP_PID" 2>/dev/null
[ -n "$TAILSCALE_UP_PID" ] && kill "$TAILSCALE_UP_PID" 2>/dev/null
[ -n "$TAILSCALED_PID" ] && kill "$TAILSCALED_PID" 2>/dev/null
rm -f "$STATUS_FILE" 2>/dev/null
exit 0
}
trap cleanup TERM INT