7 Commits

Author SHA1 Message Date
Markus F.J. Busche
fd5ff20743 Add disclaimer 2025-09-20 19:31:50 +02:00
Markus F.J. Busche
87449641f6 chore: remove scripts/upload_asset_direct.sh (deprecated) 2025-09-20 19:29:49 +02:00
Markus F.J. Busche
731cd207dd chore: stage remaining changes 2025-09-20 18:29:54 +02:00
Markus F.J. Busche
a5489de212 docs: korrigiere Screenshot-Dateiname auf .png 2025-09-20 18:24:47 +02:00
Markus F.J. Busche
c0d4448dc1 docs: symlink docs/ -> octoprint_tailscale_funnel/octoprint_tailscale_funnel/docs 2025-09-20 18:24:24 +02:00
Markus F.J. Busche
0d43e181c8 docs: Screenshot der Funnel-Einstellungen in README verlinkt 2025-09-20 18:18:37 +02:00
Markus F.J. Busche
f60068b01f release: update scripts/release_gitea.sh to jq-based flow; skip existing assets 2025-09-20 18:13:22 +02:00
5 changed files with 21 additions and 192 deletions

1
docs Symbolic link
View File

@@ -0,0 +1 @@
octoprint_tailscale_funnel/octoprint_tailscale_funnel/docs

View File

