1#! /usr/bin/env nix-shell
2#! nix-shell -i python3 -p python3 python3.pkgs.semver nix-prefetch-github
3from urllib.request import Request, urlopen
4import dataclasses
5import subprocess
6import os.path
7import semver
8from typing import (
9 Optional,
10 Dict,
11 List,
12)
13import json
14import os
15
16
17SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
18NIXPKGS = os.path.abspath(os.path.join(SCRIPT_DIR, "../../../../"))
19
20
21OWNER = "LemmyNet"
22UI_REPO = "lemmy-ui"
23SERVER_REPO = "lemmy"
24
25
26@dataclasses.dataclass
27class Pin:
28 serverVersion: str
29 uiVersion: str
30 serverHash: str = ""
31 serverCargoHash: str = ""
32 uiHash: str = ""
33 uiPNPMDepsHash: str = ""
34
35 filename: Optional[str] = None
36
37 def write(self) -> None:
38 if not self.filename:
39 raise ValueError("No filename set")
40
41 with open(self.filename, "w") as fd:
42 pin = dataclasses.asdict(self)
43 del pin["filename"]
44 json.dump(pin, fd, indent=2)
45 fd.write("\n")
46
47
48def github_get(path: str) -> Dict:
49 """Send a GET request to GitHub, optionally adding GITHUB_TOKEN auth header"""
50 url = f"https://api.github.com/{path.lstrip('/')}"
51 print(f"Retrieving {url}")
52
53 req = Request(url)
54
55 if "GITHUB_TOKEN" in os.environ:
56 req.add_header("authorization", f"Bearer {os.environ['GITHUB_TOKEN']}")
57
58 with urlopen(req) as resp:
59 return json.loads(resp.read())
60
61
62def get_latest_release(owner: str, repo: str) -> str:
63 return github_get(f"/repos/{owner}/{repo}/releases/latest")["tag_name"]
64
65
66def prefetch_github(owner: str, repo: str, rev: str) -> str:
67 """Prefetch GitHub rev and return SRI hash"""
68 print(f"Prefetching {owner}/{repo}({rev})")
69
70 proc = subprocess.run(
71 ["nix-prefetch-github", owner, repo, "--rev", rev, "--fetch-submodules"],
72 check=True,
73 stdout=subprocess.PIPE,
74 )
75
76 return json.loads(proc.stdout)["hash"]
77
78
79def get_latest_tag(owner: str, repo: str, prerelease: bool = False) -> str:
80 """Get the latest tag from a GitHub Repo"""
81 tags: List[str] = []
82
83 # As the GitHub API doesn't have any notion of "latest" for tags we need to
84 # collect all of them and sort so we can figure out the latest one.
85 i = 0
86 while i <= 100: # Prevent infinite looping
87 i += 1
88 resp = github_get(f"/repos/{owner}/{repo}/tags?page={i}")
89 if not resp:
90 break
91
92 # Filter out unparseable tags
93 for tag in resp:
94 try:
95 parsed = semver.Version.parse(tag["name"])
96 if (
97 semver.Version.parse(tag["name"])
98 and not prerelease
99 and parsed.prerelease
100 ): # Filter out release candidates
101 continue
102 except ValueError:
103 continue
104 else:
105 tags.append(tag["name"])
106
107 # Sort and return latest
108 return sorted(tags, key=lambda name: semver.Version.parse(name))[-1]
109
110
111def get_fod_hash(attr: str) -> str:
112 """
113 Get fixed output hash for attribute.
114 This depends on a fixed output derivation with an empty hash.
115 """
116
117 print(f"Getting fixed output hash for {attr}")
118
119 proc = subprocess.run(["nix-build", NIXPKGS, "-A", attr], stderr=subprocess.PIPE)
120 if proc.returncode != 1:
121 raise ValueError("Expected nix-build to fail")
122
123 # Iterate list in reverse order so we get the "got:" line early
124 for line in proc.stderr.decode().split("\n")[::-1]:
125 cols = line.split()
126 if cols and cols[0] == "got:":
127 return cols[1]
128
129 raise ValueError("No fixed output hash found")
130
131
132def make_server_pin(pin: Pin, attr: str) -> None:
133 pin.serverHash = prefetch_github(OWNER, SERVER_REPO, pin.serverVersion)
134 pin.write()
135 pin.serverCargoHash = get_fod_hash(attr)
136 pin.write()
137
138
139def make_ui_pin(pin: Pin, attr: str) -> None:
140 pin.uiHash = prefetch_github(OWNER, UI_REPO, pin.uiVersion)
141 pin.write()
142 pin.uiPNPMDepsHash = get_fod_hash(attr)
143 pin.write()
144
145
146if __name__ == "__main__":
147 # Get server version
148 server_version = get_latest_tag(OWNER, SERVER_REPO)
149
150 # Get UI version (not always the same as lemmy-server)
151 ui_version = get_latest_tag(OWNER, UI_REPO)
152
153 pin = Pin(server_version, ui_version, filename=os.path.join(SCRIPT_DIR, "pin.json"))
154 make_server_pin(pin, "lemmy-server")
155 make_ui_pin(pin, "lemmy-ui")