-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
218 lines (198 loc) · 11.4 KB
/
Makefile
File metadata and controls
218 lines (198 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# Makefile for Nextcloud app build and App Store management
app_name = $(notdir $(CURDIR))
appstore_dir = $(CURDIR)/build/artifacts/appstore
cache_dir = $(CURDIR)/build/cache
apps_cache = $(cache_dir)/apps.json
apps_etag = $(cache_dir)/apps.etag
cert_dir = $(HOME)/.nextcloud/certificates
version = $(shell xmllint --xpath 'string(//version)' appinfo/info.xml)
tarball = $(appstore_dir)/$(app_name)-$(version).tar.gz
appstore_api = https://apps.nextcloud.com/api/v1
api_token = $(shell cat $(cert_dir)/appstore_api-token 2>/dev/null | tr -d '[:space:]')
# Parse exclude list from krankerl.toml and generate --exclude flags for tar
exclude_flags = $(shell python3 -c 'c=open("krankerl.toml").read();s=c[c.index("[",c.index("exclude"))+1:c.index("]",c.index("exclude"))];items=[x.split(chr(34))[1] for x in s.split(chr(10)) if chr(34) in x];[print("--exclude=../$(app_name)/"+i) for i in items]')
.PHONY: all appstore sign release \
fetch-apps \
register publish list-releases list-releases-full list-for-author delete-release ratings \
clean help
all: appstore
# ── Build ─────────────────────────────────────────────────────────────────────
# Build the App Store tarball
appstore: appinfo/info.xml
rm -rf $(appstore_dir)
mkdir -p $(appstore_dir)
tar czf $(tarball) \
--exclude-vcs \
$(exclude_flags) \
../$(app_name)
@echo "Built: $(tarball)"
# Sign the tarball — output is the base64 signature to paste into GitHub Release
sign: $(tarball)
@echo "Signing $(tarball)..."
openssl dgst -sha512 -sign $(cert_dir)/$(app_name).key $(tarball) | openssl base64
# Build tarball and sign in one step
release: appstore sign
# ── App Store cache ───────────────────────────────────────────────────────────
# Fetch apps.json with ETag caching (always runs as prerequisite).
# 304 Not Modified → use cached file.
# 200 OK → update cache and save new ETag.
# Error + cache → warn and use stale cache.
# Error, no cache → fail.
fetch-apps:
@mkdir -p "$(cache_dir)"; \
_etag=""; \
test -f "$(apps_etag)" && _etag=$$(cat "$(apps_etag)"); \
if [ -n "$$_etag" ] && [ -f "$(apps_cache)" ]; then \
_http=$$(curl -sL --compressed -D /tmp/.fsr_hdrs -o /tmp/.fsr_apps_new -w "%{http_code}" \
-H "If-None-Match: $$_etag" "$(appstore_api)/apps.json"); \
else \
_http=$$(curl -sL --compressed -D /tmp/.fsr_hdrs -o /tmp/.fsr_apps_new -w "%{http_code}" \
"$(appstore_api)/apps.json"); \
fi; \
case "$$_http" in \
304) rm -f /tmp/.fsr_apps_new; \
echo "(apps.json not modified — using cache)";; \
200) mv /tmp/.fsr_apps_new "$(apps_cache)"; \
_new_etag=$$(grep -i '^etag:' /tmp/.fsr_hdrs | head -1 \
| sed 's/^[Ee][Tt][Aa][Gg]:[[:space:]]*//' | tr -d '\r\n'); \
[ -n "$$_new_etag" ] && printf '%s' "$$_new_etag" > "$(apps_etag)"; \
echo "(apps.json updated)";; \
*) rm -f /tmp/.fsr_apps_new; \
if [ -f "$(apps_cache)" ]; then \
echo "(apps.json fetch failed HTTP $$_http — using stale cache)"; \
else \
echo "Failed to fetch apps.json (HTTP $$_http)."; exit 1; \
fi;; \
esac
# ── App Store ─────────────────────────────────────────────────────────────────
# Register the app on the App Store (one-time setup).
# Requires: $(cert_dir)/$(app_name).cert $(cert_dir)/$(app_name).key
register:
@set -e; \
test -f "$(cert_dir)/$(app_name).cert" || { echo "Certificate not found: $(cert_dir)/$(app_name).cert"; exit 1; }; \
test -f "$(cert_dir)/$(app_name).key" || { echo "Key not found: $(cert_dir)/$(app_name).key"; exit 1; }; \
echo "Computing signature over app id '$(app_name)'..."; \
echo -n "$(app_name)" | openssl dgst -sha512 -sign "$(cert_dir)/$(app_name).key" | openssl base64 | tr -d '\n' > /tmp/.fsr_sig; \
python3 -c "import json;cert=open('$(cert_dir)/$(app_name).cert').read().strip().replace('\n','\r\n');sig=open('/tmp/.fsr_sig').read();print(json.dumps({'certificate':cert,'signature':sig}))" > /tmp/.fsr_body; \
echo "Registering $(app_name) on the App Store..."; \
http=$$(curl -s -o /tmp/.fsr_resp -w "%{http_code}" \
-X POST \
-H "Authorization: Token $(api_token)" \
-H "Content-Type: application/json" \
--data-binary @/tmp/.fsr_body \
"$(appstore_api)/apps"); \
case "$$http" in \
201) echo "Success — app registered.";; \
204) echo "Success — registration updated (certificate changed).";; \
400) echo "HTTP 400 — invalid data or signature:"; cat /tmp/.fsr_resp; echo; exit 1;; \
401) echo "HTTP 401 — check $(cert_dir)/appstore_api-token"; exit 1;; \
403) echo "HTTP 403 — not authorized."; exit 1;; \
*) echo "HTTP $$http:"; cat /tmp/.fsr_resp; echo; exit 1;; \
esac
# Publish a new release to the App Store.
# Run 'make appstore' first, upload the tarball to GitHub, then run this.
# Prompts for the GitHub release download URL.
publish:
@test -f "$(tarball)" || { echo "ERROR: $(tarball) not found — run 'make appstore' first."; exit 1; }
@read -p "GitHub release download URL (https://...): " url; \
test -n "$$url" || { echo "Aborted."; exit 0; }; \
echo "Computing signature..."; \
openssl dgst -sha512 -sign "$(cert_dir)/$(app_name).key" "$(tarball)" | openssl base64 | tr -d '\n' > /tmp/.fsr_sig; \
python3 -c "import sys,json;sig=open('/tmp/.fsr_sig').read();print(json.dumps({'download':sys.argv[1],'signature':sig}))" "$$url" > /tmp/.fsr_body; \
echo "Publishing v$(version) to the App Store..."; \
http=$$(curl -s -o /tmp/.fsr_resp -w "%{http_code}" \
-X POST \
-H "Authorization: Token $(api_token)" \
-H "Content-Type: application/json" \
--data-binary @/tmp/.fsr_body \
"$(appstore_api)/apps/releases"); \
case "$$http" in \
200) echo "Release v$(version) updated on the App Store.";; \
201) echo "Release v$(version) published successfully!";; \
400) echo "HTTP 400 — invalid data, signature or URL not reachable:"; cat /tmp/.fsr_resp; echo; exit 1;; \
401) echo "HTTP 401 — check $(cert_dir)/appstore_api-token"; exit 1;; \
403) echo "HTTP 403 — not authorized."; exit 1;; \
*) echo "HTTP $$http:"; cat /tmp/.fsr_resp; echo; exit 1;; \
esac
# List published releases of this app (compact JSON)
list-releases: fetch-apps
@python3 -c "import sys,json;apps=json.load(open('$(apps_cache)'));app=next((a for a in apps if a['id']=='$(app_name)'),None);sys.exit(1) if not app else print(json.dumps({'id':app['id'],'releases':[{'version':r['version'],'created':r['created'],'download':r['download']} for r in app['releases']]},indent=2))" 2>/dev/null \
|| echo "($(app_name) not found in App Store)"
# Full App Store entry as JSON
list-releases-full: fetch-apps
@python3 -c "import sys,json;apps=json.load(open('$(apps_cache)'));app=next((a for a in apps if a['id']=='$(app_name)'),None);sys.exit(1) if not app else print(json.dumps(app,indent=2))" 2>/dev/null \
|| echo "($(app_name) not found in App Store)"
# Find all apps by author name (prompts for search string)
list-for-author: fetch-apps
@read -p "Author search string: " term; \
test -n "$$term" || { echo "Aborted."; exit 1; }; \
python3 -c "import sys,json;apps=json.load(open('$(apps_cache)'));term=sys.argv[1].lower();matched=[{'id':a['id'],'name':next(iter(a.get('translations',{}).values()),{}).get('name',''),'authors':a.get('authors',[]),'releases':[r['version'] for r in a['releases']]} for a in apps if any(term in au['name'].lower() for au in a.get('authors',[]))];print(json.dumps(matched,indent=2))" "$$term" 2>/dev/null \
|| echo "Failed to search app list."
# Delete a specific release from the App Store (interactive)
delete-release: fetch-apps
@set -e; \
releases=$$(python3 -c "import sys,json;apps=json.load(open('$(apps_cache)'));app=next((a for a in apps if a['id']=='$(app_name)'),None);[print(r['version']) for r in (app or {}).get('releases',[])]" 2>/dev/null || true); \
if [ -n "$$releases" ]; then \
echo "Published releases:"; \
echo "$$releases" | sed 's/^/ /'; \
else \
echo "(Could not read app data — current version in info.xml: $(version))"; \
fi; \
read -p "Version to delete (empty = abort): " ver; \
test -n "$$ver" || { echo "Aborted."; exit 0; }; \
read -p "Delete $(app_name) v$$ver from the App Store? [y/N] " confirm; \
[ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ] || { echo "Aborted."; exit 0; }; \
http=$$(curl -s -o /dev/null -w "%{http_code}" \
-X DELETE \
-H "Authorization: Token $(api_token)" \
"$(appstore_api)/apps/$(app_name)/releases/$$ver"); \
case "$$http" in \
204) echo "Release $$ver deleted successfully.";; \
401) echo "HTTP 401 — check $(cert_dir)/appstore_api-token"; exit 1;; \
403) echo "HTTP 403 — not authorized."; exit 1;; \
404) echo "HTTP 404 — release $$ver not found."; exit 1;; \
*) echo "HTTP $$http — unexpected error."; exit 1;; \
esac
# Show ratings for this app from the App Store
ratings:
@curl -sf "$(appstore_api)/ratings.json" 2>/dev/null \
| python3 -c "import sys,json;d=json.load(sys.stdin);own=[r for r in d if r.get('app')=='$(app_name)'];avg=round(sum(r['rating'] for r in own)/len(own)*5,2) if own else None;print(json.dumps({'app':'$(app_name)','count':len(own),'avgRating':avg,'ratings':[{'rating':round(r['rating']*5,1),'ratedAt':r['ratedAt'],'comment':next(iter(r.get('translations',{}).values()),{}).get('comment','')} for r in sorted(own,key=lambda r:r['ratedAt'],reverse=True)]},indent=2))" 2>/dev/null \
|| echo "Failed to fetch ratings."
# ── Utility ───────────────────────────────────────────────────────────────────
# Remove all build artifacts (including cache)
clean:
rm -rf build
# Show available targets and required files
help:
@echo "Usage: make <target>"
@echo ""
@echo "Build:"
@echo " appstore Build the App Store tarball"
@echo " → $(tarball)"
@echo " sign Sign the tarball (stdout = base64 signature,"
@echo " needed for 'make publish' / App Store)"
@echo " release appstore + sign in one step"
@echo ""
@echo "App Store (token: $(cert_dir)/appstore_api-token)"
@echo " (cert: $(cert_dir)/$(app_name).cert)"
@echo " (key: $(cert_dir)/$(app_name).key)"
@echo ""
@echo " register Register app on the App Store (one-time)."
@echo " Needs .cert and .key."
@echo " publish Publish a new release."
@echo " Needs: tarball from 'make appstore'."
@echo " Prompts for: GitHub release download URL."
@echo " list-releases List published releases (compact JSON)."
@echo " list-releases-full Full App Store entry as JSON."
@echo " list-for-author Find all apps by author (prompts for name)."
@echo " delete-release Delete a release (shows list, prompts for version)."
@echo " ratings Show app ratings from the App Store."
@echo ""
@echo " apps.json cache: $(apps_cache)"
@echo " (ETag: $(apps_etag))"
@echo ""
@echo "Utility:"
@echo " clean Remove build/ directory (incl. cache)"
@echo " help Show this help"
@echo ""
@echo "Current: $(app_name) v$(version)"