Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2356fc56f7 | ||
|
|
6db26a0d99 | ||
|
|
bbcbd031fe | ||
|
|
7024a57f56 | ||
|
|
5121356113 | ||
|
|
4f2ab59345 | ||
|
|
73bf68c9a8 | ||
|
|
dea1de5fee | ||
|
|
2bd538e685 | ||
|
|
f0c39ea0d2 | ||
|
|
8a0cf15bf7 | ||
|
|
f1c898efe2 | ||
|
|
724dee55c5 | ||
|
|
50a3bcce69 | ||
|
|
ca7be8f3ad | ||
|
|
134ef48848 | ||
|
|
98aa6f01f2 | ||
|
|
4c88030b0f | ||
|
|
0c11c2785e | ||
|
|
96a6926b2b | ||
|
|
f89487231f | ||
|
|
a62d11b5cd | ||
|
|
28e242417d | ||
|
|
0ffa95c505 | ||
|
|
b708f687fa | ||
|
|
4fd9b04c61 | ||
|
|
378332c8da | ||
|
|
3d9b8e8ae6 | ||
|
|
f7b194fc9f | ||
|
|
eb20b97d5f | ||
|
|
35155b18b9 | ||
|
|
b3aa6155cc | ||
|
|
ac62e5bfd6 | ||
|
|
f57a660088 | ||
|
|
ee30aa13cf | ||
|
|
feea3512d0 | ||
|
|
5e23659d99 | ||
|
|
38f4b0e1f8 | ||
|
|
90c7493f15 | ||
|
|
8d8dec5749 | ||
|
|
7d1d9062ed | ||
|
|
8d0b13b309 | ||
|
|
ea250d418d | ||
|
|
e58c229664 | ||
|
|
ee08beef59 | ||
|
|
10ba298233 | ||
|
|
7855278ed1 | ||
|
|
aa179f1ec0 | ||
|
|
f455e71be5 | ||
|
|
f221a5b4d3 | ||
|
|
c18fa8cf77 | ||
|
|
5f572f1b92 | ||
|
|
a7aa89fafe | ||
|
|
e723cbe6dd | ||
|
|
411e75e799 | ||
|
|
0553d60a58 | ||
|
|
7da2e10064 | ||
|
|
a002b91add | ||
|
|
9c441571bc | ||
|
|
643ecf3df7 | ||
|
|
1b2f1bd720 | ||
|
|
c82965067c | ||
|
|
8abc9cae6c | ||
|
|
becc6a71a9 | ||
|
|
78a4f0e513 | ||
|
|
70e1b29913 | ||
|
|
73de83b48f | ||
|
|
26d12d9fad | ||
|
|
f0eddda573 | ||
|
|
b2c72d54c2 | ||
|
|
a56ec5d06a | ||
|
|
40e34181e7 | ||
|
|
6548da3772 | ||
|
|
3ee9b4f0c4 | ||
|
|
9303dbd284 | ||
|
|
97d8390b20 | ||
|
|
c9a48fa23f | ||
|
|
e7c8e2e01e | ||
|
|
fcca57ddf7 | ||
|
|
8b6387f321 | ||
|
|
b8bc304c5c | ||
|
|
ec295fe26e | ||
|
|
e56112cfd7 | ||
|
|
449adc5b12 | ||
|
|
240397e7ac | ||
|
|
3efb719eee | ||
|
|
382cffc015 | ||
|
|
0073dfe044 | ||
|
|
a22fb60647 | ||
|
|
89e4cb843d | ||
|
|
65a96b6b14 | ||
|
|
357355283c | ||
|
|
a2a408410a | ||
|
|
0d9eeb5752 | ||
|
|
bf0878c68b | ||
|
|
eb6d337dfb | ||
|
|
978b3c7f33 | ||
|
|
07547320f1 | ||
|
|
02870fc392 | ||
|
|
b6fc27e44d | ||
|
|
d5cf1b82f2 | ||
|
|
e022c3f735 | ||
|
|
14cc3e8a44 | ||
|
|
93d160aebd | ||
|
|
493e93ea42 | ||
|
|
85c20702cd | ||
|
|
91b248d395 | ||
|
|
b7eadc7867 | ||
|
|
1861d8415c | ||
|
|
7fe5f8f81c | ||
|
|
b3a30e0cf6 | ||
|
|
763e1324e8 | ||
|
|
5188ed89d6 | ||
|
|
9eafd4f7a7 | ||
|
|
71ffa5d1be | ||
|
|
1b843817a4 | ||
|
|
8ae9630b3e | ||
|
|
63f5c43b69 | ||
|
|
e97fe7861d | ||
|
|
19760e5722 | ||
|
|
2570de25a6 | ||
|
|
f46ccadbe4 | ||
|
|
6297ca4b16 | ||
|
|
2accdc2fad | ||
|
|
8a08293016 | ||
|
|
7591f7e313 | ||
|
|
011ce8e5a3 | ||
|
|
b2fa46c760 | ||
|
|
9e14a07e64 | ||
|
|
81aa704537 | ||
|
|
f0f9539466 | ||
|
|
3c2f95d529 | ||
|
|
64e3d04517 | ||
|
|
002381d294 | ||
|
|
9b406d5d83 | ||
|
|
4c475b8c0a | ||
|
|
f3014fe271 | ||
|
|
bd4f6a92c6 | ||
|
|
7e8c139590 | ||
|
|
d241bac55d | ||
|
|
58a24569ce | ||
|
|
53b518960c | ||
|
|
ddf8ba9948 | ||
|
|
a2219b72f6 | ||
|
|
7150cdbbab | ||
|
|
ff14613f95 | ||
|
|
78b3dc1922 | ||
|
|
2e97b337d2 | ||
|
|
369a2660b1 | ||
|
|
60aec370d6 | ||
|
|
6a76b9eafe | ||
|
|
76ba13fbad | ||
|
|
150d38be62 | ||
|
|
a4bbed0cb8 | ||
|
|
90fa9e8741 | ||
|
|
4b20d6ecd0 | ||
|
|
6cac34e968 | ||
|
|
c6f77cec2e | ||
|
|
acc97da84b | ||
|
|
6b10ac2ab5 | ||
|
|
c7563f2c39 | ||
|
|
96676182f4 | ||
|
|
3769d88ebb | ||
|
|
d9c35744f6 | ||
|
|
33991f4e22 | ||
|
|
df300919fc | ||
|
|
315245e048 | ||
|
|
ebdfeb724b | ||
|
|
e842177d9b | ||
|
|
e3a713d2df | ||
|
|
e7879ed3a2 | ||
|
|
19e72fc757 | ||
|
|
38fc5dd582 | ||
|
|
6d284adb98 | ||
|
|
bd171bf73b | ||
|
|
1ee519bb7a | ||
|
|
c53852fdb6 | ||
|
|
206905ea2d | ||
|
|
50537d077c | ||
|
|
229a559aff | ||
|
|
4e514ab151 | ||
|
|
960551f573 | ||
|
|
304ea089a2 | ||
|
|
199f6ffc3b | ||
|
|
7e569bff4c | ||
|
|
0ec5f36050 | ||
|
|
e22be4dca6 | ||
|
|
3237d5953f | ||
|
|
1325418300 | ||
|
|
4505ce96c9 | ||
|
|
b491d45fdc | ||
|
|
168091134d | ||
|
|
28e58a5a5c | ||
|
|
3c533f2d96 | ||
|
|
2e3e654748 | ||
|
|
409e8ed71a | ||
|
|
95489b692c | ||
|
|
b8c06a8622 | ||
|
|
f62add8ae4 | ||
|
|
498d781e19 | ||
|
|
0afed2c3f6 | ||
|
|
efc0ee470b | ||
|
|
fe41382074 | ||
|
|
c851609041 | ||
|
|
03338b1755 | ||
|
|
8a44451c72 | ||
|
|
d434953478 | ||
|
|
7aaa646b82 | ||
|
|
c4f6d98300 | ||
|
|
81fdc5856b | ||
|
|
4e193da3a4 | ||
|
|
29f7c35fc1 | ||
|
|
8775597d5a | ||
|
|
339a0d8e2b | ||
|
|
4ddc7266e9 | ||
|
|
42bf55641b | ||
|
|
c92e074a6f | ||
|
|
741e641bd2 | ||
|
|
9b7f189b02 | ||
|
|
0070538857 | ||
|
|
a11c53ebf7 | ||
|
|
e55def9b60 | ||
|
|
fc66211dd2 | ||
|
|
174e0a7606 | ||
|
|
30cb066b7f | ||
|
|
f1ce0fffad | ||
|
|
9c5ab62b3a | ||
|
|
6adb49eedf | ||
|
|
6482ea6ae8 | ||
|
|
f7d5fde8a9 | ||
|
|
de368a6969 | ||
|
|
e1280f311c | ||
|
|
517dcfa64e | ||
|
|
6faba5f772 | ||
|
|
cca6de2b86 | ||
|
|
c24ccde404 | ||
|
|
d28dd380e5 | ||
|
|
1de167c98d | ||
|
|
7b1a801ab8 | ||
|
|
8239a631f2 | ||
|
|
775ae0147c | ||
|
|
ece6757979 | ||
|
|
dd707bed7e | ||
|
|
276431ef4c | ||
|
|
c25c1dbac7 | ||
|
|
66ad01e7ce | ||
|
|
d4ffd19f01 | ||
|
|
4f693b0465 | ||
|
|
6437e57565 | ||
|
|
2566be2838 | ||
|
|
dc71084323 | ||
|
|
e41605640b | ||
|
|
d091362db2 | ||
|
|
653dadaeff | ||
|
|
af6fb75e6b | ||
|
|
b2fbd10965 | ||
|
|
63dca7d1b6 | ||
|
|
22d5770977 | ||
|
|
dcef36ffa7 | ||
|
|
f65969053a | ||
|
|
8eb6735387 | ||
|
|
7841359917 | ||
|
|
a45b077b7c | ||
|
|
111a72c283 | ||
|
|
b988e431e8 | ||
|
|
87f264a286 | ||
|
|
83f80eb9a7 | ||
|
|
3c6b2a5d49 | ||
|
|
1b658b631c | ||
|
|
384dc46f26 | ||
|
|
48abdb52af | ||
|
|
45d8bc9443 | ||
|
|
b8f360c04e | ||
|
|
fc5ba7d888 | ||
|
|
b7fdff9f7f | ||
|
|
dfafad15bf | ||
|
|
ceed0979a1 | ||
|
|
474d9968b5 | ||
|
|
295eb836bb | ||
|
|
7b11d6673b | ||
|
|
ce48cabb36 | ||
|
|
443fbe0c36 | ||
|
|
9c64f8772d | ||
|
|
c0596d1b5e | ||
|
|
517d9320dc | ||
|
|
bb6e8e5c5a | ||
|
|
f4f6773f26 | ||
|
|
aea2832370 | ||
|
|
59cb3a4d99 | ||
|
|
ebd63bea74 | ||
|
|
ef9c37d5ec | ||
|
|
a1c2559541 | ||
|
|
d80e883479 | ||
|
|
3cef3cbdbd | ||
|
|
ae68d599be | ||
|
|
e4bf2aa13d | ||
|
|
4ed1eb5d59 | ||
|
|
66043eb78d | ||
|
|
8a1b2a5cd6 | ||
|
|
0298256b62 | ||
|
|
a9b0c2ae47 | ||
|
|
f6f755d5c9 | ||
|
|
b3e75df7fa | ||
|
|
bcd3d901d2 | ||
|
|
30ca26cb1a | ||
|
|
bb5a9562d5 | ||
|
|
5501eb555e | ||
|
|
28a65d61f9 | ||
|
|
f98766873f | ||
|
|
7bf22639ba | ||
|
|
f991dc6334 | ||
|
|
1ad1439828 | ||
|
|
9918386257 | ||
|
|
97a399d0fa | ||
|
|
f357eb4da6 | ||
|
|
d6a259bdbe | ||
|
|
9588c3860d | ||
|
|
c0cdedc039 | ||
|
|
1f60b4b4be | ||
|
|
8d06201b41 | ||
|
|
edf3f6cd84 | ||
|
|
9f2b52dd7f | ||
|
|
87a42f09be | ||
|
|
d40e6852e6 | ||
|
|
37f5473054 | ||
|
|
3d7c95ddcc | ||
|
|
454a35d1a9 | ||
|
|
a979e8d78a | ||
|
|
5ceef06b4b | ||
|
|
33695753dd | ||
|
|
3faac7a96d | ||
|
|
38440a2e57 | ||
|
|
c5800785c9 | ||
|
|
b66c73141f | ||
|
|
0a88db1296 | ||
|
|
23e4fae275 | ||
|
|
1e058c6ed1 | ||
|
|
ebf6c0d97b | ||
|
|
5ede7ef4b9 | ||
|
|
165d148cfc | ||
|
|
8e0a52fcd0 | ||
|
|
f3420a4149 | ||
|
|
6dc9a565b0 | ||
|
|
07547a462c | ||
|
|
73e4b80a26 | ||
|
|
4b647ec2c9 | ||
|
|
03c448f1f8 | ||
|
|
e77174ff15 | ||
|
|
18ed16d07e | ||
|
|
9c57e0cff9 | ||
|
|
c45a9d047d | ||
|
|
efe477e5b5 | ||
|
|
41389b61e2 | ||
|
|
0c5fc1d331 | ||
|
|
6eb75ca559 | ||
|
|
1656890b77 | ||
|
|
3ed839334b | ||
|
|
b5162a9b95 | ||
|
|
40c7b1523b | ||
|
|
d37a2ba963 | ||
|
|
26a9752206 | ||
|
|
3e1257bafc | ||
|
|
63699b1610 | ||
|
|
27448868dd | ||
|
|
8a8e222da3 | ||
|
|
f3360e0d97 | ||
|
|
8d9d2fe802 | ||
|
|
96d2c78879 | ||
|
|
e9e3a523dc | ||
|
|
0ef0e32e18 | ||
|
|
cba2df72e2 | ||
|
|
c0445cdca2 | ||
|
|
5f3d676af0 | ||
|
|
e9b24f1242 | ||
|
|
5db95f2def | ||
|
|
10ef871777 | ||
|
|
854b59ede4 | ||
|
|
eb918b805a | ||
|
|
4a4b04d471 | ||
|
|
3943741838 | ||
|
|
e425e060d9 | ||
|
|
6754e53f56 | ||
|
|
357adffb30 | ||
|
|
d24eca12ef | ||
|
|
dfcd1dbb64 | ||
|
|
252ce998ae | ||
|
|
a940a8223d | ||
|
|
bbd1abd4f0 | ||
|
|
7adc18b1ce | ||
|
|
6a5162d615 | ||
|
|
02fe99d5aa | ||
|
|
7bbca3bd40 | ||
|
|
923a528038 | ||
|
|
bb599e7fae | ||
|
|
bb5df3a2e6 | ||
|
|
c805d5c631 | ||
|
|
747234c040 | ||
|
|
b828f59568 | ||
|
|
60ea03e72e | ||
|
|
e9853f1fb1 | ||
|
|
f8b364dce2 | ||
|
|
cd8773681f | ||
|
|
2f99f1f34b | ||
|
|
6fc851f054 | ||
|
|
7bedf4a13d | ||
|
|
a9c8b65c14 | ||
|
|
e336059079 | ||
|
|
708105ab3f | ||
|
|
e6bd1adfc2 | ||
|
|
b217d8e026 | ||
|
|
35b7c4fc2d | ||
|
|
251a4c8ae8 | ||
|
|
b4404113c4 | ||
|
|
c1efaabd36 | ||
|
|
b75483982f | ||
|
|
c1da19c9b2 | ||
|
|
46898cd0ea | ||
|
|
6f4cd7522f | ||
|
|
eaaeaaccdb | ||
|
|
d63653142d | ||
|
|
08588da0d1 | ||
|
|
464f30799a | ||
|
|
bd4bbcaca2 | ||
|
|
09b82ba171 | ||
|
|
f38eb034da | ||
|
|
a3a89db727 | ||
|
|
c099a8eef6 | ||
|
|
9437d211c8 | ||
|
|
096cf56ac9 | ||
|
|
851129e62e | ||
|
|
cc1b22f052 | ||
|
|
5df4358f0d | ||
|
|
821b5a555d | ||
|
|
a0f4a58386 | ||
|
|
7c840bcfa0 | ||
|
|
38d30acb87 | ||
|
|
ae515584cc | ||
|
|
9338d6d4ec | ||
|
|
d98b7797c1 | ||
|
|
3c739dc636 | ||
|
|
639cb8d35f | ||
|
|
f1ac482684 | ||
|
|
9cc7fd2051 | ||
|
|
d4d951b99c | ||
|
|
a4f115064e | ||
|
|
07e0642192 | ||
|
|
965ccb32d6 | ||
|
|
be08f70c8d | ||
|
|
4892c399ba | ||
|
|
687fb323f7 | ||
|
|
56cc021f98 | ||
|
|
836a4cd46b | ||
|
|
a607dff50a | ||
|
|
68751d85dc | ||
|
|
869dd45de5 | ||
|
|
352aa7dd64 | ||
|
|
801221d2da | ||
|
|
861538e9a0 | ||
|
|
cb7b49b3e7 | ||
|
|
f5cced4080 | ||
|
|
e429d7540a | ||
|
|
3bd80dcc58 | ||
|
|
0e01ac67df | ||
|
|
8259df58f6 | ||
|
|
3485ef0877 | ||
|
|
2214acd17e | ||
|
|
25b835dca9 | ||
|
|
2efa6bcd4c | ||
|
|
c67c893f8f | ||
|
|
e7e44375ef | ||
|
|
842e86012d | ||
|
|
a8a7545f40 | ||
|
|
ed7b30dc5f | ||
|
|
c09ed96978 | ||
|
|
44b55a401c | ||
|
|
f00f5b83b5 | ||
|
|
5553c4e0c5 | ||
|
|
6cdda54e10 | ||
|
|
0f9d038aaf | ||
|
|
a38e731209 | ||
|
|
b1b17ad69f | ||
|
|
add40370aa | ||
|
|
a2763d1f6f | ||
|
|
f3048f4804 | ||
|
|
6bfa9c33d0 | ||
|
|
16982aa178 | ||
|
|
a2400f8412 | ||
|
|
6e455760a3 | ||
|
|
0b9b77c632 | ||
|
|
346bff540f | ||
|
|
f8771c371f | ||
|
|
71cc3abbe9 | ||
|
|
a098c42013 | ||
|
|
4cf83443fd | ||
|
|
f5b4889663 | ||
|
|
06aa5caf0b | ||
|
|
d3534dfcf8 | ||
|
|
f7591dd196 | ||
|
|
0c5f7a45bb | ||
|
|
cdbba92f31 | ||
|
|
3b0633f6a8 | ||
|
|
d0880aa04c | ||
|
|
7cf6a5d5ab | ||
|
|
d5763737c3 | ||
|
|
00b0b215b0 | ||
|
|
5f08327c5c | ||
|
|
2645cb426a | ||
|
|
65e866064e | ||
|
|
392df12d8a | ||
|
|
6209980b27 | ||
|
|
3f04cf83a2 | ||
|
|
e5a8194c03 | ||
|
|
90cf0e1127 | ||
|
|
498481d539 | ||
|
|
65da74858b | ||
|
|
af2b7b3157 | ||
|
|
db40779492 | ||
|
|
91aeac8377 | ||
|
|
dfe2ddf34b | ||
|
|
34f2a0bd24 | ||
|
|
4227e392b5 | ||
|
|
6893205ee0 | ||
|
|
28ac23e66b | ||
|
|
cddae6719e | ||
|
|
0cb75982fc | ||
|
|
36de8131c8 | ||
|
|
2f28f45ff1 | ||
|
|
915af9ae96 | ||
|
|
db5d088330 | ||
|
|
d1a2ed8fd7 | ||
|
|
d6b114e423 | ||
|
|
e41d938a5f | ||
|
|
afa54755fd | ||
|
|
0482c040ec | ||
|
|
5dc4dbeaef | ||
|
|
f3f2778f1f | ||
|
|
c08b8979ee | ||
|
|
9a7a828618 | ||
|
|
352a8ed0d0 | ||
|
|
6d3f7e3e36 | ||
|
|
7833cca86b | ||
|
|
6edf78acd7 | ||
|
|
7d43f6e5d9 | ||
|
|
1723a9e744 | ||
|
|
2ba31fd37b | ||
|
|
327e5c15ae | ||
|
|
3efe64a062 | ||
|
|
40b726b8bc | ||
|
|
d2d0d16e4d | ||
|
|
63ad879022 | ||
|
|
2ae0c2ab43 | ||
|
|
2410ba5f5c | ||
|
|
8f0039245d | ||
|
|
0871e4cf0d | ||
|
|
db9f42520a | ||
|
|
3ebcedec26 | ||
|
|
c41a3d427e | ||
|
|
71c39aa18e | ||
|
|
000c51a131 | ||
|
|
2c44c7f2b8 | ||
|
|
c2cf25b4fb | ||
|
|
03ebb8c2db | ||
|
|
f0b153aa37 | ||
|
|
965d130ce7 | ||
|
|
a87a16edb1 | ||
|
|
1493252e28 | ||
|
|
46ff20b0a0 | ||
|
|
1bc84dd90e | ||
|
|
14cbc03621 | ||
|
|
9ec35b0cda | ||
|
|
2d6dd2f0d0 | ||
|
|
572bc71396 | ||
|
|
d4b1739c46 | ||
|
|
76495948e2 | ||
|
|
a95052c787 | ||
|
|
400127b45c | ||
|
|
f77c47b94b | ||
|
|
825636c4f6 | ||
|
|
3aa14e22ed | ||
|
|
acdfcbb18e | ||
|
|
3a1245cbc5 | ||
|
|
f0963405ca | ||
|
|
d078d761d9 | ||
|
|
84fcf02729 | ||
|
|
c308ae5c6e | ||
|
|
93192cfedc | ||
|
|
0f9ae13560 | ||
|
|
f4a6f37415 | ||
|
|
95bfaf5127 | ||
|
|
83e3b22f9a | ||
|
|
cd71404288 | ||
|
|
7a089ab177 | ||
|
|
2513e8454f | ||
|
|
527e911eef | ||
|
|
ff2cdecc86 | ||
|
|
742bde1d27 | ||
|
|
64febbce12 | ||
|
|
98bb1aecf8 | ||
|
|
06c489bd48 | ||
|
|
464d98e124 | ||
|
|
987c9ade18 | ||
|
|
6141d505e4 | ||
|
|
6178843198 | ||
|
|
a7006d746e | ||
|
|
06724cfeb2 | ||
|
|
131a5558b5 | ||
|
|
b32ad82850 | ||
|
|
622e1dcfa5 | ||
|
|
9674bde758 | ||
|
|
b479ada14f | ||
|
|
bff3383ac9 | ||
|
|
d865733838 | ||
|
|
85fcea3837 | ||
|
|
f76a0f240a | ||
|
|
637f03e107 | ||
|
|
55214ccbc5 | ||
|
|
778533a62e | ||
|
|
b4fba47a23 | ||
|
|
76ddfceb13 | ||
|
|
2f6b96e5ba | ||
|
|
f1b7a5146a | ||
|
|
05292ae1f5 | ||
|
|
48fc8db2b4 | ||
|
|
018f84f80d | ||
|
|
d58a47b776 | ||
|
|
797bdd001f | ||
|
|
49314533bf | ||
|
|
27ca058088 | ||
|
|
f4449f9581 | ||
|
|
b52bc03c8d | ||
|
|
edb56f3fcd | ||
|
|
fcc5130068 | ||
|
|
c7751e293d | ||
|
|
bd669a1a8f | ||
|
|
9b5b79525f | ||
|
|
426929556f | ||
|
|
ade640dcea | ||
|
|
61128ddcc8 | ||
|
|
6a3cee8107 | ||
|
|
06d9c07d22 | ||
|
|
49d0f1a141 | ||
|
|
ac73b9d891 | ||
|
|
489980814c | ||
|
|
14654acced | ||
|
|
2885f7a297 | ||
|
|
906c9f11fa | ||
|
|
3419805a49 | ||
|
|
51a6cbe6d4 | ||
|
|
d08eb2afa6 | ||
|
|
91a6b19ba1 | ||
|
|
e66979bab6 | ||
|
|
4ef7641cb2 | ||
|
|
f33fc838cf | ||
|
|
f0019fa213 | ||
|
|
ba8536b36d | ||
|
|
965aec5155 | ||
|
|
387db030cd | ||
|
|
8e557c9924 | ||
|
|
f26e60edf0 | ||
|
|
7bbfd74454 | ||
|
|
018b1ea4da | ||
|
|
14c3fa2e76 | ||
|
|
9168f8114b | ||
|
|
9cabb42edb | ||
|
|
bfba3c9215 | ||
|
|
7184bc09c1 | ||
|
|
9bb0c0f811 | ||
|
|
cf0e469465 | ||
|
|
8e7fa782b3 | ||
|
|
97652802f0 | ||
|
|
1706289124 | ||
|
|
dd23de7aaa | ||
|
|
f1602f67d4 | ||
|
|
c03cd97d3a | ||
|
|
a8e9a074d3 | ||
|
|
1e9d3e0656 | ||
|
|
01e0f3612d | ||
|
|
0015a6d88d | ||
|
|
77dc317da2 | ||
|
|
6aeda3faaf | ||
|
|
ac6a0c9254 | ||
|
|
ca358d9f8b | ||
|
|
03e0683892 | ||
|
|
6ac4c6a7b9 | ||
|
|
a0e0bf65ff | ||
|
|
bc018e1fe0 | ||
|
|
5e39c8813a | ||
|
|
06719cdef0 | ||
|
|
5144e14da4 | ||
|
|
8528d5068b | ||
|
|
c6c4428f7d | ||
|
|
7e4cb0201b | ||
|
|
7887ce12c0 | ||
|
|
2d450239ac | ||
|
|
bf4ccd8cb3 | ||
|
|
c473e80243 | ||
|
|
bf55bcce92 | ||
|
|
49248cedaf |
No files matched your search
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: codebase-context
|
||||
description: Navigate Homarr's monorepo architecture and reuse shared packages. Use when locating code, choosing a package boundary, understanding runtime services, tRPC, databases, widgets, integrations, cron jobs, Custom Widgets, Workshop, or checking @homarr/common before creating a utility.
|
||||
---
|
||||
|
||||
# Homarr Codebase Context
|
||||
|
||||
Orient from the current checkout before editing. Treat package manifests, exports, and source as authoritative when they differ from this snapshot.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Confirm the branch and inspect the nearest `package.json`, existing sibling modules, and package exports.
|
||||
2. Search `@homarr/common` before creating a general-purpose helper. Read [references/common.md](references/common.md) for every public entrypoint and export.
|
||||
3. Read [references/architecture.md](references/architecture.md) when the task crosses packages, runtimes, routing, data, widgets, integrations, cron jobs, Custom Widgets, or Workshop.
|
||||
4. Keep the change in the deepest package that owns the behavior. Reuse public entrypoints rather than deep-importing implementation files.
|
||||
5. Re-run targeted searches before documenting counts, consumers, or dependency edges; these change faster than the architectural seams.
|
||||
|
||||
## Current toolchain
|
||||
|
||||
- Use Node `24.18.0` from `mise.toml` and pnpm `11.15.1` from `package.json`.
|
||||
- Use pnpm workspaces with Turborepo. Workspace packages use the `@homarr/` scope and catalog-managed dependencies.
|
||||
- Use Next.js App Router, TypeScript, tRPC, Drizzle, Mantine v9, Tabler icons, Jotai, TanStack Query, next-intl, Vitest, and Playwright.
|
||||
- Use oxlint and oxfmt. Mantine is the application UI system; Tailwind is limited to the docs app.
|
||||
- `pnpm dev` starts only `@homarr/nextjs`. Start a standalone runtime explicitly when the task needs it.
|
||||
|
||||
## High-value seams
|
||||
|
||||
- Server/RSC tRPC: `@homarr/api/server`
|
||||
- Client tRPC hooks: `@homarr/api/client`
|
||||
- Main router: `packages/api/src/root.ts`
|
||||
- MCP eager router: `packages/api/src/mcp.ts`
|
||||
- Database schemas: `packages/db/schema/{sqlite,mysql,postgresql}.ts`
|
||||
- Widget registry: `packages/widgets/src/registry.ts`
|
||||
- Widget loading manifest: `packages/widgets/src/manifest.ts`
|
||||
- Integration definitions: `packages/definitions/src/integration.ts`
|
||||
- Integration factory: `packages/integrations/src/base/creator.ts`
|
||||
- Cron jobs: `packages/cron-jobs/src/jobs/`
|
||||
- App routes: `apps/nextjs/src/app/[locale]/`
|
||||
|
||||
When a code change affects users, invoke `documentation-sync`. When exposing tRPC through MCP, invoke `mcp-integration`.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Homarr Codebase Context"
|
||||
short_description: "Navigate Homarr architecture and shared APIs"
|
||||
default_prompt: "Use $codebase-context to locate the right Homarr package and reuse existing shared APIs."
|
||||
@@ -0,0 +1,110 @@
|
||||
# Homarr architecture
|
||||
|
||||
## Contents
|
||||
|
||||
- [Applications and runtimes](#applications-and-runtimes)
|
||||
- [Package map](#package-map)
|
||||
- [Dependency direction](#dependency-direction)
|
||||
- [Core patterns](#core-patterns)
|
||||
- [Commands and conventions](#commands-and-conventions)
|
||||
|
||||
## Applications and runtimes
|
||||
|
||||
| Path | Role |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `apps/nextjs` | Main Next.js application and HTTP APIs on port 3000 |
|
||||
| `apps/docs` | Docusaurus documentation site on port 3003 |
|
||||
| `apps/tasks` | Cron-job initialization and scheduling runtime; it does not expose the old port-3002 API |
|
||||
| `apps/websocket` | tRPC WebSocket subscriptions on port 3001 |
|
||||
| `apps/workshop` | Go/PocketBase Workshop service and provider integration |
|
||||
|
||||
`pnpm dev` runs only `@homarr/nextjs`. Use `pnpm --filter @homarr/tasks dev`, `pnpm --filter @homarr/websocket dev`, or `pnpm dev:docs` when a task needs those runtimes.
|
||||
|
||||
In production, `apps/nextjs/src/instrumentation-node.ts` starts the tasks and WebSocket runtimes inside the Node process. Nginx listens on port 7575, proxies `/websockets` to port 3001, and sends other traffic to port 3000. Redis can run internally or externally.
|
||||
|
||||
## Package map
|
||||
|
||||
### Foundation and domain
|
||||
|
||||
- `core`: environment validation, database and Redis factories, logging, infrastructure.
|
||||
- `definitions`: domain enums, integration definitions, widget kinds, permissions.
|
||||
- `common`: shared utilities with environment-specific entrypoints. Search it before adding helpers.
|
||||
- `validation`: shared Zod schemas.
|
||||
- `server-settings`: typed server-setting keys and access.
|
||||
|
||||
### Data, identity, and transport
|
||||
|
||||
- `db`: Drizzle schemas, queries, migrations, and three database drivers.
|
||||
- `auth`: Auth.js/NextAuth configuration, sessions, providers, and API keys.
|
||||
- `api`: tRPC routers, contexts, MCP metadata extraction, OpenAPI, and client/server entrypoints.
|
||||
- `redis`: Redis clients, pub/sub channels, and caches.
|
||||
- `cron-jobs-core`, `cron-jobs`, `cron-job-status`: scheduling primitives, job implementations, and status transport.
|
||||
|
||||
### Backends and external systems
|
||||
|
||||
- `integrations`: typed clients for external applications.
|
||||
- `request-handler`: shared server request flows.
|
||||
- `docker`, `ping`, `image-proxy`, `icons`, `analytics`: bounded infrastructure capabilities.
|
||||
- `custom-widgets`: Custom JSX v2 schema, validation, runtime, and security contracts.
|
||||
- `workshop`: TypeScript Workshop client, backend contracts, and schemas used by Homarr.
|
||||
|
||||
### UI and composed features
|
||||
|
||||
- `ui`: shared Mantine components, theme, and hooks. Search here before creating a primitive.
|
||||
- `form`, `forms-collection`: Zod-backed form setup and reusable forms.
|
||||
- `modals`, `modals-collection`: modal primitives and feature modals.
|
||||
- `boards`: board state, edit mode, and cache coordination.
|
||||
- `settings`, `spotlight`, `notifications`, `onboarding`: cross-feature UI flows.
|
||||
- `widgets`: widget definitions, dynamic component loaders, and shared widget UI.
|
||||
- `translation`: next-intl configuration and locale catalogs.
|
||||
- `cli`: Node operations CLI. `tools/homarr-dev` is the separate Go developer CLI.
|
||||
|
||||
## Dependency direction
|
||||
|
||||
Prefer these directions; verify the exact dependency in package manifests before changing a boundary.
|
||||
|
||||
1. Put infrastructure in `core` and stable domain names in `definitions`.
|
||||
2. Put reusable, dependency-light helpers in `common`; use its public environment-specific entrypoints.
|
||||
3. Build persistence in `db`, identity in `auth`, transport in `redis`, and external clients in `integrations`.
|
||||
4. Compose server behavior in `api`, `cron-jobs`, and request-handler packages.
|
||||
5. Build primitives in `ui`, `form`, and `modals`; compose product surfaces in widgets and collection packages.
|
||||
6. Keep app entrypoints in `apps/*` thin. Avoid moving reusable behavior into `apps/nextjs` when a package already owns it.
|
||||
|
||||
## Core patterns
|
||||
|
||||
### tRPC and MCP
|
||||
|
||||
`packages/api/src/root.ts` lazily composes the application router. Server components use `@homarr/api/server`; client components use `@homarr/api/client`. MCP extraction requires the eager router in `packages/api/src/mcp.ts`, so MCP-enabled subrouters must be imported and registered there directly. Use the `mcp-integration` skill for exposure and security rules.
|
||||
|
||||
### Databases
|
||||
|
||||
Drizzle schemas live in parallel files under `packages/db/schema/`. `DB_DRIVER` selects `better-sqlite3`, `mysql2`, or `node-postgres`; migrations live in matching directories under `packages/db/migrations/`. Apply schema behavior consistently across all supported drivers unless the task is explicitly driver-specific.
|
||||
|
||||
### Widgets
|
||||
|
||||
`packages/widgets/src/registry.ts` owns the explicit widget-module and component-loader registries. `packages/widgets/src/manifest.ts` caches those loaders and exposes definitions, components, and combined resources. Widget modules use `createWidgetDefinition` and typed option builders. Data commonly comes from tRPC queries or Redis-backed cron results. Use existing shared widget components before creating local variants.
|
||||
|
||||
### Integrations
|
||||
|
||||
`packages/definitions/src/integration.ts` owns `integrationDefs` and `IntegrationKind`. `packages/integrations/src/base/creator.ts` owns `createIntegrationAsync`; concrete integrations implement the shared base contract. Keep credentials and permission checks on server-owned paths.
|
||||
|
||||
### Cron jobs
|
||||
|
||||
Jobs live in `packages/cron-jobs/src/jobs/` and use `createCronJob`. `apps/tasks` initializes and starts the job group. Job results and status can flow through Redis and tRPC; the retired port-3002 management-API model is no longer part of the architecture.
|
||||
|
||||
### Routing and localization
|
||||
|
||||
User routes live below `apps/nextjs/src/app/[locale]/`; the locale segment does not add a URL prefix. Route groups organize home and board surfaces. Administrative routes live under `manage`, Custom Widget configuration has a token route, and Workshop has a management surface.
|
||||
|
||||
### Custom Widgets and Workshop
|
||||
|
||||
`packages/custom-widgets` owns the Custom JSX v2 contract. `packages/workshop` is the Homarr-side client/contract package; `apps/workshop` is the separate Go/PocketBase service. Use the `homarr-custom-widget` skill for authoring and security constraints.
|
||||
|
||||
## Commands and conventions
|
||||
|
||||
- Read current scripts from the root or package `package.json`; avoid caching commands that do not exist.
|
||||
- Use `pnpm dev` for Next.js, `pnpm dev:docs` for docs, and `pnpm dev:cli -- <args>` for the Go developer CLI.
|
||||
- Use `pnpm docker:dev:up` for the Redis development dependency.
|
||||
- Use package-filtered typechecks or focused tests for the touched behavior.
|
||||
- Use `~/*` only inside the Next.js app. Use public `@homarr/*` entrypoints across package boundaries.
|
||||
- Keep Mantine application styles separate from the Tailwind-based docs app.
|
||||
@@ -0,0 +1,86 @@
|
||||
# `@homarr/common` public API
|
||||
|
||||
## Contents
|
||||
|
||||
- [Reuse workflow](#reuse-workflow)
|
||||
- [Entrypoints](#entrypoints)
|
||||
- [Root exports](#root-exports)
|
||||
- [Types exports](#types-exports)
|
||||
- [Server exports](#server-exports)
|
||||
- [Client and environment exports](#client-and-environment-exports)
|
||||
|
||||
## Reuse workflow
|
||||
|
||||
Before writing a utility, search both the public name and the behavior:
|
||||
|
||||
```bash
|
||||
rg -n "export (const|function|class|type|interface)|export \*" packages/common
|
||||
rg -n "@homarr/common" apps packages
|
||||
```
|
||||
|
||||
Import only from an exported package entrypoint. Do not deep-import `@homarr/common/src/*`. Check `packages/common/package.json` and the source again when adding or renaming an export; this inventory describes the current repository tree.
|
||||
|
||||
## Entrypoints
|
||||
|
||||
| Import | Source | Environment |
|
||||
| ----------------------- | ---------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| `@homarr/common` | `index.ts` -> `src/index.ts` | Shared root; includes React hooks and some Next/Mantine-aware helpers |
|
||||
| `@homarr/common/types` | `src/types.ts` | Type-only utilities |
|
||||
| `@homarr/common/server` | `src/server.ts` | Node/server-only security, encryption, request, error, and bounded-response helpers |
|
||||
| `@homarr/common/client` | `src/client.ts` | Client-facing Next server action export |
|
||||
| `@homarr/common/env` | `env.ts` | Validated package environment |
|
||||
|
||||
## Root exports
|
||||
|
||||
| Area | Public exports | Purpose |
|
||||
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| Objects | `objectKeys`, `objectEntries`, `hashObjectBase64` | Typed key/entry iteration and stable query-key hashing |
|
||||
| Strings | `capitalize`, `isNullOrWhitespace`, `bestMatch`, `normalizeImageName`, `getImageMatchRank` | Text normalization and matching |
|
||||
| Cookies | `parseCookies`, `setClientCookie` | Cookie parsing and browser cookie serialization |
|
||||
| Arrays | `splitToNChunks`, `splitToChunksWithNItems` | Split by number of groups or maximum group size |
|
||||
| Dates | `formatDuration`, `toValidDate`, `isDateWithin` | Duration formatting, safe date coercion, relative-window checks |
|
||||
| Timing | `Stopwatch` | Elapsed-time measurement and formatting |
|
||||
| React hooks | `useTimeAgo`, `useIntegrationConnected` | Relative time and integration freshness state |
|
||||
| URLs | `removeTrailingSlash`, `extractBaseUrlFromHeaders`, `getPortFromUrl`, `isAbsoluteUrl`, `SAFE_NEW_TAB_REL`, `getSafeApplicationUrl`, `getSafeAppHref` | URL normalization, safe navigation, ports, and new-tab policy |
|
||||
| Numbers and bytes | `formatNumber`, `randomInt`, `formatBytes`, `formatBytesPair`, `formatByteRate`, `metricToImperial`, `imperialToMetric` | Compact numbers, byte displays, rates, and unit conversion |
|
||||
| Legacy bytes | `humanFileSize` | Deprecated; use the `formatBytes*` helpers in new code |
|
||||
| Byte types | `ByteUnitSystem`, `FormatBytesOptions` | Options for byte formatting |
|
||||
| Errors | `extractErrorMessage`, `FlattenError` | Unknown-error messages and flattenable domain errors |
|
||||
| Theme | `getMantineColor` | Resolve a Mantine color and shade |
|
||||
| Functions | `isFunction` | Unknown-value function guard |
|
||||
| Grid | `GridAlgorithmItem`, `generateResponsiveGridFor` | Responsive nested-section grid placement |
|
||||
| IDs | `createId` | CUID2 ID creation |
|
||||
|
||||
The root deliberately does not re-export `types`, `server`, `client`, or `env`; use their named entrypoints.
|
||||
|
||||
## Types exports
|
||||
|
||||
`@homarr/common/types` exports:
|
||||
|
||||
- `MaybePromise<T>`
|
||||
- `AtLeastOneOf<T>`
|
||||
- `Modify<T, R>`
|
||||
- `RemoveReadonly<T>`
|
||||
- `MaybeArray<T>`
|
||||
- `Inverse<T>`
|
||||
- `inferSearchParamsFromSchema<TSchema>`
|
||||
|
||||
## Server exports
|
||||
|
||||
`@homarr/common/server` exports:
|
||||
|
||||
- Security: `generateSecureRandomToken`.
|
||||
- Encryption: `encryptSecret`, `decryptSecret`, `decryptSecretWithKey`, `createKeyedFingerprint`, `verifyKeyedFingerprint`.
|
||||
- Requests: `userAgent`, `DeviceType`, `ipAddressFromHeaders`.
|
||||
- Bounded bodies: `readBoundedJsonResponseAsync`.
|
||||
- Parse errors: `ParseError`, `ParseErrorHandler`, `ZodParseErrorHandler`, `JsonParseErrorHandler`.
|
||||
- HTTP handlers: `HttpErrorHandler`, `FetchHttpErrorHandler`, `matchErrorCode`, `OFetchHttpErrorHandler`, `AxiosHttpErrorHandler`, `TsdavHttpErrorHandler`, `OctokitHttpErrorHandler`.
|
||||
- HTTP errors: `RequestError`, `ResponseError`, `requestErrorMap`.
|
||||
- HTTP error types: `AnyRequestError`, `AnyRequestErrorInput`, `RequestErrorInput`, `RequestErrorType`, `RequestErrorReason`, `AnyRequestErrorReason`, `RequestErrorCode`.
|
||||
|
||||
`NodeFetchHttpErrorHandler` exists as an implementation file but is not exported by the public server entrypoint. Add an intentional public export before using it across packages.
|
||||
|
||||
## Client and environment exports
|
||||
|
||||
- `@homarr/common/client` exports `revalidatePathActionAsync`, a Next server action for page revalidation.
|
||||
- `@homarr/common/env` exports `env` with validated `NODE_ENV`, `SECRET_ENCRYPTION_KEY`, and `NO_EXTERNAL_CONNECTION` values.
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: documentation-sync
|
||||
description: Keep Homarr's Fumadocs documentation aligned with user-facing code. Use when adding or changing integrations, widgets, APIs, environment variables, CLI commands, authentication, permissions, cron jobs, settings, or UI behavior that users must understand.
|
||||
---
|
||||
|
||||
# Documentation Sync
|
||||
|
||||
Update `apps/docs/` in the same change as user-facing behavior. Inspect adjacent pages and the docs types before copying a pattern; the source and docs evolve together.
|
||||
|
||||
## Map the change
|
||||
|
||||
| Code change | Documentation target |
|
||||
| ----------------------------------- | -------------------------------------------------------------------- |
|
||||
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` and `index.ts` |
|
||||
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` and `index.ts` |
|
||||
| Changed public or management API | `apps/docs/docs/management/api/index.mdx` |
|
||||
| New or changed environment variable | Relevant page under `apps/docs/docs/advanced/` or installation docs |
|
||||
| New or changed CLI command | `apps/docs/docs/advanced/command-line/` |
|
||||
| Authentication/provider change | Relevant SSO or authentication page under `apps/docs/docs/advanced/` |
|
||||
| UI or workflow change | Relevant getting-started, management, widget, or integration page |
|
||||
| Cron-job behavior | `apps/docs/docs/management/tasks.mdx` |
|
||||
| Permission behavior | `apps/docs/docs/management/users.mdx` and the affected feature page |
|
||||
| Custom Widget or Workshop behavior | Relevant Custom Widget, Assistant, or Workshop documentation |
|
||||
|
||||
If no existing row fits, search by the user-visible term and update the page where a user would look for the behavior:
|
||||
|
||||
```bash
|
||||
rg -n "<term>" apps/docs/docs apps/docs/src
|
||||
```
|
||||
|
||||
## Follow existing typed patterns
|
||||
|
||||
For an integration:
|
||||
|
||||
- Add or update `apps/docs/docs/integrations/<slug>/index.ts` with the local `IntegrationDefinition` pattern.
|
||||
- Add or update `index.mdx` with the established `IntegrationHeader`, `IntegrationCapabilites`, and `IntegrationSecrets` components as applicable.
|
||||
- Reuse metadata from `@homarr/definitions` when the docs app already exposes it.
|
||||
|
||||
For a widget:
|
||||
|
||||
- Add or update `apps/docs/docs/widgets/<slug>/index.ts` with the local `WidgetDefinition` pattern.
|
||||
- Add or update `index.mdx` with the established `WidgetHeader`, `WidgetConfig`, and `AddingWidget` components as applicable.
|
||||
- Document options, required integrations, permissions, empty states, and changed behavior that affect setup or use.
|
||||
|
||||
## Completion criteria
|
||||
|
||||
1. Describe the shipped behavior, not an implementation plan.
|
||||
2. Keep names, defaults, paths, screenshots, links, and prerequisites consistent with code.
|
||||
3. Update every affected page and remove superseded guidance.
|
||||
4. Run the narrowest useful docs validation. Use `pnpm turbo build --filter=@homarr/docs` when links, MDX, generated definitions, or navigation can fail; otherwise run the docs package formatter on touched files.
|
||||
5. Treat broken links and anchors as failures; run `pnpm --filter @homarr/docs validate:links` after the static build.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Homarr Documentation Sync"
|
||||
short_description: "Keep user-facing docs aligned with code"
|
||||
default_prompt: "Use $documentation-sync to map this Homarr code change to the required documentation updates."
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: homarr-custom-widget
|
||||
description: Author, validate, preview, test, install, or configure API-backed Homarr Custom JSX v2 widgets.
|
||||
---
|
||||
|
||||
# Homarr Custom Widget
|
||||
|
||||
Author one widget or a coordinated set of widgets. Load only needed release-matched context. Run lifecycle tools alone; independent reads may run together. For a set, research once and finish each widget's validation, evidence, and persistence before the next.
|
||||
|
||||
1. Read primary API documentation. Use web search when documentation is not supplied or may have changed.
|
||||
2. Create credential-free definitions with keyed `sources`, `requests`, optional `options`, and safe JSX `template`.
|
||||
3. While drafting, use `customWidget_validateTemplate` for focused JSX diagnostics without resending the manifest.
|
||||
4. Send the definition once to `customWidget_previewCreate` and test its queries/actions. For a JSX-only fix, validate, call `customWidget_previewReviseTemplate` with its session, and retest; it inherits the manifest and resets evidence. Create a preview only for source/request/option changes.
|
||||
5. Configure deployment-specific source URLs and credentials through Homarr; never repeat plaintext.
|
||||
6. Persist each exact final tested preview with `customWidget_createFromPreview`. Do not resend a large definition through `customWidget_create` when a preview session is available.
|
||||
|
||||
Treat a supplied sample or successful preview response as the binding contract. Render every core requested field, guard optional arrays and nested values before indexing, and do not silently drop returned items. Humanize numeric enums with indexed literal label arrays, omit absent numeric values instead of inventing zero, and label timestamp timezones. Give recoverable load errors and empty states a clear refresh or retry path.
|
||||
|
||||
Use `{option:name}` or `$option` for saved options. Use `{param:name}` or `$param` for values supplied by `SubFetch`, `ActionButton`, or `ToggleSwitch`. Load queries cannot use invocation parameters. Render load queries from `data` and `status` with `RefreshButton`; reserve `SubFetch` for manual parameterized queries. Templates read `data`, `status`, `options`, and temporary `inputs`.
|
||||
|
||||
For a bound control that depends on another input, declare its default and set `resetKey` to that scalar dependency. For example, `<Pagination bind="page" defaultValue={1} resetKey={inputs.search} />` restores page 1 when a search changes without fetching by itself.
|
||||
|
||||
`SubFetch` with `trigger="manual"` renders its own load button. Pass a card or image through `triggerContent` with `triggerAriaLabel` when that content should launch the request. Its callback is `(result, meta)`; callback names must not shadow the reserved roots `data`, `status`, `options`, or `inputs`. Never author `onClick` or a fetch callback. Use `Icon` or `TablerIcon` with a `name`; never invent an `IconFoo` component.
|
||||
|
||||
Plan capabilities. Use one `customWidget_findComponents` search per job. Batch selected non-obvious binding or interaction documentation with `customWidget_getComponents`; reserve `customWidget_getComponent` for one unknown-prop repair. Failed validation reopens discovery; otherwise search only for a missing capability. The full catalog is for broad exploration; load at most one example. Context never limits composition. Prefer clear hierarchy, responsive grids, divided lists, and aligned actions over nested row cards.
|
||||
|
||||
Do not simplify a useful workflow merely because the template is interpreted. Freely compose any supported installed components with multiple sources, requests, options, `choicesFrom`, bound filters, charts, responsive detail areas, manual queries, and safe actions when they improve the user's job. Complexity must remain purposeful rather than decorative.
|
||||
|
||||
Polish with a divided list or responsive media grid. Make one metric/action asymmetrically primary; keep headers compact, identity/state clear, metadata quiet, and one badge. At base artwork fills its row and caps above xs; never combine full-width media and nowrap.
|
||||
|
||||
Make initial states actionable and wrap variable labels/values on narrow tiles. Do not use an unlabeled decorative icon as an empty state.
|
||||
|
||||
Do not use imports, hooks, refs, raw HTML, raw event callbacks, browser requests, arbitrary functions, eval, bigint, npm packages, authored statement blocks, IIFEs, or recursion. Do not pretend MCP tools exist in an offline chat session.
|
||||
|
||||
For a community widget, use `customWidget_workshopSearch`, `customWidget_workshopGet`, then `customWidget_workshopInstall`; configure its source securely. Preview configuration expires, so create before persisting configuration.
|
||||
|
||||
Reference routing:
|
||||
|
||||
- Load `schema` once before a new manifest; otherwise only for concrete request or option ambiguity.
|
||||
- Load `runtime` for bound inputs, manual queries, actions, or other interaction.
|
||||
- Load `security` once for any authenticated source or mutation, or to resolve a URL or interpreter limitation.
|
||||
|
||||
Repository installations expose these as files under `references/`. MCP clients can call `customWidget_getReference` or read `homarr://custom-widgets/references/{name}` instead of loading every reference.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Homarr Custom Widget"
|
||||
short_description: "Build and deploy safe Custom JSX widgets"
|
||||
default_prompt: "Use $homarr-custom-widget to author, validate, preview, and configure a safe Custom JSX v2 widget."
|
||||
@@ -0,0 +1,37 @@
|
||||
# Runtime
|
||||
|
||||
Templates read `data.requestId`, `status.requestId`, `options.name`, and temporary `inputs.name`. Status is `{ loading, ok, status, statusText, error }`. Render load queries directly from `data` and `status` with `RefreshButton`; never wrap them in `SubFetch`.
|
||||
|
||||
`bind="search"` creates an in-memory input. It is never persisted. Supply invocation values only through `params`, for example:
|
||||
|
||||
```jsx
|
||||
<TextInput bind="search" label="Search" />
|
||||
<Pagination bind="page" resetKey={inputs.search} defaultValue={1} total={5} />
|
||||
<SubFetch requestId="search" trigger="manual" params={{ query: inputs.search }}>
|
||||
{(result) => <Stack>{(result.results ?? []).map(item => <Text key={item.id}>{item.name}</Text>)}</Stack>}
|
||||
</SubFetch>
|
||||
```
|
||||
|
||||
Manual queries require `trigger: "manual"` on request and `SubFetch`; otherwise they run automatically. `triggerContent` with `triggerAriaLabel` makes custom content the launcher. `SubFetch` owns loading/error/retry; its child receives success plus `{ ok, status, statusText, loading: false }`. Never author `onClick` or fetch callbacks.
|
||||
|
||||
`SubFetch`, `ActionButton`, and `ToggleSwitch` need literal `requestId`; validation rejects missing/computed IDs.
|
||||
|
||||
Inside a successful manual result, `<RefreshButton requestId="search" label="Run again" />` reruns the same parameters.
|
||||
|
||||
When a manual SubFetch request ID, parameters, or effective definition changes, Homarr immediately hides its prior result and returns to the trigger. It cannot fetch the new parameters until the user triggers it again.
|
||||
|
||||
The `SubFetch` callback receives the entire JSON response exactly as previewed. If the response is `{ "results": [...] }`, render and map `result.results`; never map the envelope itself. Trace every rendered field from the preview response before persistence.
|
||||
|
||||
Format timestamps with safe static helpers; never use `new Date`. Never invent a formatter component. Use `Date.toLocaleString(value, "en-US", "UTC")` plus a visible `UTC` label. Also available: `Date.toISOString`, `Date.toLocaleDateString`, and `Date.toLocaleTimeString`.
|
||||
|
||||
For compact numeric enums, index a literal label array with a fallback:
|
||||
|
||||
```jsx
|
||||
<Text>{["Unknown", "Pending", "Ready"][(item.status ?? 1) - 1] ?? "Unknown"}</Text>
|
||||
```
|
||||
|
||||
Every stateful control must use `bind`, and its `inputs.<name>` value must feed a supported request/helper when it is meant to change remote data. For dependent pagination, declare `defaultValue={1}` and use `resetKey={inputs.search}` to restore page 1 when the query changes. If a control cannot affect the workflow through a binding, option, or runtime helper, render concise context instead of a dead control.
|
||||
|
||||
Callback parameters must not shadow the reserved roots `data`, `status`, `options`, or `inputs`. Use `<Icon name="refresh" />` or `<TablerIcon name="refresh" />`; never invent components such as `<IconFoo />`.
|
||||
|
||||
Use expression callbacks for supported collections and trusted slots. No callback blocks, IIFEs, authored recursion, or raw events. Regex is limited to safe string operations.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Schema
|
||||
|
||||
```ts
|
||||
interface HomarrCustomWidgetV2 {
|
||||
$schema: "homarr-custom-widget-v2";
|
||||
name: string;
|
||||
description?: string;
|
||||
iconUrl?: string;
|
||||
sources: Record<string, CustomWidgetSource>;
|
||||
requests: Record<string, CustomWidgetRequest>;
|
||||
options?: Record<string, CustomWidgetOption>;
|
||||
template: string;
|
||||
}
|
||||
```
|
||||
|
||||
The object key `default` is the required source ID; `default` is not a property on a source. Source properties are `name?`, `baseUrl`, `networkScope`, and `auth?`:
|
||||
|
||||
```json
|
||||
{
|
||||
"sources": {
|
||||
"default": {
|
||||
"name": "Service",
|
||||
"baseUrl": "http://service.local:5055/api/v1",
|
||||
"networkScope": "private",
|
||||
"auth": { "type": "apiKeyHeader", "name": "X-Api-Key" }
|
||||
}
|
||||
},
|
||||
"requests": {
|
||||
"summary": { "path": "/summary" },
|
||||
"search": { "trigger": "manual", "path": "/search", "query": { "q": { "$param": "query" } } },
|
||||
"create": {
|
||||
"kind": "action",
|
||||
"method": "POST",
|
||||
"path": "/items",
|
||||
"body": { "id": { "$param": "id" } },
|
||||
"confirmation": "Create this item?",
|
||||
"invalidates": ["search"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Auth is `none`, `bearer`, `basic`, `{ "type": "apiKeyHeader", "name": "X-Api-Key" }`, or `{ "type": "apiKeyQuery", "name": "api_key" }`. A request defaults to source `default`, kind `query`, method `GET`, trigger `load`, inherited auth, and view permission. Set `trigger: "manual"` for a parameterized query. An action defaults to manual and modify permission. DELETE uses full permission and confirmation. Do not use `load: false`.
|
||||
|
||||
Use stable real URLs for public APIs and clear suggested URLs for self-hosted services. Homarr collects the installer's server URL, network scope, and credentials as source setup; credentials remain outside the manifest.
|
||||
|
||||
Paths use `{option:name}` and `{param:name}`; query/body references use `{ "$option": "name" }` and `{ "$param": "name" }`. Constants stay primitive (`take: 10`); `$param` is only for manual helpers, never load queries. Names and types are inferred.
|
||||
|
||||
Every option has `label`, `control`, and `default`. Optional fields are `description`, `choices`, `choicesFrom`, `min`, `max`, `step`, `advanced`, and `group`.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Security
|
||||
|
||||
All requests use Homarr's protected server executor. Source origin, network scope, DNS, redirects, SSRF, rate limits, permissions, size limits, timeouts, and encrypted credential injection remain enforced.
|
||||
|
||||
The JSX interpreter blocks imports, hooks, refs, raw event callbacks, browser requests, eval, arbitrary functions, prototype access, unsafe URLs, global CSS escape, arbitrary portals, bigint, statement blocks, IIFEs, and recursion. Regex literals must be bounded and reject backreferences, lookbehind, nested quantifiers, excessive length, and unsupported flags.
|
||||
|
||||
Credentials are stored separately and never exported or returned to an agent. A published self-hosted source URL is only a suggestion: installers must confirm or replace private and loopback URLs for their own Homarr deployment. Source origins cannot be controlled through widget options.
|
||||
@@ -0,0 +1,98 @@
|
||||
---
|
||||
name: mcp-integration
|
||||
description: Expose Homarr tRPC procedures safely through MCP. Use when adding or changing tRPC procedures under packages/api, adding MCP metadata, registering an eager MCP router, shaping tool inputs and descriptions, or reviewing MCP authorization, sensitive data, and destructive actions.
|
||||
---
|
||||
|
||||
# MCP Integration
|
||||
|
||||
Expose a procedure only when an AI client should call it. Preserve the procedure's existing authorization and add MCP-specific safeguards for sensitive or destructive behavior.
|
||||
|
||||
## Assess exposure
|
||||
|
||||
Prefer:
|
||||
|
||||
- Data queries: list, search, get, health, stats, and summaries.
|
||||
- User-requested actions with explicit permission checks and bounded inputs.
|
||||
- Serializable results that do not reveal credentials or internal-only state.
|
||||
|
||||
Skip subscriptions, session/onboarding internals, file streams, blobs, credential values, and procedures whose authorization or audit behavior is unclear. Ask for a security review when a mutation is destructive, tenant boundaries are ambiguous, or returned data is sensitive.
|
||||
|
||||
## Add metadata
|
||||
|
||||
Place `.meta()` before `.input()` and `.query()` or `.mutation()`:
|
||||
|
||||
```typescript
|
||||
getAll: protectedProcedure
|
||||
.meta({
|
||||
mcp: {
|
||||
enabled: true,
|
||||
description: "List resources the current user can access, including stable IDs used by resource_get",
|
||||
},
|
||||
})
|
||||
.input(z.object({ limit: z.number().int().min(1).max(100).default(50) }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
// Preserve normal procedure authorization.
|
||||
});
|
||||
```
|
||||
|
||||
No-input procedures can omit `.input()`; the MCP extractor accepts an empty object for them.
|
||||
|
||||
For mutations, state the effect and required permission in the description:
|
||||
|
||||
```typescript
|
||||
remove: permissionRequiredProcedure
|
||||
.meta({
|
||||
mcp: {
|
||||
enabled: true,
|
||||
description: "Delete one resource by ID. Requires full access; get the ID from resource_all",
|
||||
},
|
||||
})
|
||||
.input(z.object({ id: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// Perform the permission-scoped action.
|
||||
});
|
||||
```
|
||||
|
||||
## Write discoverable descriptions
|
||||
|
||||
Include every item that applies:
|
||||
|
||||
- What the tool returns or changes.
|
||||
- Supported integrations or services.
|
||||
- Required IDs and the tool that returns them.
|
||||
- Permission requirements and the meaning of permission fields.
|
||||
- Important bounds, confirmation semantics, or irreversible effects.
|
||||
|
||||
Use precise domain language. A description such as `Get calendar events` is insufficient because it omits supported services, required IDs, and result scope.
|
||||
|
||||
## Shape inputs for clients
|
||||
|
||||
- Use a top-level `z.object({...})` for parameterized tools.
|
||||
- Bound strings, arrays, numeric ranges, pagination, and payload sizes.
|
||||
- Use defaults where a safe, unsurprising value exists.
|
||||
- Prefer explicit fields over a top-level union or discriminated union.
|
||||
- Keep identifiers stable and describe how to obtain them.
|
||||
- Keep secrets out of inputs unless the procedure is explicitly a credential-configuration flow with server-owned encryption and non-return guarantees.
|
||||
|
||||
## Register the eager router
|
||||
|
||||
The application router in `packages/api/src/root.ts` is lazy. MCP tool extraction is synchronous, so add an eager import and registration in `packages/api/src/mcp.ts`:
|
||||
|
||||
```typescript
|
||||
import { resourceRouter } from "./router/resource";
|
||||
|
||||
export const mcpRouter = createTRPCRouter({
|
||||
resource: resourceRouter,
|
||||
});
|
||||
```
|
||||
|
||||
Register the smallest router that owns the enabled procedures. If a parent router uses `lazy()`, import the needed subrouter directly. Metadata on a procedure is not enough; a procedure absent from `mcpRouter` is not exposed.
|
||||
|
||||
## Validate the tool surface
|
||||
|
||||
1. Confirm the procedure still enforces its ordinary auth and permission boundary.
|
||||
2. Confirm `packages/api/src/mcp.ts` eagerly includes the procedure.
|
||||
3. Confirm the generated tool name, description, and JSON schema are unambiguous.
|
||||
4. Run the focused API MCP spec when the tool list or extraction changes: `pnpm test packages/api/src/test/mcp.spec.ts`.
|
||||
5. Run the focused route spec when protocol or transport behavior changes: `pnpm test apps/nextjs/src/app/api/mcp/[transport]/route.spec.ts`.
|
||||
6. If manually probing `/api/mcp/<transport>`, use a scoped API key and avoid printing credentials or secret-bearing results.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Homarr MCP Integration"
|
||||
short_description: "Expose tRPC procedures safely through MCP"
|
||||
default_prompt: "Use $mcp-integration to expose this Homarr tRPC procedure as a safe, discoverable MCP tool."
|
||||
@@ -0,0 +1 @@
|
||||
../.agents/skills
|
||||
@@ -1,206 +0,0 @@
|
||||
---
|
||||
description: Comprehensive Homarr codebase architecture and conventions reference
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Homarr Codebase Context
|
||||
|
||||
Homarr is an open-source, self-hosted dashboard for managing homelab services. It integrates with 50+ self-hosted apps (media servers, download clients, DNS, NAS, etc.) and provides a drag-and-drop widget-based UI.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Monorepo**: pnpm workspaces + Turborepo
|
||||
- **Framework**: Next.js (App Router, `output: "standalone"`) — T3 Stack
|
||||
- **Language**: TypeScript throughout
|
||||
- **ORM**: Drizzle (supports SQLite via better-sqlite3, MySQL, PostgreSQL)
|
||||
- **API**: tRPC (HTTP + WebSocket subscriptions) with superjson + OpenAPI bridge via `trpc-to-openapi`
|
||||
- **Auth**: NextAuth v5 (database sessions, Credentials/LDAP/OIDC providers)
|
||||
- **UI**: Mantine v9 (not Tailwind) + Tabler icons
|
||||
- **State**: Jotai (atoms), TanStack Query (server state via tRPC)
|
||||
- **Realtime**: WebSocket server (`ws`) for tRPC subscriptions, backed by Redis pub/sub
|
||||
- **Cron**: `node-cron` in a standalone Fastify service (`apps/tasks`)
|
||||
- **i18n**: next-intl with `[locale]` dynamic segment (prefix mode: `"never"`, locale from cookie)
|
||||
- **Testing**: Vitest + jsdom, Playwright for E2E
|
||||
- **Lint/Format**: oxlint + oxfmt (not ESLint/Prettier)
|
||||
- **Docs**: Docusaurus 3 in `apps/docs/` (`@homarr/docs`)
|
||||
- **Package manager**: pnpm 10.34.1, Node >= 24.16.0
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
homarr/
|
||||
├── apps/
|
||||
│ ├── nextjs/ # Main Next.js application (port 3000)
|
||||
│ ├── docs/ # Docusaurus 3 documentation site (@homarr/docs)
|
||||
│ ├── tasks/ # Cron job runner + Fastify tRPC API (port 3002)
|
||||
│ └── websocket/ # Standalone tRPC WebSocket server (port 3001)
|
||||
├── packages/
|
||||
│ ├── api/ # tRPC appRouter, procedures, OpenAPI
|
||||
│ ├── auth/ # NextAuth config, providers, session, API keys
|
||||
│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries
|
||||
│ ├── core/ # Env validation, DB/Redis driver factories, logging
|
||||
│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions
|
||||
│ ├── widgets/ # All 39 dashboard widgets (definitions + components)
|
||||
│ ├── integrations/ # Integration classes (HTTP clients to external apps)
|
||||
│ ├── redis/ # Redis pub/sub channels, caching abstractions
|
||||
│ ├── translation/ # next-intl setup, locale configs, lang JSON files
|
||||
│ ├── ui/ # Shared Mantine components, theme, hooks
|
||||
│ ├── validation/ # Shared zod schemas for API/forms
|
||||
│ ├── common/ # Shared utilities, IDs, errors
|
||||
│ ├── cron-jobs/ # Cron job implementations (25+ jobs)
|
||||
│ ├── cron-jobs-core/ # Cron scheduling primitives
|
||||
│ ├── cron-job-api/ # tRPC router for job management (start/stop/trigger)
|
||||
│ ├── cron-job-status/ # Cron status via Redis
|
||||
│ ├── boards/ # Board context, edit mode, cache updater
|
||||
│ ├── modals/ # Modal primitives on Mantine
|
||||
│ ├── modals-collection/ # Feature modals (apps, boards, docker, etc.)
|
||||
│ ├── form/ # useZodForm (Mantine + zod resolver)
|
||||
│ ├── forms-collection/# Reusable form UIs (new app, icon picker, upload)
|
||||
│ ├── spotlight/ # Command palette / search with multiple modes
|
||||
│ ├── request-handler/ # Server request handlers (feeds, integrations)
|
||||
│ ├── notifications/ # Mantine notifications wrapper
|
||||
│ ├── docker/ # Dockerode-based Docker access
|
||||
│ ├── icons/ # Icon DB/repo integration
|
||||
│ ├── image-proxy/ # Image proxy + caching
|
||||
│ ├── ping/ # Reachability / ping utilities
|
||||
│ ├── analytics/ # Server-side analytics (Umami)
|
||||
│ ├── server-settings/ # Server setting keys/types
|
||||
│ ├── settings/ # User-facing settings UI context
|
||||
│ ├── old-import/ # Legacy Homarr import
|
||||
│ ├── old-schema/ # Legacy Homarr zod schemas
|
||||
│ └── cli/ # Node CLI for ops (brocli)
|
||||
├── tooling/
|
||||
│ ├── typescript/ # Base tsconfig
|
||||
│ └── github/ # CI setup action
|
||||
├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)
|
||||
├── e2e/ # E2E test specs
|
||||
└── Dockerfile # Multi-stage production build
|
||||
```
|
||||
|
||||
## Three Runtime Services
|
||||
|
||||
| Service | Port | Role |
|
||||
|---------|------|------|
|
||||
| Next.js | 3000 | Main web app (SSR + API routes) |
|
||||
| WebSocket | 3001 | tRPC subscriptions via `ws` |
|
||||
| Tasks | 3002 | Cron job runner + management API via Fastify |
|
||||
|
||||
In Docker, nginx listens on **7575** and proxies to all three. The tasks service authenticates via `CRON_JOB_API_KEY` header.
|
||||
|
||||
## Package Dependency Layers
|
||||
|
||||
1. **Foundation**: `core` (env, DB drivers, Redis client, logging) → no `@homarr` deps
|
||||
2. **Domain**: `definitions` → `common` → `core`
|
||||
3. **Data**: `db` → `core`, `definitions`, `common`
|
||||
4. **Auth**: `auth` → `db`, `definitions`, `core`, `validation`
|
||||
5. **Cross-cutting**: `translation`, `validation`, `redis`, `server-settings`
|
||||
6. **Feature backends**: `integrations`, `request-handler`, `cron-jobs`, `docker`, `ping`
|
||||
7. **API surface**: `api` → pulls in auth, db, redis, integrations, etc.
|
||||
8. **UI stack**: `ui`, `modals`, `form`, `notifications`, `boards`, `spotlight`
|
||||
9. **Composed features**: `widgets`, `modals-collection`, `forms-collection`
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### tRPC
|
||||
|
||||
- **Server caller** (RSC): `import { api } from "@homarr/api/server"` → cached caller with auth context
|
||||
- **Client hooks**: `import { clientApi } from "@homarr/api/client"` → React Query hooks
|
||||
- **Router structure**: `packages/api/src/root.ts` defines top-level namespaces (`user`, `board`, `widget`, `integration`, `docker`, `kubernetes`, etc.)
|
||||
- **Procedures**: `publicProcedure`, `protectedProcedure`, `permissionRequiredProcedure`, `onboardingProcedure`
|
||||
- **WebSocket subscriptions**: Client uses `wsLink` when `type === "subscription"`, connects to port 3001
|
||||
|
||||
### Multi-Database Support
|
||||
|
||||
Drizzle schemas exist in three parallel implementations:
|
||||
- `packages/db/schema/sqlite.ts`
|
||||
- `packages/db/schema/mysql.ts`
|
||||
- `packages/db/schema/postgresql.ts`
|
||||
|
||||
Selected at runtime via `DB_DRIVER` env (`better-sqlite3` | `mysql2` | `node-postgres`). Migrations are per-engine in `packages/db/migrations/{sqlite,mysql,postgresql}/`.
|
||||
|
||||
### Widget System
|
||||
|
||||
Widgets are registered in `packages/widgets/src/index.tsx` as `widgetImports` (must satisfy `Record<WidgetKind, ...>`). Each widget:
|
||||
- Has a `definition` created via `createWidgetDefinition(kind, { icon, createOptions, supportedIntegrations? })`
|
||||
- Uses `optionsBuilder.from(factory => ({ ... }))` for typed options
|
||||
- Loads dynamically via `next/dynamic`
|
||||
- Fetches data via tRPC client hooks (backed by cron job caches)
|
||||
|
||||
### Integration System
|
||||
|
||||
Integrations are defined in `packages/definitions/src/integration.ts` (`integrationDefs`) and instantiated via `createIntegrationAsync` in `packages/integrations/src/base/creator.ts`. Each extends abstract `Integration` class with `testConnectionAsync` and typed methods.
|
||||
|
||||
### Cron Job System
|
||||
|
||||
Jobs defined in `packages/cron-jobs/src/jobs/`. Each job:
|
||||
- Uses `createCronJob` with a cron expression
|
||||
- Publishes results to Redis channels
|
||||
- Widget subscriptions consume those channels via tRPC subscriptions
|
||||
- Job management (start/stop/trigger) via `cronJobApi` HTTP client to port 3002
|
||||
|
||||
### Modals
|
||||
|
||||
Created with `createModal(component).withOptions({ defaultTitle })` from `@homarr/modals`. Opened via `useModalAction(SomeModal)`. Feature modals live in `packages/modals-collection/`.
|
||||
|
||||
### Forms
|
||||
|
||||
`useZodForm` from `@homarr/form` wraps Mantine `useForm` with zod validation. Shared form UIs in `packages/forms-collection/`.
|
||||
|
||||
### Routing (App Router)
|
||||
|
||||
- `apps/nextjs/src/app/[locale]/` — all UI routes under locale segment
|
||||
- Route groups: `(home)`, `(content)`, `(board)` — don't affect URLs
|
||||
- Key routes: `boards/[name]`, `manage/...` (admin), `auth/login`, `init` (onboarding), `widgets/[kind]`
|
||||
- Locale driven by cookie (no URL prefix)
|
||||
|
||||
### Auth Providers
|
||||
|
||||
Configured via `AUTH_PROVIDERS` env. Supports: `credentials` (local), `ldap`, `oidc`. Database sessions with httpOnly cookies. API key auth for programmatic access.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Key env vars (from `.env.example`):
|
||||
- `DB_DRIVER`: `better-sqlite3` | `mysql2` | `node-postgres`
|
||||
- `DB_URL`: Database connection string
|
||||
- `AUTH_SECRET`: NextAuth secret
|
||||
- `SECRET_ENCRYPTION_KEY`: AES-256-CBC for integration secrets
|
||||
- `AUTH_PROVIDERS`: Comma-separated (`credentials`, `ldap`, `oidc`)
|
||||
- `CRON_JOB_API_KEY`: Shared key between Next.js and tasks service
|
||||
- `REDIS_*`: Redis connection (host, port, password)
|
||||
- `ENABLE_KUBERNETES`: Optional Kubernetes integration
|
||||
|
||||
## Development Commands
|
||||
|
||||
- `pnpm dev` — runs all three services in parallel via Turborepo
|
||||
- `pnpm docker:dev` — starts Redis + MySQL + PostgreSQL containers
|
||||
- `pnpm db:push` — push schema to SQLite (dev)
|
||||
- `pnpm db:studio` — Drizzle Studio
|
||||
- `pnpm test` — Vitest unit tests
|
||||
- `pnpm test:e2e` — Playwright E2E
|
||||
- `pnpm lint` / `pnpm format` — oxlint + oxfmt
|
||||
- `pnpm dev:docs` — Docusaurus docs site (port 3003)
|
||||
- `pnpm typecheck` — tsc --noEmit across all packages
|
||||
|
||||
## Conventions
|
||||
|
||||
- Workspace packages use `@homarr/` scope
|
||||
- All versions managed via pnpm `catalog:` in workspace
|
||||
- Path alias `~/*` → `./src/*` in Next.js app
|
||||
- Mantine for all UI (no Tailwind) — primary color is red, `autoContrast: true`
|
||||
- Icons from `@tabler/icons-react`
|
||||
- Drag-and-drop via `@dnd-kit/*`
|
||||
- `typescript.ignoreBuildErrors: true` in next.config (types checked separately via `typecheck`)
|
||||
- `serverExternalPackages`: `dockerode`, `isomorphic-dompurify`, `jsdom`
|
||||
|
||||
## Documentation (`apps/docs`)
|
||||
|
||||
Docusaurus 3 site at `apps/docs/`, package name `@homarr/docs`.
|
||||
|
||||
- Run with `pnpm dev:docs` from root
|
||||
- Build with `pnpm turbo build --filter=@homarr/docs`
|
||||
- Uses Tailwind CSS 3 for custom pages (not Mantine)
|
||||
- Imports `@homarr/definitions` for type-safe integration/widget metadata
|
||||
- When adding or modifying widgets, integrations, cron jobs, API routes, env vars, or user-facing features, you MUST also update corresponding docs in `apps/docs/docs/`
|
||||
- Integration docs: `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts`
|
||||
- Widget docs: `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts`
|
||||
- Strict link checking: broken links/anchors cause build failure
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
description: Keep documentation in sync with code changes
|
||||
globs:
|
||||
- packages/definitions/src/**
|
||||
- packages/widgets/src/**
|
||||
- packages/integrations/src/**
|
||||
- packages/api/src/**
|
||||
- apps/nextjs/src/**
|
||||
- apps/docs/**
|
||||
---
|
||||
|
||||
# Documentation Sync Rule
|
||||
|
||||
When modifying code that affects user-facing behavior, you MUST also update the documentation in `apps/docs/`.
|
||||
|
||||
## What triggers a doc update
|
||||
|
||||
| Code change | Doc location |
|
||||
|---|---|
|
||||
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts` |
|
||||
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts` |
|
||||
| Changed API surface | `apps/docs/docs/management/api.mdx` |
|
||||
| New/changed env vars | `apps/docs/docs/advanced/` |
|
||||
| New CLI commands | `apps/docs/docs/advanced/command-line/` |
|
||||
| Changed auth providers | `apps/docs/docs/advanced/` SSO pages |
|
||||
| UI/UX changes | Relevant getting-started or management pages |
|
||||
| New cron job | `apps/docs/docs/management/tasks.mdx` |
|
||||
| Changed permissions | `apps/docs/docs/management/users.mdx` |
|
||||
|
||||
## Integration doc pattern
|
||||
|
||||
Each integration has a folder under `apps/docs/docs/integrations/<slug>/`:
|
||||
|
||||
- `index.ts` — exports a typed `IntegrationDefinition` with name, description, iconUrl, path
|
||||
- `index.mdx` — uses shared components: `IntegrationHeader`, `IntegrationCapabilites`, `IntegrationSecrets`
|
||||
- Import metadata from `@homarr/definitions` where possible
|
||||
|
||||
## Widget doc pattern
|
||||
|
||||
Each widget has a folder under `apps/docs/docs/widgets/<slug>/`:
|
||||
|
||||
- `index.ts` — exports a typed `WidgetDefinition` with icon, name, description, path, configuration
|
||||
- `index.mdx` — uses shared components: `WidgetHeader`, `WidgetConfig`, `WidgetAdding`
|
||||
|
||||
## Development
|
||||
|
||||
- Run docs locally: `pnpm dev:docs` from root
|
||||
- Build docs: `pnpm turbo build --filter=@homarr/docs`
|
||||
- The docs app can import from `@homarr/definitions` for type-safe integration/widget metadata
|
||||
- Docusaurus strict mode throws on broken links — fix any broken references before committing
|
||||
@@ -1,103 +0,0 @@
|
||||
---
|
||||
description: How to expose tRPC procedures as MCP tools for AI clients
|
||||
globs: packages/api/src/router/**/*.ts
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# MCP Integration Guide
|
||||
|
||||
Every new tRPC procedure that provides useful functionality should be exposed as an MCP tool so AI assistants (Claude, Cursor, etc.) can use it. This is a first-class feature of Homarr.
|
||||
|
||||
## Adding MCP to a procedure
|
||||
|
||||
Add `.meta({ mcp: { enabled: true, description: "..." } })` to the procedure chain:
|
||||
|
||||
```typescript
|
||||
// Read-only tool (query)
|
||||
myProcedure: protectedProcedure
|
||||
.meta({ mcp: { enabled: true, description: "Get all items with their status and metadata" } })
|
||||
.input(z.object({ limit: z.number().default(50) }))
|
||||
.query(async ({ ctx, input }) => { ... })
|
||||
|
||||
// Write tool (mutation)
|
||||
myAction: protectedProcedure
|
||||
.meta({ mcp: { enabled: true, description: "Delete an item by ID. Requires interact permission on the integration" } })
|
||||
.input(z.object({ id: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => { ... })
|
||||
|
||||
// No-input procedure (z.void) — works fine, no .input() needed
|
||||
getAll: protectedProcedure
|
||||
.meta({ mcp: { enabled: true, description: "List all resources" } })
|
||||
.query(async ({ ctx }) => { ... })
|
||||
```
|
||||
|
||||
The `.meta()` call must come **before** `.input()` and `.query()`/`.mutation()` in the chain.
|
||||
|
||||
## Writing good descriptions
|
||||
|
||||
The description is the **only thing** an AI sees to decide when and how to use the tool. Write it like you're explaining to a colleague who has never used Homarr.
|
||||
|
||||
**Must include:**
|
||||
- What the tool returns or does
|
||||
- Which integrations/services it works with (if applicable)
|
||||
- How to get required IDs (e.g., "Use integration_all to get the integrationId")
|
||||
- Permission requirements (if non-obvious)
|
||||
|
||||
**Good:**
|
||||
```
|
||||
"Get calendar events for upcoming and recent media releases. Fetches from all connected Sonarr (TV), Radarr (movies), Lidarr (music), and Readarr (books) integrations. Requires integrationIds from integration_all"
|
||||
```
|
||||
|
||||
**Bad:**
|
||||
```
|
||||
"Get calendar events"
|
||||
```
|
||||
|
||||
**For tools that return permission fields**, explain what they mean:
|
||||
```
|
||||
"List all integrations. Returns permissions.hasUseAccess (read) and permissions.hasInteractAccess (actions) — false means the API key lacks that permission, not an error"
|
||||
```
|
||||
|
||||
## Registering in the MCP router
|
||||
|
||||
After adding `.meta()` to procedures, you must register the router in `packages/api/src/mcp.ts`. This file uses **eager imports** (no `lazy()`) because MCP tool extraction requires synchronous access to all procedure definitions.
|
||||
|
||||
```typescript
|
||||
// packages/api/src/mcp.ts
|
||||
import { myNewRouter } from "./router/my-new-feature";
|
||||
|
||||
export const mcpRouter = createTRPCRouter({
|
||||
// ... existing routers
|
||||
myFeature: myNewRouter,
|
||||
});
|
||||
```
|
||||
|
||||
**If your router is nested** (e.g., widget sub-routers), import and register the sub-router directly — do not import a parent that uses `lazy()`.
|
||||
|
||||
## What to expose vs. what to skip
|
||||
|
||||
**Expose:**
|
||||
- Data queries (list, search, get by ID)
|
||||
- Actions users would ask an AI to do (toggle, create, delete, approve)
|
||||
- Status checks (health, stats, summaries)
|
||||
|
||||
**Skip:**
|
||||
- Subscriptions (WebSocket-only, not supported by MCP)
|
||||
- Internal procedures (session management, onboarding steps)
|
||||
- File upload/download procedures
|
||||
- Procedures that return non-serializable data (streams, blobs)
|
||||
|
||||
## Input schema rules
|
||||
|
||||
- Use `z.object({...})` for inputs — this maps cleanly to MCP tool parameters
|
||||
- `z.void()` (no `.input()`) works — the MCP handler patches these to accept empty objects
|
||||
- Avoid `z.union()` or `z.discriminatedUnion()` at the top level — AI clients struggle with these
|
||||
- Use `.default()` on optional fields so the AI doesn't need to guess values
|
||||
|
||||
## Checklist for new features
|
||||
|
||||
1. Add `.meta({ mcp: { enabled: true, description: "..." } })` to relevant procedures
|
||||
2. Import and register the router in `packages/api/src/mcp.ts` (eager, no lazy)
|
||||
3. Write descriptions that explain what the tool does, what it needs, and what it returns
|
||||
4. If the tool requires an ID from another tool, say which one in the description
|
||||
5. Test with `curl` against `/api/mcp/mcp` to verify the tool appears in `tools/list`
|
||||
@@ -1,8 +0,0 @@
|
||||
version = 1
|
||||
|
||||
[[analyzers]]
|
||||
name = "javascript"
|
||||
|
||||
[analyzers.meta]
|
||||
plugins = ["react"]
|
||||
environment = ["nodejs"]
|
||||
@@ -17,7 +17,8 @@ coverage
|
||||
dev
|
||||
.build
|
||||
e2e
|
||||
apps/docs
|
||||
apps/docs/*
|
||||
!apps/docs/package.json
|
||||
apps/nextjs/.next
|
||||
.env
|
||||
.env.*
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
# Since .env is gitignored, you can use .env.example to build a new `.env` file when you clone the repo.
|
||||
# Keep this file up-to-date when you add new variables to \`.env\`.
|
||||
# Keep this file up-to-date when you add new variables to `.env`.
|
||||
|
||||
# This file will be committed to version control, so make sure not to have any secrets in it.
|
||||
# If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets.
|
||||
|
||||
# The below secret is not used anywhere but required for Auth.js (Would encrypt JWTs and Mail hashes, both not used)
|
||||
AUTH_SECRET="supersecret"
|
||||
# Generate two different 64-character hex values with `openssl rand -hex 32` before starting Homarr.
|
||||
# Never reuse these values or commit the populated `.env` file.
|
||||
AUTH_SECRET=
|
||||
|
||||
# The below secret is used to encrypt integration secrets in the database.
|
||||
# It should be a 32-byte string, generated by running `openssl rand -hex 32` on Unix
|
||||
# or starting the project without any (which will show a randomly generated one).
|
||||
SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||
# Homarr will reject an empty or invalid value and print a randomly generated candidate.
|
||||
SECRET_ENCRYPTION_KEY=
|
||||
|
||||
LOG_LEVEL='info'
|
||||
|
||||
# This is how you can use the sqlite driver:
|
||||
DB_DRIVER='better-sqlite3'
|
||||
DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
|
||||
# Safe local source-development path. SQLite files are ignored by Git.
|
||||
# Container images use /appdata/db/db.sqlite by default instead.
|
||||
DB_URL='./db.sqlite'
|
||||
|
||||
# Those are the two ways to use the mysql2 driver:
|
||||
# 1. Using the URL format:
|
||||
@@ -61,9 +63,33 @@ TURBO_TELEMETRY_DISABLED=1
|
||||
# Enable mock integration
|
||||
UNSAFE_ENABLE_MOCK_INTEGRATION=true
|
||||
|
||||
# Public URL contract. Values must be absolute HTTP(S) URLs without credentials,
|
||||
# query strings, or fragments. Trailing slashes are normalized at startup.
|
||||
# Public Homarr origin used by MCP OAuth metadata and redirects behind proxies.
|
||||
# BASE_URL=https://homarr.example.com
|
||||
HOMARR_WEBSITE_URL=https://homarr.dev
|
||||
# PocketBase API used by Homarr and by the standalone documentation site.
|
||||
# Defaults to HOMARR_WEBSITE_URL.
|
||||
# WORKSHOP_API_URL=https://homarr.dev
|
||||
# Public Workshop website used by links in Homarr, Custom Widgets, onboarding,
|
||||
# and assistant context. Defaults to HOMARR_WEBSITE_URL/workshop.
|
||||
# WORKSHOP_WEB_URL=https://homarr.dev/workshop
|
||||
|
||||
# Documentation and PocketBase deployment settings.
|
||||
# WORKSHOP_PUBLIC_ORIGIN=https://homarr.dev
|
||||
# PB_ALLOWED_ORIGINS=*
|
||||
|
||||
# Local Workshop stack:
|
||||
# HOMARR_WEBSITE_URL=http://127.0.0.1:3003
|
||||
# WORKSHOP_API_URL=http://127.0.0.1:8090
|
||||
# WORKSHOP_WEB_URL=http://127.0.0.1:3003/workshop
|
||||
# WORKSHOP_PUBLIC_ORIGIN=http://127.0.0.1:3003
|
||||
# Optional: GITHUB_CLIENT_ID=
|
||||
# Optional: GITHUB_CLIENT_SECRET=
|
||||
|
||||
# Enable demo mode (seeds a demo user "demo"/"demo", a sample board and a mock integration)
|
||||
# DEMO_MODE=true
|
||||
|
||||
# Whether demo mode is read-only. Defaults to true (the public demo blocks all mutations).
|
||||
# Set to false for writable preview deployments so the demo user can modify the dashboard.
|
||||
# DEMO_READ_ONLY=false
|
||||
# DEMO_READ_ONLY=false
|
||||
@@ -33,6 +33,14 @@ body:
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 1.76.2
|
||||
- 1.76.1
|
||||
- 1.76.0
|
||||
- 1.75.0
|
||||
- 1.74.0
|
||||
- 1.73.0
|
||||
- 1.72.0
|
||||
- 1.71.0
|
||||
- 1.70.0
|
||||
- 1.69.2
|
||||
- 1.69.0
|
||||
|
||||
@@ -20,7 +20,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prebuilt debian dependencies
|
||||
uses: homarr-labs/homarr/.github/actions/prebuilt-debian@dev
|
||||
uses: ./.github/actions/prebuilt-debian
|
||||
id: prebuilt-debian
|
||||
with:
|
||||
architecture: ${{ inputs.architecture }}
|
||||
|
||||
@@ -11,9 +11,6 @@ outputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 582 KiB |
|
After Width: | Height: | Size: 649 KiB |
@@ -4,18 +4,18 @@
|
||||
<h3>Homarr</h3>
|
||||
</div>
|
||||
|
||||
**Thank you for your contribution. Please ensure that your pull request meets the following pull request:**
|
||||
**Thank you for your contribution. Please ensure that your pull request meets the following requirements:**
|
||||
|
||||
- [ ] Builds without warnings or errors (`pnpm build`, autofix with `pnpm format:fix`)
|
||||
- [ ] Pull request targets `dev` branch
|
||||
- [ ] Relevant checks pass without warnings or errors
|
||||
- [ ] Pull request targets the intended base branch (`dev` by default; `release/v2` for v2 release work)
|
||||
- [ ] Commits follow the [conventional commits guideline](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
- [ ] No shorthand variable names are used (eg. `x`, `y`, `i` or any abbrevation)
|
||||
- [ ] Documentation is up to date. Create a pull request [here](https://github.com/homarr-labs/documentation/).
|
||||
- [ ] When using AI; No temp files are checked in, the code style follows the rest of the project
|
||||
- [ ] No shorthand variable names are used (for example, `x`, `y`, `i`, or abbreviations)
|
||||
- [ ] User-facing changes include matching documentation updates under `apps/docs`
|
||||
- [ ] When using AI, no temporary files are checked in and the code style follows the rest of the project
|
||||
|
||||
**When contributing a new integration or widget:**
|
||||
|
||||
- [ ] I confirmed manually that the integration or widget is working with a real system and performed basic smoke
|
||||
tests (e.g. what happens if it is offline)
|
||||
tests (e.g. what happens if it is offline)
|
||||
- [ ] Optional but recommended: I confirm that I am willing to be added to the `CODEOWNERS` of this feature and will
|
||||
provide support and maintain the integration (e.g. in case of breaking or failure)
|
||||
provide support and maintain the integration (e.g. in case of breaking or failure)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
||||
extends: ["config:recommended"],
|
||||
extends: ["config:recommended", ":dependencyDashboardApproval"],
|
||||
packageRules: [
|
||||
{
|
||||
matchPackageNames: ["^@homarr/"],
|
||||
@@ -47,7 +47,7 @@
|
||||
rangeStrategy: "bump",
|
||||
automerge: false,
|
||||
baseBranchPatterns: ["dev"],
|
||||
dependencyDashboard: false,
|
||||
dependencyDashboard: true,
|
||||
// prevent special characters in branch names (otherwise git errors occur on windows)
|
||||
branchNameStrict: true,
|
||||
}
|
||||
@@ -1,48 +1,35 @@
|
||||
name: Auto-fix lint and format
|
||||
name: Formatting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: ["*"]
|
||||
branches: ["**"]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
name: Auto-fix
|
||||
if: github.event.pull_request.head.repo.id == github.repository_id
|
||||
format:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.head_ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Get changed files
|
||||
id: changed
|
||||
- name: Check changed files
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
run: |
|
||||
git diff --name-only --diff-filter=ACMR "origin/${BASE_REF}...HEAD" -- '*.ts' '*.tsx' '*.js' '*.jsx' > /tmp/changed-files
|
||||
echo "count=$(wc -l < /tmp/changed-files | tr -d '[:space:]')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Auto-fix lint + format
|
||||
if: steps.changed.outputs.count != '0'
|
||||
run: |
|
||||
xargs -r -a /tmp/changed-files pnpm exec oxlint --fix || true
|
||||
xargs -r -a /tmp/changed-files pnpm exec oxfmt --write
|
||||
|
||||
- name: Commit fixes
|
||||
uses: stefanzweifel/git-auto-commit-action@v7
|
||||
with:
|
||||
commit_message: "style: auto-fix lint and format"
|
||||
git diff --name-only --diff-filter=ACMR -z "origin/${BASE_REF}...HEAD" -- \
|
||||
'*.css' '*.js' '*.json' '*.jsonc' '*.jsx' '*.md' '*.mdx' '*.scss' '*.ts' '*.tsx' '*.yaml' '*.yml' \
|
||||
| xargs -0 -r pnpm exec oxfmt --check --
|
||||
@@ -1,35 +0,0 @@
|
||||
name: "[Dependency Updates] Auto Approve"
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
approve-automatic-prs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
# 158783068: homarr-renovate[bot]
|
||||
# 190541745: homarr-crowdin[bot]
|
||||
# 210161987: homarr-update-contributors[bot]
|
||||
# 175486441: homarr-releases[bot]
|
||||
# Extracted from https://api.github.com/users/<username>
|
||||
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175486441
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write # required to approve pull request
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt-get install -y gh
|
||||
- name: Approve automatic PRs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
gh pr review ${{github.event.pull_request.number}} --approve --body "Automatically approved by GitHub Action"
|
||||
@@ -0,0 +1,43 @@
|
||||
name: "[Bots] Reconcile Auto-merge"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "*/30 * * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
reconcile:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
|
||||
permission-pull-requests: write # required to enable auto-merge
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt-get install -y gh
|
||||
|
||||
- name: Re-enable auto-merge for prepared bot PRs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
# 158783068: homarr-renovate[bot]
|
||||
# 190541745: homarr-crowdin[bot]
|
||||
# 210161987: homarr-update-contributors[bot]
|
||||
# 175441: homarr-releases[bot]
|
||||
gh pr list --repo "${{ github.repository }}" --base dev --state open --limit 100 \
|
||||
--json number,author,reviewDecision,mergeStateStatus,autoMergeRequest \
|
||||
--jq '.[] | select(.author.login as $login | ["app/homarr-renovate", "app/homarr-crowdin", "app/homarr-update-contributors", "app/homarr-releases"] | index($login)) | select(.reviewDecision == "APPROVED") | select(.mergeStateStatus == "CLEAN") | select(.autoMergeRequest == null) | .number' \
|
||||
| while read -r pr; do
|
||||
echo "Re-enabling auto-merge on PR #$pr"
|
||||
gh pr merge "$pr" --auto --squash
|
||||
done
|
||||
@@ -0,0 +1,47 @@
|
||||
name: "[Bots] Approve and Auto-merge"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
approve-and-automerge:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
# 158783068: homarr-renovate[bot]
|
||||
# 190541745: homarr-crowdin[bot]
|
||||
# 210161987: homarr-update-contributors[bot]
|
||||
# 175441: homarr-releases[bot]
|
||||
# Extracted from https://api.github.com/users/<username>
|
||||
if: |
|
||||
(github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175441) &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event.pull_request.base.ref == 'dev'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
|
||||
permission-pull-requests: write # required to approve a pull request and enable auto-merge
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt-get install -y gh
|
||||
|
||||
- name: Approve and enable auto-merge
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
if [ "$(gh pr view "$PR_NUMBER" --json reviewDecision --jq .reviewDecision)" != "APPROVED" ]; then
|
||||
gh pr review "$PR_NUMBER" --approve --body "Automatically approved by Homarr bot policy"
|
||||
fi
|
||||
if [ "$(gh pr view "$PR_NUMBER" --json autoMergeRequest --jq '.autoMergeRequest != null')" != "true" ]; then
|
||||
gh pr merge "$PR_NUMBER" --auto --squash
|
||||
fi
|
||||
@@ -3,12 +3,12 @@ name: CI
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches: ["*"]
|
||||
branches: ["**"]
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
branches: ["*"]
|
||||
branches: ["**"]
|
||||
push:
|
||||
branches: [main, dev, beta, next]
|
||||
branches: [main, dev, beta, next, v2, release/v2]
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -34,8 +34,10 @@ permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.pr_number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
# Every event for a PR shares one group so a close event cancels stale image
|
||||
# publication and a newer synchronize run cannot be overwritten by an older run.
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.pr_number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' || github.event_name == 'workflow_dispatch' || github.ref != 'refs/heads/main' }}
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 3
|
||||
@@ -49,7 +51,7 @@ jobs:
|
||||
name: Fast gate
|
||||
if: github.event_name != 'pull_request_target' && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
TURBO_API: ${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
@@ -60,7 +62,7 @@ jobs:
|
||||
TURBO_PLATFORM: linux/amd64
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: '!env.ACT'
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
@@ -81,37 +83,45 @@ jobs:
|
||||
- name: Copy environment defaults
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Check formatting in affected workspaces
|
||||
run: pnpm turbo run format --affected --continue -- --check
|
||||
|
||||
- name: Lint, typecheck, and build affected workspaces
|
||||
run: pnpm turbo run lint typecheck build --affected --continue=dependencies-successful
|
||||
run: pnpm turbo run lint typecheck build --affected --continue=dependencies-successful --concurrency=2
|
||||
|
||||
- name: Validate workspace configuration
|
||||
run: pnpm lint:ws
|
||||
|
||||
- name: Validate Custom Widget architecture
|
||||
run: pnpm check:custom-widgets
|
||||
|
||||
- name: Validate checked-in OpenAPI schema
|
||||
run: pnpm check:openapi
|
||||
|
||||
- name: Validate Custom Widget bundle budget
|
||||
run: |
|
||||
pnpm --filter nextjs build
|
||||
pnpm check:custom-widget-bundle
|
||||
|
||||
- name: Run unit tests
|
||||
run: pnpm test
|
||||
|
||||
- name: Report coverage
|
||||
if: always() && !env.ACT
|
||||
uses: davelosert/vitest-coverage-report-action@v2
|
||||
|
||||
container:
|
||||
name: Container, E2E, and memory
|
||||
needs: fast-gate
|
||||
name: Container and E2E
|
||||
needs: [fast-gate, preview-amd64]
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'workflow_dispatch' && (inputs.operation == 'validate' || inputs.operation == 'publish'))
|
||||
!cancelled() && needs.fast-gate.result == 'success' &&
|
||||
(
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'push' && github.ref == 'refs/heads/release/v2') ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'workflow_dispatch' && (inputs.operation == 'validate' || inputs.operation == 'publish'))
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
packages: read
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: '!env.ACT'
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
|
||||
@@ -120,11 +130,21 @@ jobs:
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: '!env.ACT'
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Reuse amd64 preview image
|
||||
if: needs.preview-amd64.outputs.digest != '' && !env.ACT
|
||||
env:
|
||||
DIGEST: ${{ needs.preview-amd64.outputs.digest }}
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||
docker pull "${PREVIEW_IMAGE}@${DIGEST}"
|
||||
docker tag "${PREVIEW_IMAGE}@${DIGEST}" homarr-ci
|
||||
docker tag "${PREVIEW_IMAGE}@${DIGEST}" homarr-e2e
|
||||
|
||||
- name: Build container once
|
||||
if: '!env.ACT'
|
||||
if: needs.preview-amd64.outputs.digest == '' && !env.ACT
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
@@ -139,8 +159,8 @@ jobs:
|
||||
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.ref.name=pr-${{ github.event.pull_request.number || inputs.pr_number || 'none' }}
|
||||
cache-from: type=gha,scope=homarr-linux-amd64
|
||||
cache-to: type=gha,scope=homarr-linux-amd64,mode=max
|
||||
cache-from: type=gha,scope=homarr-e2e
|
||||
cache-to: type=gha,scope=homarr-e2e,mode=max
|
||||
secrets: |
|
||||
TURBO_API=${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
|
||||
@@ -165,7 +185,7 @@ jobs:
|
||||
--tag homarr-ci --tag homarr-e2e "${secret_args[@]}" .
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
if: '!env.ACT'
|
||||
if: "!env.ACT"
|
||||
id: playwright-cache
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
@@ -181,7 +201,10 @@ jobs:
|
||||
run: pnpm exec playwright install-deps chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
run: pnpm test:e2e
|
||||
env:
|
||||
NODE_ENV: development
|
||||
CI: "true"
|
||||
run: pnpm exec vitest e2e --retry 1 --maxWorkers 4
|
||||
|
||||
- name: Upload E2E artifacts
|
||||
if: failure() && !env.ACT
|
||||
@@ -191,113 +214,189 @@ jobs:
|
||||
path: e2e/test-results/
|
||||
retention-days: 7
|
||||
|
||||
- name: Run memory regression test
|
||||
env:
|
||||
CONTAINER_NAME: homarr-mem-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
SECRET_ENCRYPTION_KEY: 0000000000000000000000000000000000000000000000000000000000000000
|
||||
run: |
|
||||
docker run -d --name "$CONTAINER_NAME" -p 127.0.0.1::7575 \
|
||||
-e NO_EXTERNAL_CONNECTION=true \
|
||||
-e SECRET_ENCRYPTION_KEY="$SECRET_ENCRYPTION_KEY" \
|
||||
homarr-ci
|
||||
PORT=$(docker port "$CONTAINER_NAME" 7575/tcp | sed 's/.*://')
|
||||
[[ "$PORT" =~ ^[0-9]+$ ]] || { echo "ERROR: invalid port mapping: '$PORT'"; exit 1; }
|
||||
URL="http://127.0.0.1:${PORT}"
|
||||
|
||||
healthy=false
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf "$URL/api/health/ready" >/dev/null 2>&1; then
|
||||
echo "Container healthy after $((i * 2))s"
|
||||
healthy=true
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$healthy" != "true" ]; then
|
||||
echo "ERROR: Container never became healthy"
|
||||
docker logs "$CONTAINER_NAME" --tail 100
|
||||
exit 1
|
||||
fi
|
||||
|
||||
warmup_ok=0
|
||||
for _ in $(seq 1 5); do
|
||||
curl -sf "$URL/" >/dev/null 2>&1 && warmup_ok=$((warmup_ok + 1))
|
||||
sleep 2
|
||||
done
|
||||
if [ "$warmup_ok" -eq 0 ]; then
|
||||
echo "ERROR: All warmup requests failed"
|
||||
docker logs "$CONTAINER_NAME" --tail 50
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" | grep -qx true || {
|
||||
echo "ERROR: Container died before memory measurement"
|
||||
docker logs "$CONTAINER_NAME" --tail 100
|
||||
exit 1
|
||||
}
|
||||
|
||||
RAW_MEMORY=$(docker stats --no-stream --format '{{.MemUsage}}' "$CONTAINER_NAME" | sed 's| /.*||')
|
||||
MEMORY_MB=$(echo "$RAW_MEMORY" | awk '
|
||||
/GiB/ { gsub(/GiB/, ""); print $1 * 1024; next }
|
||||
/MiB/ { gsub(/MiB/, ""); print $1; next }
|
||||
/KiB/ { gsub(/KiB/, ""); print $1 / 1024; next }
|
||||
{ print $1 / 1048576 }
|
||||
')
|
||||
|
||||
echo "Memory usage: ${MEMORY_MB} MB (${RAW_MEMORY})"
|
||||
[[ "$MEMORY_MB" =~ ^[0-9]+(\.[0-9]+)?$ ]] || {
|
||||
echo "ERROR: non-numeric memory value: '$MEMORY_MB' (raw: '$RAW_MEMORY')"
|
||||
docker logs "$CONTAINER_NAME" --tail 100
|
||||
exit 1
|
||||
}
|
||||
if awk "BEGIN { exit !($MEMORY_MB < 50) }"; then
|
||||
echo "ERROR: Memory suspiciously low (${MEMORY_MB} MB) — container may have crashed"
|
||||
docker logs "$CONTAINER_NAME" --tail 100
|
||||
exit 1
|
||||
elif awk "BEGIN { exit !($MEMORY_MB > 700) }"; then
|
||||
echo "ERROR: Memory usage ${MEMORY_MB} MB exceeds 700 MB"
|
||||
docker logs "$CONTAINER_NAME" --tail 100
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Clean up memory test container
|
||||
if: always()
|
||||
env:
|
||||
CONTAINER_NAME: homarr-mem-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
run: docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
||||
|
||||
- name: Determine preview tag
|
||||
id: preview
|
||||
if: |
|
||||
!env.ACT &&
|
||||
(
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
|
||||
)
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
|
||||
run: echo "tag=pr-${PR_NUMBER}" >> "$GITHUB_OUTPUT"
|
||||
preview-amd64:
|
||||
name: Build amd64 preview image
|
||||
needs: fast-gate
|
||||
if: |
|
||||
(
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'push' && github.ref == 'refs/heads/release/v2') ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
digest: ${{ steps.preview-build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
|
||||
|
||||
- name: Log in to GHCR
|
||||
if: steps.preview.outputs.tag != ''
|
||||
if: "!env.ACT"
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Push preview image
|
||||
if: steps.preview.outputs.tag != ''
|
||||
env:
|
||||
PREVIEW_TAG: ${{ steps.preview.outputs.tag }}
|
||||
run: |
|
||||
docker tag homarr-ci "${PREVIEW_IMAGE}:${PREVIEW_TAG}"
|
||||
docker push "${PREVIEW_IMAGE}:${PREVIEW_TAG}"
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Show Act publication result
|
||||
if: env.ACT
|
||||
run: echo "Act validated the publication path; external GHCR writes are disabled locally."
|
||||
- name: Build and push amd64 preview image by digest
|
||||
id: preview-build
|
||||
if: "!env.ACT"
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
network: host
|
||||
platforms: linux/amd64
|
||||
outputs: type=image,"name=${{ env.PREVIEW_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=homarr-linux-amd64
|
||||
cache-to: type=gha,scope=homarr-linux-amd64,mode=max
|
||||
secrets: |
|
||||
TURBO_API=${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
|
||||
preview-arm64:
|
||||
name: Build arm64 preview image
|
||||
needs: fast-gate
|
||||
if: |
|
||||
(
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'push' && github.ref == 'refs/heads/release/v2') ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
|
||||
)
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
digest: ${{ steps.preview-build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
|
||||
|
||||
- name: Log in to GHCR
|
||||
if: "!env.ACT"
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build and push arm64 preview image by digest
|
||||
id: preview-build
|
||||
if: "!env.ACT"
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
network: host
|
||||
platforms: linux/arm64
|
||||
outputs: type=image,"name=${{ env.PREVIEW_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=homarr-linux-arm64
|
||||
cache-to: type=gha,scope=homarr-linux-arm64,mode=max
|
||||
secrets: |
|
||||
TURBO_API=${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
|
||||
publish-preview:
|
||||
name: Publish multi-platform preview image
|
||||
needs: [container, preview-amd64, preview-arm64]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
needs.container.result == 'success' &&
|
||||
needs.preview-amd64.result == 'success' &&
|
||||
needs.preview-arm64.result == 'success' &&
|
||||
(
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'push' && github.ref == 'refs/heads/release/v2') ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
env:
|
||||
PREVIEW_TAG: ${{ github.event_name == 'push' && 'v2' || format('pr-{0}', github.event.pull_request.number || inputs.pr_number) }}
|
||||
steps:
|
||||
- name: Log in to GHCR
|
||||
if: "!env.ACT"
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Verify PR head before publication
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
with:
|
||||
script: |
|
||||
const { data: pull } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number,
|
||||
});
|
||||
if (pull.state !== "open") {
|
||||
core.setFailed(`PR #${pull.number} is ${pull.state}; refusing to publish its preview.`);
|
||||
return;
|
||||
}
|
||||
if (pull.head.sha !== process.env.EXPECTED_HEAD_SHA) {
|
||||
core.setFailed(`PR #${pull.number} moved from ${process.env.EXPECTED_HEAD_SHA} to ${pull.head.sha}.`);
|
||||
}
|
||||
|
||||
- name: Publish preview manifest
|
||||
if: "!env.ACT"
|
||||
env:
|
||||
AMD64_DIGEST: ${{ needs.preview-amd64.outputs.digest }}
|
||||
ARM64_DIGEST: ${{ needs.preview-arm64.outputs.digest }}
|
||||
run: |
|
||||
tags=(-t "${PREVIEW_IMAGE}:${PREVIEW_TAG}")
|
||||
docker buildx imagetools create "${tags[@]}" \
|
||||
"${PREVIEW_IMAGE}@${AMD64_DIGEST}" \
|
||||
"${PREVIEW_IMAGE}@${ARM64_DIGEST}"
|
||||
|
||||
- name: Verify preview manifest platforms
|
||||
if: "!env.ACT"
|
||||
run: |
|
||||
tags=("${PREVIEW_TAG}")
|
||||
for tag in "${tags[@]}"; do
|
||||
image="${PREVIEW_IMAGE}:${tag}"
|
||||
manifest="$(docker buildx imagetools inspect "$image" --raw)"
|
||||
for architecture in amd64 arm64; do
|
||||
jq --exit-status --arg architecture "$architecture" \
|
||||
'any(.manifests[]; .platform.os == "linux" and .platform.architecture == $architecture)' \
|
||||
<<<"$manifest" >/dev/null
|
||||
done
|
||||
docker buildx imagetools inspect "$image"
|
||||
done
|
||||
|
||||
cleanup:
|
||||
name: Clean up preview image
|
||||
@@ -316,7 +415,7 @@ jobs:
|
||||
run: test -n "$PR_NUMBER" || { echo "pr_number is required for cleanup"; exit 1; }
|
||||
|
||||
- name: Delete GHCR package version
|
||||
if: '!env.ACT'
|
||||
if: "!env.ACT"
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
@@ -334,6 +433,14 @@ jobs:
|
||||
core.info(`No package version found with tag "${tag}"`);
|
||||
return;
|
||||
}
|
||||
const targetTags = target.metadata?.container?.tags ?? [];
|
||||
const extraTags = targetTags.filter((targetTag) => targetTag !== tag);
|
||||
if (extraTags.length > 0) {
|
||||
core.warning(
|
||||
`Package version ${target.id} also carries tag(s) ${extraTags.map((targetTag) => `"${targetTag}"`).join(", ")}; refusing cleanup.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await github.rest.packages.deletePackageVersionForOrg({
|
||||
package_type: "container",
|
||||
package_name: packageName,
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.CROWDIN_APP_ID }}
|
||||
client-id: ${{ vars.CROWDIN_CLIENT_ID }}
|
||||
permission-contents: write # required to commit to crowdin branch
|
||||
permission-pull-requests: write # required to create pull request
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
|
||||
permission-pull-requests: write
|
||||
- name: Approve PR
|
||||
env:
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
|
||||
permission-contents: write # write to beta branch (due to merge)
|
||||
permission-pull-requests: write # merge pull request
|
||||
- id: automerge
|
||||
|
||||
@@ -13,7 +13,7 @@ on:
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
description: Send notifications
|
||||
description: Send notifications when redeploying an existing release
|
||||
|
||||
permissions:
|
||||
contents: write # Required to update package.json version
|
||||
@@ -38,10 +38,11 @@ jobs:
|
||||
SKIP_RELEASE: ${{ github.event_name == 'workflow_dispatch' || github.ref_name == 'dev' || github.ref_name == 'next' }}
|
||||
outputs:
|
||||
version: ${{ steps.read-semver.outputs.version || steps.version-fallback.outputs.version }}
|
||||
git_ref: ${{ steps.read-git-ref.outputs.ref || github.ref }}
|
||||
git_ref: ${{ steps.read-git-ref.outputs.ref || steps.read-semver.outputs.version || github.ref }}
|
||||
skipped: ${{ env.SKIP_RELEASE }}
|
||||
upstream_sync: ${{ steps.update-upstream.outcome }}
|
||||
steps:
|
||||
- run: echo "Skipping release for workflow_dispatch event"
|
||||
- run: echo "Manual dispatch redeploys the latest existing release without creating a new one"
|
||||
if: env.SKIP_RELEASE == 'true'
|
||||
# The below generated version fallback represents a normalized branch name, for example "feature/branch-name" -> "feature-branch-name"
|
||||
- run: echo "version="$(echo ${{github.ref_name}} | sed 's/[^a-zA-Z0-9\-]/-/g') >> "$GITHUB_OUTPUT"
|
||||
@@ -54,12 +55,13 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
|
||||
permission-contents: write # required to commit package.json & changelog changes, merge them to dev / next and publish the release
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
if: env.SKIP_RELEASE == 'false' || github.ref_name == 'main' || github.ref_name == 'beta'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: pnpm/action-setup@v6
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
@@ -68,12 +70,29 @@ jobs:
|
||||
with:
|
||||
node-version: 24.18.0
|
||||
package-manager-cache: false
|
||||
- run: npm i -g pnpm
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
- name: Install dependencies
|
||||
- name: Verify major release intent
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
run: |
|
||||
pnpm install
|
||||
latest_tag="$(git describe --tags --abbrev=0)"
|
||||
latest_major="${latest_tag#v}"
|
||||
latest_major="${latest_major%%.*}"
|
||||
package_version="$(jq --raw-output '.version' package.json)"
|
||||
package_major="${package_version%%.*}"
|
||||
|
||||
if (( package_major <= latest_major )); then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if git log --format='%s%n%b' "${latest_tag}..HEAD" \
|
||||
| grep --extended-regexp --quiet '(^[[:alnum:]-]+(\([^)]*\))?!:)|(^BREAKING[ -]CHANGE:)'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::error::package.json declares ${package_version}, but commits since ${latest_tag} contain no breaking-change signal. Use a conventional ! commit or BREAKING CHANGE footer so Semantic Release creates the intended major version."
|
||||
exit 1
|
||||
- name: Install dependencies
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run Semantic Release
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
env:
|
||||
@@ -97,8 +116,10 @@ jobs:
|
||||
run: |
|
||||
echo "ref=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
- name: Update dev / next branch
|
||||
id: update-upstream
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
continue-on-error: true # Prevent pipeline from failing when merge fails
|
||||
# Finish publishing the already-created release, then fail in the publish job if synchronization did not work.
|
||||
continue-on-error: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
UPSTREAM_BRANCH: ${{ github.ref_name == 'main' && 'dev' || 'next' }}
|
||||
@@ -205,9 +226,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
persist-credentials: false
|
||||
- name: Extract amd64
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
uses: homarr-labs/homarr/.github/actions/extract-build-artifact@dev
|
||||
uses: ./.github/actions/extract-build-artifact
|
||||
with:
|
||||
digest: "${{ env.GHCR_REPO }}@${{ needs.build-amd64.outputs.digest }}"
|
||||
architecture: amd64
|
||||
@@ -221,9 +247,14 @@ jobs:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
persist-credentials: false
|
||||
- name: Extract arm64
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
uses: homarr-labs/homarr/.github/actions/extract-build-artifact@dev
|
||||
uses: ./.github/actions/extract-build-artifact
|
||||
with:
|
||||
digest: "${{ env.GHCR_REPO }}@${{ needs.build-arm64.outputs.digest }}"
|
||||
architecture: arm64
|
||||
@@ -246,7 +277,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
|
||||
permission-contents: write # required to publish the release
|
||||
|
||||
- name: Log in to the Container registry
|
||||
@@ -283,9 +314,30 @@ jobs:
|
||||
run: |
|
||||
gh release edit --repo ${{ github.repository }} ${{ needs.release.outputs.version }} --draft=false
|
||||
|
||||
- name: Verify dev / next synchronization
|
||||
if: needs.release.outputs.skipped == 'false' && needs.release.outputs.upstream_sync != 'success'
|
||||
run: |
|
||||
echo "::error::The released commit was not synchronized back to the upstream development branch."
|
||||
exit 1
|
||||
|
||||
- name: Discord notification
|
||||
if: github.event_name != 'workflow_dispatch' || inputs['send-notifications']
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "Successfully deployed images for branch **${{ github.ref_name }}**. Tagged as **${{env.NEXT_VERSION}}**."
|
||||
|
||||
notify-failure:
|
||||
name: Notify deployment failure
|
||||
if: ${{ always() && (failure() || cancelled()) && (github.event_name != 'workflow_dispatch' || inputs['send-notifications']) }}
|
||||
needs: [release, build-amd64, build-arm64, extract-asset-amd64, extract-asset-arm64, publish]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Discord failure notification
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "The deployment for branch **${{ github.ref_name }}** failed or was cancelled: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Upload pages artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
with:
|
||||
path: "apps/docs/build"
|
||||
path: "apps/docs/out"
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -19,10 +19,10 @@ permissions:
|
||||
jobs:
|
||||
create-and-merge-pr:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Discord notification
|
||||
if: ${{ github.event.inputs.send-notifications }}
|
||||
if: ${{ github.event_name == 'schedule' || inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
@@ -38,13 +38,30 @@ jobs:
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
branch: dev
|
||||
noNewCommitBehavior: silent
|
||||
noVersionBumpBehavior: silent
|
||||
- name: No release needed
|
||||
if: ${{ steps.semver.outputs.bump == '' || steps.semver.outputs.bump == 'none' }}
|
||||
run: echo "No release-worthy commits were found since ${{ steps.semver.outputs.current }}." >> "$GITHUB_STEP_SUMMARY"
|
||||
- name: Create pull request
|
||||
id: create-pull-request
|
||||
run: 'gh pr create --title "chore(release): automatic release ${{ steps.semver.outputs.next }}" --body "**This is an automatic release**.<br/>Manual action may be required for major bumps.<br/>Detected change to be ``${{ steps.semver.outputs.bump }}``<br/>Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}``" --base main --head dev --label automerge'
|
||||
if: ${{ steps.semver.outputs.bump != '' && steps.semver.outputs.bump != 'none' }}
|
||||
run: |
|
||||
pr_url="$(gh pr list --base main --head dev --state open --json url --jq '.[0].url // empty')"
|
||||
if [[ -z "$pr_url" ]]; then
|
||||
pr_url="$(gh pr create \
|
||||
--title "chore(release): automatic release ${{ steps.semver.outputs.next }}" \
|
||||
--body "**This is an automatic release**.<br/>Manual action may be required for major bumps.<br/>Detected change to be ``${{ steps.semver.outputs.bump }}``<br/>Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}``" \
|
||||
--base main \
|
||||
--head dev \
|
||||
--label automerge)"
|
||||
fi
|
||||
echo "url=$pr_url" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_number=${pr_url##*/}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Discord notification
|
||||
if: ${{ github.event.inputs.send-notifications }}
|
||||
if: ${{ steps.create-pull-request.outcome == 'success' && (github.event_name == 'schedule' || inputs.send-notifications) }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
@@ -52,26 +69,29 @@ jobs:
|
||||
args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }} (new behaviour: ${{ steps.semver.outputs.bump }})"
|
||||
- name: Obtain token
|
||||
id: obtainApprovalToken
|
||||
if: ${{ steps.create-pull-request.outcome == 'success' }}
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
|
||||
permission-pull-requests: write
|
||||
- name: Approve PR
|
||||
if: ${{ steps.create-pull-request.outcome == 'success' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
|
||||
run: |
|
||||
gh pr review --approve --body "Automatically approved by GitHub Action"
|
||||
gh pr review "${{ steps.create-pull-request.outputs.url }}" --approve --body "Automatically approved by GitHub Action"
|
||||
- name: Obtain token
|
||||
id: obtainMergeToken
|
||||
if: ${{ steps.create-pull-request.outcome == 'success' }}
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
|
||||
permission-contents: write # write to main branch (due to merge)
|
||||
permission-pull-requests: write # merge pull request
|
||||
- id: automerge
|
||||
if: ${{ steps.semver.outputs.bump != 'major' }}
|
||||
if: ${{ steps.create-pull-request.outcome == 'success' && steps.semver.outputs.bump != 'major' }}
|
||||
name: automerge
|
||||
uses: "pascalgn/automerge-action@v0.16.4"
|
||||
env:
|
||||
@@ -85,21 +105,21 @@ jobs:
|
||||
MERGE_REQUIRED_APPROVALS: 0 # do not require approvals
|
||||
|
||||
- name: Merged Discord notification
|
||||
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.event.inputs.send-notifications }}
|
||||
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && (github.event_name == 'schedule' || inputs.send-notifications) }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "Merged PR ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}"
|
||||
- name: Major Bump Discord notification
|
||||
if: ${{ steps.semver.outputs.bump == 'major' && github.event.inputs.send-notifications }}
|
||||
if: ${{ steps.semver.outputs.bump == 'major' && (github.event_name == 'schedule' || inputs.send-notifications) }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "The release PR must be manually merged because the next version is a major version: ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}"
|
||||
- name: Discord Fail Notification
|
||||
if: failure() && github.event.inputs.send-notifications
|
||||
if: ${{ failure() && (github.event_name == 'schedule' || inputs.send-notifications) }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
name: "[Docs] Algolia Recrawl"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["[Deployment] Documentation"]
|
||||
types: [completed]
|
||||
branches: [dev]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
recrawl:
|
||||
if: >-
|
||||
${{
|
||||
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'pull_request')
|
||||
|| github.event_name == 'workflow_dispatch'
|
||||
}}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Check Algolia crawler secrets
|
||||
run: |
|
||||
missing=0
|
||||
for secret in ALGOLIA_CRAWLER_ID ALGOLIA_CRAWLER_USER_ID ALGOLIA_CRAWLER_API_KEY; do
|
||||
if [ -z "${!secret}" ]; then
|
||||
echo "::warning::Missing repository secret: $secret"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [ "$missing" -eq 1 ]; then
|
||||
echo "Add crawler secrets to enable automatic recrawls."
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
ALGOLIA_CRAWLER_ID: ${{ secrets.ALGOLIA_CRAWLER_ID }}
|
||||
ALGOLIA_CRAWLER_USER_ID: ${{ secrets.ALGOLIA_CRAWLER_USER_ID }}
|
||||
ALGOLIA_CRAWLER_API_KEY: ${{ secrets.ALGOLIA_CRAWLER_API_KEY }}
|
||||
|
||||
- name: Wait for GitHub Pages to serve the new deployment
|
||||
run: sleep 60
|
||||
|
||||
- name: Trigger Algolia DocSearch recrawl
|
||||
uses: cssnr/algolia-crawler-action@v2
|
||||
with:
|
||||
crawler_id: ${{ secrets.ALGOLIA_CRAWLER_ID }}
|
||||
crawler_user_id: ${{ secrets.ALGOLIA_CRAWLER_USER_ID }}
|
||||
crawler_api_key: ${{ secrets.ALGOLIA_CRAWLER_API_KEY }}
|
||||
|
||||
- name: Verify search index
|
||||
working-directory: apps/docs
|
||||
run: pnpm verify:search
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
- name: Commit updated contributors
|
||||
uses: EndBug/add-and-commit@v10
|
||||
with:
|
||||
add: "apps/docs/static"
|
||||
add: "apps/docs/public"
|
||||
default_author: github_actor
|
||||
message: "chore(docs): update contributors"
|
||||
push: true
|
||||
@@ -54,13 +54,13 @@ jobs:
|
||||
exit 1
|
||||
}
|
||||
|
||||
cp /tmp/openapi.json apps/docs/static/api/open-api-schema.json
|
||||
cp /tmp/openapi.json apps/docs/public/api/open-api-schema.json
|
||||
echo "OpenAPI spec updated ($(jq '.paths | length' /tmp/openapi.json) paths)"
|
||||
|
||||
- name: Commit updated spec
|
||||
uses: EndBug/add-and-commit@v10
|
||||
with:
|
||||
add: "apps/docs/static/api/"
|
||||
add: "apps/docs/public/api/"
|
||||
default_author: github_actor
|
||||
message: "chore(docs): update OpenAPI spec"
|
||||
push: true
|
||||
@@ -5,7 +5,7 @@ permissions:
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["*"]
|
||||
branches: ["**"]
|
||||
paths: [".github/actions/prebuilt-debian/**", "deployments/prebuilt-debian/**"]
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -5,7 +5,7 @@ permissions:
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["*"]
|
||||
branches: ["**"]
|
||||
paths: [".github/renovate.json5"]
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -5,39 +5,6 @@ on:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
trigger-docs-release:
|
||||
name: Trigger Documentation Release
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
|
||||
owner: homarr-labs
|
||||
repositories: |
|
||||
documentation
|
||||
permission-contents: write # required to dispatch repository workflow
|
||||
- name: Trigger documentation release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
SOURCE_TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/homarr-labs/documentation/dispatches \
|
||||
-d @- <<EOF
|
||||
{
|
||||
"event_type": "trigger-release",
|
||||
"client_payload": {
|
||||
"tag": "${SOURCE_TAG}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
update-bug-report-template:
|
||||
name: Update Bug Report Template
|
||||
runs-on: ubuntu-latest
|
||||
@@ -48,7 +15,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
|
||||
permission-contents: write # required to commit to branch
|
||||
permission-pull-requests: write # required to create pr & enable automerge
|
||||
- name: Checkout code
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
permissions:
|
||||
statuses: write
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -16,12 +17,20 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
run: |
|
||||
gh api \
|
||||
--method POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
/repos/${{github.repository}}/statuses/${{github.event.pull_request.head.sha}} \
|
||||
-f 'state=success' \
|
||||
-f 'target_url=https://docs.renovatebot.com/configuration-options#minimumreleaseage' \
|
||||
-f 'description=Skipped for non renovate branches' \
|
||||
-f 'context=renovate/stability-days'
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if gh api \
|
||||
--method POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
/repos/${{github.repository}}/statuses/${{github.event.pull_request.head.sha}} \
|
||||
-f 'state=success' \
|
||||
-f 'target_url=https://docs.renovatebot.com/configuration-options#minimumreleaseage' \
|
||||
-f 'description=Skipped for non renovate branches' \
|
||||
-f 'context=renovate/stability-days' \
|
||||
--silent; then
|
||||
exit 0
|
||||
fi
|
||||
echo "status POST failed (attempt $attempt/5), retrying in 5s"
|
||||
sleep 5
|
||||
done
|
||||
exit 1
|
||||
@@ -15,16 +15,13 @@ jobs:
|
||||
update-contributors:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [22]
|
||||
steps:
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_CLIENT_ID }}
|
||||
permission-contents: write # required to commit to branch
|
||||
permission-pull-requests: write # required to create pr & enable automerge
|
||||
|
||||
@@ -34,10 +31,10 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-version: 24.18.0
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Run update script
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_CLIENT_ID }}
|
||||
permission-contents: write # required to commit to branch
|
||||
permission-pull-requests: write # required to create pr & enable automerge
|
||||
- name: Checkout code
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
name: Workshop
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [dev, v2, release/v2, main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ (github.ref_name == 'v2' || github.ref_name == 'release/v2') && 'v2-preview' || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24.18.0
|
||||
- uses: pnpm/action-setup@v6
|
||||
with:
|
||||
run_install: false
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm --filter @homarr/workshop typecheck
|
||||
- run: pnpm --filter @homarr/docs typecheck
|
||||
- run: docker compose --env-file apps/workshop/.env.staging.example -f apps/workshop/docker-compose.staging.yml config --quiet
|
||||
- run: pnpm test:workshop
|
||||
- run: pnpm test:workshop-image
|
||||
|
||||
publish-amd64:
|
||||
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
|
||||
needs: validate
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: docker/build-push-action@v7
|
||||
id: build
|
||||
with:
|
||||
context: .
|
||||
file: apps/workshop/Dockerfile
|
||||
target: production
|
||||
platforms: linux/amd64
|
||||
outputs: type=image,"name=ghcr.io/homarr-labs/workshop",push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=workshop-linux-amd64
|
||||
cache-to: type=gha,scope=workshop-linux-amd64,mode=max
|
||||
|
||||
publish-arm64:
|
||||
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
|
||||
needs: validate
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: docker/build-push-action@v7
|
||||
id: build
|
||||
with:
|
||||
context: .
|
||||
file: apps/workshop/Dockerfile
|
||||
target: production
|
||||
platforms: linux/arm64
|
||||
outputs: type=image,"name=ghcr.io/homarr-labs/workshop",push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=workshop-linux-arm64
|
||||
cache-to: type=gha,scope=workshop-linux-arm64,mode=max
|
||||
|
||||
publish:
|
||||
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
|
||||
needs: [publish-amd64, publish-arm64]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
env:
|
||||
WORKSHOP_IMAGE: ghcr.io/homarr-labs/workshop
|
||||
steps:
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Verify branch head before publication
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const branchName = context.ref.replace("refs/heads/", "");
|
||||
const { data: branch } = await github.rest.repos.getBranch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
branch: branchName,
|
||||
});
|
||||
if (branch.commit.sha !== context.sha) {
|
||||
core.setFailed(`Branch ${branchName} moved from ${context.sha} to ${branch.commit.sha}.`);
|
||||
}
|
||||
- name: Publish multi-platform image
|
||||
env:
|
||||
AMD64_DIGEST: ${{ needs.publish-amd64.outputs.digest }}
|
||||
ARM64_DIGEST: ${{ needs.publish-arm64.outputs.digest }}
|
||||
run: |
|
||||
tags=(-t "${WORKSHOP_IMAGE}:sha-${GITHUB_SHA}")
|
||||
if [[ "${GITHUB_REF_NAME}" == "v2" || "${GITHUB_REF_NAME}" == "release/v2" ]]; then
|
||||
tags+=(-t "${WORKSHOP_IMAGE}:v2")
|
||||
fi
|
||||
if [[ "${GITHUB_REF_NAME}" == "main" ]]; then
|
||||
tags+=(-t "${WORKSHOP_IMAGE}:latest")
|
||||
fi
|
||||
docker buildx imagetools create "${tags[@]}" \
|
||||
"${WORKSHOP_IMAGE}@${AMD64_DIGEST}" \
|
||||
"${WORKSHOP_IMAGE}@${ARM64_DIGEST}"
|
||||
- name: Verify multi-platform image
|
||||
run: |
|
||||
image="${WORKSHOP_IMAGE}:sha-${GITHUB_SHA}"
|
||||
manifest="$(docker buildx imagetools inspect "$image" --raw)"
|
||||
for architecture in amd64 arm64; do
|
||||
jq --exit-status --arg architecture "$architecture" \
|
||||
'any(.manifests[]; .platform.os == "linux" and .platform.architecture == $architecture)' \
|
||||
<<<"$manifest" >/dev/null
|
||||
done
|
||||
docker buildx imagetools inspect "$image"
|
||||
@@ -16,6 +16,7 @@ out/
|
||||
next-env.d.ts
|
||||
|
||||
# artifacts
|
||||
.ai-evaluations/
|
||||
packages/db/migrations/*/migrate.cjs
|
||||
|
||||
# nitro
|
||||
@@ -24,6 +25,8 @@ packages/db/migrations/*/migrate.cjs
|
||||
|
||||
# production
|
||||
build
|
||||
tools/homarr-dev/homarr
|
||||
tools/homarr-dev/homarr-dev
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
@@ -71,10 +74,23 @@ apps/nextjs/public/images/background.png
|
||||
|
||||
# personal agent config
|
||||
.opencode
|
||||
.agents/
|
||||
.agents/*
|
||||
!.agents/skills/
|
||||
skills-lock.json
|
||||
|
||||
# docusaurus
|
||||
apps/docs/.docusaurus
|
||||
apps/docs/build
|
||||
.pnpm-store
|
||||
benchmark-results
|
||||
|
||||
# local performance investigation (never ship benchmark data or restored backups)
|
||||
.widget-performance/
|
||||
.bench/
|
||||
homarr-backup*.zip
|
||||
*.sqlite-wal
|
||||
*.sqlite-shm
|
||||
/scripts/benchmarks/
|
||||
|
||||
# PR description screenshots belong on the pull request, not in the repository
|
||||
.screenshots/
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"mantine": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@mantine/mcp-server@9.6.0"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,19 @@
|
||||
"printWidth": 120,
|
||||
"endOfLine": "lf",
|
||||
"sortPackageJson": true,
|
||||
"ignorePatterns": ["**/*.cjs", "**/lang/**", "static-data/**", "CHANGELOG.md", "apps/docs/.docusaurus/**", "apps/docs/build/**", "apps/docs/blog/**"]
|
||||
"ignorePatterns": [
|
||||
"**/*.cjs",
|
||||
"**/lang/**",
|
||||
"static-data/**",
|
||||
"CHANGELOG.md",
|
||||
"apps/docs/.docusaurus/**",
|
||||
"apps/docs/build/**",
|
||||
"apps/docs/blog/2022/**",
|
||||
"apps/docs/blog/2023/**",
|
||||
"apps/docs/blog/2024/**",
|
||||
"apps/docs/blog/2025/**",
|
||||
"apps/docs/blog/authors.yml",
|
||||
"packages/custom-widgets/src/core/component-catalog.generated.json",
|
||||
"apps/docs/static/custom-widgets/component-catalog-v1.json"
|
||||
]
|
||||
}
|
||||
@@ -30,7 +30,35 @@
|
||||
"options": {
|
||||
"reportUnusedDisableDirectives": "off"
|
||||
},
|
||||
"ignorePatterns": ["**/*.config.js", "**/*.config.mjs", "**/*.cjs", "**/lang/*.d.json.ts", ".next/**", "apps/docs/.docusaurus/**", "apps/docs/build/**"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["apps/nextjs/src/**/*.ts", "apps/nextjs/src/**/*.tsx"],
|
||||
"excludeFiles": ["**/*.spec.ts", "**/*.spec.tsx"],
|
||||
"rules": {
|
||||
"eslint/no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "@homarr/widgets",
|
||||
"allowTypeImports": true,
|
||||
"message": "Import a narrow @homarr/widgets export to preserve widget lazy loading."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"**/*.config.js",
|
||||
"**/*.config.mjs",
|
||||
"**/*.cjs",
|
||||
"**/lang/*.d.json.ts",
|
||||
".next/**",
|
||||
"apps/docs/.docusaurus/**",
|
||||
"apps/docs/build/**"
|
||||
],
|
||||
"settings": {
|
||||
"react": { "version": "19" }
|
||||
}
|
||||
|
||||
@@ -6,23 +6,24 @@
|
||||
homarr/
|
||||
├── apps/
|
||||
│ ├── nextjs/ # Main Next.js application (port 3000)
|
||||
│ ├── docs/ # Docusaurus 3 documentation site (@homarr/docs)
|
||||
│ ├── tasks/ # Cron job runner + Fastify tRPC API (port 3002)
|
||||
│ └── websocket/ # Standalone tRPC WebSocket server (port 3001)
|
||||
│ ├── docs/ # Next.js + Fumadocs documentation site (@homarr/docs)
|
||||
│ ├── tasks/ # Cron-job initialization and scheduling runtime
|
||||
│ ├── websocket/ # Standalone tRPC WebSocket server (port 3001)
|
||||
│ └── workshop/ # Go/PocketBase Workshop service
|
||||
├── packages/
|
||||
│ ├── api/ # tRPC appRouter, procedures, OpenAPI
|
||||
│ ├── auth/ # NextAuth config, providers, session, API keys
|
||||
│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries
|
||||
│ ├── core/ # Env validation, DB/Redis driver factories, logging
|
||||
│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions
|
||||
│ ├── widgets/ # All 39 dashboard widgets (definitions + components)
|
||||
│ ├── widgets/ # Dashboard widget definitions and components
|
||||
│ ├── integrations/ # Integration classes (HTTP clients to external apps)
|
||||
│ ├── redis/ # Redis pub/sub channels, caching abstractions
|
||||
│ ├── translation/ # next-intl setup, locale configs, lang JSON files
|
||||
│ ├── ui/ # Shared Mantine components, theme, hooks
|
||||
│ ├── validation/ # Shared zod schemas for API/forms
|
||||
│ ├── common/ # Shared utilities, IDs, errors
|
||||
│ ├── cron-jobs/ # Cron job implementations (25+ jobs)
|
||||
│ ├── cron-jobs/ # Cron job implementations
|
||||
│ ├── cron-jobs-core/ # Cron scheduling primitives
|
||||
│ ├── cron-job-status/ # Cron status via Redis
|
||||
│ ├── boards/ # Board context, edit mode, cache updater
|
||||
@@ -40,12 +41,15 @@ homarr/
|
||||
│ ├── analytics/ # Server-side analytics (Umami)
|
||||
│ ├── server-settings/ # Server setting keys/types
|
||||
│ ├── settings/ # User-facing settings UI context
|
||||
│ ├── old-import/ # Legacy Homarr import
|
||||
│ ├── old-schema/ # Legacy Homarr zod schemas
|
||||
│ ├── custom-widgets/ # Custom JSX v2 schema, validation, and runtime
|
||||
│ ├── onboarding/ # Onboarding studio and setup flow
|
||||
│ ├── workshop/ # Homarr-side Workshop client and contracts
|
||||
│ └── cli/ # Node CLI for ops (brocli)
|
||||
├── tooling/
|
||||
│ ├── typescript/ # Base tsconfig
|
||||
│ └── github/ # CI setup action
|
||||
├── tools/
|
||||
│ └── homarr-dev/ # Go CLI for local and PR Docker images
|
||||
├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)
|
||||
├── e2e/ # E2E test specs
|
||||
└── Dockerfile # Multi-stage production build
|
||||
@@ -53,13 +57,13 @@ homarr/
|
||||
|
||||
## Documentation Sync
|
||||
|
||||
The documentation site lives at `apps/docs/` (Docusaurus 3, `@homarr/docs`).
|
||||
The documentation site lives at `apps/docs/` (Next.js + Fumadocs, `@homarr/docs`).
|
||||
|
||||
When modifying user-facing code, you MUST also update the corresponding documentation:
|
||||
|
||||
- New integration → `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts`
|
||||
- New widget → `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts`
|
||||
- Changed API → `apps/docs/docs/management/api.mdx`
|
||||
- Changed API → `apps/docs/docs/management/api/index.mdx`
|
||||
- New/changed env vars → `apps/docs/docs/advanced/`
|
||||
- New CLI commands → `apps/docs/docs/advanced/command-line/`
|
||||
- Auth changes → `apps/docs/docs/advanced/` SSO pages
|
||||
@@ -68,7 +72,10 @@ When modifying user-facing code, you MUST also update the corresponding document
|
||||
## Monorepo Commands
|
||||
|
||||
- `pnpm dev` — Next.js app only
|
||||
- `pnpm dev:docs` — Docusaurus docs site only
|
||||
- `pnpm dev:cli -- dev` — run the developer CLI without installing a global binary
|
||||
- `pnpm db:seed` — seed default database data explicitly
|
||||
- `pnpm docker:dev:up` — start the Redis development service in the background
|
||||
- `pnpm dev:docs` — Fumadocs site only
|
||||
- `pnpm turbo build` — build all packages
|
||||
- `pnpm turbo build --filter=@homarr/docs` — build docs only
|
||||
- `pnpm turbo typecheck` — typecheck all packages
|
||||
@@ -79,5 +86,19 @@ When modifying user-facing code, you MUST also update the corresponding document
|
||||
- Lint: oxlint (not ESLint)
|
||||
- Format: oxfmt (not Prettier)
|
||||
- UI: Mantine (not Tailwind) — Tailwind is only used in docs app
|
||||
- Mantine: use the project-scoped MCP server in `.mcp.json` for current v9 APIs, and check `packages/ui/` before creating a new primitive.
|
||||
- Icons: @tabler/icons-react
|
||||
- Docs app can import from `@homarr/definitions` for shared types
|
||||
- Run `pnpm dev:cli -- dev` to browse local `homarr:*` images and remote PR images.
|
||||
- Run `pnpm dev:cli -- build <name>` from a Homarr checkout to build `homarr:<name>` with rebuild provenance.
|
||||
- Run `pnpm dev:cli -- build --pr <number>` to build a PR locally from a temporary checkout.
|
||||
- Run Go checks from `tools/homarr-dev` with `go test ./...` and `go vet ./...`.
|
||||
|
||||
## Agent Skills
|
||||
|
||||
Portable skills live in `.agents/skills/`. Read the relevant `SKILL.md` before working in that domain; detailed references are loaded only when needed. Claude-compatible discovery is provided through `.claude/skills`.
|
||||
|
||||
- `codebase-context` — architecture, package boundaries, and shared utilities
|
||||
- `documentation-sync` — required user-facing documentation updates
|
||||
- `mcp-integration` — safe tRPC-to-MCP exposure
|
||||
- `homarr-custom-widget` — safe Custom JSX v2 authoring
|
||||
@@ -1,3 +1,19 @@
|
||||
## [1.76.2](https://github.com/homarr-labs/homarr/compare/v1.76.1...v1.76.2) (2026-08-27)
|
||||
|
||||
## [1.76.1](https://github.com/homarr-labs/homarr/compare/v1.76.0...v1.76.1) (2026-08-26)
|
||||
|
||||
## [1.76.0](https://github.com/homarr-labs/homarr/compare/v1.75.0...v1.76.0) (2026-08-21)
|
||||
|
||||
## [1.75.0](https://github.com/homarr-labs/homarr/compare/v1.74.0...v1.75.0) (2026-08-14)
|
||||
|
||||
## [1.74.0](https://github.com/homarr-labs/homarr/compare/v1.73.0...v1.74.0) (2026-08-07)
|
||||
|
||||
## [1.73.0](https://github.com/homarr-labs/homarr/compare/v1.72.0...v1.73.0) (2026-07-31)
|
||||
|
||||
## [1.72.0](https://github.com/homarr-labs/homarr/compare/v1.71.0...v1.72.0) (2026-07-24)
|
||||
|
||||
## [1.71.0](https://github.com/homarr-labs/homarr/compare/v1.70.0...v1.71.0) (2026-07-17)
|
||||
|
||||
## [1.70.0](https://github.com/homarr-labs/homarr/compare/v1.69.2...v1.70.0) (2026-07-10)
|
||||
|
||||
## [1.69.2](https://github.com/homarr-labs/homarr/compare/v1.69.1...v1.69.2) (2026-07-06)
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## Goal
|
||||
|
||||
Remove widget-only services from Homarr's global Integration system.
|
||||
|
||||
The affected services are release providers and Search.ch. They are only used by specific widgets, so keeping them as global integrations added unnecessary setup, UI, database rows, docs pages, and integration package code.
|
||||
|
||||
ArchiveTeam Warrior remains a proper integration but is no longer auto-seeded on first install.
|
||||
|
||||
## Why
|
||||
|
||||
These services do not need shared integration state.
|
||||
|
||||
- Release providers are selected per Releases widget repository.
|
||||
- Search.ch is only used by the Timetable widget.
|
||||
|
||||
Moving them into widget options makes configuration simpler and removes dead-weight integration code from `packages/integrations`.
|
||||
|
||||
## What Changed
|
||||
|
||||
### Release Providers
|
||||
|
||||
- Added release provider definitions in `packages/definitions/src/release-provider.ts`.
|
||||
- Removed release providers from `IntegrationKind`.
|
||||
- Moved release-fetching code out of `packages/integrations` and into `packages/request-handler/src/release-providers.ts`.
|
||||
- Updated the Releases widget to store provider configuration directly in widget options:
|
||||
- `provider`
|
||||
- `identifier`
|
||||
- `versionFilter`
|
||||
- `providerUrl`
|
||||
- Updated the Releases widget editor to use a provider select instead of an integration select.
|
||||
- Updated Docker import mapping so `ghcr.io` maps to `gitHubContainerRegistry`.
|
||||
- GitHub Container Registry now uses the public OCI registry API and accepts full image references such as `ghcr.io/homarr-labs/homarr:latest`.
|
||||
|
||||
### Timetable / Search.ch
|
||||
|
||||
- Removed Search.ch as an integration.
|
||||
- Added a Timetable widget `baseUrl` option, defaulting to `https://search.ch`.
|
||||
- Moved Search.ch request/parsing logic into `packages/request-handler/src/timetable.ts`.
|
||||
- Updated the Timetable API to receive `baseUrl` directly from widget options.
|
||||
|
||||
### ArchiveTeam Warrior
|
||||
|
||||
- Kept as a proper integration (not removed).
|
||||
- Removed `defaultUrl` from the integration definition to prevent auto-seeding on first install.
|
||||
|
||||
### Integration Cleanup
|
||||
|
||||
- Removed creators, exports, docs references, and implementation files for the deleted integrations.
|
||||
- Removed unused dependencies from `packages/integrations/package.json`.
|
||||
|
||||
### Database Migration
|
||||
|
||||
- Added `packages/db/migrations/custom/0003_remove_widget_only_integrations.ts`.
|
||||
- The migration moves old integration-linked widget config into widget options, then deletes the old widget-only integration rows.
|
||||
- Updated the earlier release widget migration to use the new `provider` field instead of `providerIntegrationId`.
|
||||
|
||||
### Docs
|
||||
|
||||
- Updated Releases widget docs to describe providers instead of integrations.
|
||||
- Updated Timetable docs to remove Search.ch integration setup.
|
||||
- Removed obsolete integration docs pages for the deleted integration kinds.
|
||||
- Updated docs sitemap and integration docs mappings.
|
||||
|
||||
### Translations
|
||||
|
||||
- Added English labels for the new widget options:
|
||||
- `widget.timetable.option.baseUrl.label`
|
||||
- `widget.releases.option.repositories.providerUrl.label`
|
||||
|
||||
## How It Works Now
|
||||
|
||||
Release provider configuration lives on each Releases widget repository entry. The request handler resolves provider metadata and fetches releases directly from the provider-specific endpoint. GHCR uses anonymous OCI registry access for public packages.
|
||||
|
||||
Timetable passes its widget URL option directly to its API router and request handler. No global integration lookup is needed.
|
||||
|
||||
ArchiveTeam Warrior continues to use the standard integration system, but users must manually add the integration (it is not auto-created on install).
|
||||
|
||||
## Deliberate Non-Goals
|
||||
|
||||
- No provider secret/token support was added.
|
||||
- No new secret storage model was introduced.
|
||||
- No compatibility layer was kept in `packages/integrations`.
|
||||
|
||||
Widget options are client-visible, so storing provider credentials there would be wrong. If authenticated provider requests are needed later, they need a separate secret-storage design.
|
||||
|
||||
## Verification So Far
|
||||
|
||||
- Focused typechecks passed for affected packages.
|
||||
- Full `pnpm turbo typecheck` passed.
|
||||
- Full `pnpm lint` passed with existing warnings.
|
||||
- Focused release provider normalization tests passed.
|
||||
- Widget translation test passed.
|
||||
- Live GHCR smoke checks passed for `ghcr.io/muchobien/pocketbase:latest` and `ghcr.io/homarr-labs/homarr:latest`.
|
||||
- `pnpm test packages/integrations/test/pi-hole.spec.ts` passed earlier, but failed on rebased reruns because Pi-hole reported `domainsBeingBlocked` as `-2`/`0` instead of `> 1`.
|
||||
@@ -206,6 +206,14 @@ packages/integrations/src/unraid @manuel-rw
|
||||
apps/docs/docs/integrations/uptime-kuma @ajnart
|
||||
packages/integrations/src/uptime-kuma @ajnart
|
||||
|
||||
# What's Up Docker (WUD) - introduced in https://github.com/homarr-labs/homarr/pull/6523 by @tiimonn
|
||||
apps/docs/docs/integrations/whats-up-docker @tiimonn
|
||||
apps/docs/docs/widgets/whats-up-docker @tiimonn
|
||||
packages/api/src/router/widgets/wud.ts @tiimonn
|
||||
packages/integrations/src/wud @tiimonn
|
||||
packages/request-handler/src/wud.ts @tiimonn
|
||||
packages/widgets/src/wud @tiimonn
|
||||
|
||||
# Traefik - by @Nerdy-Technician
|
||||
apps/docs/docs/integrations/traefik @Nerdy-Technician
|
||||
apps/docs/docs/widgets/traefik @Nerdy-Technician
|
||||
|
||||
@@ -3,12 +3,18 @@
|
||||
FROM node:24.18.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
ARG TARGETPLATFORM
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache libc6-compat curl bash && apk update
|
||||
# Native dependencies normally use prebuilds, but the build must remain
|
||||
# reproducible when a prebuild download times out and compilation is required.
|
||||
RUN apk add --no-cache libc6-compat curl bash python3 make g++
|
||||
|
||||
RUN corepack enable pnpm
|
||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||
COPY .npmrc pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||
COPY patches ./patches
|
||||
COPY --parents ./apps/*/package.json ./packages/*/package.json ./tooling/*/package.json ./
|
||||
# @homarr/definitions generates documentation types during install.
|
||||
COPY --parents ./packages/definitions/src ./
|
||||
# Workaround for pnpm/pnpm#5268: pnpm fetch crashes when patchedDependencies
|
||||
# are configured with nodeLinker: hoisted. The applyPatchToDir function tries
|
||||
# to chdir into node_modules/<pkg> which doesn't exist during fetch (only the
|
||||
@@ -16,17 +22,19 @@ COPY patches ./patches
|
||||
# isolated linker, patches apply inside the virtual store (node_modules/.pnpm/...)
|
||||
# which IS created by pnpm fetch. The original hoisted linker is restored
|
||||
# before the install step so the final node_modules layout stays flat.
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
|
||||
pnpm config set store-dir /pnpm/store && \
|
||||
sed -i 's/nodeLinker: hoisted/nodeLinker: isolated/' pnpm-workspace.yaml && \
|
||||
pnpm fetch && \
|
||||
pnpm fetch --ignore-scripts && \
|
||||
sed -i 's/nodeLinker: isolated/nodeLinker: hoisted/' pnpm-workspace.yaml
|
||||
|
||||
# Install only from the fetched, committed lockfile so local and Docker builds
|
||||
# resolve the same dependency graph. Serial lifecycle builds avoid esbuild's
|
||||
# atomic binary replacement racing across the hoisted workspace (pnpm/pnpm#8200).
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
|
||||
npm_config_nodedir=/usr/local pnpm install --recursive --offline --frozen-lockfile --child-concurrency=1
|
||||
|
||||
COPY . .
|
||||
# Follow the pnpm fetch pattern from https://pnpm.io/cli/fetch
|
||||
# --frozen-lockfile is omitted as recommended by the pnpm fetch docs
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
||||
pnpm install --recursive
|
||||
|
||||
ARG SKIP_ENV_VALIDATION='true'
|
||||
ARG CI='true'
|
||||
@@ -37,7 +45,9 @@ RUN --mount=type=secret,id=TURBO_API,env=TURBO_API \
|
||||
--mount=type=secret,id=TURBO_TEAM,env=TURBO_TEAM \
|
||||
--mount=type=secret,id=TURBO_TOKEN,env=TURBO_TOKEN \
|
||||
--mount=type=secret,id=TURBO_REMOTE_CACHE_SIGNATURE_KEY,env=TURBO_REMOTE_CACHE_SIGNATURE_KEY \
|
||||
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" pnpm build
|
||||
--mount=type=cache,id=homarr-next-build-${TARGETPLATFORM},target=/app/apps/nextjs/.next/cache,sharing=locked \
|
||||
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" \
|
||||
pnpm turbo build --filter=@homarr/nextjs... --filter=@homarr/cli
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
@@ -85,5 +95,6 @@ ENV AUTH_PROVIDERS='credentials'
|
||||
ENV REDIS_IS_EXTERNAL='false'
|
||||
ENV NODE_ENV='production'
|
||||
|
||||
EXPOSE 7575
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
CMD ["sh", "run.sh"]
|
||||
@@ -13,6 +13,7 @@
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
/.source/
|
||||
|
||||
# production
|
||||
/build
|
||||
@@ -55,4 +56,4 @@ static/data/configs
|
||||
|
||||
# CI Stuff
|
||||
screenshots
|
||||
test-results
|
||||
test-results
|
||||
@@ -23,9 +23,6 @@
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"eslint/no-unused-expressions": "warn"
|
||||
},
|
||||
"options": {
|
||||
"reportUnusedDisableDirectives": "off"
|
||||
},
|
||||
"ignorePatterns": ["**/*.config.js", "**/*.config.mjs", "**/*.cjs", ".docusaurus/**", "build/**"],
|
||||
"settings": {
|
||||
"react": { "version": "19" }
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Homarr",
|
||||
"Lidarr",
|
||||
"Nextcloud",
|
||||
"Radarr",
|
||||
"Readarr",
|
||||
"Sonarr"
|
||||
]
|
||||
}
|
||||
"cSpell.words": ["Homarr", "Lidarr", "Nextcloud", "Radarr", "Readarr", "Sonarr"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<!-- BEGIN:nextjs-agent-rules -->
|
||||
|
||||
# This is NOT the Next.js you know
|
||||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
||||
|
||||
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
||||
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
@@ -0,0 +1 @@
|
||||
@AGENTS.md
|
||||
@@ -0,0 +1,37 @@
|
||||
# Homarr Documentation
|
||||
|
||||
## Register
|
||||
|
||||
product
|
||||
|
||||
## Users
|
||||
|
||||
People who run Homarr, administrators configuring self-hosted infrastructure, and contributors extending Homarr. They arrive with a concrete task such as installing Homarr, connecting a service, configuring a widget, managing users, or building a custom widget, and need an answer they can scan and apply quickly.
|
||||
|
||||
## Product Purpose
|
||||
|
||||
The documentation is the complete, authoritative operating guide for Homarr. It keeps setup steps, configuration contracts, screenshots, generated integration and widget metadata, API references, and Workshop tooling next to the application code. Success means a reader can find the right page quickly, understand the next action, copy reliable instructions, and verify the result without searching elsewhere.
|
||||
|
||||
## Brand Personality
|
||||
|
||||
Focused, dependable, and approachable. The writing is direct and practical. The interface should feel calm and familiar during long technical sessions while retaining Homarr's red identity.
|
||||
|
||||
## Anti-references
|
||||
|
||||
- Generic marketing layouts that replace Homarr's product identity with unrelated slogans or decorative conversion copy.
|
||||
- Decorative dashboard aesthetics that compete with reading, including glass effects, large radii, card grids, and gratuitous motion.
|
||||
- Generic generated-docs styling that obscures Homarr's identity or makes navigation feel detached from the main product.
|
||||
- Dense reference portals that expose structure without giving readers a clear path through a task.
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. Put the reader's task first: search, navigation, page outline, and the answer must remain easy to reach.
|
||||
2. Preserve one source of truth: typed Homarr metadata and existing custom components stay connected to the monorepo.
|
||||
3. Prefer familiar documentation patterns: the interface should disappear while a reader scans, copies, and follows instructions.
|
||||
4. Make complete content feel manageable through strong hierarchy, progressive disclosure, and precise cross-links.
|
||||
5. Treat machine readers as users through canonical Markdown, structured metadata, and standard LLM discovery endpoints.
|
||||
6. Keep the root landing page recognizably Homarr: preserve its self-hosted dashboard promise and real product evidence while documentation routes remain task-first.
|
||||
|
||||
## Accessibility & Inclusion
|
||||
|
||||
Meet WCAG 2.2 AA for color contrast, keyboard operation, focus visibility, landmarks, headings, and interactive controls. Respect reduced-motion and system color-scheme preferences. Diagrams, screenshots, icons, and emoji must have meaningful text alternatives when they carry information.
|
||||
@@ -1,105 +1,52 @@
|
||||
# Homarr Documentation (`@homarr/docs`)
|
||||
# Homarr documentation (`@homarr/docs`)
|
||||
|
||||
The official [homarr.dev](https://homarr.dev) documentation site, built with [Docusaurus 3](https://docusaurus.io/) and living inside the Homarr monorepo.
|
||||
The source for [homarr.dev](https://homarr.dev), built with Next.js and Fumadocs UI inside the Homarr monorepo.
|
||||
|
||||
## Why it's in the monorepo
|
||||
## Develop
|
||||
|
||||
Having documentation alongside the application code enables:
|
||||
|
||||
- **Two-way linking** — the docs app imports from `@homarr/definitions` for type-safe integration/widget metadata, and the main app references doc paths via generated sitemap types
|
||||
- **Atomic PRs** — code changes and their documentation updates ship in the same pull request
|
||||
- **Shared tooling** — same Node version, pnpm catalog, oxlint/oxfmt, CI pipeline
|
||||
- **Build-time validation** — Docusaurus strict mode catches broken links and references at build time
|
||||
|
||||
## Development
|
||||
|
||||
From the **monorepo root**:
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
# Start the docs dev server (port 3003)
|
||||
pnpm dev:docs
|
||||
|
||||
# Or from within this directory
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Build
|
||||
The site runs on `http://127.0.0.1:3003`. To use a local Workshop API:
|
||||
|
||||
```bash
|
||||
# Build docs only
|
||||
pnpm turbo build --filter=@homarr/docs
|
||||
|
||||
# Build everything (docs + main app + tasks + websocket)
|
||||
pnpm build
|
||||
WORKSHOP_API_URL=http://127.0.0.1:8090 pnpm dev:docs
|
||||
```
|
||||
|
||||
## Lint & Format
|
||||
|
||||
Uses oxlint and oxfmt (same as the rest of the monorepo):
|
||||
## Validate
|
||||
|
||||
```bash
|
||||
pnpm lint # oxlint
|
||||
pnpm format # oxfmt (check)
|
||||
pnpm format -- --write # oxfmt (fix)
|
||||
pnpm --filter @homarr/docs typecheck
|
||||
pnpm --filter @homarr/docs build
|
||||
pnpm --filter @homarr/docs validate:links
|
||||
```
|
||||
|
||||
## Typecheck
|
||||
The static export is written to `out/`. The build validates that every registered integration and widget has a docs
|
||||
page. The link check validates internal Markdown and MDX links against that export.
|
||||
|
||||
```bash
|
||||
pnpm typecheck
|
||||
```
|
||||
## Write content
|
||||
|
||||
## Content Structure
|
||||
Documentation lives in `docs/`; project posts live in `blog/`. Navigation is controlled by the nearest `meta.json`.
|
||||
Integration and widget folders pair typed `index.ts` metadata with `index.mdx` content.
|
||||
|
||||
```
|
||||
docs/
|
||||
├── getting-started/ # Installation guides, glossary, prerequisites
|
||||
├── management/ # Boards, apps, users, integrations, settings, tasks, API
|
||||
├── integrations/ # 55 integration guides (one folder each)
|
||||
├── widgets/ # 42 widget guides (one folder each)
|
||||
├── advanced/ # SSO, env vars, proxy, styling, CLI, development
|
||||
└── community/ # FAQ, donate, license, translations, get-in-touch
|
||||
```
|
||||
Use standard Markdown and MDX. Shared components live in `src/components/`. Mermaid fences, tabs, callouts, zoomable
|
||||
images, and syntax-highlighted code are supported by the Fumadocs pipeline.
|
||||
|
||||
Each integration/widget doc follows a consistent pattern:
|
||||
When a user-facing change affects setup or behavior, update the matching page in the same pull request. See the
|
||||
[documentation-sync skill](../../.agents/skills/documentation-sync/SKILL.md) for the code-to-content map.
|
||||
|
||||
- `index.ts` — typed metadata (`IntegrationDefinition` / `WidgetDefinition`)
|
||||
- `index.mdx` — content using shared React components (`IntegrationHeader`, `WidgetHeader`, etc.)
|
||||
## Published formats
|
||||
|
||||
## Importing from homarr packages
|
||||
- `/docs` — searchable documentation with typed navigation
|
||||
- `/api-reference` — interactive reference generated from the OpenAPI schema
|
||||
- `/llms.txt` — compact documentation and blog index for agents
|
||||
- `/llms-full.txt` — complete processed Markdown corpus
|
||||
- `/llms.mdx/docs/.../content.md` — raw Markdown for each docs page
|
||||
- `/llms.mdx/blog/.../content.md` — raw Markdown for each blog post
|
||||
- `/blog/rss.xml` — RSS feed for project posts
|
||||
|
||||
The docs app has `@homarr/definitions` as a workspace dependency. You can import integration kinds, widget kinds, and other definitions directly:
|
||||
|
||||
```typescript
|
||||
import { IntegrationKind } from '@homarr/definitions';
|
||||
```
|
||||
|
||||
This enables type-safe references to homarr domain concepts within documentation code.
|
||||
|
||||
## Search (Algolia DocSearch)
|
||||
|
||||
Search uses the `Docusaurus` Algolia index (crawler `cd77a285-2756-4557-bf21-ee703748df15`). A separate `markdown` index is built for LLM markdown indexing.
|
||||
|
||||
Crawler reference config: [`docsearch.config.js`](docsearch.config.js).
|
||||
|
||||
**Verify search health:**
|
||||
|
||||
```bash
|
||||
pnpm verify:search
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Layer | Technology |
|
||||
|---|---|
|
||||
| Site generator | Docusaurus 3 (`@docusaurus/preset-classic`) |
|
||||
| Styling | Tailwind CSS 3 + Docusaurus Infima |
|
||||
| Icons | `@tabler/icons-react` |
|
||||
| Diagrams | Mermaid (`@docusaurus/theme-mermaid`) |
|
||||
| Search | Algolia DocSearch |
|
||||
| Analytics | PostHog |
|
||||
| Charts | @nivo/line |
|
||||
|
||||
## Contributing
|
||||
|
||||
When making changes to the homarr codebase that affect user-facing behavior, **always update the corresponding documentation in this app**. See `.cursor/rules/documentation-sync.mdc` for the complete mapping of code changes to doc locations.
|
||||
Every docs page and blog post includes copy-Markdown and view-source actions. Search is generated at build time and
|
||||
runs locally in the browser; it does not depend on an external crawler.
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
slug: documentation-migration
|
||||
title: Migration of Documentation
|
||||
date: 2022-06-22
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
@@ -12,7 +13,7 @@ The documentation has been re-written partly and includes now many animations, g
|
||||
|
||||
We are still figuring things out and highly depend on your feedback.
|
||||
|
||||
<!-- truncate -->
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
## Why we migrated
|
||||
|
||||
@@ -32,4 +33,4 @@ After we've reviewed your changes, we'll merge your PR and this documentation wi
|
||||
|
||||
## TL;DR
|
||||
|
||||
The documentation on GitHub Wiki will soon disappear. It will be replaced with this Docusaurus-powered website. You'll find much more information here with higher detail and searchability.
|
||||
The documentation on GitHub Wiki will soon disappear. It will be replaced with this Docusaurus-powered website. You'll find much more information here with higher detail and searchability.
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
slug: translations
|
||||
title: Help us make Homarr multilingual
|
||||
date: 2022-08-28
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
@@ -13,8 +14,8 @@ We've been working hard to offer Homarr in many languages. The next update of Ho
|
||||
 <br/>
|
||||
Yes, we need **YOU**! We want to make Homarr accessible to as many people as possible. Thus, we need to translate all content into many different languages. We need your help for that! [You can help us now to translate Homarr into as many languages as possible](/docs/community/translations).
|
||||
|
||||
<!-- truncate -->
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
### About global translations / translations per user
|
||||
|
||||
We are aware that many people would like to configure translations globally or configure language per user. Currently, translations are stored in the Cookies, which will expire after some time (currently, it's set to 30 days). We will implement more granular language settings in future versions of Homarr, since it requires major changes to Homarr. Stay tuned! 😎
|
||||
We are aware that many people would like to configure translations globally or configure language per user. Currently, translations are stored in the Cookies, which will expire after some time (currently, it's set to 30 days). We will implement more granular language settings in future versions of Homarr, since it requires major changes to Homarr. Stay tuned! 😎
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: "We present: Homarr 0.11 - the most complex Homarr version yet!"
|
||||
date: 2023-01-11
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
@@ -20,7 +21,7 @@ It contains many breaking changes and huge improvements to your favourite dashbo
|
||||
- Huge design and usability improvements to older components to Homarr
|
||||
- Better handeling of exceptions and unexpected issues
|
||||
|
||||
<!-- truncate -->
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
## Complete implementation of highly customizable drag and drop library "Gridstack"
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: "We present: Homarr 0.12 - The fastest homarr version ever!"
|
||||
date: 2023-04-16
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
@@ -15,14 +16,12 @@ This applies to all widgets, but in the case of the IFrame widget, it enables yo
|
||||
- Integrate monitoring tools like Grafana, Splunk or Elasticsearch
|
||||
- Integrate your services (for example Sonarr, Radarr and Overseerr) directly into Homarr
|
||||
|
||||

|
||||
|
||||
## Edit mode password
|
||||
We've added a setting to disable editing completely. This will allow you to restrict edit access without password-protection.
|
||||
use the `EDIT_MODE_PASSWORD` ENV variable to set a password for edit mode.
|
||||
turn on `DISABLE_EDIT_MODE=TRUE` to disable edit mode on startup.
|
||||
|
||||
<!-- truncate -->
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
## 😎 Blazingly fast
|
||||
We've implemented a new caching system, that will cache all data in your browser, meaning that Homarr will load within milliseconds, as if it was *powered by rust and blazingly fast* !!!
|
||||
@@ -72,4 +71,4 @@ Now, you can use the following environment variables to configure this:
|
||||
- ``DOCKER_HOST``
|
||||
- ``DOCKER_PORT``
|
||||
|
||||
**Full Changelog**: https://github.com/ajnart/homarr/compare/v0.11.5...v0.12.0
|
||||
**Full Changelog**: https://github.com/ajnart/homarr/compare/v0.11.5...v0.12.0
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: "Authentication in Homarr - 0.14 is here!"
|
||||
date: 2023-11-10
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
@@ -23,7 +24,7 @@ provide you a viable solution today.
|
||||
- 📝 Overhauled notepad widget with a complex graphical editor that enables easy editing right from your browser.
|
||||
- :globe_with_meridians: Live Translation on Crowdin that enable you to directly translate using images to see what exactly you're translating
|
||||
|
||||
<!-- truncate -->
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
## Breaking changes
|
||||
|
||||
@@ -83,4 +84,4 @@ We are aware that this update has many breaking changes and may confuse some use
|
||||
For that reason, we updated our documentation and will enhance it further within the next few weeks.
|
||||
We've also added this small button at the bottom of the page were you can ask **your questions** to an AI assistant:
|
||||
|
||||

|
||||

|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: "Updated documentation with better organization & design"
|
||||
date: 2023-12-22
|
||||
authors:
|
||||
- manuel-rw
|
||||
tags: [homarr, documentation, design, docs]
|
||||
@@ -8,7 +9,7 @@ tags: [homarr, documentation, design, docs]
|
||||
We'd like to let you know that we've updated our documentation and reorganized the pages.
|
||||
This means, that some links may no longer be valid - we apologise if this causes any inconvenience for you.
|
||||
|
||||
<!-- truncate -->
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
A few highlights include:
|
||||
|
||||
@@ -18,4 +19,4 @@ A few highlights include:
|
||||
- Bug fixes for image zoom
|
||||
- Added documentation for management pages and [new CLI tool](/docs/advanced/command-line/password-recovery/)
|
||||
|
||||
Please [let us know](/docs/community/get-in-touch) is something is not working as expected.
|
||||
Please [let us know](/docs/community/get-in-touch) is something is not working as expected.
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: Homarr 1.0 - complete rewrite of all systems
|
||||
date: 2024-09-23
|
||||
authors:
|
||||
- manuel-rw
|
||||
---
|
||||
|
||||
# Homarr 1.0 - complete rewrite of all systems
|
||||
We proudly present to you - Homarr ``1.0``.
|
||||
We have started work on this new version back in 2023 and have worked tirelessly for months on this brand-new dashboard.
|
||||
|
||||
@@ -13,8 +14,6 @@ This will be a one time occurrence and version > ``1.0`` will have less breaking
|
||||
This time more important than every; **read the breaking changes** list and **create a backup** before updating.
|
||||
Some third party installation methods may take multiple days or weeks to update to the new version.
|
||||
|
||||
See [this guide on how to import your previous Homarr dashboard](/docs/getting-started/after-the-installation#importing-a-zip-from-version-before-100)
|
||||
|
||||
## Changes at a glance
|
||||
- **Asynchronous integrations system**: In versions prior to ``1.0``, we fetched data upon each request synchronously.
|
||||
This means that the user had to wait for the requests to finish so any data would appear.
|
||||
@@ -30,7 +29,7 @@ reliability and scalability compared to < ``1.0``.
|
||||
- **Improved performance**: We have upgraded the underlying framework of Homarr and take advantage
|
||||
of an in-memory message bus and websockets to deliver data near realtime to you.
|
||||
|
||||
<!-- truncate -->
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
## Breaking changes
|
||||
:::warning
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
---
|
||||
title: Open Beta 1.0
|
||||
date: 2024-12-17
|
||||
authors:
|
||||
- meierschlumpf
|
||||
---
|
||||
|
||||
# Open Beta 1.0
|
||||
|
||||
We are proud to announce the open beta of Homarr 1.0. This is a major milestone for us and we are excited to share it with you.
|
||||
The open beta allows us to polish the last rough edges and gather feedback from you.
|
||||
We are looking forward to your feedback and hope you enjoy the new features.
|
||||
Let us know throgh discord or a [github issues](https://github.com/homarr-labs/homarr/issues/new/choose) if you encounter any issues.
|
||||
|
||||
<!-- truncate -->
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
## How to use the beta with docker
|
||||
|
||||
@@ -36,4 +36,4 @@ services:
|
||||
|
||||
Then, run `docker compose up -d` in the same directory. This will start the Homarr container in the background.
|
||||
Please note that the mounts have changed and it's not backwards compatible with versions before 1.0.0-beta.1.
|
||||
You can find a list of all breaking changes [here](/blog/2024/09/23/version-1.0)
|
||||
You can find a list of all breaking changes [here](/blog/2024/09/23/version-1.0)
|
||||
@@ -1,21 +1,20 @@
|
||||
---
|
||||
title: Migrate from 0.15.10+ to 1.0.0
|
||||
date: 2025-01-19
|
||||
authors:
|
||||
- meierschlumpf
|
||||
---
|
||||
|
||||
# Migrate from 0.15.10+ to 1.0.0
|
||||
|
||||
In this guide, we will show you how to migrate from Homarr 0.15.10+ to 1.0.0.
|
||||
If you are still on a version before 0.15.10, please update to 0.15.10 where we introduced the migration export page.
|
||||
|
||||
<!-- truncate -->
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
## Export your data
|
||||
## Preserve your old installation
|
||||
|
||||
As 1.0 is a complete rewrite of Homarr and therefor not compatible with the old docker mounts, we need to export your data first.
|
||||
For this you can navigate to the `Migrate to 1.0` page which you can find under `Management` > `Tools` > `Migrate to 1.0`.
|
||||
Here you can select which things you want to export and download the zip file.
|
||||
It will also show you a secret key which you will need to import the secret data like user password hashes, salts and integration secrets.
|
||||
Homarr 1.0 is a complete rewrite and cannot reuse the old Docker mounts. Keep the 0.15 installation and its data intact while you build the replacement. The historical `Management` > `Tools` > `Migrate to 1.0` export can still be kept as a record, but current Homarr releases cannot import that ZIP or its secret key.
|
||||
|
||||
Plan to recreate your apps, integrations, and boards manually in the new installation. A SQLite database backup can be restored from the onboarding welcome screen only when it was created by a compatible current Homarr release; it is not a conversion path for a 0.15 database.
|
||||
|
||||

|
||||
|
||||
@@ -28,28 +27,15 @@ Please note that if you are using docker you need to specify a diffrent external
|
||||
So in your compose file the ports would be `7576:7575` instead of `7575:7575`.
|
||||
|
||||
## Import your data
|
||||
Once the installation is completed you can access the new installation. During the onboarding you'll need to select `Import from Homarr before 1.0`.
|
||||
|
||||

|
||||
:::warning Legacy guide
|
||||
|
||||
After that you can upload the zip file you downloaded in the first step.
|
||||
The pre-1.0 ZIP importer is no longer included in current Homarr releases. Keep the old installation available until you have manually verified the replacement. Compatible SQLite backups from current releases can still be restored from the onboarding welcome screen.
|
||||
|
||||

|
||||
|
||||
After that you'll get a list with all your boards that can be imported. You can configure how it should be imported and can select which screensizes it should import.
|
||||
|
||||

|
||||
|
||||
Once you have selected everything you can click on `Confirm import and continue` which will open the encryption key input dialog where you can input the secret key you got during the export.
|
||||
|
||||

|
||||
|
||||
After that you can continue the onboarding and you should see all your boards and data in the new installation.
|
||||
|
||||
See [After the installation](https://homarr.dev/docs/getting-started/after-the-installation) for more information.
|
||||
:::
|
||||
|
||||
## Remove the old installation
|
||||
|
||||
After you are happy with the new installation and everything is working as expected you can remove the old installation.
|
||||
Please make sure to backup the data before you remove the old installation.
|
||||
Also once the port 7575 is free again you can change the port mapping of the new installation to `7575:7575` if you want to.
|
||||
Also once the port 7575 is free again you can change the port mapping of the new installation to `7575:7575` if you want to.
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: How to use Argus with Homarr
|
||||
date: 2025-08-02
|
||||
authors:
|
||||
- meierschlumpf
|
||||
---
|
||||
|
||||
# How to use Argus with Homarr
|
||||
|
||||
In `v1.32.0` we added the endpoint `/api/info` which returns the current version of Homarr and can therefore be used in Argus to watch for updates.
|
||||
This guide will show you how to set up a user with apiKey to access the endpoint without providing admin access through it.
|
||||
|
||||
<!-- truncate -->
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||

|
||||
|
||||
@@ -64,4 +64,4 @@ Now you can configure Argus to use the user and apiKey we created.
|
||||
|
||||

|
||||
|
||||
Replace the `<secret>` with the apiKey you created and the deployed version url with the url of your Homarr instance.
|
||||
Replace the `<secret>` with the apiKey you created and the deployed version url with the url of your Homarr instance.
|
||||
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 418 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 618 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,164 @@
|
||||
---
|
||||
title: Homarr 2.0.0 - Workshop, Custom Widgets and a rebuilt board
|
||||
date: 2026-09-03
|
||||
authors:
|
||||
- ajnart
|
||||
tags: [homarr, update, version, assistant, custom widgets, dashboards, docker]
|
||||
draft: false
|
||||
---
|
||||
|
||||
# Homarr 2.0.0 - Workshop, Custom Widgets and a rebuilt board
|
||||
|
||||
First, thank you.
|
||||
|
||||
Many of you used the v2 demo when it was still rough, found bugs and came back to test the next build. Others translated Homarr, answered questions, contributed code or simply kept using it. We would not be shipping this without you.
|
||||
|
||||
We are also planning a small batch of **free, limited-edition Homarr swag** for Workshop contributors and a few randomly picked community members. It is not for sale and it is not for profit. No Homarr merch empire, sorry.
|
||||
|
||||
Homarr 2.0 is an upgrade, not a brand-new app wearing the same logo. Here is what actually changed.
|
||||
|
||||

|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
:::warning Back up before upgrading
|
||||
|
||||
Create a [Homarr backup](/docs/management/backup) first. Do not test a prerelease image on your only production volume. Backups are boring. Restores are worse.
|
||||
|
||||
:::
|
||||
|
||||
## Custom Widgets v2 and Workshop
|
||||
|
||||
### Build almost any widget
|
||||
|
||||
**This is the big one.** We think it is the most important Homarr update we have made.
|
||||
|
||||
Custom Widgets v2 lets you build widgets from JSX, API requests, actions and typed settings. Write one yourself or describe it to Assistant, then test it in the workbench. We think this is enough to make almost any dashboard widget—even that oddly specific component only three people need. Those three people no longer have to wait for us.
|
||||
|
||||
This was partly inspired by [Google's upcoming Create My Widget feature](https://blog.google/products-and-platforms/platforms/android/gemini-intelligence/): describe a widget in normal language and let AI make it. Coding knowledge is optional. Knowing what the API does still helps; AI has not fixed bad documentation. Yet.
|
||||
|
||||

|
||||
|
||||
A `homarr-custom-widget-v2` definition can use several fixed-origin API sources. Credentials are encrypted separately and stay out of exports and Workshop submissions. Actions support `GET`, `POST`, `PUT`, `PATCH` and `DELETE`; requests have host checks, timeouts, size limits, permissions and rate limits. Custom Widgets remain **Beta** in 2.0.
|
||||
|
||||
### Share widgets and Custom CSS
|
||||
|
||||
**Workshop is the other half.** Publish and install Custom Widgets, then update, vote on, comment on and report submissions. Workshop is not only for widgets: you can share your **Custom CSS** there too. Deployment URLs and credentials stay on your Homarr instance.
|
||||
|
||||

|
||||
|
||||
:::warning Workshop is moderated
|
||||
|
||||
AI makes widgets easy to produce. It does not make the first thing it coughs up worth publishing. **Do not dump untested slop into Workshop.** Test it, add a useful screenshot and explain its setup. Workshop is moderated.
|
||||
|
||||
:::
|
||||
|
||||
### The ultimate Homarr update
|
||||
|
||||
We have called this the **"ultimate Homarr update"**. Not because work stops, but because every new service or widget idea no longer needs a full Homarr release; the community can build and share it instead.
|
||||
|
||||
## Board editing, rebuilt
|
||||
|
||||
### Drag and drop
|
||||
|
||||
We threw out the old drag-and-drop system and rebuilt it on [`dnd-kit`](https://dndkit.com/). Moves and eight-direction resizes are transactional: Homarr previews the result, checks collisions and only saves a valid layout. Bad moves roll back instead of wrecking the board. In short: less fighting with your own dashboard.
|
||||
|
||||

|
||||
|
||||
### Goodbye Groups and sections, bonjour Containers
|
||||
|
||||
Groups and dynamic sections did nearly the same job, so **Containers replace both**. They hold apps, widgets and other Containers. Hold <kbd>Ctrl</kbd> or <kbd>Command</kbd> to move several items at once.
|
||||
|
||||
### One board, several layouts
|
||||
|
||||
Boards also have Base and Mobile layouts, plus optional breakpoints. **Reset from Base** creates a Mobile starting point without duplicating the board.
|
||||
|
||||
## Homarr Assistant
|
||||
|
||||
### Work with live Homarr data
|
||||
|
||||
Assistant reads live Homarr data and has tools for boards, apps, integrations, widgets, Docker and supported services. It can answer questions, make approved changes and build Custom Widgets. It follows your permissions: reads can run automatically, while changes require approval by default.
|
||||
|
||||

|
||||
|
||||
Open it with <kbd>Shift</kbd> + <kbd>A</kbd>. <kbd>Cmd</kbd>/<kbd>Ctrl</kbd> + <kbd>/</kbd> searches it.
|
||||
|
||||
### The Homarr provider
|
||||
|
||||
:::note
|
||||
|
||||
Assistant ships with a **"Homarr provider"**. It is free, will never offer paid plans, and is paid for by [Ajnart](https://github.com/ajnart) using the ad revenue from this website.
|
||||
|
||||
It only uses zero-data-retention (ZDR) providers. [The provider is open source](https://github.com/homarr-labs/homarr/blob/release/v2/apps/workshop/homarr_provider.go), and neither prompts nor responses are logged. We are not selling your data. Pinky promise.
|
||||
|
||||
:::
|
||||
|
||||
### External assistants through MCP
|
||||
|
||||
External assistants can use the same permission-checked tools through `/api/mcp`, with an API key or OAuth 2.1 with PKCE.
|
||||
|
||||
## The rest (there is a lot)
|
||||
|
||||
### Brand-new onboarding
|
||||
|
||||
A six-stage setup studio handles the first admin, core settings, Docker discovery, apps, integrations and the first board.
|
||||
|
||||

|
||||
|
||||
### Board switcher
|
||||
|
||||
Boards now live in a searchable gallery with keyboard navigation. Open it from the header or press <kbd>Shift</kbd> + <kbd>C</kbd>.
|
||||
|
||||

|
||||
|
||||
### Command menu and keybinds
|
||||
|
||||
<kbd>Cmd</kbd>/<kbd>Ctrl</kbd> + <kbd>K</kbd> finds resources, pages, settings, users, groups and commands. More
|
||||
keybinds cover Assistant, editing, selection and advanced widget views.
|
||||
|
||||
### Customizable header
|
||||
|
||||
Pick which buttons appear, put them on the left, center or right, and change their order.
|
||||
|
||||

|
||||
|
||||
### Instance branding
|
||||
|
||||
Change the name, logo, favicon, colors, corner styles and login page. Global CSS applies instance-wide; board CSS can override it. Only give that power to admins you trust. CSS remains CSS.
|
||||
|
||||

|
||||
|
||||
### Widgets
|
||||
|
||||
Add content without leaving the board. Settings have live previews, and supported widgets have advanced views. Weather and Clock were redesigned; Air Quality, Countdown and Timer are new.
|
||||
|
||||
### Docker and Podman
|
||||
|
||||
`DOCKER_ENDPOINTS` supports several hosts. Assisted setup and container labels can create apps, integrations, Containers, widgets and health checks.
|
||||
|
||||

|
||||
|
||||
Homepage-compatible labels work too.
|
||||
|
||||
### Permissions
|
||||
|
||||
Groups use a new Viewer, Editor and Admin matrix. The API, Assistant and MCP follow the same rules. LDAP can create users on first sign-in; OIDC can link verified accounts and synchronize groups.
|
||||
|
||||
### Performance
|
||||
|
||||
Board startup work runs in parallel, heavy screens load when opened, and identical integration requests share work instead of doing it twice.
|
||||
|
||||
## Before upgrading
|
||||
|
||||
- Keep a backup and use the final release image, not a prerelease tag.
|
||||
- Legacy Custom JSX definitions enter a migration state. Homarr keeps encrypted credentials and an archived copy until the v2 definition validates.
|
||||
- Mobile layouts place rail content in the main flow instead of displaying rails.
|
||||
- Custom Widgets remain Beta.
|
||||
- Assistant and MCP do not bypass permissions.
|
||||
|
||||
## Links
|
||||
|
||||
- Try the [Homarr 2.0 preview](https://app-v2.preview.homarr.dev).
|
||||
- Follow the [v2 release pull request](https://github.com/homarr-labs/homarr/pull/6545) and [public beta thread](https://github.com/homarr-labs/homarr/issues/6600).
|
||||
- Compare [v1.76.2 with the release/v2 branch](https://github.com/homarr-labs/homarr/compare/v1.76.2...release/v2).
|
||||
- Read the guides for [Boards](/docs/management/boards), [Custom Widgets](/docs/management/custom-widgets), [Assistant](/docs/management/assistant), [MCP](/docs/management/mcp) and [Docker labels](/docs/integrations/docker-labels).
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "base-nova",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/app/global.css",
|
||||
"baseColor": "mist",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "tabler",
|
||||
"rtl": false,
|
||||
"menuColor": "default",
|
||||
"menuAccent": "subtle"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"label": "Advanced",
|
||||
"collapsed": true,
|
||||
"position": 4,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"title": "Advanced Concepts or features"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"collapsed": true,
|
||||
"label": "Command line interface"
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
# Developer Docker CLI
|
||||
|
||||
The host-side developer CLI builds and runs local Homarr images and pull-request previews with non-blocking background operations. Run it directly from a checkout with `pnpm dev:cli -- ...`; no shell-specific setup script is required. It is separate from the recovery CLI included in production images.
|
||||
|
||||
To install an optional `homarr` binary, run `pnpm dev:cli:install` and make your Go bin directory available on `PATH`. GitHub CLI authentication (`gh auth login`) is only needed for pull-request features.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Docker or OrbStack
|
||||
- Go 1.25+ (when building the CLI from source)
|
||||
- GitHub CLI authenticated with `gh auth login` (for pull-request and CI features)
|
||||
- A [Nerd Font](https://www.nerdfonts.com/) enabled in your terminal (recommended for icons, status indicators, and TUI glyphs)
|
||||
|
||||
## Interactive Development Browser
|
||||
|
||||
```bash
|
||||
pnpm dev:cli --
|
||||
# or with global binary:
|
||||
homarr
|
||||
```
|
||||
|
||||
The main browser combines local `homarr:*` images with open pull requests and their GHCR preview status. Select an image to start or stop it, use `R` to build a remote pull request locally in the background or rebuild a local image, or use `p` to force a remote preview pull. Press `d` (or `1`/`2`) to toggle between the image browser and the instance dashboard.
|
||||
|
||||
A locally built PR image is folded into its remote PR row only when its recorded revision matches the current GitHub head SHA (marked `◆ local`). When the matching GHCR image is also available, pressing `Enter` opens a local/remote selector with local preselected. Outdated local builds remain separate rows.
|
||||
|
||||
### Multi-Source Sidebar
|
||||
|
||||
Press `Tab` to cycle between sidebar tabs:
|
||||
|
||||
- **`[logs]`**: Live container output streams (press `f` to follow/pause, `L` to pin to the selected container, `Page Up`/`Page Down` to scroll).
|
||||
- **`[build]`**: Real-time BuildKit step streaming for active local builds.
|
||||
- **`[ci]`**: GitHub Actions CI check statuses, run duration, and workflow links for pull requests.
|
||||
|
||||
### Background Task Overlay
|
||||
|
||||
Pulls and builds run asynchronously in the background. Press `t` to open the tasks overlay, showing:
|
||||
|
||||
- Layer-by-layer download progress and transfer speeds (via Docker Engine API).
|
||||
- Active build stages and logs.
|
||||
- Task cancellation and status.
|
||||
|
||||
### Browser Shortcuts
|
||||
|
||||
| Key | Action |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| `/` | Filter rows by PR number, title, author, branch, CI, image, state, or port |
|
||||
| `↑` / `↓` or `j` / `k` | Navigate rows |
|
||||
| `Enter` or `Space` | Start the available image; choose local or remote when both exist; stop if running |
|
||||
| `R` | Build locally in background (or rebuild local image) |
|
||||
| `p` | Pull remote PR image in background and start container |
|
||||
| `m` | Manage mode (`b` rebuild, `i` delete image, `d` delete data, `c` remove container, `p` prune) |
|
||||
| `t` | Toggle background tasks overlay |
|
||||
| `Tab` | Cycle sidebar tabs (`[logs]`, `[build]`, `[ci]`) |
|
||||
| `l` / `L` | Toggle sidebar visibility / Pin logs to selected instance |
|
||||
| `f` | Follow or pause logs |
|
||||
| `M` | Toggle demo mode for next launch |
|
||||
| `b` | Toggle bot pull requests |
|
||||
| `o` / `a` | Open PR on GitHub / Open running instance in browser |
|
||||
| `c` | Copy instance URL to clipboard |
|
||||
| `r` | Refresh data |
|
||||
| `1` / `2` / `d` | Switch screen (development browser / instance dashboard) |
|
||||
| `?` / `q` | Help overlay / Quit (cancels active background tasks) |
|
||||
|
||||
## Instance Dashboard
|
||||
|
||||
```bash
|
||||
homarr dash
|
||||
# or
|
||||
pnpm dev:cli -- dash
|
||||
```
|
||||
|
||||
The instance dashboard displays all running and stopped Homarr containers, published ports, health states, and resource volumes.
|
||||
|
||||
| Key | Action |
|
||||
| ------------------ | ---------------------------------------- |
|
||||
| `Enter` or `Space` | Start or stop selected instance |
|
||||
| `s` / `S` | Stop / Restart instance |
|
||||
| `x` | Remove container (preserves data volume) |
|
||||
| `m` | Manage mode for selected instance |
|
||||
| `R` | Rebuild from recorded build provenance |
|
||||
|
||||
## CLI Commands
|
||||
|
||||
The commands below use the installed `homarr` binary. Without global installation, prefix each command with `pnpm dev:cli --` (for example, `pnpm dev:cli -- list`).
|
||||
|
||||
### Launch Instances
|
||||
|
||||
```bash
|
||||
homarr run dev
|
||||
homarr run --detach dev
|
||||
homarr run --pr 6441
|
||||
homarr run --pr 6441 --demo
|
||||
homarr --pr 6441 --env WORKSHOP_WEB_URL=https://app-v2.preview.homarr.dev/
|
||||
```
|
||||
|
||||
- `--pr <number>`: Pull and run a PR image from GHCR (shorthand: `homarr --pr <number>`).
|
||||
- `--demo` (`-m`): Start with mock integrations and sample data.
|
||||
- `--detach`: Start container in background.
|
||||
- `--env` (`-e`): Pass additional environment variables (repeatable: `--env KEY=VALUE`).
|
||||
|
||||
### Build & Rebuild
|
||||
|
||||
```bash
|
||||
# Build current checkout as homarr:<name>
|
||||
homarr build feature
|
||||
|
||||
# Build a pull request locally in a temporary checkout
|
||||
homarr build --pr 6441
|
||||
|
||||
# Rebuild an existing image from recorded provenance
|
||||
homarr rebuild feature
|
||||
```
|
||||
|
||||
Images built with the CLI record their source checkout, revision SHA, and optional PR number. `homarr rebuild` reads this provenance to reconstruct the build without manual configuration.
|
||||
|
||||
### GitHub CI Checks
|
||||
|
||||
```bash
|
||||
# View CI checks for a specific PR
|
||||
homarr ci 6441
|
||||
|
||||
# View CI checks for the current branch
|
||||
homarr ci
|
||||
|
||||
# Watch CI checks until completion
|
||||
homarr ci 6441 --watch
|
||||
```
|
||||
|
||||
### Interactive Shell
|
||||
|
||||
```bash
|
||||
# Open interactive shell in running container
|
||||
homarr shell homarr-dev
|
||||
|
||||
# Run specific command in container
|
||||
homarr shell homarr-dev pnpm db:migration:sqlite:run
|
||||
```
|
||||
|
||||
### Image & Volume Management
|
||||
|
||||
```bash
|
||||
# List local images with build provenance (source, revision, PR)
|
||||
homarr images
|
||||
|
||||
# Delete local image
|
||||
homarr images rm feature
|
||||
|
||||
# List data volumes with owner PR/tag and in-use status
|
||||
homarr data
|
||||
|
||||
# Delete data volume (safely stops and removes owning container first)
|
||||
homarr data rm homarr_feature_data
|
||||
|
||||
# Remove stopped containers (prompts without --force)
|
||||
homarr prune --force
|
||||
```
|
||||
|
||||
### Instance Operations & Diagnostics
|
||||
|
||||
```bash
|
||||
# List instances in a script-friendly table
|
||||
homarr list
|
||||
|
||||
# Follow container logs
|
||||
homarr logs homarr-dev
|
||||
|
||||
# Stop, restart, remove, or open container in browser
|
||||
homarr stop homarr-dev
|
||||
homarr restart homarr-dev
|
||||
homarr remove homarr-dev
|
||||
homarr open homarr-dev
|
||||
|
||||
# Verify Docker daemon, Engine API, GHCR access, and gh CLI authentication
|
||||
homarr doctor
|
||||
```
|
||||
@@ -14,4 +14,4 @@ homarr fix-usernames
|
||||
|
||||
This will convert all credential usernames to lowercase and update the database accordingly.
|
||||
|
||||

|
||||

|
||||
|
After Width: | Height: | Size: 768 KiB |