Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b77e5cbaf | |||
| cfcaafffd6 | |||
| c23046007f | |||
| bd9165311f | |||
| 76ddfcca2f | |||
| 57c76771d4 | |||
| fa9a5ad430 | |||
| 150be6cf33 | |||
| e2ed830d1a | |||
| 8a15cf1b23 | |||
| e11a9c3ac9 | |||
| fe4886a305 | |||
| 32400d8413 | |||
| e56b2027cd | |||
| 327cb22bfd |
@@ -0,0 +1,71 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Compute image tag
|
||||
id: tag
|
||||
run: |
|
||||
BRANCH="${{ gitea.ref_name }}"
|
||||
|
||||
if [ "$BRANCH" = "main" ]; then
|
||||
TAG="latest"
|
||||
elif [[ "$BRANCH" == v* ]]; then
|
||||
TAG="$BRANCH"
|
||||
else
|
||||
TAG="test"
|
||||
fi
|
||||
|
||||
echo "tag=$TAG" >> "$GITEA_OUTPUT"
|
||||
echo "branch=$BRANCH" >> "$GITEA_OUTPUT"
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: blinkfink182/qbt-gluetun-portmgr:${{ steps.tag.outputs.tag }}
|
||||
|
||||
- name: Notify Apprise (success)
|
||||
if: success()
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"tags\": \"all\",
|
||||
\"title\": \"Gitea Build Succeeded\",
|
||||
\"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ steps.tag.outputs.branch }}\\nImage tag built successfully\"
|
||||
}" \
|
||||
${{ secrets.APPRISE_URL }}
|
||||
|
||||
- name: Notify Apprise (failure)
|
||||
if: failure()
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"tags\": \"all\",
|
||||
\"title\": \"Gitea Build Failed\",
|
||||
\"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ steps.tag.outputs.branch }}\\nCheck logs in Gitea\"
|
||||
}" \
|
||||
${{ secrets.APPRISE_URL }}
|
||||
@@ -0,0 +1,400 @@
|
||||
name: Security
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare security reports
|
||||
run: |
|
||||
mkdir -p security-results
|
||||
|
||||
- name: Compute image tag
|
||||
id: tag
|
||||
run: |
|
||||
BRANCH="${{ gitea.ref_name }}"
|
||||
|
||||
if [ "$BRANCH" = "main" ]; then
|
||||
TAG="latest"
|
||||
elif [[ "$BRANCH" == v* ]]; then
|
||||
TAG="$BRANCH"
|
||||
else
|
||||
TAG="test"
|
||||
fi
|
||||
|
||||
echo "tag=$TAG" >> "$GITEA_OUTPUT"
|
||||
|
||||
###########################################################
|
||||
# GITLEAKS
|
||||
###########################################################
|
||||
|
||||
- name: Gitleaks
|
||||
run: |
|
||||
set +e
|
||||
cid="$(docker create \
|
||||
ghcr.io/gitleaks/gitleaks:latest \
|
||||
detect \
|
||||
--source /repo \
|
||||
--report-format json \
|
||||
--report-path /repo/security-results/gitleaks.json \
|
||||
--exit-code 1)"
|
||||
docker cp . "$cid:/repo"
|
||||
docker start -a "$cid"
|
||||
status="$?"
|
||||
docker cp "$cid:/repo/security-results/gitleaks.json" security-results/gitleaks.json || true
|
||||
docker rm -f "$cid" >/dev/null 2>&1 || true
|
||||
echo "$status" > security-results/gitleaks.exit
|
||||
|
||||
###########################################################
|
||||
# SEMGREP
|
||||
###########################################################
|
||||
|
||||
- name: Semgrep
|
||||
run: |
|
||||
set +e
|
||||
cid="$(docker create \
|
||||
--workdir /src \
|
||||
semgrep/semgrep \
|
||||
semgrep scan \
|
||||
--config auto \
|
||||
--json \
|
||||
--output /src/security-results/semgrep.json \
|
||||
--error \
|
||||
/src)"
|
||||
docker cp . "$cid:/src"
|
||||
docker start -a "$cid"
|
||||
status="$?"
|
||||
docker cp "$cid:/src/security-results/semgrep.json" security-results/semgrep.json || true
|
||||
docker rm -f "$cid" >/dev/null 2>&1 || true
|
||||
echo "$status" > security-results/semgrep.exit
|
||||
|
||||
###########################################################
|
||||
# TRIVY FS
|
||||
###########################################################
|
||||
|
||||
- name: Trivy Filesystem
|
||||
run: |
|
||||
set +e
|
||||
cid="$(docker create \
|
||||
aquasec/trivy:latest \
|
||||
fs \
|
||||
--scanners vuln,secret,misconfig \
|
||||
--severity HIGH,CRITICAL \
|
||||
--format json \
|
||||
--output /workspace/security-results/trivy-fs.json \
|
||||
--exit-code 1 \
|
||||
/workspace)"
|
||||
docker cp . "$cid:/workspace"
|
||||
docker start -a "$cid"
|
||||
status="$?"
|
||||
docker cp "$cid:/workspace/security-results/trivy-fs.json" security-results/trivy-fs.json || true
|
||||
docker rm -f "$cid" >/dev/null 2>&1 || true
|
||||
echo "$status" > security-results/trivy-fs.exit
|
||||
|
||||
###########################################################
|
||||
# DOCKER IMAGE BUILD
|
||||
###########################################################
|
||||
|
||||
- name: Build Image for scan
|
||||
run: |
|
||||
set +e
|
||||
docker build \
|
||||
-t app:${{ gitea.sha }} \
|
||||
-t blinkfink182/qbt-gluetun-portmgr:${{ steps.tag.outputs.tag }} \
|
||||
.
|
||||
echo "$?" > security-results/docker-build.exit
|
||||
|
||||
###########################################################
|
||||
# TRIVY IMAGE
|
||||
###########################################################
|
||||
|
||||
- name: Trivy Image Scan
|
||||
run: |
|
||||
set +e
|
||||
if [ "$(cat security-results/docker-build.exit)" != "0" ]; then
|
||||
echo "Skipping image scan because the image build failed."
|
||||
echo "1" > security-results/trivy-image.exit
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cid="$(docker create \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
aquasec/trivy:latest \
|
||||
image \
|
||||
--severity HIGH,CRITICAL \
|
||||
--format json \
|
||||
--output /tmp/trivy-image.json \
|
||||
--exit-code 1 \
|
||||
app:${{ gitea.sha }})"
|
||||
docker start -a "$cid"
|
||||
status="$?"
|
||||
docker cp "$cid:/tmp/trivy-image.json" security-results/trivy-image.json || true
|
||||
docker rm -f "$cid" >/dev/null 2>&1 || true
|
||||
echo "$status" > security-results/trivy-image.exit
|
||||
|
||||
- name: Create Gitea issues for security findings
|
||||
if: always()
|
||||
env:
|
||||
GITEA_API_URL: ${{ gitea.api_url }}
|
||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||
GITEA_REF_NAME: ${{ gitea.ref_name }}
|
||||
GITEA_RUN_ID: ${{ gitea.run_id }}
|
||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||
GITEA_SHA: ${{ gitea.sha }}
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
python3 <<'PY'
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
token = os.environ.get("GITEA_TOKEN", "")
|
||||
api_url = os.environ["GITEA_API_URL"].rstrip("/")
|
||||
repo = os.environ["GITEA_REPOSITORY"]
|
||||
ref_name = os.environ.get("GITEA_REF_NAME", "")
|
||||
sha = os.environ.get("GITEA_SHA", "")
|
||||
run_id = os.environ.get("GITEA_RUN_ID", "")
|
||||
server_url = os.environ.get("GITEA_SERVER_URL", "").rstrip("/")
|
||||
|
||||
if not token:
|
||||
print("GITEA_TOKEN is unavailable; skipping issue creation.")
|
||||
raise SystemExit(0)
|
||||
|
||||
owner, name = repo.split("/", 1)
|
||||
issues_url = f"{api_url}/repos/{urllib.parse.quote(owner)}/{urllib.parse.quote(name)}/issues"
|
||||
headers = {
|
||||
"Authorization": f"token {token}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
def load_json(path, fallback):
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
return json.load(handle)
|
||||
except FileNotFoundError:
|
||||
return fallback
|
||||
except json.JSONDecodeError as exc:
|
||||
print(f"Could not parse {path}: {exc}")
|
||||
return fallback
|
||||
|
||||
def trim(value, limit):
|
||||
value = str(value or "unknown")
|
||||
return value if len(value) <= limit else value[: limit - 3] + "..."
|
||||
|
||||
def find_existing(title):
|
||||
query = urllib.parse.urlencode({"state": "open", "type": "issues", "q": title})
|
||||
request = urllib.request.Request(f"{issues_url}?{query}", headers=headers)
|
||||
with urllib.request.urlopen(request, timeout=30) as response:
|
||||
issues = json.load(response)
|
||||
return any(issue.get("title") == title for issue in issues)
|
||||
|
||||
def create_issue(title, body):
|
||||
if find_existing(title):
|
||||
print(f"Open issue already exists: {title}")
|
||||
return
|
||||
payload = json.dumps({"title": title, "body": body}).encode("utf-8")
|
||||
request = urllib.request.Request(issues_url, data=payload, headers=headers, method="POST")
|
||||
with urllib.request.urlopen(request, timeout=30) as response:
|
||||
created = json.load(response)
|
||||
print(f"Created issue #{created.get('number')}: {title}")
|
||||
|
||||
def body(scanner, summary, details):
|
||||
run_url = f"{server_url}/{repo}/actions/runs/{run_id}" if server_url and run_id else "Unavailable"
|
||||
lines = [
|
||||
f"Security scanner: `{scanner}`",
|
||||
f"Summary: {summary}",
|
||||
f"Repository: `{repo}`",
|
||||
f"Branch/ref: `{ref_name}`",
|
||||
f"Commit: `{sha}`",
|
||||
f"Action run: {run_url}",
|
||||
"",
|
||||
"Details:",
|
||||
]
|
||||
lines.extend(f"- {key}: {value}" for key, value in details if value not in (None, ""))
|
||||
return "\n".join(lines)
|
||||
|
||||
findings = []
|
||||
|
||||
for finding in load_json("security-results/gitleaks.json", []):
|
||||
rule = finding.get("RuleID") or finding.get("Description") or "secret"
|
||||
file_path = finding.get("File", "unknown")
|
||||
line = finding.get("StartLine", "unknown")
|
||||
title = trim(f"[security][gitleaks] {rule} in {file_path}:{line}", 255)
|
||||
findings.append((
|
||||
title,
|
||||
body("gitleaks", f"{rule} in `{file_path}:{line}`", [
|
||||
("Rule", rule),
|
||||
("Description", finding.get("Description")),
|
||||
("File", file_path),
|
||||
("Line", line),
|
||||
("Fingerprint", finding.get("Fingerprint")),
|
||||
]),
|
||||
))
|
||||
|
||||
semgrep = load_json("security-results/semgrep.json", {})
|
||||
for result in semgrep.get("results", []):
|
||||
extra = result.get("extra", {})
|
||||
start = result.get("start", {})
|
||||
check_id = result.get("check_id", "semgrep finding")
|
||||
file_path = result.get("path", "unknown")
|
||||
line = start.get("line", "unknown")
|
||||
title = trim(f"[security][semgrep] {check_id} in {file_path}:{line}", 255)
|
||||
findings.append((
|
||||
title,
|
||||
body("semgrep", extra.get("message", check_id), [
|
||||
("Check", check_id),
|
||||
("Severity", extra.get("severity")),
|
||||
("File", file_path),
|
||||
("Line", line),
|
||||
("Message", extra.get("message")),
|
||||
]),
|
||||
))
|
||||
|
||||
def add_trivy_findings(path, scanner):
|
||||
report = load_json(path, {})
|
||||
for result in report.get("Results", []):
|
||||
target = result.get("Target", "unknown")
|
||||
for vuln in result.get("Vulnerabilities", []) or []:
|
||||
vuln_id = vuln.get("VulnerabilityID", "vulnerability")
|
||||
package = vuln.get("PkgName", "unknown package")
|
||||
severity = vuln.get("Severity", "UNKNOWN")
|
||||
title = trim(f"[security][{scanner}] {severity} {vuln_id} in {package} on {target}", 255)
|
||||
findings.append((
|
||||
title,
|
||||
body(scanner, vuln.get("Title") or vuln_id, [
|
||||
("Type", "Vulnerability"),
|
||||
("Severity", severity),
|
||||
("Target", target),
|
||||
("Package", package),
|
||||
("Installed version", vuln.get("InstalledVersion")),
|
||||
("Fixed version", vuln.get("FixedVersion")),
|
||||
("Vulnerability ID", vuln_id),
|
||||
("Primary URL", vuln.get("PrimaryURL")),
|
||||
]),
|
||||
))
|
||||
for misconfig in result.get("Misconfigurations", []) or []:
|
||||
misconfig_id = misconfig.get("ID", "misconfiguration")
|
||||
severity = misconfig.get("Severity", "UNKNOWN")
|
||||
title = trim(f"[security][{scanner}] {severity} {misconfig_id} in {target}", 255)
|
||||
findings.append((
|
||||
title,
|
||||
body(scanner, misconfig.get("Title") or misconfig_id, [
|
||||
("Type", "Misconfiguration"),
|
||||
("Severity", severity),
|
||||
("Target", target),
|
||||
("ID", misconfig_id),
|
||||
("Message", misconfig.get("Message")),
|
||||
("Resolution", misconfig.get("Resolution")),
|
||||
]),
|
||||
))
|
||||
for secret in result.get("Secrets", []) or []:
|
||||
rule = secret.get("RuleID", "secret")
|
||||
line = secret.get("StartLine", "unknown")
|
||||
title = trim(f"[security][{scanner}] {rule} in {target}:{line}", 255)
|
||||
findings.append((
|
||||
title,
|
||||
body(scanner, f"{rule} in `{target}:{line}`", [
|
||||
("Type", "Secret"),
|
||||
("Rule", rule),
|
||||
("Category", secret.get("Category")),
|
||||
("Severity", secret.get("Severity")),
|
||||
("Target", target),
|
||||
("Line", line),
|
||||
]),
|
||||
))
|
||||
|
||||
add_trivy_findings("security-results/trivy-fs.json", "trivy-fs")
|
||||
add_trivy_findings("security-results/trivy-image.json", "trivy-image")
|
||||
|
||||
if not findings:
|
||||
print("No security findings found in scanner reports.")
|
||||
raise SystemExit(0)
|
||||
|
||||
for title, issue_body in findings:
|
||||
try:
|
||||
create_issue(title, issue_body)
|
||||
except Exception as exc:
|
||||
print(f"Failed to create issue for {title}: {exc}")
|
||||
PY
|
||||
|
||||
- name: Stop on failed security scans
|
||||
if: always()
|
||||
run: |
|
||||
failed=0
|
||||
for result in security-results/*.exit; do
|
||||
name="$(basename "$result" .exit)"
|
||||
code="$(cat "$result")"
|
||||
if [ "$code" != "0" ]; then
|
||||
echo "$name failed with exit code $code"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$failed" != "0" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Notify Apprise (failure)
|
||||
if: failure()
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"tags\": \"all\",
|
||||
\"title\": \"Gitea Security Scan Failed\",
|
||||
\"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ gitea.ref_name }}\\nSecurity scan failed; check logs and generated issues in Gitea\"
|
||||
}" \
|
||||
${{ secrets.APPRISE_URL }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
id: docker-login
|
||||
if: ${{ gitea.event_name == 'push' }}
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login \
|
||||
--username "${{ secrets.DOCKER_USERNAME }}" \
|
||||
--password-stdin
|
||||
|
||||
- name: Push scanned image
|
||||
id: push
|
||||
if: ${{ gitea.event_name == 'push' }}
|
||||
run: |
|
||||
docker push blinkfink182/qbt-gluetun-portmgr:${{ steps.tag.outputs.tag }}
|
||||
|
||||
- name: Notify Apprise (success)
|
||||
if: ${{ success() && gitea.event_name == 'push' }}
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"tags\": \"all\",
|
||||
\"title\": \"Gitea Build Succeeded\",
|
||||
\"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ gitea.ref_name }}\\nImage tag ${{ steps.tag.outputs.tag }} pushed successfully\"
|
||||
}" \
|
||||
${{ secrets.APPRISE_URL }}
|
||||
|
||||
- name: Notify Apprise (failure)
|
||||
if: ${{ failure() && (steps.docker-login.outcome == 'failure' || steps.push.outcome == 'failure') }}
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"tags\": \"all\",
|
||||
\"title\": \"Gitea Build Failed\",
|
||||
\"body\": \"Repo: ${{ gitea.repository }}\\nBranch: ${{ gitea.ref_name }}\\nCheck logs in Gitea\"
|
||||
}" \
|
||||
${{ secrets.APPRISE_URL }}
|
||||
+12
-2
@@ -1,4 +1,14 @@
|
||||
FROM mcr.microsoft.com/powershell
|
||||
FROM mcr.microsoft.com/powershell:7.5-ubuntu-24.04
|
||||
|
||||
USER root
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends tzdata \
|
||||
&& apt-get dist-upgrade -y \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
USER 1000:1000
|
||||
|
||||
ENV SCHEDULE=60
|
||||
ENV GLUETUNFORWARDEDPORTFILE="/tmp/forwarded_port"
|
||||
@@ -6,6 +16,6 @@ ENV GLUETUNFORWARDEDPORTFILE="/tmp/forwarded_port"
|
||||
VOLUME /tmp
|
||||
|
||||
WORKDIR /data
|
||||
ADD ["Start-QBTGluetunPortMgr.ps1", "/data/"]
|
||||
COPY ["Start-QBTGluetunPortMgr.ps1", "/data/"]
|
||||
|
||||
ENTRYPOINT ["pwsh", "-Command", "/data/Start-QBTGluetunPortMgr.ps1"]
|
||||
+70
-23
@@ -5,6 +5,58 @@
|
||||
[string] $qbtUser = $ENV:QBTUSER
|
||||
[string] $qbtPassword = $ENV:QBTPASSWORD
|
||||
[string] $notificationWebhookURL = $ENV:NOTIFICATIONWEBHOOKURL
|
||||
[string] $discordWebhookURL = $ENV:DISCORDWEBHOOKURL
|
||||
[string] $appriseWebhookURL = $ENV:APPRISEWEBHOOKURL
|
||||
[string] $appriseWebhookTag = $ENV:APPRISEWEBHOOKTAG
|
||||
|
||||
|
||||
function Send-Notification
|
||||
{
|
||||
param(
|
||||
# Content of the notification
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$content
|
||||
)
|
||||
|
||||
if ($notificationWebhookURL -or $discordWebhookURL)
|
||||
{
|
||||
if ($discordWebhookURL) { $notificationWebhookURL = $discordWebhookURL }
|
||||
$headers = @{
|
||||
"Content-Type"="application/json"
|
||||
}
|
||||
$body = @{
|
||||
content=$content
|
||||
# content="QBitTorrent Listening Port updated to $gluetunForwardedPort"
|
||||
}
|
||||
$json = $body | ConvertTo-Json
|
||||
try {
|
||||
Invoke-WebRequest -URI $notificationWebhookURL -Method post -Body $json -Headers $headers
|
||||
}
|
||||
catch {
|
||||
Write-Host "Notification Error Encountered: $($global:Error[0])"
|
||||
}
|
||||
}
|
||||
elseif ($appriseWebhookURL)
|
||||
{
|
||||
$headers = @{
|
||||
"Content-Type"="application/json"
|
||||
}
|
||||
$body = @{
|
||||
title="QBitTorrent Port Manager"
|
||||
body=$content
|
||||
# body="QBitTorrent Listening Port updated to $gluetunForwardedPort"
|
||||
tags="$appriseWebhookTag"
|
||||
}
|
||||
$json = $body | ConvertTo-Json
|
||||
try {
|
||||
Invoke-WebRequest -URI $appriseWebhookURL -Method post -Body $json -Headers $headers
|
||||
}
|
||||
catch {
|
||||
Write-Host "Notification Error Encountered: $($global:Error[0])"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ($true)
|
||||
{
|
||||
@@ -18,32 +70,27 @@ while ($true)
|
||||
username = $qbtUser
|
||||
password = $qbtPassword
|
||||
}
|
||||
$qbtSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
|
||||
$qbtResponse = Invoke-WebRequest -Uri "http://$($qbtHost):$($qbtPort)/api/v2/auth/login" -WebSession $qbtSession -Method POST -Headers $qbtHeaders -Body $qbtBody
|
||||
|
||||
$qbtQueryResponse = Invoke-WebRequest -Uri "http://$($qbtHost):$($qbtPort)/api/v2/app/preferences" -WebSession $qbtSession -Method GET
|
||||
$qbtListenPort = (ConvertFrom-Json $qbtQueryResponse.Content).listen_port
|
||||
Write-Host "Current QBT Listening port: $qbtListenPort"
|
||||
try {
|
||||
$qbtSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
|
||||
$qbtResponse = Invoke-WebRequest -Uri "http://$($qbtHost):$($qbtPort)/api/v2/auth/login" -WebSession $qbtSession -Method POST -Headers $qbtHeaders -Body $qbtBody
|
||||
$qbtQueryResponse = Invoke-WebRequest -Uri "http://$($qbtHost):$($qbtPort)/api/v2/app/preferences" -WebSession $qbtSession -Method GET
|
||||
$qbtListenPort = (ConvertFrom-Json $qbtQueryResponse.Content).listen_port
|
||||
Write-Host "Current QBT Listening port: $qbtListenPort"
|
||||
|
||||
if ($gluetunForwardedPort -eq $qbtListenPort)
|
||||
{
|
||||
Write-Host "Ports match!"
|
||||
}
|
||||
else
|
||||
{
|
||||
$qbtPortChangeBody = 'json={"listen_port":' + $gluetunForwardedPort + '}'
|
||||
$qbtPortChangeResponse = Invoke-WebRequest -Uri "http://$($qbtHost):$($qbtPort)/api/v2/app/setPreferences" -WebSession $qbtSession -Method POST -Headers $qbtHeaders -Body $qbtPortChangeBody
|
||||
if ($notificationWebhookURL)
|
||||
if ($gluetunForwardedPort -eq $qbtListenPort)
|
||||
{
|
||||
$headers = @{
|
||||
"Content-Type"="application/json"
|
||||
}
|
||||
$body = @{
|
||||
content="QBitTorrent Listening Port updated to $gluetunForwardedPort"
|
||||
}
|
||||
$json = $body | ConvertTo-Json
|
||||
Invoke-WebRequest -URI $notificationWebhookURL -Method post -Body $json -Headers $headers
|
||||
Write-Host "Ports match!"
|
||||
}
|
||||
else
|
||||
{
|
||||
$qbtPortChangeBody = 'json={"listen_port":' + $gluetunForwardedPort + '}'
|
||||
$qbtPortChangeResponse = Invoke-WebRequest -Uri "http://$($qbtHost):$($qbtPort)/api/v2/app/setPreferences" -WebSession $qbtSession -Method POST -Headers $qbtHeaders -Body $qbtPortChangeBody
|
||||
Send-Notification -content "QBitTorrent Listening Port updated to $gluetunForwardedPort"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "Error encountered: $($global:Error[0])"
|
||||
Send-Notification -content "Error encountered: $($global:Error[0])"
|
||||
}
|
||||
Write-Host "Sleeping for $schedule minutes..."
|
||||
Start-Sleep ($schedule * 60)
|
||||
|
||||
+4
-1
@@ -10,6 +10,9 @@ services:
|
||||
- QBTPORT=8787 # qbittorrent port
|
||||
- QBTUSER=admin # qbittorrent webUI username
|
||||
- QBTPASSWORD=adminpass # qbittorrent webUI password
|
||||
- NOTIFICATIONWEBHOOKURL=https://discord.com/api/webhooks/<webhookURL> # discord notification wehook URL
|
||||
#- NOTIFICATIONWEBHOOKURL=https://discord.com/api/webhooks/<webhookURL> # DEPRECATED discord notification wehook URL
|
||||
#- DISCORDWEBHOOKURL=https://discord.com/api/webhooks/<webhookURL> # discord notification wehook URL
|
||||
#- APPRISEWEBHOOKURL=https://apprisehost/notify/config # apprise notification url
|
||||
#- APPRISEWEBHOOKTAG=all # apprise notification tag
|
||||
volumes:
|
||||
- C:/Docker/qbittorrent/gluetun/tmp:/tmp # REQUIRED: Path to where the 'forwarded_port' file from gluetun is stored
|
||||
Reference in New Issue
Block a user