mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-10-13 11:41:36 +02:00
Compare commits
17 Commits
39da9cf9b5
...
fix/komga-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
805f225618 | ||
|
|
4f9cbd0e90 | ||
|
|
08029b9e37 | ||
|
|
1133720b5b | ||
|
|
bc2ef6cbec | ||
|
|
9c36f9b480 | ||
|
|
4e043f7b94 | ||
|
|
9e4e38535f | ||
|
|
96380fdf5f | ||
|
|
aff8931eaf | ||
|
|
69a6a1a62f | ||
|
|
9ffe457e5f | ||
|
|
e219d241bc | ||
|
|
1b0969d537 | ||
|
|
a830293736 | ||
|
|
4f7558050b | ||
|
|
6194f26f00 |
402
.github/generate_map.py
vendored
402
.github/generate_map.py
vendored
@@ -3,17 +3,20 @@
|
||||
Generate a static PNG world map colour-coded by the percentage of your
|
||||
stargazers that come from each country. The script maintains a CSV
|
||||
in ".github/stargazer_countries.csv" cache so that locations are only looked
|
||||
up once (unless the country entry is blank).
|
||||
up once. Blank answers are cached too and retried at most every RECHECK_DAYS,
|
||||
no more than MAX_RECHECKS_PER_RUN re-checks per run.
|
||||
"""
|
||||
|
||||
import csv
|
||||
import datetime
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
import plotly.express as px
|
||||
import plotly.graph_objects as go
|
||||
import pycountry
|
||||
import requests
|
||||
from geopy.geocoders import Nominatim
|
||||
@@ -25,12 +28,87 @@ GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") # provided by workflow
|
||||
CSV_PATH = Path(".github/stargazer_countries.csv")
|
||||
PNG_PATH = Path(".github/stargazer_map.png")
|
||||
|
||||
# ---- Cache policy -----------------------------------------------------------
|
||||
# Most blank rows are permanent: the user simply has no public "location" on
|
||||
# their profile. Re-asking GitHub and Nominatim for them every week is ~1700
|
||||
# wasted requests per run, so a blank answer is cached too and only refreshed
|
||||
# after RECHECK_DAYS. A row with no "last_checked" (i.e. written before the
|
||||
# column existed) counts as never checked and is looked up once, which
|
||||
# stamps it.
|
||||
RECHECK_DAYS = 90
|
||||
|
||||
# Cap on how many already-checked rows one run may *re*-check, oldest first.
|
||||
# It applies only to rows that carry a real last_checked date and have since
|
||||
# expired: left uncapped, they all fall due on the same day and land as one
|
||||
# spike. Rows that have never been checked -- new stargazers, and every row
|
||||
# migrated from the pre-"last_checked" CSV -- are always looked up in full, so
|
||||
# the first run after this lands still sweeps the whole backlog.
|
||||
MAX_RECHECKS_PER_RUN = 200
|
||||
|
||||
# ---- Rendering theme --------------------------------------------------------
|
||||
# Dark, opaque panel: GitHub does not swap the image between README themes, so
|
||||
# a single background has to work in both. A dark canvas with a bright
|
||||
# sequential ramp stays readable on light and dark pages alike.
|
||||
BG = "#0d1117" # page / ocean
|
||||
LAND = "#2b323c" # countries with zero stargazers (still visible)
|
||||
BORDER = "#0d1117" # country outlines, same as background
|
||||
FG = "#e6edf3" # primary text
|
||||
MUTED = "#8b98a5" # secondary text
|
||||
|
||||
# Viridis truncated at 35 %: even a single stargazer gets a colour that is
|
||||
# clearly distinct from the empty-land grey.
|
||||
SCALE = ["#2c728e", "#21918c", "#35b779", "#90d743", "#fde725"]
|
||||
|
||||
# pycountry names that are too long / too formal for a top-5 list
|
||||
SHORT_NAMES = {
|
||||
"Russian Federation": "Russia",
|
||||
"Korea, Republic of": "South Korea",
|
||||
"Korea, Democratic People's Republic of": "North Korea",
|
||||
"Iran, Islamic Republic of": "Iran",
|
||||
"Taiwan, Province of China": "Taiwan",
|
||||
"Viet Nam": "Vietnam",
|
||||
"Moldova, Republic of": "Moldova",
|
||||
"Bolivia, Plurinational State of": "Bolivia",
|
||||
"Venezuela, Bolivarian Republic of": "Venezuela",
|
||||
"Tanzania, United Republic of": "Tanzania",
|
||||
"Syrian Arab Republic": "Syria",
|
||||
}
|
||||
|
||||
HEADERS = {
|
||||
"Authorization": f"token {GITHUB_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
GEOL = Nominatim(user_agent="gh-stargazer-map")
|
||||
|
||||
# Non-answers that Nominatim happily resolves to a real place: "Earth" is a
|
||||
# town in Texas, "Remote" is a settlement in Oregon. Matched on the whole
|
||||
# stripped, lowercased string only -- "Earth, TX" is someone's actual address
|
||||
# and must still geocode.
|
||||
JUNK_LOCATIONS = {
|
||||
"127.0.0.1",
|
||||
"/dev/null",
|
||||
"anywhere",
|
||||
"earth",
|
||||
"everywhere",
|
||||
"here",
|
||||
"home",
|
||||
"internet",
|
||||
"localhost",
|
||||
"mars",
|
||||
"moon",
|
||||
"n/a",
|
||||
"none",
|
||||
"nowhere",
|
||||
"null",
|
||||
"planet earth",
|
||||
"remote",
|
||||
"space",
|
||||
"the internet",
|
||||
"unknown",
|
||||
"world",
|
||||
"worldwide",
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -52,20 +130,50 @@ def fetch_stargazer_usernames():
|
||||
return [s["login"] for s in github_paginated(url)]
|
||||
|
||||
|
||||
def _checked_date(value):
|
||||
"""Normalise a last_checked cell: a non-ISO-date value reads as never."""
|
||||
value = (value or "").strip()
|
||||
try:
|
||||
datetime.date.fromisoformat(value)
|
||||
except ValueError:
|
||||
return ""
|
||||
return value
|
||||
|
||||
|
||||
def load_cache():
|
||||
"""Map each username to (country, last_checked). Reads 2- and 3-column CSVs."""
|
||||
if not CSV_PATH.exists():
|
||||
return {}
|
||||
with CSV_PATH.open(newline="", encoding="utf-8") as f:
|
||||
return {row["username"]: row["country"] for row in csv.DictReader(f)}
|
||||
return {
|
||||
row["username"]: (
|
||||
row["country"],
|
||||
_checked_date(row.get("last_checked")),
|
||||
)
|
||||
for row in csv.DictReader(f)
|
||||
}
|
||||
|
||||
|
||||
def save_cache(cache):
|
||||
"""Write the cache back as username,country,last_checked."""
|
||||
CSV_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
with CSV_PATH.open("w", newline="", encoding="utf-8") as f:
|
||||
w = csv.writer(f)
|
||||
w.writerow(["username", "country"])
|
||||
for user, country in sorted(cache.items()):
|
||||
w.writerow([user, country or ""])
|
||||
w.writerow(["username", "country", "last_checked"])
|
||||
for user, (country, last_checked) in sorted(cache.items()):
|
||||
w.writerow([user, country or "", last_checked])
|
||||
|
||||
|
||||
def needs_lookup(entry, cutoff):
|
||||
"""True if this entry must be (re)queried. entry is None if absent."""
|
||||
if entry is None:
|
||||
return True # new stargazer
|
||||
country, last_checked = entry
|
||||
if country:
|
||||
return False # a known country never changes here
|
||||
if not last_checked:
|
||||
return True # blank, never checked (pre-"last_checked" row)
|
||||
return last_checked < cutoff # blank, and stale enough to retry
|
||||
|
||||
|
||||
def username_to_country(login):
|
||||
@@ -75,47 +183,223 @@ def username_to_country(login):
|
||||
loc = (resp.json() or {}).get("location") or ""
|
||||
if not loc.strip():
|
||||
return ""
|
||||
if loc.strip().strip(".!").lower() in JUNK_LOCATIONS:
|
||||
return ""
|
||||
try:
|
||||
g = GEOL.geocode(loc, language="en", timeout=10)
|
||||
g = GEOL.geocode(loc, language="en", addressdetails=True, timeout=10)
|
||||
except Exception:
|
||||
return ""
|
||||
if not g or "display_name" not in g.raw:
|
||||
return ""
|
||||
# take the last comma-separated component that matches a country
|
||||
for part in reversed(g.raw["display_name"].split(",")):
|
||||
part = part.strip()
|
||||
# Use the ISO code from the structured address: Nominatim's English display
|
||||
# names ("Russia", "Turkey", "Ivory Coast") do not all match pycountry's ISO
|
||||
# names ("Russian Federation", "Türkiye", "Côte d'Ivoire").
|
||||
code = ((g.raw.get("address") or {}).get("country_code") or "") if g else ""
|
||||
country = pycountry.countries.get(alpha_2=code.upper()) if code else None
|
||||
return country.name if country else ""
|
||||
|
||||
|
||||
def count_by_country(cache):
|
||||
"""Counter of country name -> stargazers, ignoring blank locations."""
|
||||
return Counter(country for country, _ in cache.values() if country)
|
||||
|
||||
|
||||
def _log_ticks(lo, hi):
|
||||
"""Colourbar ticks at ... 0.1, 0.3, 1, 3, 10, 30 ... spanning [lo, hi]."""
|
||||
candidates = [m * 10**k for k in range(-3, 3) for m in (1, 3)]
|
||||
ticks = [t for t in candidates if lo / 1.5 <= t <= hi]
|
||||
return ticks or [hi]
|
||||
|
||||
|
||||
def _fmt_pct(value):
|
||||
"""1 -> '1%', 0.3 -> '0.3%' -- no trailing zeros."""
|
||||
return f"{value:.2f}".rstrip("0").rstrip(".") + "%"
|
||||
|
||||
|
||||
def build_figure(counts, total_stargazers):
|
||||
"""Build the choropleth figure from a {country name: stargazers} mapping."""
|
||||
by_iso = {}
|
||||
for name, n in counts.items():
|
||||
try:
|
||||
country = pycountry.countries.lookup(part).name
|
||||
return country
|
||||
code = pycountry.countries.lookup(name).alpha_3
|
||||
except LookupError:
|
||||
pass
|
||||
return ""
|
||||
print("Skip unknown country:", name)
|
||||
continue
|
||||
# two spellings can resolve to the same ISO code, so accumulate
|
||||
by_iso[code] = by_iso.get(code, 0) + n
|
||||
|
||||
iso = list(by_iso)
|
||||
vals = [by_iso[k] for k in iso]
|
||||
# count only what is actually drawn, so the caption matches the map
|
||||
located = sum(vals) or 1
|
||||
pcts = [v / located * 100 for v in vals]
|
||||
lo, hi = (min(pcts), max(pcts)) if pcts else (1.0, 1.0)
|
||||
|
||||
def build_choropleth(percent_by_iso):
|
||||
iso, vals = zip(*percent_by_iso.items())
|
||||
fig = px.choropleth(
|
||||
locations=list(iso),
|
||||
locationmode="ISO-3",
|
||||
color=list(vals),
|
||||
color_continuous_scale="Greens",
|
||||
range_color=(0, max(vals) if vals else 1),
|
||||
# The distribution is heavily long-tailed (the top country holds ~200x the
|
||||
# share of the tail), so a linear ramp collapses everything but a handful
|
||||
# of countries into the first colour step. Colour on log10 of the share.
|
||||
ticks = _log_ticks(lo, hi)
|
||||
fig = go.Figure(
|
||||
go.Choropleth(
|
||||
locations=iso,
|
||||
locationmode="ISO-3",
|
||||
z=[math.log10(p) for p in pcts],
|
||||
zmin=math.log10(lo) - 0.15, # keep the smallest share off the floor
|
||||
zmax=math.log10(hi),
|
||||
colorscale=SCALE,
|
||||
marker_line_color=BORDER,
|
||||
marker_line_width=0.5,
|
||||
colorbar=dict(
|
||||
title=dict(
|
||||
text="share of located stargazers (log scale)",
|
||||
font=dict(color=MUTED, size=13),
|
||||
side="top",
|
||||
),
|
||||
orientation="h",
|
||||
x=0.52,
|
||||
y=0.02,
|
||||
xanchor="center",
|
||||
yanchor="bottom",
|
||||
thickness=12,
|
||||
len=0.34,
|
||||
outlinewidth=0,
|
||||
tickvals=[math.log10(t) for t in ticks],
|
||||
ticktext=[_fmt_pct(t) for t in ticks],
|
||||
tickfont=dict(color=MUTED, size=12),
|
||||
),
|
||||
)
|
||||
)
|
||||
fig.update_layout(
|
||||
coloraxis_colorbar=dict(
|
||||
title="% stargazers",
|
||||
orientation="h", # <-- échelle horizontale
|
||||
x=0.5, # <-- centré
|
||||
y=0, # <-- tout en bas
|
||||
xanchor="center",
|
||||
yanchor="bottom",
|
||||
thickness=15,
|
||||
len=0.7, # <-- longueur de l'échelle, ajustable
|
||||
|
||||
fig.update_geos(
|
||||
projection_type="natural earth",
|
||||
showframe=False,
|
||||
showcoastlines=False,
|
||||
showland=True,
|
||||
landcolor=LAND,
|
||||
showocean=True,
|
||||
oceancolor=BG,
|
||||
showlakes=False,
|
||||
bgcolor=BG,
|
||||
lataxis_range=[-56, 84], # crop Antarctica, it is always empty
|
||||
lonaxis_range=[-176, 186],
|
||||
domain=dict(x=[0.0, 1.0], y=[0.04, 0.92]),
|
||||
)
|
||||
|
||||
repo = REPO or "this repository"
|
||||
caption = (
|
||||
f"{total_stargazers:,} stargazers"
|
||||
f" | {located:,} mapped to a country"
|
||||
f" | {len(by_iso)} countries"
|
||||
)
|
||||
annotations = [
|
||||
dict(
|
||||
text=f"<b>Stargazers of {repo}</b>",
|
||||
x=0.012,
|
||||
y=0.985,
|
||||
xref="paper",
|
||||
yref="paper",
|
||||
xanchor="left",
|
||||
yanchor="top",
|
||||
showarrow=False,
|
||||
font=dict(color=FG, size=25),
|
||||
),
|
||||
dict(
|
||||
text=caption,
|
||||
x=0.012,
|
||||
y=0.925,
|
||||
xref="paper",
|
||||
yref="paper",
|
||||
xanchor="left",
|
||||
yanchor="top",
|
||||
showarrow=False,
|
||||
font=dict(color=MUTED, size=15),
|
||||
),
|
||||
dict(
|
||||
text="Countries in grey have no located stargazer.<br>"
|
||||
"Location is read from the public GitHub profile,<br>"
|
||||
"so the map covers the located subset only.<br>"
|
||||
"Country lookup by Nominatim geocoding,<br>"
|
||||
"data © OpenStreetMap contributors.",
|
||||
x=0.988,
|
||||
y=0.05,
|
||||
xref="paper",
|
||||
yref="paper",
|
||||
xanchor="right",
|
||||
yanchor="bottom",
|
||||
align="right",
|
||||
showarrow=False,
|
||||
font=dict(color=MUTED, size=12),
|
||||
),
|
||||
]
|
||||
|
||||
# Top 5, laid out as two separate annotations (names, share) so each column
|
||||
# stays aligned whatever the country name length -- HTML text in an SVG
|
||||
# annotation collapses padding spaces, so a monospace table would not line
|
||||
# up.
|
||||
top = counts.most_common(5)
|
||||
if top:
|
||||
base_y = 0.40
|
||||
columns = [
|
||||
(
|
||||
0.022,
|
||||
"left",
|
||||
"<br>".join(
|
||||
f"{i}. {SHORT_NAMES.get(name, name)}"
|
||||
for i, (name, _) in enumerate(top, 1)
|
||||
),
|
||||
FG,
|
||||
),
|
||||
(
|
||||
0.215,
|
||||
"right",
|
||||
"<br>".join(f"{n / located * 100:.1f}%" for _, n in top),
|
||||
FG,
|
||||
),
|
||||
]
|
||||
annotations.append(
|
||||
dict(
|
||||
text="<b>TOP COUNTRIES</b>",
|
||||
x=0.022,
|
||||
y=base_y,
|
||||
xref="paper",
|
||||
yref="paper",
|
||||
xanchor="left",
|
||||
yanchor="top",
|
||||
showarrow=False,
|
||||
font=dict(color=MUTED, size=13),
|
||||
)
|
||||
)
|
||||
annotations += [
|
||||
dict(
|
||||
text=text,
|
||||
x=x,
|
||||
y=base_y - 0.055,
|
||||
xref="paper",
|
||||
yref="paper",
|
||||
xanchor=anchor,
|
||||
yanchor="top",
|
||||
align=anchor,
|
||||
showarrow=False,
|
||||
font=dict(color=color, size=15),
|
||||
)
|
||||
for x, anchor, text, color in columns
|
||||
]
|
||||
|
||||
fig.update_layout(
|
||||
width=1240,
|
||||
height=680,
|
||||
paper_bgcolor=BG,
|
||||
plot_bgcolor=BG,
|
||||
margin=dict(l=0, r=0, t=0, b=0),
|
||||
annotations=annotations,
|
||||
)
|
||||
PNG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
fig.write_image(str(PNG_PATH), scale=2)
|
||||
return fig
|
||||
|
||||
|
||||
def build_choropleth(counts, total_stargazers, path=PNG_PATH):
|
||||
fig = build_figure(counts, total_stargazers)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
# 1.5x of 1240x680 -> 1860x1020, sharp on HiDPI at README width without
|
||||
# committing a multi-megabyte PNG every week.
|
||||
fig.write_image(str(path), scale=1.5)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -128,40 +412,46 @@ def main():
|
||||
|
||||
cache = load_cache()
|
||||
|
||||
# Determine which usernames need a lookup
|
||||
to_lookup = [u for u in users if cache.get(u, "") == ""]
|
||||
print(f"Need geocode for {len(to_lookup)} users")
|
||||
# Determine which usernames need a lookup. Anything never checked -- a new
|
||||
# stargazer, or a row migrated from the pre-"last_checked" CSV -- is looked
|
||||
# up in full. Rows that were checked before and have since expired are
|
||||
# rate-limited to MAX_RECHECKS_PER_RUN, oldest first, so the recurring
|
||||
# RECHECK_DAYS wave arrives in slices rather than all at once.
|
||||
now = datetime.date.today()
|
||||
today = now.isoformat()
|
||||
cutoff = (now - datetime.timedelta(days=RECHECK_DAYS)).isoformat()
|
||||
due = [u for u in users if needs_lookup(cache.get(u), cutoff)]
|
||||
never = [u for u in due if not cache.get(u, ("", ""))[1]]
|
||||
expired = sorted(
|
||||
(u for u in due if cache.get(u, ("", ""))[1]),
|
||||
key=lambda u: (cache[u][1], u),
|
||||
)
|
||||
rechecks = expired[:MAX_RECHECKS_PER_RUN]
|
||||
to_lookup = never + rechecks
|
||||
print(
|
||||
f"Need geocode for {len(to_lookup)} users "
|
||||
f"({len(never)} never checked, {len(rechecks)} of {len(expired)} expired)"
|
||||
)
|
||||
|
||||
for i, login in enumerate(to_lookup, 1):
|
||||
country = username_to_country(login)
|
||||
cache[login] = country
|
||||
cache[login] = (country, today)
|
||||
print(f"{i}/{len(to_lookup)}: {login:<20} -> {country}")
|
||||
# Nominatim polite usage
|
||||
time.sleep(1)
|
||||
|
||||
# Ensure all stargazers are in cache (even those with blank location)
|
||||
for u in users:
|
||||
cache.setdefault(u, "")
|
||||
cache.setdefault(u, ("", today))
|
||||
|
||||
save_cache(cache)
|
||||
|
||||
# Build stats
|
||||
countries = [c for c in cache.values() if c]
|
||||
counts = Counter(countries)
|
||||
total = sum(counts.values()) or 1
|
||||
pct_by_country = {c: v / total for c, v in counts.items()}
|
||||
|
||||
# convert to ISO-3 for plotly
|
||||
pct_by_iso = {}
|
||||
for c, pct in pct_by_country.items():
|
||||
try:
|
||||
iso = pycountry.countries.lookup(c).alpha_3
|
||||
pct_by_iso[iso] = pct * 100 # plotly wants numeric
|
||||
except LookupError:
|
||||
print("Skip unknown country:", c)
|
||||
# The cache is never pruned, so it still holds users who have since
|
||||
# unstarred. Keep them for future geocoding, but render only current stars.
|
||||
counts = count_by_country({u: cache[u] for u in users})
|
||||
|
||||
print("Rendering PNG map…")
|
||||
build_choropleth(pct_by_iso)
|
||||
build_choropleth(counts, len(users))
|
||||
print(
|
||||
"Done – files saved:",
|
||||
CSV_PATH.relative_to("."),
|
||||
|
||||
5308
.github/stargazer_countries.csv
vendored
5308
.github/stargazer_countries.csv
vendored
File diff suppressed because it is too large
Load Diff
BIN
.github/stargazer_map.png
vendored
BIN
.github/stargazer_map.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 404 KiB |
2
.github/workflows/daily_ai_fix.yaml
vendored
2
.github/workflows/daily_ai_fix.yaml
vendored
@@ -125,7 +125,7 @@ jobs:
|
||||
|
||||
- name: Analyse and fix
|
||||
if: steps.batch.outputs.count != '0'
|
||||
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
||||
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Skip the OIDC -> Claude App token exchange. The scheduled path
|
||||
|
||||
2
.github/workflows/on_claude_mention.yml
vendored
2
.github/workflows/on_claude_mention.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code
|
||||
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
||||
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.
|
||||
|
||||
2
.github/workflows/on_issue_approved.yaml
vendored
2
.github/workflows/on_issue_approved.yaml
vendored
@@ -135,7 +135,7 @@ jobs:
|
||||
|
||||
- name: Execute the plan
|
||||
if: steps.bundle.outputs.has_plan == 'true'
|
||||
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
||||
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||
|
||||
2
.github/workflows/on_issues_ai_triage.yaml
vendored
2
.github/workflows/on_issues_ai_triage.yaml
vendored
@@ -166,7 +166,7 @@ jobs:
|
||||
id: classify
|
||||
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
|
||||
continue-on-error: true
|
||||
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
||||
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Without this the action falls back to the OIDC -> Claude App token
|
||||
|
||||
2
.github/workflows/on_pr_coderabbit.yml
vendored
2
.github/workflows/on_pr_coderabbit.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
||||
|
||||
- name: Address CodeRabbit comments
|
||||
if: steps.claim.outputs.go == 'true'
|
||||
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
||||
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||
|
||||
@@ -70,9 +70,15 @@ If you want to do add the repository manually, please follow the procedure highl
|
||||
- %%STATS_AMD64%%
|
||||
- %%STATS_AARCH64%%
|
||||
|
||||
### Stars evolution
|
||||
### Star History
|
||||
|
||||
[](https://star-history.com/#alexbelgium/hassio-addons&Date)
|
||||
<a href="https://www.star-history.com/?type=date&repos=alexbelgium%2Fhassio-addons">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&theme=dark&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## Add-ons provided by this repository
|
||||
|
||||
|
||||
10
README.md
10
README.md
@@ -70,9 +70,15 @@ If you want to do add the repository manually, please follow the procedure highl
|
||||
- amd64: 93%
|
||||
- aarch64: 7%
|
||||
|
||||
### Stars evolution
|
||||
### Star History
|
||||
|
||||
[](https://star-history.com/#alexbelgium/hassio-addons&Date)
|
||||
<a href="https://www.star-history.com/?type=date&repos=alexbelgium%2Fhassio-addons">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&theme=dark&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## Add-ons provided by this repository
|
||||
|
||||
|
||||
6
komga/CHANGELOG.md
Normal file
6
komga/CHANGELOG.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## 1.26.1 (2026-08-11)
|
||||
|
||||
- Initial release, based on gotson/komga ([changelog](https://github.com/gotson/komga/releases))
|
||||
- Ingress support : Komga is served on the `/komga` servlet context path, nginx prefixes it back with the ingress entry
|
||||
- Supports local disks and SMB network shares for libraries (`localdisks` / `networkdisks` options)
|
||||
- Supports extra environment variables via the `env_vars` option, see the [documentation](https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2)
|
||||
121
komga/Dockerfile
Normal file
121
komga/Dockerfile
Normal file
@@ -0,0 +1,121 @@
|
||||
#============================#
|
||||
# ALEXBELGIUM'S DOCKERFILE #
|
||||
#============================#
|
||||
# _.------.
|
||||
# _.-` ('>.-`"""-.
|
||||
# '.--'` _'` _ .--.)
|
||||
# -' '-.-';` `
|
||||
# ' - _.' ``'--.
|
||||
# '---` .-'""`
|
||||
# /`
|
||||
#=== Home Assistant Addon ===#
|
||||
|
||||
#################
|
||||
# 1 Build Image #
|
||||
#################
|
||||
|
||||
ARG BUILD_FROM
|
||||
ARG BUILD_VERSION
|
||||
ARG BUILD_UPSTREAM="1.26.1"
|
||||
FROM ${BUILD_FROM}
|
||||
ENV BASHIO_VERSION=0.14.3
|
||||
|
||||
##################
|
||||
# 2 Modify Image #
|
||||
##################
|
||||
|
||||
# No S6_* tuning here : the upstream image ships no s6-overlay, so the vars the
|
||||
# other addons set would be read by nobody
|
||||
|
||||
# Komga is served from a fixed servlet context path. Ingress strips its own
|
||||
# prefix before forwarding, and Komga renders absolute asset urls, so nginx
|
||||
# needs a stable subpath to prefix back. See rootfs/etc/nginx/servers/ingress.conf
|
||||
ENV SERVER_SERVLET_CONTEXTPATH="/komga"
|
||||
|
||||
##################
|
||||
# 3 Install apps #
|
||||
##################
|
||||
|
||||
# Add rootfs
|
||||
# Absolute paths on purpose : the upstream image sets WORKDIR /app, so the
|
||||
# relative "find ." used by the other addons would miss /etc entirely
|
||||
COPY rootfs/ /
|
||||
RUN find /etc/cont-init.d /etc/services.d -type f \( -name "*.sh" -o -name "run" \) -print -exec chmod +x {} \;
|
||||
|
||||
# Uses /bin for compatibility purposes
|
||||
# hadolint ignore=DL4005
|
||||
RUN if [ ! -f /bin/sh ] && [ -f /usr/bin/sh ]; then ln -s /usr/bin/sh /bin/sh; fi && \
|
||||
if [ ! -f /bin/bash ] && [ -f /usr/bin/bash ]; then ln -s /usr/bin/bash /bin/bash; fi
|
||||
|
||||
# Modules
|
||||
ARG MODULES="00-banner.sh 00-global_var.sh 01-custom_script.sh 00-local_mounts.sh 00-smb_mounts.sh"
|
||||
|
||||
# Automatic modules download
|
||||
COPY ha_automodules.sh /ha_automodules.sh
|
||||
RUN chmod 744 /ha_automodules.sh && /ha_automodules.sh "$MODULES" && rm /ha_automodules.sh
|
||||
|
||||
# Manual apps
|
||||
ENV PACKAGES="nginx"
|
||||
|
||||
# Automatic apps & bashio
|
||||
COPY ha_autoapps.sh /ha_autoapps.sh
|
||||
RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps.sh
|
||||
|
||||
################
|
||||
# 4 Entrypoint #
|
||||
################
|
||||
|
||||
# The upstream image is a plain jre image without s6-overlay, so ha_entrypoint
|
||||
# runs as pid 1 : it executes /etc/cont-init.d, then supervises /etc/services.d
|
||||
COPY ha_entrypoint.sh /ha_entrypoint.sh
|
||||
RUN chmod 777 /ha_entrypoint.sh
|
||||
ENTRYPOINT ["/ha_entrypoint.sh"]
|
||||
|
||||
# Install bashio
|
||||
COPY bashio-standalone.sh /usr/local/lib/bashio-standalone.sh
|
||||
RUN chmod 0755 /usr/local/lib/bashio-standalone.sh
|
||||
|
||||
############
|
||||
# 5 Labels #
|
||||
############
|
||||
|
||||
ARG BUILD_ARCH
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_DESCRIPTION
|
||||
ARG BUILD_NAME
|
||||
ARG BUILD_REF
|
||||
ARG BUILD_REPOSITORY
|
||||
ARG BUILD_VERSION
|
||||
ENV BUILD_VERSION="${BUILD_VERSION}"
|
||||
LABEL \
|
||||
io.hass.name="${BUILD_NAME}" \
|
||||
io.hass.description="${BUILD_DESCRIPTION}" \
|
||||
io.hass.arch="${BUILD_ARCH}" \
|
||||
io.hass.type="addon" \
|
||||
io.hass.version=${BUILD_VERSION} \
|
||||
maintainer="alexbelgium (https://github.com/alexbelgium)" \
|
||||
org.opencontainers.image.title="${BUILD_NAME}" \
|
||||
org.opencontainers.image.description="${BUILD_DESCRIPTION}" \
|
||||
org.opencontainers.image.vendor="Home Assistant Add-ons" \
|
||||
org.opencontainers.image.authors="alexbelgium (https://github.com/alexbelgium)" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.url="https://github.com/alexbelgium" \
|
||||
org.opencontainers.image.source="https://github.com/${BUILD_REPOSITORY}" \
|
||||
org.opencontainers.image.documentation="https://github.com/${BUILD_REPOSITORY}/blob/main/README.md" \
|
||||
org.opencontainers.image.created=${BUILD_DATE} \
|
||||
org.opencontainers.image.revision=${BUILD_REF} \
|
||||
org.opencontainers.image.version=${BUILD_VERSION}
|
||||
|
||||
#################
|
||||
# 6 Healthcheck #
|
||||
#################
|
||||
|
||||
# Komga is a jvm app, first boot builds the database : leave it time to settle
|
||||
ENV HEALTH_PORT="25600" \
|
||||
HEALTH_URL="/komga/"
|
||||
HEALTHCHECK \
|
||||
--interval=30s \
|
||||
--retries=5 \
|
||||
--start-period=180s \
|
||||
--timeout=25s \
|
||||
CMD curl -A "HealthCheck: Docker/1.0" -s -f "http://127.0.0.1:${HEALTH_PORT}${HEALTH_URL}" >/dev/null 2>&1 || exit 1
|
||||
70
komga/README.md
Normal file
70
komga/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Home Assistant Add-on: Komga
|
||||
|
||||
Free and open source comics/mangas media server.
|
||||
|
||||
[Komga](https://komga.org) organizes your comics, mangas, BDs, magazines and ebooks, serves them
|
||||
through a web reader, and exposes OPDS, Kobo sync and a REST API for third-party readers
|
||||
(Tachiyomi/Mihon, Panels, Chunky, ...).
|
||||
|
||||
## About
|
||||
|
||||
- Browse and read CBZ, CBR, PDF and EPUB files from any browser
|
||||
- Import metadata, edit series/books, build collections and read lists
|
||||
- Multi-user, with per-user library restrictions and age ratings
|
||||
- OPDS v1/v2, Kobo sync, and a documented REST API
|
||||
|
||||
## Installation
|
||||
|
||||
1. Add this repository to Home Assistant.
|
||||
2. Install the **Komga** add-on.
|
||||
3. Start the add-on and open it from the sidebar (ingress), or on port `25600` at
|
||||
`http://homeassistant:25600/komga`.
|
||||
4. Create the initial user account when the web interface asks for it.
|
||||
5. Add a library pointing at your comics, for example `/media/comics` or `/share/comics`.
|
||||
|
||||
The first start takes longer than usual: Komga is a JVM application and builds its database and
|
||||
search index on first boot.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `PUID` / `PGID` | Ownership applied to the add-on configuration directory. Defaults to `0` (root). |
|
||||
| `TZ` | Timezone, e.g. `Europe/Paris`. |
|
||||
| `localdisks` | Local disks to mount, e.g. `sda1` or a disk label. |
|
||||
| `networkdisks` | SMB shares to mount, e.g. `//192.168.1.2/comics`. Mounted under `/mnt`. |
|
||||
| `cifsusername` / `cifspassword` / `cifsdomain` | Credentials for the SMB shares. |
|
||||
| `smbv1` | Allow the legacy SMBv1 protocol. |
|
||||
| `env_vars` | Extra environment variables passed to Komga. See the [wiki](https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2). |
|
||||
|
||||
Most Komga settings can be passed through `env_vars` using the upstream naming, see the
|
||||
[Komga configuration options](https://komga.org/docs/installation/configuration/). A common one:
|
||||
|
||||
- `JAVA_TOOL_OPTIONS` = `-Xmx1g` — cap the JVM heap on small machines.
|
||||
|
||||
`SERVER_SERVLET_CONTEXTPATH` and `SERVER_PORT` are reserved by the add-on: ingress is built
|
||||
around the `/komga` path on port 25600, and overriding either breaks the sidebar panel.
|
||||
|
||||
## Ingress and URLs
|
||||
|
||||
Komga is served from the `/komga` subpath so that it works behind Home Assistant ingress:
|
||||
|
||||
- from the Home Assistant sidebar: ingress, no extra setup
|
||||
- directly: `http://homeassistant:25600/komga`
|
||||
|
||||
External clients — OPDS readers, Kobo sync, Tachiyomi/Mihon, Panels — must use the direct
|
||||
`http://homeassistant:25600/komga` url. Ingress is browser-session based, so those clients cannot
|
||||
authenticate through it.
|
||||
|
||||
|
||||
## Data
|
||||
|
||||
Komga's database, logs and search index live in `/config` inside the add-on, which Home Assistant
|
||||
maps to this add-on's own configuration directory — `/addon_configs/<repository_id>_komga`, browsable
|
||||
with the Filebrowser add-on. They survive add-on updates. Libraries stay where you put them, under
|
||||
`/media`, `/share` or a mounted disk.
|
||||
|
||||
## Support
|
||||
|
||||
- [Komga upstream project](https://github.com/gotson/komga)
|
||||
- [Add-on repository issues](https://github.com/alexbelgium/hassio-addons/issues)
|
||||
6
komga/build.json
Normal file
6
komga/build.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"build_from": {
|
||||
"aarch64": "gotson/komga:1.26.1",
|
||||
"amd64": "gotson/komga:1.26.1"
|
||||
}
|
||||
}
|
||||
104
komga/config.yaml
Normal file
104
komga/config.yaml
Normal file
@@ -0,0 +1,104 @@
|
||||
arch:
|
||||
- aarch64
|
||||
- amd64
|
||||
description: Free and open source comics/mangas media server
|
||||
devices:
|
||||
- /dev/dri
|
||||
- /dev/dri/card0
|
||||
- /dev/dri/card1
|
||||
- /dev/dri/renderD128
|
||||
- /dev/vchiq
|
||||
- /dev/video10
|
||||
- /dev/video11
|
||||
- /dev/video12
|
||||
- /dev/video13
|
||||
- /dev/video14
|
||||
- /dev/video15
|
||||
- /dev/video16
|
||||
- /dev/ttyUSB0
|
||||
- /dev/sda
|
||||
- /dev/sdb
|
||||
- /dev/sdc
|
||||
- /dev/sdd
|
||||
- /dev/sde
|
||||
- /dev/sdf
|
||||
- /dev/sdg
|
||||
- /dev/nvme
|
||||
- /dev/nvme0
|
||||
- /dev/nvme0n1
|
||||
- /dev/nvme0n1p1
|
||||
- /dev/nvme0n1p2
|
||||
- /dev/nvme0n1p3
|
||||
- /dev/nvme1n1
|
||||
- /dev/nvme1n1p1
|
||||
- /dev/nvme1n1p2
|
||||
- /dev/nvme1n1p3
|
||||
- /dev/nvme2n1
|
||||
- /dev/nvme2n1p1
|
||||
- /dev/nvme2n1p2
|
||||
- /dev/nvme2n3p3
|
||||
- /dev/mmcblk
|
||||
- /dev/fuse
|
||||
- /dev/sda1
|
||||
- /dev/sdb1
|
||||
- /dev/sdc1
|
||||
- /dev/sdd1
|
||||
- /dev/sde1
|
||||
- /dev/sdf1
|
||||
- /dev/sdg1
|
||||
- /dev/sda2
|
||||
- /dev/sdb2
|
||||
- /dev/sdc2
|
||||
- /dev/sdd2
|
||||
- /dev/sde2
|
||||
- /dev/sdf2
|
||||
- /dev/sdg2
|
||||
- /dev/sda3
|
||||
- /dev/sdb3
|
||||
- /dev/sda4
|
||||
- /dev/sdb4
|
||||
- /dev/sda5
|
||||
- /dev/sda6
|
||||
- /dev/sda7
|
||||
- /dev/sda8
|
||||
- /dev/nvme0
|
||||
- /dev/nvme1
|
||||
- /dev/nvme2
|
||||
image: ghcr.io/alexbelgium/komga-{arch}
|
||||
ingress: true
|
||||
ingress_entry: komga
|
||||
init: false
|
||||
map:
|
||||
- addon_config:rw
|
||||
- media:rw
|
||||
- share:rw
|
||||
name: Komga
|
||||
options:
|
||||
env_vars: []
|
||||
PGID: 0
|
||||
PUID: 0
|
||||
panel_icon: mdi:book-open-page-variant
|
||||
ports:
|
||||
25600/tcp: 25600
|
||||
ports_description:
|
||||
25600/tcp: Web interface (path /komga)
|
||||
privileged:
|
||||
- SYS_ADMIN
|
||||
- DAC_READ_SEARCH
|
||||
schema:
|
||||
env_vars:
|
||||
- name: match(^[A-Za-z0-9_]+$)
|
||||
value: str?
|
||||
PGID: int
|
||||
PUID: int
|
||||
TZ: str?
|
||||
cifsdomain: str?
|
||||
cifspassword: str?
|
||||
cifsusername: str?
|
||||
localdisks: str?
|
||||
networkdisks: str?
|
||||
smbv1: bool?
|
||||
slug: komga
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons/tree/master/komga
|
||||
version: "1.26.1"
|
||||
BIN
komga/icon.png
Normal file
BIN
komga/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
BIN
komga/logo.png
Normal file
BIN
komga/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
15
komga/rootfs/etc/cont-init.d/20-config_location.sh
Executable file
15
komga/rootfs/etc/cont-init.d/20-config_location.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
# Komga stores its database, logs and search index in KOMGA_CONFIGDIR, which the
|
||||
# upstream image sets to /config -- that is the addon_config mount
|
||||
|
||||
CONFIG_LOCATION="/config"
|
||||
bashio::log.info "Config stored in $CONFIG_LOCATION"
|
||||
|
||||
mkdir -p "$CONFIG_LOCATION"
|
||||
# Numbered 20- on purpose : it must sort after 00-global_var.sh, which is what
|
||||
# exports PUID/PGID from the addon options. The upstream image sets neither, so
|
||||
# the fallbacks only apply when the module is absent.
|
||||
chown -R "${PUID:-0}:${PGID:-0}" "$CONFIG_LOCATION"
|
||||
17
komga/rootfs/etc/cont-init.d/32-nginx_ingress.sh
Executable file
17
komga/rootfs/etc/cont-init.d/32-nginx_ingress.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
#################
|
||||
# NGINX SETTING #
|
||||
#################
|
||||
declare ingress_interface
|
||||
declare ingress_port
|
||||
declare ingress_entry
|
||||
|
||||
ingress_port=$(bashio::addon.ingress_port)
|
||||
ingress_interface=$(bashio::addon.ip_address)
|
||||
ingress_entry=$(bashio::addon.ingress_entry)
|
||||
sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf
|
||||
sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf
|
||||
sed -i "s|%%ingress_entry%%|${ingress_entry}|g" /etc/nginx/servers/ingress.conf
|
||||
96
komga/rootfs/etc/nginx/includes/mime.types
Normal file
96
komga/rootfs/etc/nginx/includes/mime.types
Normal file
@@ -0,0 +1,96 @@
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/png png;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/webp webp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
1
komga/rootfs/etc/nginx/includes/resolver.conf
Normal file
1
komga/rootfs/etc/nginx/includes/resolver.conf
Normal file
@@ -0,0 +1 @@
|
||||
resolver 127.0.0.11 ipv6=off;
|
||||
56
komga/rootfs/etc/nginx/nginx.conf
Normal file
56
komga/rootfs/etc/nginx/nginx.conf
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
# Run nginx in foreground.
|
||||
daemon off;
|
||||
|
||||
# This is run inside Docker.
|
||||
user root;
|
||||
|
||||
# Pid storage location.
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
# Set number of worker processes.
|
||||
worker_processes 1;
|
||||
|
||||
# Enables the use of JIT for regular expressions to speed-up their processing.
|
||||
pcre_jit on;
|
||||
|
||||
# Write error log to Hass.io add-on log.
|
||||
error_log /proc/1/fd/1 error;
|
||||
|
||||
# Load allowed environment vars
|
||||
env HASSIO_TOKEN;
|
||||
|
||||
# Load dynamic modules.
|
||||
include /etc/nginx/modules-enabled/*.conf;
|
||||
|
||||
# Max num of simultaneous connections by a worker process.
|
||||
events {
|
||||
worker_connections 512;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/includes/mime.types;
|
||||
|
||||
log_format hassio '[$time_local] $status '
|
||||
'$http_x_forwarded_for($remote_addr) '
|
||||
'$request ($http_user_agent)';
|
||||
|
||||
access_log /proc/1/fd/1 hassio;
|
||||
client_max_body_size 4G;
|
||||
default_type application/octet-stream;
|
||||
gzip on;
|
||||
keepalive_timeout 65;
|
||||
sendfile on;
|
||||
server_tokens off;
|
||||
tcp_nodelay on;
|
||||
tcp_nopush on;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
include /etc/nginx/includes/resolver.conf;
|
||||
|
||||
include /etc/nginx/servers/*.conf;
|
||||
}
|
||||
60
komga/rootfs/etc/nginx/servers/ingress.conf
Normal file
60
komga/rootfs/etc/nginx/servers/ingress.conf
Normal file
@@ -0,0 +1,60 @@
|
||||
server {
|
||||
listen %%interface%%:%%port%% default_server;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
# Home Assistant opens the ingress panel at <ingress_entry>/ and forwards it
|
||||
# as / , but Komga only answers below its servlet context path (/komga), so
|
||||
# bounce the panel there. absolute_redirect off keeps the Location relative
|
||||
# to the HA host instead of nginx's own listen address.
|
||||
location = / {
|
||||
absolute_redirect off;
|
||||
return 302 %%ingress_entry%%/komga/;
|
||||
}
|
||||
|
||||
location / {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
proxy_connect_timeout 30m;
|
||||
proxy_send_timeout 30m;
|
||||
proxy_read_timeout 30m;
|
||||
proxy_pass http://127.0.0.1:25600;
|
||||
|
||||
# Komga pushes live events over SSE (/komga/sse/v1/events), which must
|
||||
# not be buffered or the UI stops refreshing until the buffer fills
|
||||
proxy_buffering off;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
||||
# Spring redirects /komga to /komga/ ; the Location it produces is
|
||||
# absolute against the upstream address, so rewrite it back onto the
|
||||
# ingress path (the second rule covers an already relative Location).
|
||||
absolute_redirect off;
|
||||
proxy_redirect http://127.0.0.1:25600/ %%ingress_entry%%/;
|
||||
proxy_redirect / %%ingress_entry%%/;
|
||||
|
||||
# Komga renders its index page with Thymeleaf @{...} link expressions,
|
||||
# so every asset url and window.resourceBaseUrl carry the context path
|
||||
# (/komga). Ingress strips its own prefix before forwarding, so the
|
||||
# browser needs that prefix added back. Only text/html is rewritten
|
||||
# (the nginx default for sub_filter_types) : the SPA derives its api
|
||||
# origin and router base from resourceBaseUrl at runtime, so json
|
||||
# responses and book pages stream through untouched.
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once off;
|
||||
sub_filter "/komga" "%%ingress_entry%%/komga";
|
||||
|
||||
# The epub/divina reader fetches a Readium manifest whose links Komga
|
||||
# builds with ServletUriComponentsBuilder.fromCurrentContextPath(), so
|
||||
# they are fully absolute against the upstream address nginx talks to
|
||||
# (http://127.0.0.1:25600/komga). Rewriting them to a root relative
|
||||
# ingress path also fixes the scheme : Home Assistant may be served over
|
||||
# https, and an absolute http:// link would be blocked as mixed content.
|
||||
# Only the json/xml document types are added here, so book pages are
|
||||
# never scanned.
|
||||
sub_filter "http://127.0.0.1:25600/komga" "%%ingress_entry%%/komga";
|
||||
sub_filter_types application/json application/webpub+json
|
||||
application/divina+json application/opds+json
|
||||
application/atom+xml;
|
||||
}
|
||||
}
|
||||
17
komga/rootfs/etc/services.d/komga/run
Executable file
17
komga/rootfs/etc/services.d/komga/run
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
# ==============================================================================
|
||||
|
||||
# Same invocation as the upstream image entrypoint (gotson/komga), which is
|
||||
# replaced by ha_entrypoint.sh so that cont-init.d and nginx can run too.
|
||||
# SERVER_SERVLET_CONTEXTPATH is set in the Dockerfile, see ingress.conf.
|
||||
|
||||
bashio::log.info "Starting Komga (served on the /komga path, see the addon documentation)"
|
||||
|
||||
cd /app
|
||||
exec java \
|
||||
-Dspring.profiles.include=docker \
|
||||
--enable-native-access=ALL-UNNAMED \
|
||||
-jar application.jar \
|
||||
--spring.config.additional-location=file:/config/
|
||||
30
komga/rootfs/etc/services.d/nginx/run
Executable file
30
komga/rootfs/etc/services.d/nginx/run
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
# ==============================================================================
|
||||
|
||||
# Wait for komga to answer before nginx starts serving ingress. First boot
|
||||
# builds the database, so leave a wide margin, but poll rather than call
|
||||
# bashio::net.wait_for : bashio takes (port host timeout) while the bundled
|
||||
# bashio-standalone.sh takes (host port timeout), and picking the wrong one
|
||||
# would either fail instantly or block for the whole timeout.
|
||||
# The per probe timeouts keep the 15 minute ceiling real : without them a
|
||||
# half open connection would hang a single probe, and the loop, forever.
|
||||
komga_ready=false
|
||||
for _ in $(seq 1 180); do
|
||||
if curl -sf --connect-timeout 2 --max-time 5 -o /dev/null "http://127.0.0.1:25600/komga/"; then
|
||||
komga_ready=true
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# Deliberately not fatal : nginx serving a 502 tells the user something is wrong
|
||||
# and starts working by itself once komga finally answers, while refusing to
|
||||
# start would take ingress down for good after ha_entrypoint gives up retrying.
|
||||
if [ "$komga_ready" != true ]; then
|
||||
bashio::log.warning "Komga did not answer within 15 minutes. Starting NGinx anyway : ingress will return 502 until it does."
|
||||
fi
|
||||
|
||||
bashio::log.info "Starting NGinx..."
|
||||
exec nginx
|
||||
9
komga/updater.json
Normal file
9
komga/updater.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"github_beta": "false",
|
||||
"last_update": "2026-08-11",
|
||||
"repository": "alexbelgium/hassio-addons",
|
||||
"slug": "komga",
|
||||
"source": "github",
|
||||
"upstream_repo": "gotson/komga",
|
||||
"upstream_version": "1.26.1"
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
|
||||
## 2.6.2.5534.9 (2026-08-11)
|
||||
- Update to latest version from linuxserver/docker-prowlarr (changelog : https://github.com/linuxserver/docker-prowlarr/releases)
|
||||
- Upstream tag : nightly-2.6.2.5534-ls9
|
||||
|
||||
## 2.6.2.5517.9 (2026-08-02)
|
||||
- Update to latest version from linuxserver/docker-prowlarr (changelog : https://github.com/linuxserver/docker-prowlarr/releases)
|
||||
- Upstream tag : nightly-2.6.2.5517-ls9
|
||||
|
||||
@@ -110,4 +110,4 @@ schema:
|
||||
slug: prowlarr
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "2.6.2.5517.9"
|
||||
version: "2.6.2.5534.9"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"github_beta": "true",
|
||||
"github_fulltag": "true",
|
||||
"last_update": "2026-08-02",
|
||||
"last_update": "2026-08-11",
|
||||
"repository": "alexbelgium/hassio-addons",
|
||||
"slug": "prowlarr",
|
||||
"source": "github",
|
||||
"upstream_repo": "linuxserver/docker-prowlarr",
|
||||
"upstream_version": "nightly-2.6.2.5517-ls9"
|
||||
"upstream_version": "nightly-2.6.2.5534-ls9"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
|
||||
## 6.3.0.10514.1 (2026-08-11)
|
||||
|
||||
- Bump the addon version so Home Assistant offers the rebuilt image. The `6.3.0.10514` tag was rebuilt on 2026-08-03 and now ships LinuxServer.io `ls313` instead of the `ls311` build that existing installations pulled, but because the addon version string was unchanged the Supervisor saw nothing new and never offered the update. Radarr itself is still 6.3.0.10514 — only the LinuxServer.io base image moved
|
||||
|
||||
## 6.3.0.10514 (2026-07-13)
|
||||
- Update to latest version from linuxserver/docker-radarr (changelog : https://github.com/linuxserver/docker-radarr/releases)
|
||||
|
||||
|
||||
@@ -108,4 +108,4 @@ schema:
|
||||
slug: radarr_nas
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons/tree/master/radarr
|
||||
version: "6.3.0.10514"
|
||||
version: "6.3.0.10514.1"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
|
||||
## 4.0.19.3001 (2026-08-11)
|
||||
- Update to latest version from linuxserver/docker-sonarr (changelog : https://github.com/linuxserver/docker-sonarr/releases)
|
||||
- Upstream tag : develop-4.0.19.3001-ls184
|
||||
|
||||
## 4.0.19.2997 (2026-08-08)
|
||||
- Update to latest version from linuxserver/docker-sonarr (changelog : https://github.com/linuxserver/docker-sonarr/releases)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
ARG BUILD_FROM
|
||||
ARG BUILD_VERSION
|
||||
ARG BUILD_UPSTREAM="4.0.19.2997"
|
||||
ARG BUILD_UPSTREAM="4.0.19.3001"
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
##################
|
||||
|
||||
@@ -110,4 +110,4 @@ schema:
|
||||
slug: sonarr_nas
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons/tree/master/sonarr
|
||||
version: "4.0.19.2997"
|
||||
version: "4.0.19.3001"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"github_beta": true,
|
||||
"last_update": "2026-08-08",
|
||||
"last_update": "2026-08-11",
|
||||
"repository": "alexbelgium/hassio-addons",
|
||||
"slug": "sonarr",
|
||||
"source": "github",
|
||||
"upstream_repo": "linuxserver/docker-sonarr",
|
||||
"upstream_version": "4.0.19.2997"
|
||||
"upstream_version": "4.0.19.3001"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user