Skip to content

Commit 439e240

Browse files
authored
Merge pull request #5 from linuxserver/add-new-fields
Add new fields
2 parents e06d9b8 + e151a3a commit 439e240

File tree

5 files changed

+85
-19
lines changed

5 files changed

+85
-19
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ docker run -d \
3535
-e DB_FILE=/config/api.db `#optional` \
3636
-e INVALIDATE_HOURS=24 `#optional` \
3737
-e PAT=token `#optional` \
38+
-e SCARF_TOKEN=token `#optional` \
3839
-e URL=http://localhost:8000 `#optional` \
3940
-p 8000:8000 \
4041
-v /path/to/lsio-api/config:/config \

readme-vars.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ full_custom_readme: |
4040
-e DB_FILE=/config/api.db `#optional` \
4141
-e INVALIDATE_HOURS=24 `#optional` \
4242
-e PAT=token `#optional` \
43+
-e SCARF_TOKEN=token `#optional` \
4344
-e URL=http://localhost:8000 `#optional` \
4445
-p 8000:8000 \
4546
-v /path/to/lsio-api/config:/config \

root/app/api.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,18 @@
1818
async def swagger_ui_html():
1919
return get_swagger_ui_html(openapi_url="/openapi.json", title="LinuxServer API", swagger_favicon_url="/static/logo.png")
2020

21+
async def get_status():
22+
with KeyValueStore() as kv:
23+
return kv["status"]
24+
2125
@api.get("/health", summary="Get the health status")
2226
async def health():
23-
return "Success"
27+
try:
28+
content = await get_status()
29+
return JSONResponse(content=content)
30+
except Exception:
31+
print(traceback.format_exc())
32+
raise HTTPException(status_code=404, detail="Not found")
2433

2534
async def get_images():
2635
with KeyValueStore() as kv:

root/app/models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from pydantic import BaseModel
22

33
# Increment when updating schema or forcing an update on start
4-
IMAGES_SCHEMA_VERSION = 2
4+
IMAGES_SCHEMA_VERSION = 3
5+
SCARF_SCHEMA_VERSION = 1
56

67

78
class Tag(BaseModel):
@@ -92,6 +93,7 @@ class Config(BaseModel):
9293

9394
class Image(BaseModel):
9495
name: str
96+
initial_date: str | None = None
9597
github_url: str
9698
project_url: str | None = None
9799
project_logo: str | None = None
@@ -102,6 +104,7 @@ class Image(BaseModel):
102104
stable: bool
103105
deprecated: bool
104106
stars: int
107+
monthly_pulls: int | None = None
105108
tags: list[Tag]
106109
architectures: list[Architecture]
107110
changelog: list[Changelog] | None = None

root/app/updater.py

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22
from keyvaluestore import KeyValueStore, set_db_schema
33
from models import Architecture, Changelog, Tag, EnvVar, Volume, Port, Config
44
from models import Custom, SecurityOpt, Device, Cap, Hostname, MacAddress, Image
5-
from models import Repository, ImagesData, ImagesResponse, IMAGES_SCHEMA_VERSION
5+
from models import Repository, ImagesData, ImagesResponse, IMAGES_SCHEMA_VERSION, SCARF_SCHEMA_VERSION
66

77
import datetime
8+
import json
89
import os
10+
import requests
911
import time
12+
import traceback
1013

1114
CI = os.environ.get("CI", None)
1215
INVALIDATE_HOURS = int(os.environ.get("INVALIDATE_HOURS", "24"))
16+
SCARF_TOKEN = os.environ.get("SCARF_TOKEN", None)
1317

1418

1519
def get_tags(readme_vars):
@@ -31,13 +35,18 @@ def get_architectures(readme_vars):
3135
archs.append(Architecture(arch=item["arch"], tag=item["tag"]))
3236
return archs
3337

34-
def get_changelogs(readme_vars):
38+
def get_changelog(readme_vars):
3539
if "changelogs" not in readme_vars:
36-
return None
37-
changelogs = []
40+
return None, None
41+
changelog = []
3842
for item in readme_vars["changelogs"][0:3]:
39-
changelogs.append(Changelog(date=item["date"][0:-1], desc=item["desc"]))
40-
return changelogs
43+
date = item["date"][0:-1]
44+
normalized_date = str(datetime.datetime.strptime(date, "%d.%m.%y").date())
45+
changelog.append(Changelog(date=normalized_date, desc=item["desc"]))
46+
first_changelog = readme_vars["changelogs"][-1]
47+
initial_date = first_changelog["date"][0:-1]
48+
normalized_initial_date = str(datetime.datetime.strptime(initial_date, "%d.%m.%y").date())
49+
return changelog, normalized_initial_date
4150

4251
def get_description(readme_vars):
4352
description = readme_vars.get("project_blurb", "No description")
@@ -136,7 +145,7 @@ def get_mac_address(readme_vars):
136145
hostname = readme_vars.get("param_mac_address", False)
137146
return MacAddress(mac_address=hostname, desc=readme_vars.get("param_mac_address_desc", ""), optional=optional)
138147