@@ -2,6 +2,8 @@
This plugin makes your OctoPrint instance accessible from anywhere via Tailscale Funnel, without needing to configure port forwarding, dynamic DNS, or complex firewall settings. This plugin makes your OctoPrint instance accessible from anywhere via Tailscale Funnel, without needing to configure port forwarding, dynamic DNS, or complex firewall settings.
Disclaimer: *This plugin was partially vibe-coded*.
## Features ## Features
* Enable/disable Tailscale Funnel access directly from OctoPrint's settings * Enable/disable Tailscale Funnel access directly from OctoPrint's settings
@@ -9,6 +11,10 @@ This plugin makes your OctoPrint instance accessible from anywhere via Tailscale
* Display the public URL for accessing OctoPrint remotely * Display the public URL for accessing OctoPrint remotely
* Configure the port to expose via Funnel * Configure the port to expose via Funnel
## Screenshot
![Tailscale Funnel Settings](octoprint_tailscale_funnel/octoprint_tailscale_funnel/docs/screenshots/funnel-settings.png)
## Requirements ## Requirements
* OctoPrint 1.3.0 or higher * OctoPrint 1.3.0 or higher

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -54,31 +54,11 @@ fi
# Try to fetch existing release by tag first # Try to fetch existing release by tag first
get_resp=$(curl -sS -H "Authorization: token ${TOKEN}" "${API_URL}/repos/${OWNER}/${REPO}/releases/tags/${TAG}" || true) get_resp=$(curl -sS -H "Authorization: token ${TOKEN}" "${API_URL}/repos/${OWNER}/${REPO}/releases/tags/${TAG}" || true)
rel_id=$(python3 - <<'PY' rel_id=$(echo "$get_resp" | jq -r '.id // empty')
import sys, json
data=sys.stdin.read().strip()
try:
obj=json.loads(data) if data else {}
print(obj.get('id',''))
except Exception:
print('')
PY
<<<"$get_resp")
if [ -z "$rel_id" ]; then if [ -z "$rel_id" ]; then
# Build minimal JSON payload (use existing tag) # Build minimal JSON payload (use existing tag)
create_payload=$(REL_TAG="$TAG" REL_NAME="$NAME" REL_BODY_TXT="$BODY" python3 - <<'PY' create_payload=$(jq -n --arg tag "$TAG" --arg name "$NAME" --arg body "$BODY" '{tag_name:$tag, name:$name, body:$body, draft:false, prerelease:false}')
import json, os
payload = {
"tag_name": os.environ["REL_TAG"],
"name": os.environ["REL_NAME"],
"body": os.environ["REL_BODY_TXT"],
"draft": False,
"prerelease": False
}
print(json.dumps(payload))
PY
)
create_resp=$(curl -sS -X POST \ create_resp=$(curl -sS -X POST \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
@@ -87,35 +67,13 @@ PY
"${API_URL}/repos/${OWNER}/${REPO}/releases" || true) "${API_URL}/repos/${OWNER}/${REPO}/releases" || true)
# Extract id from create response # Extract id from create response
rel_id=$(python3 - <<'PY' rel_id=$(echo "$create_resp" | jq -r '.id // empty')
import sys, json
data=sys.stdin.read().strip()
try:
obj=json.loads(data) if data else {}
print(obj.get('id',''))
except Exception:
print('')
PY
<<<"$create_resp")
fi fi
# Fallback: search releases list for matching tag if still empty # Fallback: search releases list for matching tag if still empty
if [ -z "$rel_id" ]; then if [ -z "$rel_id" ]; then
list_resp=$(curl -sS -H "Authorization: token ${TOKEN}" "${API_URL}/repos/${OWNER}/${REPO}/releases?limit=100") list_resp=$(curl -sS -H "Authorization: token ${TOKEN}" "${API_URL}/repos/${OWNER}/${REPO}/releases?limit=100")
rel_id=$(python3 - <<'PY' rel_id=$(echo "$list_resp" | jq -r --arg tag "$TAG" '[.[] | select(.tag_name==$tag)][0].id // empty')
import sys, json, os
data=sys.stdin.read().strip()
try:
arr=json.loads(data) if data else []
tag=os.environ.get('TAG')
for rel in arr:
if rel.get('tag_name')==tag:
print(rel.get('id',''))
break
except Exception:
pass
PY
<<<"$list_resp")
fi fi
if [ -z "$rel_id" ]; then if [ -z "$rel_id" ]; then
@@ -124,37 +82,18 @@ if [ -z "$rel_id" ]; then
exit 1 exit 1
fi fi
# Delete existing asset with same name (if any) # Check if asset exists (skip upload if present)
assets_json=$(curl -sS -H "Authorization: token ${TOKEN}" "${API_URL}/repos/${OWNER}/${REPO}/releases/${rel_id}/assets") assets_json=$(curl -sS -H "Authorization: token ${TOKEN}" "${API_URL}/repos/${OWNER}/${REPO}/releases/${rel_id}/assets")
asset_id=$(python3 - <<'PY' asset_exists=$(echo "$assets_json" | jq -r --arg name "$ASSET_NAME" 'any(.[]; .name==$name)')
import sys, json, os
name=os.environ['ASSET_NAME']
try:
arr=json.loads(sys.stdin.read())
for a in arr:
if a.get('name')==name:
print(a.get('id',''))
break
except Exception:
pass
PY
<<<"$assets_json")
if [ -n "$asset_id" ]; then if [ "$asset_exists" = "true" ]; then
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" "${API_URL}/repos/${OWNER}/${REPO}/releases/${rel_id}/assets/${asset_id}" >/dev/null || true echo "Asset already exists, skipping upload: ${ASSET_NAME}"
else
echo "Uploading asset: ${ASSET_NAME}"
upload_resp=$(curl -sS -H "Authorization: token ${TOKEN}" -F attachment=@"${ASSET_PATH}" "${API_URL}/repos/${OWNER}/${REPO}/releases/${rel_id}/assets?name=${ASSET_NAME}")
html_url=$(echo "$upload_resp" | jq -r '.browser_download_url // empty')
echo "Asset uploaded: ${html_url}"
fi fi
# Upload asset echo "Release ${TAG} ready (id=${rel_id})."
upload_resp=$(curl -sS -H "Authorization: token ${TOKEN}" -F attachment=@"${ASSET_PATH}" "${API_URL}/repos/${OWNER}/${REPO}/releases/${rel_id}/assets?name=${ASSET_NAME}")
html_url=$(python3 - <<'PY'
import sys, json
try:
print(json.loads(sys.stdin.read()).get('browser_download_url',''))
except Exception:
print("")
PY
<<<"$upload_resp")
echo "Release ${TAG} created (id=${rel_id}). Asset uploaded: ${html_url}"