139-
def get_image(repo):
148+
def get_image(repo, scarf_data):
140149
print(f"Processing {repo.name}")
141150
if not repo.name.startswith("docker-") or repo.name.startswith("docker-baseimage-"):
142151
return None
@@ -153,6 +162,7 @@ def get_image(repo):
153162
application_setup = None
154163
if readme_vars.get("app_setup_block_enabled", False):
155164
application_setup = f"{repo.html_url}?tab=readme-ov-file#application-setup"
165+
changelog, initial_date = get_changelog(readme_vars)
156166
config = Config(
157167
application_setup=application_setup,
158168
readonly_supported=readme_vars.get("readonly_supported", None),
@@ -171,8 +181,8 @@ def get_image(repo):
171181
)
172182
return Image(
173183
name=project_name,
184+
initial_date=initial_date,
174185
github_url=repo.html_url,
175-
stars=repo.stargazers_count,
176186
project_url=readme_vars.get("project_url", None),
177187
project_logo=readme_vars.get("project_logo", None),
178188
description=get_description(readme_vars),
@@ -181,40 +191,82 @@ def get_image(repo):
181191
category=categories,
182192
stable=stable,
183193
deprecated=deprecated,
194+
stars=repo.stargazers_count,
195+
monthly_pulls=scarf_data.get(project_name, None),
184196
tags=tags,
185197
architectures=get_architectures(readme_vars),
186-
changelog=get_changelogs(readme_vars),
198+
changelog=changelog,
187199
config=config,
188200
)
189201

190202
def update_images():
191203
with KeyValueStore(invalidate_hours=INVALIDATE_HOURS, readonly=False) as kv:
192204
is_current_schema = kv.is_current_schema("images", IMAGES_SCHEMA_VERSION)
193205
if ("images" in kv and is_current_schema) or CI == "1":
194-
print(f"{datetime.datetime.now()} - skipped - already updated")
206+
print(f"{datetime.datetime.now()} - images skipped - already updated")
195207
return
196208
print(f"{datetime.datetime.now()} - updating images")
197209
images = []
210+
scarf_data = json.loads(kv["scarf"])
198211
repos = gh.get_repos()
199212
for repo in sorted(repos, key=lambda repo: repo.name):
200-
image = get_image(repo)
213+
image = get_image(repo, scarf_data)
201214
if not image:
202215
continue
203216
images.append(image)
204217

205218
data = ImagesData(repositories=Repository(linuxserver=images))
206-
last_updated = datetime.datetime.now(datetime.timezone.utc).isoformat(' ', 'seconds')
219+
last_updated = datetime.datetime.now(datetime.timezone.utc).isoformat(" ", "seconds")
207220
response = ImagesResponse(status="OK", last_updated=last_updated, data=data)
208221
new_state = response.model_dump_json(exclude_none=True)
209222
kv.set_value("images", new_state, IMAGES_SCHEMA_VERSION)
210223
print(f"{datetime.datetime.now()} - updated images")
211224

225+
def get_monthly_pulls():
226+
pulls_map = {}
227+
response = requests.get("https://api.scarf.sh/v2/packages/linuxserver-ci/overview?per_page=1000", headers={"Authorization": f"Bearer {SCARF_TOKEN}"})
228+
results = response.json()["results"]
229+
for result in results:
230+
name = result["package"]["name"].replace("linuxserver/", "")
231+
if "total_installs" not in result:
232+
continue
233+
monthly_pulls = result["total_installs"]
234+
pulls_map[name] = monthly_pulls
235+
return pulls_map
236+
237+
def update_scarf():
238+
with KeyValueStore(invalidate_hours=INVALIDATE_HOURS, readonly=False) as kv:
239+
is_current_schema = kv.is_current_schema("scarf", SCARF_SCHEMA_VERSION)
240+
if ("scarf" in kv and is_current_schema) or CI == "1":
241+
print(f"{datetime.datetime.now()} - scarf skipped - already updated")
242+
return
243+
print(f"{datetime.datetime.now()} - updating scarf")
244+
pulls_map = get_monthly_pulls()
245+
if not pulls_map:
246+
return
247+
new_state = json.dumps(pulls_map)
248+
kv.set_value("scarf", new_state, SCARF_SCHEMA_VERSION)
249+
print(f"{datetime.datetime.now()} - updated scarf")
250+
251+
def update_status(status):
252+
with KeyValueStore(invalidate_hours=0, readonly=False) as kv:
253+
print(f"{datetime.datetime.now()} - updating status")
254+
kv.set_value("status", status, 0)
255+
print(f"{datetime.datetime.now()} - updated status")
256+
212257
def main():
213-
set_db_schema()
214-
while True:
215-
gh.print_rate_limit()
216-
update_images()
217-
gh.print_rate_limit()
258+
try:
259+
set_db_schema()
260+
while True:
261+
gh.print_rate_limit()
262+
update_scarf()
263+
update_images()
264+
gh.print_rate_limit()
265+
update_status("Success")
266+
time.sleep(INVALIDATE_HOURS*60*60)
267+
except:
268+
print(traceback.format_exc())
269+
update_status("Failed")
218270
time.sleep(INVALIDATE_HOURS*60*60)
219271

220272
if __name__ == "__main__":

0 commit comments

Comments
 (0)