View File

@@ -1,117 +0,0 @@
#!/usr/bin/env bash
set -e
set +u
# Load .env
if [ -f "$(dirname "$0")/../.env" ]; then
# shellcheck disable=SC1091
. "$(dirname "$0")/../.env"
fi
API_URL=${GITEA_API_URL:-"https://gitea.elpatron.me/api/v1"}
OWNER=${GITEA_OWNER:-"elpatron"}
REPO=${GITEA_REPO:-"octo-funnel"}
TOKEN=${GITEA_API_TOKEN:-""}
TAG=${1:-v0.1.6.1}
ASSET_PATH=${2:-"$(pwd)/octoprint_tailscale_funnel/dist/OctoPrint-Tailscale-Funnel-0.1.6.1.zip"}
if [ -z "$TOKEN" ]; then
echo "GITEA_API_TOKEN not set" >&2
exit 1
fi
if [ ! -f "$ASSET_PATH" ]; then
echo "Asset not found: $ASSET_PATH" >&2
exit 1
fi
ASSET_NAME=$(basename "$ASSET_PATH")
# Get release by tag
REL_JSON=$(curl -sS -H "Authorization: token $TOKEN" "$API_URL/repos/$OWNER/$REPO/releases/tags/$TAG" || true)
REL_ID=$(python3 - <<'PY'
import sys, json
s=sys.stdin.read().strip()
try:
d=json.loads(s) if s else {}
print(d.get('id',''))
except Exception:
print('')
PY
<<<"$REL_JSON")
# Fallback list search
if [ -z "$REL_ID" ]; then
LIST=$(curl -sS -H "Authorization: token $TOKEN" "$API_URL/repos/$OWNER/$REPO/releases?limit=100")
REL_ID=$(TAG="$TAG" python3 - <<'PY'
import os, sys, json
tag=os.environ['TAG']
try:
arr=json.loads(sys.stdin.read())
for r in arr:
if r.get('tag_name')==tag:
print(r.get('id',''))
break
except Exception:
print('')
PY
<<<"$LIST")
fi
# Create release if still missing
if [ -z "$REL_ID" ]; then
PAYLOAD=$(TAG="$TAG" python3 - <<'PY'
import json, os
print(json.dumps({
'tag_name': os.environ['TAG'],
'name': os.environ['TAG'],
'body': 'Automated release'
}))
PY
)
CREATE=$(curl -sS -X POST -H 'Content-Type: application/json' -H "Authorization: token $TOKEN" -d "$PAYLOAD" "$API_URL/repos/$OWNER/$REPO/releases")
REL_ID=$(python3 - <<'PY'
import sys, json
try:
d=json.loads(sys.stdin.read())
print(d.get('id',''))
except Exception:
print('')
PY
<<<"$CREATE")
fi
if [ -z "$REL_ID" ]; then
echo "Failed to resolve release id for tag $TAG" >&2
exit 1
fi
# Check existing assets
ASSETS=$(curl -sS -H "Authorization: token $TOKEN" "$API_URL/repos/$OWNER/$REPO/releases/$REL_ID/assets")
HAS=$(NAME="$ASSET_NAME" python3 - <<'PY'
import os, sys, json
name=os.environ['NAME']
try:
arr=json.loads(sys.stdin.read())
print(any(a.get('name')==name for a in arr))
except Exception:
print(False)
PY
<<<"$ASSETS")
if [ "$HAS" = "True" ]; then
echo "Asset already exists: $ASSET_NAME"
else
echo "Uploading asset: $ASSET_NAME"
curl -sS -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/octet-stream" \
--data-binary @"$ASSET_PATH" \
"$API_URL/repos/$OWNER/$REPO/releases/$REL_ID/assets?name=$ASSET_NAME" >/dev/null
echo "Upload done."
fi
DL_URL="https://gitea.elpatron.me/$OWNER/$REPO/releases/download/$TAG/$ASSET_NAME"
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$DL_URL")
echo "DOWNLOAD_URL=$DL_URL"
echo "HTTP_STATUS=$CODE"