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 |
No files matched your search
@@ -17,11 +17,11 @@ Orient from the current checkout before editing. Treat package manifests, export
|
||||
|
||||
## Current toolchain
|
||||
|
||||
- Use Node `24.18.0` for application runtimes and Bun `1.4.2` for package management, pinned in `mise.toml` and `package.json`.
|
||||
- Use Bun workspaces with Turborepo and a shared hoisted install. Workspace packages use the `@homarr/` scope and catalog-managed dependencies.
|
||||
- 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.
|
||||
- `bun run dev` starts only `@homarr/nextjs`. Start a standalone runtime explicitly when the task needs it.
|
||||
- `pnpm dev` starts only `@homarr/nextjs`. Start a standalone runtime explicitly when the task needs it.
|
||||
|
||||
## High-value seams
|
||||
|
||||
@@ -29,7 +29,7 @@ Orient from the current checkout before editing. Treat package manifests, export
|
||||
- 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,postgresql}.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`
|
||||
@@ -37,4 +37,4 @@ Orient from the current checkout before editing. Treat package manifests, export
|
||||
- Cron jobs: `packages/cron-jobs/src/jobs/`
|
||||
- App routes: `apps/nextjs/src/app/[locale]/`
|
||||
|
||||
Invoke `documentation-sync` only when users need new or corrected information to set up, use, or troubleshoot Homarr; follow the Documentation Sync criteria in `AGENTS.md`. When exposing tRPC through MCP, invoke `mcp-integration`.
|
||||
When a code change affects users, invoke `documentation-sync`. When exposing tRPC through MCP, invoke `mcp-integration`.
|
||||
@@ -13,14 +13,14 @@
|
||||
| Path | Role |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `apps/nextjs` | Main Next.js application and HTTP APIs on port 3000 |
|
||||
| `apps/docs` | Next.js and Fumadocs documentation site on port 3003 |
|
||||
| `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 |
|
||||
|
||||
`bun run dev` runs only `@homarr/nextjs`. Use `bun run --filter @homarr/tasks dev`, `bun run --filter @homarr/websocket dev`, or `bun run dev:docs` when a task needs those runtimes.
|
||||
`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 Bun process. Nginx listens on port 7575, proxies `/websockets` to port 3001, and sends other traffic to port 3000. Redis can run internally or externally.
|
||||
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
|
||||
|
||||
@@ -34,7 +34,7 @@ In production, `apps/nextjs/src/instrumentation-node.ts` starts the tasks and We
|
||||
|
||||
### Data, identity, and transport
|
||||
|
||||
- `db`: Drizzle schemas, queries, migrations, and SQLite and PostgreSQL drivers.
|
||||
- `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.
|
||||
@@ -57,7 +57,7 @@ In production, `apps/nextjs/src/instrumentation-node.ts` starts the tasks and We
|
||||
- `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`: Bun operations CLI. `tools/homarr-dev` is the separate Go developer CLI.
|
||||
- `cli`: Node operations CLI. `tools/homarr-dev` is the separate Go developer CLI.
|
||||
|
||||
## Dependency direction
|
||||
|
||||
@@ -78,7 +78,7 @@ Prefer these directions; verify the exact dependency in package manifests before
|
||||
|
||||
### Databases
|
||||
|
||||
Drizzle schemas live in `packages/db/schema/`. `DB_DRIVER` selects `better-sqlite3` or `node-postgres`; MySQL and MariaDB are no longer runtime drivers in v2. Existing MySQL installations must use the documented converter before upgrading. Migrations live under `packages/db/migrations/sqlite/` and `packages/db/migrations/postgresql/`.
|
||||
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
|
||||
|
||||
@@ -103,8 +103,8 @@ User routes live below `apps/nextjs/src/app/[locale]/`; the locale segment does
|
||||
## Commands and conventions
|
||||
|
||||
- Read current scripts from the root or package `package.json`; avoid caching commands that do not exist.
|
||||
- Use `bun run dev` for Next.js, `bun run dev:docs` for docs, and `bun run dev:cli -- <args>` for the Go developer CLI.
|
||||
- Use `bun run docker:dev:up` for the Redis development dependency.
|
||||
- Follow the Testing section in `AGENTS.md` for test creation and validation scope.
|
||||
- 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.
|
||||
@@ -32,23 +32,24 @@ Import only from an exported package entrypoint. Do not deep-import `@homarr/com
|
||||
|
||||
## Root exports
|
||||
|
||||
| Area | Public exports | Purpose |
|
||||
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| Objects | `objectKeys`, `objectEntries`, `isRecord`, `hashObjectBase64` | Typed key/entry iteration, record guards, 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`, `formatBytes`, `formatBytesPair`, `formatByteRate`, `formatBitRate`, `defaultByteUnitSystem`, `metricToImperial` | Compact numbers, byte displays, byte/bit rates, and unit conversion |
|
||||
| Byte types | `ByteUnitSystem`, `FormatBytesOptions`, `FormatBitRateOptions` | Options for byte and bit-rate 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 |
|
||||
| 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.
|
||||
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
---
|
||||
name: documentation-sync
|
||||
description: Write Homarr docs only when users need information to set up, use, or troubleshoot the app. Skip routine, obvious, or self-explanatory changes.
|
||||
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
|
||||
|
||||
Apply the audience and scope rules in `AGENTS.md` first. Documentation exists only for information users need to set up, use, or troubleshoot Homarr. A code, UI, or API change alone does not warrant documentation; if a user does not need to know it, do not add it.
|
||||
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.
|
||||
|
||||
For hidden capabilities such as the advanced widget feature, explain discovery, activation, and non-obvious behavior. For visible standard controls such as Delete, omit narration of what the label already conveys. Use the shortest explanation that preserves necessary technical detail; remove redundant prose in the passage being edited.
|
||||
|
||||
When an update is warranted, inspect adjacent pages for structure and types, not as a verbosity target. The mappings below locate needed information; they do not require a page for every feature or code change.
|
||||
|
||||
## Locate a needed update
|
||||
|
||||
These are destinations for warranted updates, not triggers to add documentation:
|
||||
## Map the change
|
||||
|
||||
| Code change | Documentation target |
|
||||
| ----------------------------------- | -------------------------------------------------------------------- |
|
||||
@@ -46,12 +40,12 @@ 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.
|
||||
- Explain only non-obvious option semantics, integration prerequisites, permission constraints, or behavior that the interface does not make clear.
|
||||
- Document options, required integrations, permissions, empty states, and changed behavior that affect setup or use.
|
||||
|
||||
## Completion criteria
|
||||
|
||||
1. Every passage adds information a user needs and cannot reasonably infer from the interface. Remove obvious, repetitive, or unnecessary prose.
|
||||
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 `bun run 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 `bun run --filter @homarr/docs validate:links` after the static build.
|
||||
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.
|
||||
@@ -1,4 +1,4 @@
|
||||
interface:
|
||||
display_name: "Homarr Documentation Sync"
|
||||
short_description: "Document only what users need to know"
|
||||
default_prompt: "Use $documentation-sync only when users need new or corrected information to set up, use, or troubleshoot Homarr."
|
||||
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."
|
||||
@@ -5,54 +5,39 @@ description: Author, validate, preview, test, install, or configure API-backed H
|
||||
|
||||
# Homarr Custom Widget
|
||||
|
||||
Author requested widgets with release context; validate, test, persist, and return artifacts.
|
||||
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.
|
||||
|
||||
- Read primary API docs when missing/changed. Samples and successful previews are binding; load only needed schema, runtime,
|
||||
security, or component context.
|
||||
- Batch unknown component searches/details. `contextAlreadyLoaded` reuses context; `phaseComplete` advances. Stop only for
|
||||
genuine provider/model, lifecycle-service, or workbench-closure failure.
|
||||
- Community widgets use `customWidget_workshopSearch`, `customWidget_workshopGet`, and
|
||||
`customWidget_workshopInstall`; configure and persist.
|
||||
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.
|
||||
|
||||
Return one fenced `json` block with the complete definition; keep evidence prose outside it. The definition has keyed
|
||||
`sources`, `requests`, `template`, and optional `options`; actions are requests with `kind: "action"`.
|
||||
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.
|
||||
|
||||
- `sources.default` is required. HTTP has `baseUrl`, `networkScope`, and credential-free `auth`; localhost/loopback requires
|
||||
`networkScope: "loopback"`; never widen an explicit scope. Saved sources use `type: "integration"`/`integrationKind`;
|
||||
Homarr holds credentials.
|
||||
- Saved integrations: discover kinds/full-access entries with `integration_getKinds`/`integration_all`, bind `integrationId`
|
||||
before preview, omit URL/auth, and keep non-GET requests as actions.
|
||||
- Paths are slash-prefixed: strings use `{option:name}`/`{param:name}`; query/body uses `{"$option":"name"}`/`{"$param":"name"}`.
|
||||
Loads use `trigger: "load"`; manual helpers use `trigger: "manual"`.
|
||||
- Actions stay manual; preserve `confirmation`, `permission`, and `invalidates`; DELETE requires full permission/confirmation.
|
||||
`$param` is manual-only; `$option` may drive loads.
|
||||
- Load data.requestId/status.requestId with RefreshButton; status.requestId?.ok === false is error. Manual SubFetch never
|
||||
populates data/status; its child receives (result, metadata) and renders its fields.
|
||||
- Options have `label`, `control`, `default`; installation config is `options.name`, never `inputs`. Request-bound TextInput,
|
||||
Select, NumberInput, Pagination use literal `bind` + default and manual `SubFetch params` map `inputs.<name>` to `$param`.
|
||||
Dependent pagination uses `defaultValue={1}`/`resetKey={inputs.query}`. Remove controls without an option/request/helper; guard arrays/nested with `??`;
|
||||
preserve documented timezone values; use UTC only when the contract says UTC.
|
||||
- Templates are one expression: no imports, hooks, refs, raw HTML/events, browser requests, eval, recursion, IIFEs,
|
||||
statement blocks, or arbitrary functions. Use registered component names; `Icon` may alias `TablerIcon`. Keep hierarchy,
|
||||
theme tokens, useful states, and narrow/wide layouts purposeful.
|
||||
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`.
|
||||
|
||||
## Bounded lifecycle
|
||||
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.
|
||||
|
||||
1. Build a credential-free definition from request, verified context, and sample. Preserve a migration's API path, method,
|
||||
body, options, and behavior; omit unknown requests rather than guessing.
|
||||
2. Send the coherent complete definition directly to `customWidget_previewCreate`; it validates both manifest and JSX.
|
||||
Use `customWidget_validateTemplate` only for isolated JSX diagnostics, never as a preview prerequisite. Use
|
||||
`customWidget_previewReviseTemplate` for JSX-only corrections after a preview exists. In the Assistant wrapper, multiline
|
||||
JSX uses `templateLines` and preview creation receives the complete definition.
|
||||
3. Test every returned query/simulated action once, batching independent queries. On a concrete schema/preview error, fix only
|
||||
that field and retry `customWidget_previewCreate` with the corrected definition; use `customWidget_previewReviseTemplate`
|
||||
only for JSX errors after a preview exists. Stop only for genuine provider/model, lifecycle-service, or
|
||||
workbench-closure failure.
|
||||
4. If `previewCreate` used `definitionId`, persist with `customWidget_updateFromPreview`; otherwise use
|
||||
`customWidget_createFromPreview`. Follow create `nextAction` once. Configure credentials in Homarr; never repeat plaintext
|
||||
secrets.
|
||||
`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.
|
||||
|
||||
## Delivery
|
||||
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.
|
||||
|
||||
Report actual lifecycle results. If unavailable, add one post-artifact `Unverified:` line naming missing validation, preview,
|
||||
renderer, or persistence. Never claim rendering/persistence from schema checks.
|
||||
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.
|
||||
@@ -1,13 +1,13 @@
|
||||
# Runtime
|
||||
|
||||
Load-query templates read `data.requestId` and `status.requestId`, plus `options.name` and temporary `inputs.name`. Manual `SubFetch` results stay local to that instance and never populate `data`/`status`; render them in its child callback. Status is `{ loading, ok, status, statusText, error }`. Render load queries directly from `data` and `status` with `RefreshButton`; never wrap them in `SubFetch`.
|
||||
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` is temporary; manual request values go in `params` and map to `$param`:
|
||||
`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" />
|
||||
<NumberInput bind="page" label="Page" defaultValue={1} resetKey={inputs.search} min={1} />
|
||||
<SubFetch requestId="search" trigger="manual" params={{ query: inputs.search ?? "", page: inputs.page ?? 1 }}>
|
||||
<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>
|
||||
```
|
||||
@@ -18,11 +18,11 @@ Manual queries require `trigger: "manual"` on request and `SubFetch`; otherwise
|
||||
|
||||
Inside a successful manual result, `<RefreshButton requestId="search" label="Run again" />` reruns the same parameters.
|
||||
|
||||
When a manual SubFetch ID, params, or definition changes, Homarr hides the prior result and returns to the trigger; new params fetch only after triggering again.
|
||||
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.
|
||||
|
||||
Exact paths: load request ID `q` with raw preview body `B` -> `data.q === B`. `q=events` body`{"events":[...]}` -> `data.events.events`; do not flatten repeated keys. Manual `SubFetch` receives `B` as result -> `result.events`. Before persistence inspect core paths against preview; revise JSX and retest if mismatched
|
||||
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", documentedTimezone)` and label the documented timezone; if no timezone is documented, preserve the source value or omit any timezone label; use UTC only when the response contract says UTC. Also available: `Date.toISOString`, `Date.toLocaleDateString`, and `Date.toLocaleTimeString`.
|
||||
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:
|
||||
|
||||
@@ -30,8 +30,8 @@ For compact numeric enums, index a literal label array with a fallback:
|
||||
<Text>{["Unknown", "Pending", "Ready"][(item.status ?? 1) - 1] ?? "Unknown"}</Text>
|
||||
```
|
||||
|
||||
Request-bound controls use literal `bind` plus a default (`defaultChecked` for Switch/Checkbox); pass `inputs.<name>` through manual `SubFetch params` to matching `$param`. Options are installation config via `options.name`, never `inputs`; dependent pagination uses `defaultValue={1}`/`resetKey={inputs.query}`. Remove dead controls.
|
||||
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 registered component names returned by discovery; `Icon` is an accepted alias for canonical `TablerIcon`. Never invent components such as `<IconFoo />`.
|
||||
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.
|
||||
@@ -13,7 +13,7 @@ interface HomarrCustomWidgetV2 {
|
||||
}
|
||||
```
|
||||
|
||||
Key `default` is the required source ID, not a source property. Fields: `name?`, `baseUrl`, `networkScope`, `auth?`; localhost/loopback URLs require `networkScope: "loopback"`; never widen explicit scope:
|
||||
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
|
||||
{
|
||||
@@ -40,14 +40,10 @@ Key `default` is the required source ID, not a source property. Fields: `name?`,
|
||||
}
|
||||
```
|
||||
|
||||
Saved sources use `{"type":"integration","integrationKind":"sonarr","integrationId":"saved-id"}`. Discover and bind a full-access entry before preview. Omit URL/auth; paths append to its URL, non-GET requests are actions, and exports omit `integrationId`.
|
||||
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`.
|
||||
|
||||
Auth is `none`, `bearer`, `basic`, `apiKeyHeader`, or `apiKeyQuery`. Requests default to source `default`, query/GET/load, inherited auth, and view permission. Use `load` for initial/current display and `manual` only for explicit interactions or invocation params. Actions are manual/modify; DELETE requires full permission and confirmation.
|
||||
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.
|
||||
|
||||
JSON responses become their decoded value. Responses with `application/x-ndjson` become an array with one decoded object per non-empty line.
|
||||
|
||||
Use real public API URLs and clear self-hosted placeholders. Homarr collects URL, scope, and credentials outside the manifest.
|
||||
|
||||
Paths use `{option:name}`/`{param:name}`; query/body objects use `{"$option":"name"}`/`{"$param":"name"}`. `$param` is manual-only; `$option` may drive loads. Constants stay primitive.
|
||||
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`.
|
||||
@@ -93,6 +93,6 @@ Register the smallest router that owns the enabled procedures. If a parent route
|
||||
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. When validating tool-list or extraction changes, use the focused API MCP spec: `bun run test packages/api/src/test/mcp.spec.ts`.
|
||||
5. When validating protocol or transport changes, use the focused route spec: `bun run test apps/nextjs/src/app/api/mcp/[transport]/route.spec.ts`.
|
||||
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.
|
||||
@@ -5,18 +5,9 @@ node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
**/.output
|
||||
.turbo
|
||||
**/.turbo
|
||||
**/next-env.d.ts
|
||||
packages/db/migrations/*/migrate.cjs
|
||||
packages/cli/cli.cjs
|
||||
apps/tasks/tasks.cjs
|
||||
apps/tasks/tasks.css
|
||||
apps/websocket/wssServer.cjs
|
||||
apps/websocket/wssServer.css
|
||||
.bun-cache
|
||||
**/.bun-cache
|
||||
.pnpm-store
|
||||
**/.pnpm-store
|
||||
coverage
|
||||
**/coverage
|
||||
**/.docusaurus
|
||||
@@ -32,8 +23,3 @@ apps/nextjs/.next
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
benchmark-results
|
||||
scripts/benchmarks/results
|
||||
**/__pycache__
|
||||
**/*.tsbuildinfo
|
||||
@@ -16,10 +16,21 @@ LOG_LEVEL='info'
|
||||
|
||||
# This is how you can use the sqlite driver:
|
||||
DB_DRIVER='better-sqlite3'
|
||||
# Set an absolute writable SQLite path so migrations and the app use the same file.
|
||||
# Their working directories differ; do not use a relative path here.
|
||||
# Safe local source-development path. SQLite files are ignored by Git.
|
||||
# Container images use /appdata/db/db.sqlite by default instead.
|
||||
DB_URL=
|
||||
DB_URL='./db.sqlite'
|
||||
|
||||
# Those are the two ways to use the mysql2 driver:
|
||||
# 1. Using the URL format:
|
||||
# DB_DRIVER='mysql2'
|
||||
# DB_URL='mysql://user:password@host:port/database'
|
||||
# 2. Using the connection options format:
|
||||
# DB_DRIVER='mysql2'
|
||||
# DB_HOST='localhost'
|
||||
# DB_PORT='3306'
|
||||
# DB_USER='username'
|
||||
# DB_PASSWORD='password'
|
||||
# DB_NAME='name-of-database'
|
||||
|
||||
# The following is an example on how to use the node-postgres driver:
|
||||
# DB_DRIVER='node-postgres'
|
||||
@@ -79,6 +90,6 @@ HOMARR_WEBSITE_URL=https://homarr.dev
|
||||
# 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; Assistant conversation history remains writable for canned chats.
|
||||
# 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
|
||||
@@ -25,18 +25,6 @@ body:
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Add any other information related to the bug here, screenshots if applicable.
|
||||
- type: input
|
||||
id: exactBuild
|
||||
attributes:
|
||||
label: Exact build or image
|
||||
description: For preview or development builds, include the commit SHA or image digest. A moving tag such as v2 is not enough to identify the build.
|
||||
placeholder: "Commit SHA, image digest, or release version"
|
||||
- type: input
|
||||
id: lastWorkingVersion
|
||||
attributes:
|
||||
label: Last working version
|
||||
description: If this worked before, include the last version or build where you tried the same action. Leave blank if unknown.
|
||||
placeholder: "Version or commit SHA"
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
@@ -45,10 +33,6 @@ body:
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 2.2.0
|
||||
- 2.1.2
|
||||
- 2.0.0
|
||||
- 1.77.0
|
||||
- 1.76.2
|
||||
- 1.76.1
|
||||
- 1.76.0
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
name: 🐞 Custom Widget bug
|
||||
description: Report a problem with a Custom JSX widget, its requests, preview, or editor
|
||||
title: "bug: custom widget "
|
||||
labels: ["needs triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Share a small reproduction so we can distinguish widget code, upstream API, and Homarr problems.
|
||||
Remove API keys, passwords, tokens, cookies, authorization headers, private URLs, and personal data from everything you share, including exports and screenshots. Replace private hosts with `https://example.invalid` while preserving relevant paths and response structure.
|
||||
- type: textarea
|
||||
id: behavior
|
||||
attributes:
|
||||
label: Actual and expected behavior
|
||||
description: Describe what happens and what you expected. Include the exact error text and a sanitized screenshot if useful.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Include the affected request or control, relevant widget options, and whether this occurs in Test, preview, or on the dashboard. For layout issues, include the widget size and desktop or mobile viewport.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: definition
|
||||
attributes:
|
||||
label: Sanitized widget definition
|
||||
description: Paste or attach the exported JSON with sources, requests, options, and JSX template. Prefer the smallest definition that reproduces the problem. If export fails, include the relevant JSX and request configuration. Check for private URLs and sensitive data before sharing.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Include the exact Homarr release, commit SHA, or image digest; installation method; browser/version; and desktop or mobile OS. For request issues, include the source type (HTTP or integration), integration kind, and upstream service version. Do not include server URLs or credentials.
|
||||
placeholder: |
|
||||
Homarr version / commit / image digest:
|
||||
Installation method:
|
||||
Browser and OS:
|
||||
Source type, integration kind, and service version:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: diagnostics
|
||||
attributes:
|
||||
label: Sanitized API response and logs
|
||||
description: For request or data issues, include the request ID, method, relative API path, HTTP status, and a small response sample preserving field names and types. Add relevant Homarr or browser console errors. Remove sensitive values and private URLs; leave blank for purely visual issues.
|
||||
@@ -3,20 +3,18 @@
|
||||
Install [Act](https://nektosact.com/installation/index.html) and ensure Docker is running, then use:
|
||||
|
||||
```bash
|
||||
bun run ci:act:fast
|
||||
bun run ci:act:docker
|
||||
bun run ci:act:all
|
||||
bun run ci:act:cleanup
|
||||
pnpm ci:act:fast
|
||||
pnpm ci:act:docker
|
||||
pnpm ci:act:all
|
||||
pnpm ci:act:cleanup
|
||||
```
|
||||
|
||||
`fast` runs the database checks and quality gate and requires an `origin/dev` tracking ref. `docker` and `all` use the manual validation event to run those checks, build the container once, and run the converter boot/restart smoke test. `cleanup` validates the manual cleanup path without calling GitHub. The wrapper supports regular clones and Git worktrees, including non-default Docker contexts. Linux dependencies are kept in Docker volumes instead of overwriting host `node_modules`. The first run downloads the Act runner and Playwright browser dependencies.
|
||||
|
||||
For rootless Docker, set `ACT_TESTCONTAINERS_HOST` to a host address reachable from the runner when loopback callbacks are unavailable.
|
||||
`fast` runs the quality gate. `docker` and `all` run the gate followed by the single container build, E2E suite, and memory regression test. `cleanup` validates the manual cleanup path without calling GitHub. The wrapper supports regular clones and Git worktrees, including non-default Docker contexts. Linux dependencies are kept in Docker volumes instead of overwriting host `node_modules`. The first run downloads the Act runner and Playwright browser dependencies.
|
||||
|
||||
Remote caching is optional locally. Copy `.github/act/.secrets.example` to `.github/act/.secrets`, populate it with rotated credentials, and run with:
|
||||
|
||||
```bash
|
||||
ACT_SECRET_FILE=.github/act/.secrets bun run ci:act:all
|
||||
ACT_SECRET_FILE=.github/act/.secrets pnpm ci:act:all
|
||||
```
|
||||
|
||||
Act never publishes or deletes GHCR images. The secret file is ignored by Git.
|
||||
|
||||
@@ -19,6 +19,11 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prebuilt debian dependencies
|
||||
uses: ./.github/actions/prebuilt-debian
|
||||
id: prebuilt-debian
|
||||
with:
|
||||
architecture: ${{ inputs.architecture }}
|
||||
- name: Start docker container for ${{ inputs.architecture }}
|
||||
run: |
|
||||
docker run --name homarr \
|
||||
@@ -38,6 +43,8 @@ runs:
|
||||
shell: bash
|
||||
- name: Extract source from ${{ inputs.architecture }} container (debian)
|
||||
run: |
|
||||
docker cp ${{ steps.prebuilt-debian.outputs.path }}/. homarr:/app/build && \
|
||||
docker cp ${{ steps.prebuilt-debian.outputs.path }}/. homarr:/app/node_modules/better-sqlite3/build/Release && \
|
||||
docker exec homarr tar -czf extraction-debian.tar.gz -C /app . && \
|
||||
docker cp homarr:/app/extraction-debian.tar.gz ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-debian-${{ inputs.architecture }}.tar.gz
|
||||
shell: bash
|
||||
|
||||
|
After Width: | Height: | Size: 582 KiB |
|
After Width: | Height: | Size: 649 KiB |
@@ -10,7 +10,7 @@
|
||||
- [ ] 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 (for example, `x`, `y`, `i`, or abbreviations)
|
||||
- [ ] Documentation under `apps/docs` is updated only when users need new or corrected information to use the app
|
||||
- [ ] 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:**
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
matchPackageNames: ["^@homarr/"],
|
||||
enabled: false,
|
||||
},
|
||||
// Reenable once https://github.com/privatenumber/tsx/issues/737 is fixed
|
||||
{
|
||||
matchPackageNames: ["tsx"],
|
||||
enabled: false,
|
||||
},
|
||||
// Reenable once bug with undici v8 is fixed (https://github.com/homarr-labs/homarr/issues/5467)
|
||||
{
|
||||
matchPackageNames: ["undici"],
|
||||
@@ -17,10 +22,11 @@
|
||||
automerge: true,
|
||||
},
|
||||
{
|
||||
// disallow automerge for trustedDependencies (native builds need manual verification)
|
||||
// disallow automerge for allowBuilds dependencies (native builds need manual verification)
|
||||
matchPackageNames: [
|
||||
"@tree-sitter-grammars/tree-sitter-yaml",
|
||||
"bcrypt",
|
||||
"better-sqlite3",
|
||||
"cpu-features",
|
||||
"esbuild",
|
||||
"sharp",
|
||||
@@ -31,6 +37,12 @@
|
||||
automerge: false,
|
||||
},
|
||||
],
|
||||
// Updates lockfile for prebuilt-debian after if package.json is updated, otherwise only root lockfile is updated
|
||||
postUpgradeTasks: {
|
||||
commands: ["cd deployments/prebuilt-debian && pnpm install --ignore-workspace --lockfile-only"],
|
||||
fileFilters: ["deployments/prebuilt-debian/pnpm-lock.yaml"],
|
||||
executionMode: "update",
|
||||
},
|
||||
updateInternalDeps: true,
|
||||
rangeStrategy: "bump",
|
||||
automerge: false,
|
||||
|
||||
@@ -32,4 +32,4 @@ jobs:
|
||||
run: |
|
||||
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 bun x oxfmt --check --no-error-on-unmatched-pattern --
|
||||
| xargs -0 -r pnpm exec oxfmt --check --
|
||||
@@ -10,8 +10,6 @@ on:
|
||||
push:
|
||||
branches: [main, dev, beta, next, v2, release/v2]
|
||||
merge_group:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
operation:
|
||||
@@ -49,15 +47,9 @@ env:
|
||||
PREVIEW_IMAGE: ghcr.io/homarr-labs/homarr-test
|
||||
|
||||
jobs:
|
||||
database-validation:
|
||||
name: Database validation
|
||||
if: github.event_name != 'pull_request_target' && (github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v2.')) && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup')
|
||||
uses: ./.github/workflows/database.yml
|
||||
|
||||
fast-gate:
|
||||
name: Fast gate
|
||||
needs: database-validation
|
||||
if: ${{ !cancelled() && github.event_name != 'pull_request_target' && (github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v2.')) && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup') }}
|
||||
if: github.event_name != 'pull_request_target' && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
@@ -67,12 +59,8 @@ jobs:
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before || 'HEAD~1' }}
|
||||
TURBO_SCM_HEAD: ${{ github.sha }}
|
||||
TURBO_PLATFORM: linux/amd64
|
||||
steps:
|
||||
- name: Require database validation
|
||||
env:
|
||||
DATABASE_RESULT: ${{ needs.database-validation.result }}
|
||||
run: test "$DATABASE_RESULT" = success
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
@@ -91,42 +79,41 @@ jobs:
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
with:
|
||||
next-cache: "all"
|
||||
turbo-cache: "true"
|
||||
|
||||
- name: Copy environment defaults
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Lint, typecheck, and build workspaces
|
||||
run: |
|
||||
scope=(--affected)
|
||||
if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then
|
||||
scope=()
|
||||
fi
|
||||
bun run turbo run lint typecheck build "${scope[@]}" --continue=dependencies-successful --concurrency=2
|
||||
- name: Lint, typecheck, and build affected workspaces
|
||||
run: pnpm turbo run lint typecheck build --affected --continue=dependencies-successful --concurrency=2
|
||||
|
||||
- name: Validate workspace configuration
|
||||
run: bun run lint:ws
|
||||
run: pnpm lint:ws
|
||||
|
||||
- name: Validate Custom Widget architecture
|
||||
run: bun run check:custom-widgets
|
||||
run: pnpm check:custom-widgets
|
||||
|
||||
- name: Validate checked-in OpenAPI schema
|
||||
run: bun run check:openapi
|
||||
run: pnpm check:openapi
|
||||
|
||||
- name: Validate Custom Widget bundle budget
|
||||
run: |
|
||||
bun run turbo run build --filter=@homarr/nextjs
|
||||
bun run check:custom-widget-bundle
|
||||
pnpm --filter nextjs build
|
||||
pnpm check:custom-widget-bundle
|
||||
|
||||
- name: Run unit tests
|
||||
run: pnpm test
|
||||
|
||||
container:
|
||||
name: Container and migration smoke
|
||||
# Legacy converter verification is opt-in, not a release publication gate.
|
||||
name: Container and E2E
|
||||
needs: [fast-gate, preview-amd64]
|
||||
if: |
|
||||
!cancelled() && needs.fast-gate.result == 'success' &&
|
||||
github.event_name == 'workflow_dispatch' && inputs.operation == 'validate'
|
||||
(
|
||||
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:
|
||||
@@ -142,6 +129,10 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Reuse amd64 preview image
|
||||
if: needs.preview-amd64.outputs.digest != '' && !env.ACT
|
||||
env:
|
||||
@@ -149,12 +140,9 @@ jobs:
|
||||
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: Set up Docker Buildx
|
||||
if: needs.preview-amd64.outputs.digest == '' && !env.ACT
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build container once
|
||||
if: needs.preview-amd64.outputs.digest == '' && !env.ACT
|
||||
uses: docker/build-push-action@v7
|
||||
@@ -187,8 +175,14 @@ jobs:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
run: |
|
||||
bash tooling/github/docker-build.sh --platform linux/amd64 \
|
||||
--tag homarr-ci --tag homarr-e2e .
|
||||
secret_args=()
|
||||
for secret in TURBO_API TURBO_TEAM TURBO_TOKEN TURBO_REMOTE_CACHE_SIGNATURE_KEY; do
|
||||
if [ -n "${!secret:-}" ]; then
|
||||
secret_args+=(--secret "id=${secret},env=${secret}")
|
||||
fi
|
||||
done
|
||||
DOCKER_BUILDKIT=1 docker build --platform linux/amd64 \
|
||||
--tag homarr-ci --tag homarr-e2e "${secret_args[@]}" .
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
if: "!env.ACT"
|
||||
@@ -196,24 +190,21 @@ jobs:
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-${{ runner.os }}-${{ hashFiles('bun.lock') }}
|
||||
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true' || env.ACT
|
||||
run: bun x playwright install --with-deps chromium
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- name: Install Playwright system deps
|
||||
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
||||
run: bun x playwright install-deps chromium
|
||||
run: pnpm exec playwright install-deps chromium
|
||||
|
||||
- name: Install standalone converter test dependencies
|
||||
run: bun install --cwd tools/mysql-to-sqlite --frozen-lockfile
|
||||
|
||||
- name: Verify converted database boots and survives restart
|
||||
- name: Run E2E tests
|
||||
env:
|
||||
NODE_ENV: development
|
||||
CI: "true"
|
||||
run: bun x vitest run --project e2e e2e/mysql-conversion.spec.ts --maxWorkers 1
|
||||
run: pnpm exec vitest e2e --retry 1 --maxWorkers 4
|
||||
|
||||
- name: Upload E2E artifacts
|
||||
if: failure() && !env.ACT
|
||||
@@ -329,10 +320,10 @@ jobs:
|
||||
|
||||
publish-preview:
|
||||
name: Publish multi-platform preview image
|
||||
needs: [fast-gate, preview-amd64, preview-arm64]
|
||||
needs: [container, preview-amd64, preview-arm64]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
needs.fast-gate.result == 'success' &&
|
||||
needs.container.result == 'success' &&
|
||||
needs.preview-amd64.result == 'success' &&
|
||||
needs.preview-arm64.result == 'success' &&
|
||||
(
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
name: Database migrations
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NODE_ENV: development
|
||||
CI: "true"
|
||||
SKIP_ENV_VALIDATION: "true"
|
||||
TURBO_TELEMETRY_DISABLED: "1"
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
migrations: ${{ steps.select.outputs.migrations }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Select database checks
|
||||
id: select
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }}
|
||||
shell: bash
|
||||
run: |
|
||||
migrations=false
|
||||
if [[ "$EVENT_NAME" != pull_request && "$EVENT_NAME" != merge_group && "$EVENT_NAME" != push ]] ||
|
||||
[[ -z "$BASE_SHA" || "$BASE_SHA" =~ ^0+$ ]] ||
|
||||
! git cat-file -e "$BASE_SHA^{commit}" 2>/dev/null; then
|
||||
migrations=true
|
||||
else
|
||||
changed_files=$(mktemp)
|
||||
trap 'rm -f "$changed_files"' EXIT
|
||||
comparison="$BASE_SHA...HEAD"
|
||||
if [[ "$EVENT_NAME" = push ]]; then comparison="$BASE_SHA..HEAD"; fi
|
||||
git diff --name-only -z "$comparison" > "$changed_files"
|
||||
while IFS= read -r -d '' file; do
|
||||
case "$file" in
|
||||
packages/db/*|packages/core/src/infrastructure/db/*|packages/core/src/infrastructure/certificates/*|packages/auth/*|packages/api/src/router/onboard*|packages/api/src/router/test/onboard*|packages/onboarding/*|apps/nextjs/src/app/api/backup/*|apps/nextjs/src/app/*/init/*|Dockerfile|scripts/run.sh|scripts/entrypoint.sh|deployments/*|package.json|packages/core/package.json|packages/common/package.json|packages/definitions/package.json|packages/server-settings/package.json|packages/validation/package.json|mise.toml|bun.lock|bunfig.toml|patches/*|vitest*|tooling/github/*|.github/workflows/database.yml|.github/workflows/ci.yml)
|
||||
migrations=true
|
||||
;;
|
||||
esac
|
||||
done < "$changed_files"
|
||||
fi
|
||||
echo "migrations=$migrations" >> "$GITHUB_OUTPUT"
|
||||
|
||||
journals:
|
||||
name: Migration journals
|
||||
needs: changes
|
||||
if: needs.changes.outputs.migrations == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./tooling/github/setup
|
||||
- run: bun x vitest run --project db-node packages/db/test/migration-journal.spec.ts
|
||||
|
||||
migrations:
|
||||
name: SQLite and PostgreSQL migrations
|
||||
needs: changes
|
||||
if: needs.changes.outputs.migrations == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./tooling/github/setup
|
||||
- name: Reject retired database configuration
|
||||
run: bun x vitest run --project dom packages/core/src/infrastructure/db/env.spec.ts
|
||||
- run: bun x vitest run --project db-node --project integration packages/db/test/sqlite-migration.spec.ts packages/db/test/postgresql-migration.spec.ts packages/db/proxy-reader.integration.spec.ts --maxWorkers 1
|
||||
|
||||
database-gate:
|
||||
name: Database gate
|
||||
needs: [changes, journals, migrations]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Require selected database checks
|
||||
env:
|
||||
CHANGES: ${{ needs.changes.result }}
|
||||
JOURNALS: ${{ needs.journals.result }}
|
||||
MIGRATIONS: ${{ needs.migrations.result }}
|
||||
RUN_MIGRATIONS: ${{ needs.changes.outputs.migrations }}
|
||||
run: |
|
||||
test "$CHANGES" = success
|
||||
test "$RUN_MIGRATIONS" = true || test "$RUN_MIGRATIONS" = false
|
||||
if [ "$RUN_MIGRATIONS" = true ]; then
|
||||
test "$JOURNALS" = success
|
||||
test "$MIGRATIONS" = success
|
||||
fi
|
||||
@@ -16,10 +16,10 @@ permissions:
|
||||
jobs:
|
||||
create-and-merge-pr:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Discord notification
|
||||
if: ${{ inputs.send-notifications }}
|
||||
if: ${{ github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
@@ -37,22 +37,11 @@ jobs:
|
||||
branch: next
|
||||
- name: Create pull request
|
||||
id: create-pull-request
|
||||
run: |
|
||||
pr_url="$(gh pr list --base beta --head next --state open --json url --jq '.[0].url // empty')"
|
||||
if [[ -z "$pr_url" ]]; then
|
||||
pr_url="$(gh pr create \
|
||||
--title "chore(release): release ${{ steps.semver.outputs.next }}-beta" \
|
||||
--body "Detected change to be ${{ steps.semver.outputs.bump }}<br/>Bump version from ${{ steps.semver.outputs.current }} to ${{ steps.semver.outputs.next }}-beta" \
|
||||
--base beta \
|
||||
--head next \
|
||||
--label automerge)"
|
||||
fi
|
||||
echo "url=$pr_url" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_number=${pr_url##*/}" >> "$GITHUB_OUTPUT"
|
||||
run: 'gh pr create --title "chore(release): release ${{ steps.semver.outputs.next }}-beta" --body "Detected change to be ``${{ steps.semver.outputs.bump }}``<br/>Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}-beta``" --base beta --head next --label automerge'
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Discord notification
|
||||
if: ${{ inputs.send-notifications }}
|
||||
if: ${{ github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
@@ -69,7 +58,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
|
||||
run: |
|
||||
gh pr review "${{ steps.create-pull-request.outputs.url }}" --approve --body "Automatically approved by GitHub Action"
|
||||
gh pr review --approve --body "Automatically approved by GitHub Action"
|
||||
- name: Obtain token
|
||||
id: obtainMergeToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
@@ -93,21 +82,21 @@ jobs:
|
||||
MERGE_REQUIRED_APPROVALS: 0 # do not require approvals
|
||||
|
||||
- name: Merged Discord notification
|
||||
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && inputs.send-notifications }}
|
||||
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.event.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 }}-beta"
|
||||
- name: Major Bump Discord notification
|
||||
if: ${{ steps.semver.outputs.bump == 'major' && inputs.send-notifications }}
|
||||
if: ${{ steps.semver.outputs.bump == 'major' && github.event.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 }}-beta"
|
||||
- name: Discord Fail Notification
|
||||
if: ${{ failure() && inputs.send-notifications }}
|
||||
if: failure() && github.event.inputs.send-notifications
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
|
||||
@@ -63,10 +63,13 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: pnpm/action-setup@v6
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
- uses: actions/setup-node@v6
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
bun-version: 1.4.2
|
||||
node-version: 24.18.0
|
||||
package-manager-cache: false
|
||||
- name: Verify major release intent
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
run: |
|
||||
@@ -89,7 +92,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Install dependencies
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
run: bun install --frozen-lockfile
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run Semantic Release
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
env:
|
||||
@@ -99,7 +102,7 @@ jobs:
|
||||
GIT_COMMITTER_NAME: "Releases Homarr"
|
||||
GIT_COMMITTER_EMAIL: "175486441+homarr-releases[bot]@users.noreply.github.com"
|
||||
run: |
|
||||
bun run release
|
||||
pnpm release
|
||||
- name: Read semver output
|
||||
# We read the last tag either from the created release or from the current branch, this is to rerun the deployment job for the currently released version when it failed
|
||||
if: env.SKIP_RELEASE == 'false' || github.ref_name == 'main' || github.ref_name == 'beta'
|
||||
@@ -120,19 +123,15 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
UPSTREAM_BRANCH: ${{ github.ref_name == 'main' && 'dev' || 'next' }}
|
||||
RELEASE_REF: ${{ steps.read-git-ref.outputs.ref }}
|
||||
run: |
|
||||
git config user.name "Releases Homarr"
|
||||
git config user.email "175486441+homarr-releases[bot]@users.noreply.github.com"
|
||||
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
|
||||
git fetch origin "$UPSTREAM_BRANCH"
|
||||
|
||||
# Dependency installation can modify generated files in the release checkout.
|
||||
sync_dir="$(mktemp -d "${RUNNER_TEMP}/homarr-release-sync.XXXXXX")"
|
||||
trap 'git worktree remove --force "$sync_dir"' EXIT
|
||||
git worktree add --detach "$sync_dir" FETCH_HEAD
|
||||
git -C "$sync_dir" merge --no-edit "$RELEASE_REF"
|
||||
git -C "$sync_dir" push origin "HEAD:${UPSTREAM_BRANCH}"
|
||||
git remote set-url origin https://x-access-token:${{ steps.obtainToken.outputs.token }}@github.com/${{ github.repository }}.git
|
||||
git fetch origin ${{ env.UPSTREAM_BRANCH }}
|
||||
git checkout ${{ env.UPSTREAM_BRANCH }}
|
||||
git pull origin ${{ env.UPSTREAM_BRANCH }}
|
||||
git rebase ${{ github.ref_name }}
|
||||
git push origin ${{ env.UPSTREAM_BRANCH }}
|
||||
build-amd64:
|
||||
name: Build docker image for amd64
|
||||
needs: release
|
||||
|
||||
@@ -5,13 +5,7 @@ on:
|
||||
branches: ["dev"]
|
||||
paths:
|
||||
- "apps/docs/**"
|
||||
- "packages/**"
|
||||
- "tooling/**"
|
||||
- "tools/motion-reel/three/**"
|
||||
- "package.json"
|
||||
- "bun.lock"
|
||||
- "bunfig.toml"
|
||||
- "turbo.json"
|
||||
- "packages/definitions/src/docs/**"
|
||||
- ".github/workflows/deployment-docs.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -39,18 +33,10 @@ jobs:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
with:
|
||||
next-cache: "docs"
|
||||
turbo-cache: "true"
|
||||
- name: Build docs
|
||||
run: bun run turbo run build --filter=@homarr/docs
|
||||
run: pnpm turbo build --filter=@homarr/docs
|
||||
env:
|
||||
CI: "true"
|
||||
- name: Validate exported links and search
|
||||
run: |
|
||||
bun run --filter @homarr/docs validate:links
|
||||
bun run --filter @homarr/docs verify:search
|
||||
bun run --filter @homarr/docs verify:seo
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v6
|
||||
- name: Upload pages artifact
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
|
||||
- name: Dump contributors
|
||||
working-directory: apps/docs
|
||||
run: bun run api-dump-contributors
|
||||
run: pnpm api-dump-contributors
|
||||
|
||||
- name: Commit updated contributors
|
||||
uses: EndBug/add-and-commit@v10
|
||||
|
||||
@@ -26,8 +26,36 @@ jobs:
|
||||
- name: Copy env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Generate and validate OpenAPI spec
|
||||
run: bun run scripts:update-openapi
|
||||
- name: Start dev server
|
||||
run: pnpm --filter @homarr/nextjs dev &
|
||||
env:
|
||||
TURBO_TELEMETRY_DISABLED: 1
|
||||
DISABLE_TELEMETRY: 1
|
||||
|
||||
- name: Wait for server and fetch spec
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for i in $(seq 1 30); do
|
||||
if curl -fsS http://localhost:3000/api/openapi -o /tmp/openapi.json 2>/dev/null; then
|
||||
echo "Server is up, spec fetched"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for server... ($i/30)"
|
||||
sleep 3
|
||||
done
|
||||
|
||||
if [ ! -f /tmp/openapi.json ]; then
|
||||
echo "::error::Server failed to start within timeout"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -e '.openapi and .paths' /tmp/openapi.json > /dev/null || {
|
||||
echo "::error::Fetched file is not a valid OpenAPI spec"
|
||||
exit 1
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -12,15 +12,8 @@ jobs:
|
||||
renovate-validate:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
env:
|
||||
# The optional RE2 addon uses V8 APIs; validation can use JavaScript regexes.
|
||||
RENOVATE_X_IGNORE_RE2: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.4.2
|
||||
- run: |
|
||||
bun x --package renovate \
|
||||
npx --yes --package renovate -- \
|
||||
renovate-config-validator --strict .github/renovate.json5
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
ref: dev
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- run: bun run scripts:update-bug-report-template
|
||||
- run: pnpm run scripts:update-bug-report-template
|
||||
env:
|
||||
NEXT_VERSION: ${{ github.event.release.tag_name }}
|
||||
|
||||
|
||||
@@ -29,12 +29,16 @@ jobs:
|
||||
uses: actions/checkout@v7
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
bun-version: 1.4.2
|
||||
node-version: 24.18.0
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Run update script
|
||||
run: bun ./scripts/update-contributors.mjs
|
||||
run: node ./scripts/update-contributors.mjs
|
||||
|
||||
- name: Commit changes
|
||||
env:
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- run: bun run scripts:update-readme-integrations
|
||||
- run: pnpm run scripts:update-readme-integrations
|
||||
- name: Commit changes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
name: Validate documentation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/docs/**"
|
||||
- "apps/nextjs/src/app/api/**"
|
||||
- "packages/definitions/src/**"
|
||||
- "packages/widgets/src/**"
|
||||
- "packages/custom-widgets/src/**"
|
||||
- "packages/ui/src/**"
|
||||
- "packages/workshop/src/**"
|
||||
- "packages/api/src/**"
|
||||
- "packages/auth/src/**"
|
||||
- "packages/integrations/src/**"
|
||||
- "tools/motion-reel/three/**"
|
||||
- "tooling/**"
|
||||
- "bun.lock"
|
||||
- "package.json"
|
||||
- "bunfig.toml"
|
||||
- "turbo.json"
|
||||
- ".github/workflows/deployment-docs.yml"
|
||||
- ".github/workflows/validate-docs.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: validate-docs-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Build and validate docs
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
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 }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
with:
|
||||
next-cache: "docs"
|
||||
turbo-cache: "true"
|
||||
- name: Validate the published API schema against the application
|
||||
run: bun run check:openapi
|
||||
- name: Validate browser access and API schema rendering
|
||||
run: bun x vitest run 'apps/nextjs/src/app/api/[...trpc]/route.spec.ts' apps/docs/src/lib/openapi.spec.ts
|
||||
- name: Build docs
|
||||
run: bun run turbo run build --filter=@homarr/docs
|
||||
env:
|
||||
CI: "true"
|
||||
- name: Validate exported links, search, and metadata
|
||||
run: |
|
||||
bun run --filter @homarr/docs validate:links
|
||||
bun run --filter @homarr/docs verify:search
|
||||
bun run --filter @homarr/docs verify:seo
|
||||
@@ -2,52 +2,8 @@ name: Workshop
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/workshop.yml"
|
||||
- "tooling/github/**"
|
||||
- "apps/workshop/**"
|
||||
- "apps/docs/**"
|
||||
- "packages/common/**"
|
||||
- "packages/core/**"
|
||||
- "packages/custom-widgets/**"
|
||||
- "packages/definitions/**"
|
||||
- "packages/db/**"
|
||||
- "packages/server-settings/**"
|
||||
- "packages/settings/**"
|
||||
- "packages/translation/**"
|
||||
- "packages/ui/**"
|
||||
- "packages/validation/**"
|
||||
- "packages/workshop/**"
|
||||
- "tooling/typescript/**"
|
||||
- "patches/**"
|
||||
- "package.json"
|
||||
- "bun.lock"
|
||||
- "bunfig.toml"
|
||||
- "turbo.json"
|
||||
push:
|
||||
branches: [dev, v2, release/v2, main]
|
||||
paths:
|
||||
- ".github/workflows/workshop.yml"
|
||||
- "tooling/github/**"
|
||||
- "apps/workshop/**"
|
||||
- "apps/docs/**"
|
||||
- "packages/common/**"
|
||||
- "packages/core/**"
|
||||
- "packages/custom-widgets/**"
|
||||
- "packages/definitions/**"
|
||||
- "packages/db/**"
|
||||
- "packages/server-settings/**"
|
||||
- "packages/settings/**"
|
||||
- "packages/translation/**"
|
||||
- "packages/ui/**"
|
||||
- "packages/validation/**"
|
||||
- "packages/workshop/**"
|
||||
- "tooling/typescript/**"
|
||||
- "patches/**"
|
||||
- "package.json"
|
||||
- "bun.lock"
|
||||
- "bunfig.toml"
|
||||
- "turbo.json"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -57,68 +13,23 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
production-smoke:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Production image smoke (${{ matrix.arch }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
runner: ubuntu-24.04
|
||||
- arch: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.runner }}
|
||||
validate:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24.18.0
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Build the production image
|
||||
if: "!env.ACT"
|
||||
uses: docker/build-push-action@v7
|
||||
- uses: pnpm/action-setup@v6
|
||||
with:
|
||||
context: .
|
||||
file: apps/workshop/Dockerfile
|
||||
target: production
|
||||
platforms: linux/${{ matrix.arch }}
|
||||
load: true
|
||||
tags: homarr-workshop:smoke
|
||||
cache-from: type=gha,scope=workshop-linux-${{ matrix.arch }}
|
||||
cache-to: type=gha,scope=workshop-linux-${{ matrix.arch }},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 }}
|
||||
- name: Exercise the production image
|
||||
if: "!env.ACT"
|
||||
env:
|
||||
WORKSHOP_IMAGE_TEST_IMAGE: homarr-workshop:smoke
|
||||
run: sh apps/workshop/test-image.sh
|
||||
- name: Build and exercise the production image with Act
|
||||
if: env.ACT
|
||||
env:
|
||||
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 }}
|
||||
run: sh apps/workshop/test-image.sh
|
||||
|
||||
validate:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
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')
|
||||
@@ -132,7 +43,6 @@ jobs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
@@ -149,11 +59,6 @@ jobs:
|
||||
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
|
||||
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-arm64:
|
||||
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
|
||||
@@ -167,7 +72,6 @@ jobs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
@@ -184,11 +88,6 @@ jobs:
|
||||
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
|
||||
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:
|
||||
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.bun-cache/
|
||||
.pnpm-store/
|
||||
.pnp
|
||||
.pnp.js
|
||||
.idea/
|
||||
@@ -36,7 +36,7 @@ tools/homarr-dev/homarr-dev
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
bun-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
@@ -81,7 +81,7 @@ skills-lock.json
|
||||
# docusaurus
|
||||
apps/docs/.docusaurus
|
||||
apps/docs/build
|
||||
.bun-cache
|
||||
.pnpm-store
|
||||
benchmark-results
|
||||
|
||||
# local performance investigation (never ship benchmark data or restored backups)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"mantine": {
|
||||
"command": "bun",
|
||||
"args": ["x", "@mantine/mcp-server@9.6.0"]
|
||||
"command": "npx",
|
||||
"args": ["-y", "@mantine/mcp-server@9.6.0"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[tools]
|
||||
node = "24.18.0"
|
||||
@@ -0,0 +1,2 @@
|
||||
# pnpm v11: non-auth settings moved to pnpm-workspace.yaml
|
||||
# Keep this file for any future registry/auth configuration
|
||||
@@ -8,15 +8,14 @@
|
||||
"**/lang/**",
|
||||
"static-data/**",
|
||||
"CHANGELOG.md",
|
||||
"apps/docs/.next/**",
|
||||
"apps/docs/out/**",
|
||||
"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",
|
||||
"apps/docs/public/api/open-api-schema.json",
|
||||
"packages/custom-widgets/src/core/component-catalog.generated.json",
|
||||
"apps/docs/public/custom-widgets/component-catalog-v1.json"
|
||||
"apps/docs/static/custom-widgets/component-catalog-v1.json"
|
||||
]
|
||||
}
|
||||
@@ -26,11 +26,16 @@
|
||||
"changelogFile": "CHANGELOG.md"
|
||||
}
|
||||
],
|
||||
"./scripts/release-version.mjs",
|
||||
[
|
||||
"@semantic-release/npm",
|
||||
{
|
||||
"npmPublish": false
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": ["package.json", "bun.lock", "CHANGELOG.md"],
|
||||
"assets": ["package.json", "CHANGELOG.md"],
|
||||
"message": "chore(release): ${nextRelease.version} [skip ci]"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="db:migration:mysql:generate" type="js.build_tools.npm">
|
||||
<package-json value="$PROJECT_DIR$/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="db:migration:mysql:generate" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="db:migration:postgresql:generate" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="db:migration:sqlite:generate" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="db:push" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="db:studio" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="dev" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="docker:dev" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="format" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="format:fix" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="test" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="test:ui" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="typecheck" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"name": "dev",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run dev",
|
||||
"command": "pnpm dev",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -13,7 +13,7 @@
|
||||
"name": "docker dev",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run docker:dev",
|
||||
"command": "pnpm docker:dev",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -21,7 +21,7 @@
|
||||
"name": "lint fix",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run lint:fix",
|
||||
"command": "pnpm lint:fix",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -29,7 +29,7 @@
|
||||
"name": "format fix",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run format:fix",
|
||||
"command": "pnpm format:fix",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -37,7 +37,7 @@
|
||||
"name": "db push",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run db:push",
|
||||
"command": "pnpm db:push",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -45,7 +45,7 @@
|
||||
"name": "db studio",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run db:studio",
|
||||
"command": "pnpm db:studio",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -53,7 +53,7 @@
|
||||
"name": "db migration run",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run db:migration:sqlite:run",
|
||||
"command": "pnpm db:migration:run",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -61,7 +61,7 @@
|
||||
"name": "test",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run test",
|
||||
"command": "pnpm test",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -69,7 +69,7 @@
|
||||
"name": "test ui",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run test:ui",
|
||||
"command": "pnpm test:ui",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ homarr/
|
||||
├── packages/
|
||||
│ ├── api/ # tRPC appRouter, procedures, OpenAPI
|
||||
│ ├── auth/ # NextAuth config, providers, session, API keys
|
||||
│ ├── db/ # SQLite and PostgreSQL schemas, migrations, queries
|
||||
│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries
|
||||
│ ├── core/ # Env validation, DB/Redis driver factories, logging
|
||||
│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions
|
||||
│ ├── widgets/ # Dashboard widget definitions and components
|
||||
@@ -55,13 +55,11 @@ homarr/
|
||||
└── Dockerfile # Multi-stage production build
|
||||
```
|
||||
|
||||
## Documentation
|
||||
## Documentation Sync
|
||||
|
||||
Add documentation only when users need the information to set up, use, or troubleshoot Homarr and cannot reasonably discover it from the UI or generated API schema. If a user does not need to know it, do not document it. A code, UI, or API change alone is never a reason to add docs.
|
||||
The documentation site lives at `apps/docs/` (Next.js + Fumadocs, `@homarr/docs`).
|
||||
|
||||
Keep warranted documentation concise. Omit UI walkthroughs, visible control descriptions, internal implementation details, and anything already clear from the interface or schema.
|
||||
|
||||
Only after this user-need test passes, use these locations:
|
||||
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`
|
||||
@@ -73,52 +71,34 @@ Only after this user-need test passes, use these locations:
|
||||
|
||||
## Monorepo Commands
|
||||
|
||||
- `bun run dev` — Next.js app only
|
||||
- `bun run dev:cli -- dev` — run the developer CLI without installing a global binary
|
||||
- `bun run db:seed` — seed default database data explicitly
|
||||
- `bun run docker:dev:up` — start the Redis development service in the background
|
||||
- `bun run dev:docs` — Fumadocs site only
|
||||
- `bun run turbo build` — build all packages
|
||||
- `bun run turbo build --filter=@homarr/docs` — build docs only
|
||||
- `bun run turbo typecheck` — typecheck all packages
|
||||
- `bun run lint` / `bun run format` — oxlint / oxfmt
|
||||
- `pnpm dev` — Next.js app 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
|
||||
- `pnpm lint` / `pnpm format` — oxlint / oxfmt
|
||||
|
||||
## Code Style
|
||||
|
||||
- Lint: oxlint (not ESLint)
|
||||
- Format: oxfmt (not Prettier)
|
||||
- UI: Mantine (not Tailwind) — Tailwind is only used in docs app
|
||||
- Mantine: use the `mantine` MCP server in `.mcp.json` for current v9 APIs before writing component code. Prefer built-in primitives (`Combobox`/`useCombobox`, the polymorphic `component` prop, `@mantine/hooks`) and check `packages/ui/` for existing conventions first.
|
||||
- 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 `bun run dev:cli -- dev` to browse local `homarr:*` images and remote PR images.
|
||||
- Run `bun run dev:cli -- build <name>` from a Homarr checkout to build `homarr:<name>` with rebuild provenance.
|
||||
- Run `bun run dev:cli -- build --pr <number>` to build a PR locally from a temporary checkout.
|
||||
|
||||
## Testing
|
||||
|
||||
Add tests only when requested, and only when the test is extremely relevant and will plausibly prevent a future regression in user-visible behavior or a security boundary. Do not add tests that restate implementation details or duplicate stronger coverage. For focused changes, run only the relevant existing or newly requested checks when validation is needed. Do not run broad test, Docker, or E2E suites by default.
|
||||
|
||||
## MCP servers
|
||||
|
||||
`.mcp.json` declares project-scoped MCP servers. Keep only keyless definitions there; credentials belong in local dotfiles. See [Mantine LLM tooling](https://mantine.dev/guides/llms/) for client-specific setup.
|
||||
- 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` — user-critical documentation only
|
||||
- `documentation-sync` — required user-facing documentation updates
|
||||
- `mcp-integration` — safe tRPC-to-MCP exposure
|
||||
- `homarr-custom-widget` — safe Custom JSX v2 authoring
|
||||
|
||||
<!-- BEGIN:turborepo-agent-rules -->
|
||||
|
||||
# This is NOT the Turborepo you know
|
||||
|
||||
Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the `turbo` package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run `node -p "require.resolve('turbo/package.json')"` from a workspace that depends on `turbo`.
|
||||
|
||||
Read `docs/README.md` inside that installed package first, then read the relevant pages from its `docs/` directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access.
|
||||
|
||||
This block is written and re-added by `turbo` before repository-scoped commands when an AI agent is detected. In the Turborepo source repository, its template is defined in `crates/turborepo-cli/src/cli/agent_guidance.rs`. Removing the managed block while updates are enabled means a later qualifying invocation will add it again. Set `"agentGuidance": false` in the root `turbo.json` or `turbo.jsonc` to opt out; this does not remove an existing block. When agent guidance is enabled, keep the block committed to avoid an uncommitted change on the next agent invocation. This repository disables agent guidance in `turbo.json`.
|
||||
<!-- END:turborepo-agent-rules -->
|
||||
@@ -1,19 +1,3 @@
|
||||
## [2.3.0](https://github.com/homarr-labs/homarr/compare/v2.2.0...v2.3.0) (2026-10-07)
|
||||
|
||||
## [2.2.0](https://github.com/homarr-labs/homarr/compare/v2.1.2...v2.2.0) (2026-10-05)
|
||||
|
||||
## [2.1.2](https://github.com/homarr-labs/homarr/compare/v2.1.1...v2.1.2) (2026-10-04)
|
||||
|
||||
## [2.1.1](https://github.com/homarr-labs/homarr/compare/v2.1.0...v2.1.1) (2026-10-03)
|
||||
|
||||
## [2.1.0](https://github.com/homarr-labs/homarr/compare/v2.0.0...v2.1.0) (2026-10-03)
|
||||
|
||||
## [2.0.0](https://github.com/homarr-labs/homarr/compare/v1.77.2...v2.0.0) (2026-10-02)
|
||||
|
||||
## [1.77.1](https://github.com/homarr-labs/homarr/compare/v1.77.0...v1.77.1) (2026-09-11)
|
||||
|
||||
## [1.77.0](https://github.com/homarr-labs/homarr/compare/v1.76.2...v1.77.0) (2026-09-06)
|
||||
|
||||
## [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)
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
# ── CODEOWNERS ───────────────────────────────────────
|
||||
* @homarr-labs/maintainers
|
||||
|
||||
# Specific features / integrations
|
||||
# These owners come from the PR that introduced each integration implementation.
|
||||
# Specific rules override the global fallback because CODEOWNERS uses the last
|
||||
# matching pattern; do not include @homarr-labs/maintainers here unless they
|
||||
# should also be requested for that integration.
|
||||
|
||||
# AdGuard Home - introduced in https://github.com/homarr-labs/homarr/pull/929 by @hillaliy
|
||||
apps/docs/docs/integrations/adguard-home @hillaliy
|
||||
packages/integrations/src/adguard-home @hillaliy
|
||||
|
||||
# Anchor - introduced in https://github.com/homarr-labs/homarr/pull/5040 by @ohmzi
|
||||
apps/docs/docs/integrations/anchor @ohmzi
|
||||
packages/integrations/src/anchor @ohmzi
|
||||
|
||||
# ArchiveTeam Warrior - introduced in https://github.com/homarr-labs/homarr/pull/5784 by @darbyjack
|
||||
apps/docs/docs/integrations/archive-team-warrior @darbyjack
|
||||
packages/integrations/src/archive-team-warrior @darbyjack
|
||||
|
||||
# Bazarr - introduced in https://github.com/homarr-labs/homarr/pull/6156 by @Ayush7614
|
||||
apps/docs/docs/integrations/bazarr @Ayush7614
|
||||
apps/docs/docs/widgets/bazarr @Ayush7614
|
||||
packages/api/src/router/widgets/bazarr.ts @Ayush7614
|
||||
packages/integrations/src/bazarr @Ayush7614
|
||||
packages/request-handler/src/bazarr.ts @Ayush7614
|
||||
packages/widgets/src/bazarr @Ayush7614
|
||||
|
||||
# Audiobookshelf - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/audiobookshelf @ajnart
|
||||
packages/integrations/src/audiobookshelf @ajnart
|
||||
|
||||
# Beszel - introduced in https://github.com/homarr-labs/homarr/pull/5900 by @ajnart
|
||||
apps/docs/docs/integrations/beszel @ajnart
|
||||
packages/integrations/src/beszel @ajnart
|
||||
|
||||
# Codeberg - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/codeberg @Aandree5
|
||||
packages/integrations/src/codeberg @Aandree5
|
||||
|
||||
# Coolify - introduced in https://github.com/homarr-labs/homarr/pull/4789 by @Rhiz3K
|
||||
apps/docs/docs/integrations/coolify @Rhiz3K
|
||||
packages/integrations/src/coolify @Rhiz3K
|
||||
|
||||
# Dash. - introduced in https://github.com/homarr-labs/homarr/pull/1541 by @manuel-rw
|
||||
apps/docs/docs/integrations/dash-dot @manuel-rw
|
||||
packages/integrations/src/dashdot @manuel-rw
|
||||
|
||||
# Docker Hub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/docker-hub @Aandree5
|
||||
packages/integrations/src/docker-hub @Aandree5
|
||||
|
||||
# Emby - introduced in https://github.com/homarr-labs/homarr/pull/2466 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/emby @Meierschlumpf
|
||||
packages/integrations/src/emby @Meierschlumpf
|
||||
|
||||
# GitHub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/github @Aandree5
|
||||
packages/integrations/src/github @Aandree5
|
||||
|
||||
# GitHub Container Registry - introduced in https://github.com/homarr-labs/homarr/pull/3727 by @Aandree5
|
||||
apps/docs/docs/integrations/github-containerregistry @Aandree5
|
||||
packages/integrations/src/github-container-registry @Aandree5
|
||||
|
||||
# GitLab - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/gitlab @Aandree5
|
||||
packages/integrations/src/gitlab @Aandree5
|
||||
|
||||
# Glances - introduced in https://github.com/homarr-labs/homarr/pull/5102 by @manuel-rw
|
||||
apps/docs/docs/integrations/glances @manuel-rw
|
||||
packages/integrations/src/glances @manuel-rw
|
||||
|
||||
# Gluetun - introduced in https://github.com/homarr-labs/homarr/pull/5812 by @Lok3sh-kumar
|
||||
apps/docs/docs/integrations/gluetun @Lok3sh-kumar
|
||||
packages/integrations/src/gluetun @Lok3sh-kumar
|
||||
|
||||
# Gotify - introduced in https://github.com/homarr-labs/homarr/pull/5847 by @Nerdy-Technician
|
||||
apps/docs/docs/integrations/gotify @Nerdy-Technician
|
||||
packages/integrations/src/gotify @Nerdy-Technician
|
||||
|
||||
# Home Assistant - introduced in https://github.com/homarr-labs/homarr/pull/578 by @manuel-rw
|
||||
apps/docs/docs/integrations/home-assistant @manuel-rw
|
||||
packages/integrations/src/homeassistant @manuel-rw
|
||||
|
||||
# ICal - introduced in https://github.com/homarr-labs/homarr/pull/3980 by @NicolasNewman
|
||||
apps/docs/docs/integrations/ical @NicolasNewman
|
||||
packages/integrations/src/ical @NicolasNewman
|
||||
|
||||
# Immich - introduced in https://github.com/homarr-labs/homarr/pull/5165 by @manuel-rw
|
||||
apps/docs/docs/integrations/immich @manuel-rw
|
||||
packages/integrations/src/immich @manuel-rw
|
||||
|
||||
# Jellyfin - introduced in https://github.com/homarr-labs/homarr/pull/672 by @manuel-rw
|
||||
apps/docs/docs/integrations/jellyfin @manuel-rw
|
||||
packages/integrations/src/jellyfin @manuel-rw
|
||||
|
||||
# Jellyseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw
|
||||
apps/docs/docs/integrations/jellyseerr @manuel-rw
|
||||
packages/integrations/src/jellyseerr @manuel-rw
|
||||
|
||||
# LinuxServer.io - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5
|
||||
apps/docs/docs/integrations/linux-server-io @Aandree5
|
||||
packages/integrations/src/linuxserverio @Aandree5
|
||||
|
||||
# Navidrome - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/navidrome @ajnart
|
||||
packages/integrations/src/navidrome @ajnart
|
||||
|
||||
# Nextcloud - introduced in https://github.com/homarr-labs/homarr/pull/2501 by @manuel-rw
|
||||
apps/docs/docs/integrations/nextcloud @manuel-rw
|
||||
packages/integrations/src/nextcloud @manuel-rw
|
||||
|
||||
# npm - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/npm @Aandree5
|
||||
packages/integrations/src/npm @Aandree5
|
||||
|
||||
# ntfy - introduced in https://github.com/homarr-labs/homarr/pull/2900 by @itzTheMeow
|
||||
apps/docs/docs/integrations/ntfy @ajnart @manuel-rw
|
||||
packages/integrations/src/ntfy @ajnart @manuel-rw
|
||||
|
||||
# OpenMediaVault - introduced in https://github.com/homarr-labs/homarr/pull/1142 by @hillaliy
|
||||
apps/docs/docs/integrations/open-media-vault @hillaliy
|
||||
packages/integrations/src/openmediavault @hillaliy
|
||||
|
||||
# OPNsense - introduced in https://github.com/homarr-labs/homarr/pull/3424 by @Oupsman
|
||||
apps/docs/docs/integrations/opnsense @Oupsman
|
||||
packages/integrations/src/opnsense @Oupsman
|
||||
|
||||
# Overseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw
|
||||
apps/docs/docs/integrations/overseerr @manuel-rw
|
||||
packages/integrations/src/overseerr @manuel-rw
|
||||
|
||||
# Paperless-ngx - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/paperless-ngx @ajnart
|
||||
packages/integrations/src/paperless-ngx @ajnart
|
||||
|
||||
# PatchMon - introduced in https://github.com/homarr-labs/homarr/pull/6119 by @mrjoshuap
|
||||
apps/docs/docs/integrations/patchmon @mrjoshuap
|
||||
packages/integrations/src/patchmon @mrjoshuap
|
||||
packages/widgets/src/patchmon @mrjoshuap
|
||||
packages/api/src/router/widgets/patchmon.ts @mrjoshuap
|
||||
packages/request-handler/src/patchmon.ts @mrjoshuap
|
||||
|
||||
# PeaNUT - introduced in https://github.com/homarr-labs/homarr/pull/5862 by @afonsojramos
|
||||
apps/docs/docs/integrations/peanut @afonsojramos
|
||||
packages/integrations/src/peanut @afonsojramos
|
||||
|
||||
# Pi-hole - introduced in https://github.com/homarr-labs/homarr/pull/521 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/pi-hole @Meierschlumpf
|
||||
packages/integrations/src/pi-hole @Meierschlumpf
|
||||
|
||||
# Plex - introduced in https://github.com/homarr-labs/homarr/pull/1342 by @hillaliy
|
||||
apps/docs/docs/integrations/plex @hillaliy
|
||||
packages/integrations/src/plex @hillaliy
|
||||
|
||||
# Prowlarr - introduced in https://github.com/homarr-labs/homarr/pull/965 by @hillaliy
|
||||
apps/docs/docs/integrations/prowlarr @hillaliy
|
||||
packages/integrations/src/prowlarr @hillaliy
|
||||
|
||||
# Proxmox - introduced in https://github.com/homarr-labs/homarr/pull/1969 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/proxmox @Meierschlumpf
|
||||
packages/integrations/src/proxmox @Meierschlumpf
|
||||
|
||||
# Quay - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5
|
||||
apps/docs/docs/integrations/quay @Aandree5
|
||||
packages/integrations/src/quay @Aandree5
|
||||
|
||||
# search.ch - introduced in https://github.com/homarr-labs/homarr/pull/5180 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/search-ch @Meierschlumpf
|
||||
packages/integrations/src/search-ch @Meierschlumpf
|
||||
|
||||
# Speedtest Tracker - introduced in https://github.com/homarr-labs/homarr/pull/5363 by @kylegrieder
|
||||
apps/docs/docs/integrations/speedtest-tracker @kylegrieder
|
||||
packages/integrations/src/speedtest-tracker @kylegrieder
|
||||
|
||||
# Synology - introduced in https://github.com/homarr-labs/homarr/pull/6121 by @mrjoshuap
|
||||
apps/docs/docs/integrations/synology @mrjoshuap
|
||||
packages/integrations/src/synology @mrjoshuap
|
||||
|
||||
# Technitium DNS - introduced in https://github.com/homarr-labs/homarr/pull/5832 by @lopsch
|
||||
apps/docs/docs/integrations/technitium-dns @lopsch
|
||||
packages/integrations/src/technitium @lopsch
|
||||
|
||||
# Tracearr - introduced in https://github.com/homarr-labs/homarr/pull/5060 by @StrandedTurtle
|
||||
apps/docs/docs/integrations/tracearr @StrandedTurtle
|
||||
packages/integrations/src/tracearr @StrandedTurtle
|
||||
|
||||
# TrueNAS - introduced in https://github.com/homarr-labs/homarr/pull/3745 by @manuel-rw
|
||||
apps/docs/docs/integrations/truenas @manuel-rw @afonsojramos
|
||||
packages/integrations/src/truenas @manuel-rw @afonsojramos
|
||||
|
||||
# Umami - introduced in https://github.com/homarr-labs/homarr/pull/5362 by @febrile42
|
||||
apps/docs/docs/integrations/umami @febrile42
|
||||
packages/integrations/src/umami @febrile42
|
||||
|
||||
# UniFi Controller - introduced in https://github.com/homarr-labs/homarr/pull/2236 by @pitschi
|
||||
apps/docs/docs/integrations/unifi-controller @pitschi
|
||||
packages/integrations/src/unifi-controller @pitschi
|
||||
|
||||
# Unraid - introduced in https://github.com/homarr-labs/homarr/pull/4439 by @manuel-rw
|
||||
apps/docs/docs/integrations/unraid @manuel-rw
|
||||
packages/integrations/src/unraid @manuel-rw
|
||||
|
||||
# Uptime Kuma - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
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
|
||||
packages/api/src/router/widgets/traefik.ts @Nerdy-Technician
|
||||
packages/integrations/src/traefik @Nerdy-Technician
|
||||
packages/request-handler/src/traefik.ts @Nerdy-Technician
|
||||
packages/widgets/src/traefik @Nerdy-Technician
|
||||
|
||||
# Exempt Renovate-managed files (no owners)
|
||||
package.json
|
||||
**/package.json
|
||||
package-lock.json
|
||||
**/package-lock.json
|
||||
pnpm-lock.yaml
|
||||
**/pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
@@ -1,62 +1,100 @@
|
||||
# syntax=docker/dockerfile:1.25
|
||||
|
||||
FROM oven/bun:1.4.2-alpine AS bun-tool
|
||||
|
||||
FROM node:24.18.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
ARG TARGETPLATFORM
|
||||
WORKDIR /app
|
||||
COPY --from=bun-tool /usr/local/bin/bun /usr/local/bin/bun
|
||||
# Compiler tools stay in the builder for optional native dependency fallbacks.
|
||||
RUN apk add --no-cache libstdc++ python3 make g++
|
||||
COPY bun.lock bunfig.toml package.json ./
|
||||
# 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 .npmrc pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||
COPY patches ./patches
|
||||
COPY --parents ./apps/*/package.json ./packages/*/package.json ./tooling/*/package.json ./
|
||||
RUN --mount=type=cache,id=homarr-bun-cache,target=/root/.bun/install/cache,sharing=locked \
|
||||
npm_config_nodedir=/usr/local bun install --frozen-lockfile --concurrent-scripts=1
|
||||
# @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
|
||||
# content-addressable store is populated). By temporarily switching to the
|
||||
# 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,sharing=locked \
|
||||
pnpm config set store-dir /pnpm/store && \
|
||||
sed -i 's/nodeLinker: hoisted/nodeLinker: isolated/' pnpm-workspace.yaml && \
|
||||
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 . .
|
||||
|
||||
ARG SKIP_ENV_VALIDATION='true'
|
||||
ARG CI='true'
|
||||
ARG DISABLE_REDIS_LOGS='true'
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
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 \
|
||||
--mount=type=cache,id=homarr-next-build-${TARGETPLATFORM},target=/app/apps/nextjs/.next/cache,sharing=locked \
|
||||
--mount=type=cache,id=homarr-turbo-${TARGETPLATFORM},target=/app/.turbo,sharing=locked \
|
||||
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}/musl/node-24.18.0" \
|
||||
bun run turbo run build --filter=@homarr/nextjs... --filter=@homarr/cli
|
||||
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" \
|
||||
pnpm turbo build --filter=@homarr/nextjs... --filter=@homarr/cli
|
||||
|
||||
FROM alpine:3.24.1 AS runner
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
COPY --from=base /usr/local/bin/node /usr/local/bin/node
|
||||
# envsubst, privilege drop and AUTH_SECRET generation are used by the entrypoint.
|
||||
RUN apk add --no-cache libstdc++ ca-certificates redis nginx bash gettext su-exec openssl && \
|
||||
mkdir -p /appdata /var/cache/nginx /var/log/nginx /var/lib/nginx \
|
||||
/run/nginx /etc/nginx/templates /etc/nginx/ssl/certs && \
|
||||
touch /run/nginx/nginx.pid
|
||||
|
||||
# gettext is required for envsubst, openssl for generating AUTH_SECRET, su-exec for running application as non-root
|
||||
RUN apk add --no-cache redis nginx bash gettext su-exec openssl
|
||||
RUN mkdir /appdata
|
||||
VOLUME /appdata
|
||||
|
||||
# Enable homarr cli
|
||||
COPY --from=builder /app/packages/cli/cli.cjs /app/apps/cli/cli.cjs
|
||||
# Bundled CLI/migrations resolve the native binding from the application root.
|
||||
RUN echo $'#!/bin/bash\ncd /app/apps/cli && node ./cli.cjs "$@"' > /usr/bin/homarr
|
||||
RUN chmod +x /usr/bin/homarr
|
||||
|
||||
# Don't run production as root
|
||||
RUN mkdir -p /var/cache/nginx && \
|
||||
mkdir -p /var/log/nginx && \
|
||||
mkdir -p /var/lib/nginx && \
|
||||
touch /run/nginx/nginx.pid && \
|
||||
mkdir -p /etc/nginx/templates /etc/nginx/ssl/certs
|
||||
|
||||
COPY --from=builder /app/apps/nextjs/next.config.ts .
|
||||
COPY --from=builder /app/apps/nextjs/package.json .
|
||||
COPY --from=builder /app/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||
|
||||
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
|
||||
RUN printf '#!/bin/sh\ncd /app/apps/cli && exec node ./cli.cjs "$@"\n' > /usr/bin/homarr && \
|
||||
chmod +x /usr/bin/homarr
|
||||
|
||||
COPY --from=builder /app/packages/db/migrations ./db/migrations
|
||||
# Ship only Next's traced production dependencies and application assets.
|
||||
COPY --from=builder /app/apps/nextjs/.output/standalone ./
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder /app/apps/nextjs/.next/standalone ./
|
||||
COPY --from=builder /app/apps/nextjs/.next/static ./apps/nextjs/.next/static
|
||||
COPY --from=builder /app/apps/nextjs/public ./apps/nextjs/public
|
||||
COPY scripts/run.sh ./run.sh
|
||||
COPY --chmod=755 scripts/entrypoint.sh ./entrypoint.sh
|
||||
COPY packages/redis/redis.conf /app/redis.conf
|
||||
COPY nginx.conf /etc/nginx/templates/nginx.conf
|
||||
|
||||
|
||||
ENV DB_URL='/appdata/db/db.sqlite'
|
||||
ENV DB_DIALECT='sqlite'
|
||||
ENV DB_DRIVER='better-sqlite3'
|
||||
ENV AUTH_PROVIDERS='credentials'
|
||||
ENV REDIS_IS_EXTERNAL='false'
|
||||
ENV NODE_ENV='production'
|
||||
|
||||
EXPOSE 7575
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
CMD ["sh", "run.sh"]
|
||||
@@ -1,33 +0,0 @@
|
||||
# Product
|
||||
|
||||
## Register
|
||||
|
||||
brand
|
||||
|
||||
## Users
|
||||
|
||||
Self-hosted Homarr users who need to evaluate the project, install it, connect services, find extensions, and reference its APIs. They move between the homepage, documentation, Workshop, API reference, and project updates as one continuous experience.
|
||||
|
||||
## Product Purpose
|
||||
|
||||
The site explains what Homarr does and gives users reliable paths to install, configure, extend, and operate it. Success means users can identify where they are, move between major surfaces without relearning the navigation, and read technical content comfortably.
|
||||
|
||||
## Brand Personality
|
||||
|
||||
Practical, technical, welcoming.
|
||||
|
||||
## Anti-references
|
||||
|
||||
Avoid disconnected microsites, marketing-heavy copy, ornamental dashboards, hidden navigation, and dense long-form pages without visible structure.
|
||||
|
||||
## Design Principles
|
||||
|
||||
- Keep one recognizable Homarr identity across every surface.
|
||||
- Let navigation topology match the task while keeping labels and behavior consistent.
|
||||
- Prioritize readable line lengths, strong heading hierarchy, and visible document structure.
|
||||
- Make primary paths obvious without adding decorative UI.
|
||||
- Preserve useful functionality on mobile, keyboard navigation, and reduced-motion settings.
|
||||
|
||||
## Accessibility & Inclusion
|
||||
|
||||
Target WCAG 2.2 AA. Maintain keyboard access, visible focus states, sufficient contrast, semantic landmarks, touch-friendly controls, responsive layouts, and reduced-motion behavior.
|
||||
@@ -5,7 +5,7 @@
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.bun-cache/
|
||||
.pnpm-store/
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
@@ -7,12 +7,3 @@ This version has breaking changes — APIs, conventions, and file structure may
|
||||
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 -->
|
||||
|
||||
## Fumadocs workflow
|
||||
|
||||
- Start with `README.md`, `src/lib/source.ts`, `source.config.ts`, and the matching route under `src/app/`.
|
||||
- Read current [Fumadocs documentation](https://fumadocs.dev/docs) with Context7 before changing framework APIs. Check the installed version and types; latest examples may use a different search adapter.
|
||||
- Preserve static export: search uses `staticGET` with the matching static client. Do not introduce a request-time search server into this app.
|
||||
- Keep integration/widget metadata in the existing typed definitions. Check both rendered HTML and processed Markdown when changing MDX components; JSX alone does not guarantee readable search or AI output.
|
||||
- Run the README validation commands after content or navigation changes. Search results and table-of-contents links must resolve to rendered anchors.
|
||||
- Kapa is an independent hosted assistant. Keep its keyboard shortcut disabled so it does not override docs search; follow the README for its build configuration and crawler setup.
|
||||
@@ -0,0 +1 @@
|
||||
@AGENTS.md
|
||||
@@ -7,59 +7,25 @@ The source for [homarr.dev](https://homarr.dev), built with Next.js and Fumadocs
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
bun run dev:docs
|
||||
pnpm dev:docs
|
||||
```
|
||||
|
||||
The site runs on `http://127.0.0.1:3003`. To use a local Workshop API:
|
||||
|
||||
```bash
|
||||
WORKSHOP_API_URL=http://127.0.0.1:8090 bun run dev:docs
|
||||
WORKSHOP_API_URL=http://127.0.0.1:8090 pnpm dev:docs
|
||||
```
|
||||
|
||||
## Validate
|
||||
|
||||
```bash
|
||||
bun run --filter @homarr/docs typecheck
|
||||
bun run --filter @homarr/docs build
|
||||
bun run --filter @homarr/docs validate:links
|
||||
bun run --filter @homarr/docs verify:search
|
||||
bun run --filter @homarr/docs verify:seo
|
||||
pnpm --filter @homarr/docs typecheck
|
||||
pnpm --filter @homarr/docs build
|
||||
pnpm --filter @homarr/docs validate:links
|
||||
```
|
||||
|
||||
The static export is written to `out/`. The build validates docs pages and typed metadata for integrations and widgets
|
||||
that declare a documentation slug. Set the slug to `null` when a page would add no useful information for advanced users.
|
||||
CI enforces coverage alongside schema drift and export validation. The link check validates internal Markdown and MDX
|
||||
links and rendered anchors against the export.
|
||||
The search check exercises the exported index with title, heading, and body queries and verifies result destinations.
|
||||
The SEO check validates canonical URLs, descriptions, social metadata, heading structure, and sitemap coverage in the
|
||||
exported HTML. These tags are generated at build time; set `HOMARR_WEBSITE_URL` before building for a different origin.
|
||||
|
||||
## Preview the static export with PocketBase
|
||||
|
||||
After building the docs, run from the repository root:
|
||||
|
||||
```bash
|
||||
sh apps/workshop/preview-docs.sh
|
||||
```
|
||||
|
||||
Open `http://127.0.0.1:8093/docs/`. This starts an isolated PocketBase container with temporary local data and serves
|
||||
the export directly, including search, Markdown, and the custom 404 page. Workshop browser requests use the configured
|
||||
backend; a trailing `/api/` is removed because the PocketBase SDK appends its API paths. The preview does not copy or
|
||||
migrate another database. Set `WORKSHOP_API_URL` to a remote PocketBase origin when testing against one;
|
||||
`WORKSHOP_REMOTE_API_URL` can override the server-side metadata source independently. Direct item links then use that
|
||||
source for titles and social metadata. Missing remote items return 404; an unavailable backend returns 503.
|
||||
|
||||
Verify public item pages without changing the remote database:
|
||||
|
||||
```bash
|
||||
REMOTE_WORKSHOP_URL=https://workshop.example.com
|
||||
WORKSHOP_TEST_URL=http://127.0.0.1:8093 WORKSHOP_REMOTE_API_URL="$REMOTE_WORKSHOP_URL" \
|
||||
bun apps/workshop/tests/remote-workshop.integration.mjs
|
||||
```
|
||||
|
||||
Stop it with `docker stop homarr-docs-static-preview`. Rebuild and restart the preview after changing source files;
|
||||
startup recreates the runtime configuration. `DOCS_PREVIEW_PORT`, `DOCS_PREVIEW_HOST`, and `DOCS_PREVIEW_NAME` override
|
||||
the defaults. Set the host to this machine's Tailscale address to access it from your other devices.
|
||||
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.
|
||||
|
||||
## Write content
|
||||
|
||||
@@ -69,9 +35,8 @@ Integration and widget folders pair typed `index.ts` metadata with `index.mdx` c
|
||||
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.
|
||||
|
||||
Update docs in the same pull request only when a smart, advanced user cannot infer the changed behavior from the UI or
|
||||
generated API schema. Routine or self-explanatory changes need no docs update. See the
|
||||
[documentation-sync skill](../../.agents/skills/documentation-sync/SKILL.md) for target paths.
|
||||
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.
|
||||
|
||||
## Published formats
|
||||
|
||||
@@ -81,88 +46,7 @@ generated API schema. Routine or self-explanatory changes need no docs update. S
|
||||
- `/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
|
||||
- `/llms.mdx/api-reference/.../content.md` — operation parameters, bodies, responses, and authentication
|
||||
- `/blog/rss.xml` — RSS feed for project posts
|
||||
- `/search?q=...` — opens documentation search with the supplied query, including existing Homarr search-engine links
|
||||
|
||||
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. Markdown exports and search resolve
|
||||
integration credentials and widget defaults from the same typed metadata used by the pages.
|
||||
The search dialog and its client-side engine load when search is first opened. Fumadocs caches the downloaded index
|
||||
for subsequent queries. The docs index, individual Markdown pages, and full corpus share the `llms()` renderer.
|
||||
API operations are indexed alongside docs and exported as Markdown from the same OpenAPI schema. The reference uses
|
||||
Fumadocs with Scalar: select an instance URL and API key, then explicitly send a request. Authentication is not
|
||||
persisted in local storage. Browser access requires the documented REST CORS configuration.
|
||||
|
||||
The Custom JSX guide includes a lazily loaded, editable example using the application renderer and real component
|
||||
registry. Its bindings work locally; configured widget queries and actions do not run.
|
||||
|
||||
The getting-started overview and complete Custom JSX component catalog are also included, with property types,
|
||||
binding contracts, and blocked capabilities taken from the published catalog JSON.
|
||||
|
||||
Published Docusaurus `/docs/category/...` addresses remain available as static compatibility pages. They forward to
|
||||
the corresponding overview or developer setup guide, preserving query strings and anchors, and provide a direct
|
||||
link when JavaScript is disabled. Their canonical metadata points to the destination; compatibility pages are
|
||||
excluded from search and the sitemap.
|
||||
|
||||
## Fumadocs upgrade notes
|
||||
|
||||
The September 2026 update moves Core/UI from 16.15.4 to 16.15.12, MDX from 15.4.0 to 15.4.3, and OpenAPI from
|
||||
11.3.5 to 12.0.1. Review the upstream [Core](https://github.com/fuma-nama/fumadocs/blob/dev/packages/core/CHANGELOG.md),
|
||||
[UI](https://github.com/fuma-nama/fumadocs/blob/dev/packages/radix-ui/CHANGELOG.md),
|
||||
[MDX](https://github.com/fuma-nama/fumadocs/blob/dev/packages/mdx/CHANGELOG.md), and
|
||||
[OpenAPI](https://github.com/fuma-nama/fumadocs/blob/dev/packages/openapi/CHANGELOG.md) changelogs when upgrading.
|
||||
|
||||
- Core/UI and MDX now declare side effects for better tree shaking. UI also fixes tabs reverting to the URL hash.
|
||||
- Core's `llms({ renderPage })` API provides the shared `index()`, `page()`, and `full()` exports.
|
||||
- OpenAPI 12 replaces several custom renderer options and hooks. This site uses `createOpenAPIPage()` and
|
||||
`getOpenAPIPageProps()` without those removed overrides; the built-in renderer retains all request languages.
|
||||
The Scalar adapter receives an equivalent nonrecursive union for unconstrained JSON values to avoid recursive
|
||||
schema expansion during rendering. The generated public OpenAPI document remains unchanged.
|
||||
- MDX 15.4.3 fixes duplicate compilation output when its experimental build cache is enabled. We leave that cache
|
||||
disabled: its current key checks source content without accounting for all compiler configuration changes.
|
||||
- The existing macro collections, processed-Markdown component adapters, and static search remain supported.
|
||||
Typed version roots and server-side MCP tools are available upstream but are not needed by this static site.
|
||||
|
||||
## Kapa AI
|
||||
|
||||
The compact Homarr mascot in the bottom-right opens **Ask AI**. It slowly spins when idle and gives a subtle claw movement and wink on hover. Animation respects reduced motion.
|
||||
|
||||
The launcher uses Homarr's existing public Kapa Website ID. The root layout loads the widget once across
|
||||
client-side navigation and follows the docs light or dark theme. **Search** and `Ctrl+K` / `Cmd+K` always use Fumadocs search, including when Kapa is unavailable.
|
||||
|
||||
Set `KAPA_WEBSITE_ID` at build time to use a different Kapa Website Widget integration. An explicitly empty value
|
||||
disables the widget, for example `KAPA_WEBSITE_ID= bun run --filter @homarr/docs build`. This is a public integration ID
|
||||
included in exported pages, not a Kapa API key. Rebuild the static export after changing it.
|
||||
|
||||
In the Kapa dashboard, confirm the integration is live and enable the production and preview domains. Restoring the
|
||||
script does not verify that the historical integration is still active. Follow the
|
||||
[Kapa widget setup](https://docs.kapa.ai/integrations/website-widget/quickstart) for domain restrictions and CSP settings.
|
||||
|
||||
After changing the documentation structure, review the Website Crawl source in Kapa: use the deployed `/docs` URL as
|
||||
the start URL and preview `main` as the content selector. Replace any old Docusaurus-specific selector and check that
|
||||
titles, headings, code samples, and integration details appear in the extracted Markdown. Docs are exported as HTML;
|
||||
the site also publishes `/sitemap.xml` and permits crawling in `/robots.txt`. The `/llms.txt` exports do not configure
|
||||
Kapa ingestion automatically. See [Kapa source setup](https://docs.kapa.ai/getting-started/index-your-first-source).
|
||||
|
||||
Before publishing, check that Ask AI opens after navigating between pages, ask a documentation question, and verify
|
||||
its citations point to the current docs. In a separate check, block `widget.kapa.ai` and confirm search still works.
|
||||
|
||||
## Release verification
|
||||
|
||||
The production image compiles the docs itself; it does not depend on a host `out/` directory.
|
||||
Build-time `HOMARR_WEBSITE_URL` controls canonical metadata. Runtime URL overrides configure Workshop connections
|
||||
but do not rewrite already-exported canonical URLs.
|
||||
|
||||
PostHog records SPA pageviews, installation clicks, searches with no results (including the search query), and Workshop
|
||||
content downloads and submission starts/completions through `hog.homarr.dev`. Downloads include JSON files and
|
||||
successful JSON/CSS clipboard copies. All events carry `site` and `source_path`. Autocapture, page-leave events, and
|
||||
session replay are disabled; tracked URL query strings and fragments are removed. Localhost and `?analytics_test`
|
||||
traffic carries `verification=true`; exclude it from production reports.
|
||||
|
||||
Carbon loads one visible placement after the desktop TOC, below API examples, or below content on other layouts.
|
||||
The homepage is excluded at every viewport size. Navigation reloads the ad script; resizing changes placement only when crossing its breakpoint. Ad blockers
|
||||
or no-fill responses must not prevent content, navigation, or search from working.
|
||||
|
||||
To preview Carbon locally, start docs with `NEXT_PUBLIC_ENABLE_CARBON_ADS=true bun run dev:docs`. Production builds
|
||||
enable ads automatically. When the TOC collapses into a toggle, the ad appears below the content before “Edit this page”.
|
||||
runs locally in the browser; it does not depend on an external crawler.
|
||||
@@ -32,11 +32,11 @@ of an in-memory message bus and websockets to deliver data near realtime to you.
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
## Breaking changes
|
||||
<Alert type="warning">
|
||||
:::warning
|
||||
|
||||
Please consult the breaking changes list & upgrade guide if you used third-party installation methods.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
- Docker Image
|
||||
- Removed support for ``arm/v7`` -> upgrade to newer architectures as an alternative
|
||||
|
||||
@@ -22,17 +22,17 @@ Plan to recreate your apps, integrations, and boards manually in the new install
|
||||
|
||||
We highly recommend to create a parallel installation of Homarr 1.0.0.
|
||||
So if something with for example the import does not quite work, the data in the old installation is still available.
|
||||
To do this you can follow the appropriate [installation guide](/docs/getting-started) for your system.
|
||||
To do this you can follow the appropriate [installation guide](https://homarr.dev/docs/category/installation-1) for your system.
|
||||
Please note that if you are using docker you need to specify a diffrent external port for the new installation as the old one had the same port.
|
||||
So in your compose file the ports would be `7576:7575` instead of `7575:7575`.
|
||||
|
||||
## Import your data
|
||||
|
||||
<Alert type="warning" title="Legacy guide">
|
||||
:::warning Legacy guide
|
||||
|
||||
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.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
## Remove the old installation
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
@@ -1,64 +1,31 @@
|
||||
---
|
||||
title: Homarr 2.0.0 - Workshop, Custom Widgets and a rebuilt board
|
||||
date: 2026-10-02
|
||||
date: 2026-09-03
|
||||
authors:
|
||||
- ajnart
|
||||
tags: [homarr, update, version, assistant, custom widgets, dashboards, docker, mcp]
|
||||
description: Homarr 2.0 introduces Custom Widgets and Workshop, rebuilt board editing, Assistant and MCP tooling that turns Homarr into the front door to your homelab, responsive layouts, onboarding, and more.
|
||||
image: ./img/homarr-v2-recap.webp
|
||||
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.
|
||||
|
||||
If you're thinking, “I ain't reading all dat,” don't worry. We made a video for your short attention span.
|
||||
|
||||
<figure style={{ margin: "1.5rem 0" }}>
|
||||
<video
|
||||
controls
|
||||
playsInline
|
||||
preload="metadata"
|
||||
poster={require("./img/homarr-v2-recap.webp").default.src}
|
||||
width={1920}
|
||||
height={1080}
|
||||
style={{ width: "100%", height: "auto", borderRadius: "0.5rem" }}
|
||||
aria-label="Homarr 2.0 feature overview."
|
||||
>
|
||||
<source src="https://media.homarr.dev/videos/homarr-2.0/homarr-v2-showreel.mp4" type="video/mp4" />
|
||||
</video>
|
||||
<figcaption>Homarr 2.0 in under two minutes.</figcaption>
|
||||
</figure>
|
||||
|
||||
Homarr 2.0 builds on the dashboard you already use. Here is what changed.
|
||||
Homarr 2.0 is an upgrade, not a brand-new app wearing the same logo. Here is what actually changed.
|
||||
|
||||

|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
|
||||
**Back up before upgrading:** database migrations run automatically at startup and change the existing database in place. Follow the [upgrade and rollback guidance](/docs/getting-started/installation/docker#update), including the note on board sizing. **MySQL users must convert before upgrading**, as explained below. Workshop uses the production service by default, with no preview URL configuration required.
|
||||
:::warning Back up before upgrading
|
||||
|
||||
## Database support in v2
|
||||
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.
|
||||
|
||||
Homarr v2 drops MySQL support and keeps SQLite (the default) and PostgreSQL. Supporting fewer database engines lets us focus migration testing on those two. Existing MySQL installations have a standalone conversion script to move their data to SQLite before upgrading.
|
||||
|
||||
<details>
|
||||
<summary>Using MySQL? How to migrate</summary>
|
||||
|
||||
1. Update to the latest stable v1 release, then back up your MySQL database, appdata, and encryption key.
|
||||
2. Stop Homarr and run the MySQL-to-SQLite converter. It checks the copied data and leaves MySQL unchanged.
|
||||
3. Start v2 with the converted database, your existing appdata, and the **same `SECRET_ENCRYPTION_KEY`**.
|
||||
|
||||
The converter is verified against the v1.77.1 schema and rejects unknown schemas. See the [migration guide](/docs/advanced/mysql-to-sqlite) for commands, supported inputs, and rollback instructions.
|
||||
|
||||
</details>
|
||||
:::
|
||||
|
||||
## Custom Widgets v2 and Workshop
|
||||
|
||||
@@ -70,11 +37,9 @@ Custom Widgets v2 lets you build widgets from JSX, API requests, actions and typ
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -82,17 +47,11 @@ A `homarr-custom-widget-v2` definition can use several fixed-origin API sources.
|
||||
|
||||

|
||||
|
||||
<Alert type="warning" title="Workshop is moderated">
|
||||
:::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.
|
||||
|
||||
</Alert>
|
||||
|
||||
### Docusaurus + PocketBase for the docs and Workshop
|
||||
|
||||
Our docs and Workshop now share a Docusaurus + PocketBase deployment. Docusaurus compiles the website into static HTML, CSS and JavaScript at build time. A Go service built on PocketBase serves those files and provides the Workshop backend for accounts, submissions, votes, comments and moderation.
|
||||
|
||||
The production image packages both together under the same origin. Node.js is used to build the website, but is not needed to serve it in production. Workshop's persistent data lives separately in `/pb_data`, so replacing the image does not replace community content. This is the website and Workshop architecture; your self-hosted Homarr dashboard remains a separate application.
|
||||
:::
|
||||
|
||||
### The ultimate Homarr update
|
||||
|
||||
@@ -104,110 +63,16 @@ We have called this the **"ultimate Homarr update"**. Not because work stops, bu
|
||||
|
||||
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.
|
||||
|
||||
<figure style={{ margin: "1.5rem 0" }}>
|
||||
<video
|
||||
controls
|
||||
muted
|
||||
playsInline
|
||||
preload="none"
|
||||
poster={require("./img/drag-and-resize-poster.webp").default.src}
|
||||
width={1770}
|
||||
height={706}
|
||||
style={{ width: "100%", height: "auto", borderRadius: "0.5rem" }}
|
||||
aria-label="Move and resize a tile with the new drag-and-drop system."
|
||||
>
|
||||
<source src="https://media.homarr.dev/videos/homarr-2.0/drag-and-resize.mp4" type="video/mp4" />
|
||||
<track kind="captions" src="/media/homarr-2.0/drag-and-resize.vtt" srcLang="en" label="English" />
|
||||
</video>
|
||||
<figcaption>Move and resize a tile with the new drag-and-drop system.</figcaption>
|
||||
</figure>
|
||||

|
||||
|
||||
### 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. Move a Container with its contents, or collapse it when you need the space.
|
||||
|
||||
<figure style={{ margin: "1.5rem 0" }}>
|
||||
<video
|
||||
controls
|
||||
muted
|
||||
playsInline
|
||||
preload="none"
|
||||
poster={require("./img/containers-poster.webp").default.src}
|
||||
width={1770}
|
||||
height={716}
|
||||
style={{ width: "100%", height: "auto", borderRadius: "0.5rem" }}
|
||||
aria-label="Move, collapse and reopen a Container without rearranging its contents."
|
||||
>
|
||||
<source src="https://media.homarr.dev/videos/homarr-2.0/containers.mp4" type="video/mp4" />
|
||||
<track kind="captions" src="/media/homarr-2.0/containers.vtt" srcLang="en" label="English" />
|
||||
</video>
|
||||
<figcaption>Move, collapse and reopen a Container without rearranging its contents.</figcaption>
|
||||
</figure>
|
||||
|
||||
Hold <kbd>Ctrl</kbd> or <kbd>Command</kbd> while clicking to select several items, then use **Move to** to put them in a Container.
|
||||
|
||||
<figure style={{ margin: "1.5rem 0" }}>
|
||||
<video
|
||||
controls
|
||||
muted
|
||||
playsInline
|
||||
preload="none"
|
||||
poster={require("./img/multi-select-poster.webp").default.src}
|
||||
width={1770}
|
||||
height={716}
|
||||
style={{ width: "100%", height: "auto", borderRadius: "0.5rem" }}
|
||||
aria-label="Command-click two apps, then move them into the same Container."
|
||||
>
|
||||
<source src="https://media.homarr.dev/videos/homarr-2.0/multi-select.mp4" type="video/mp4" />
|
||||
<track kind="captions" src="/media/homarr-2.0/multi-select.vtt" srcLang="en" label="English" />
|
||||
</video>
|
||||
<figcaption>Command-click two apps, then move them into the same Container.</figcaption>
|
||||
</figure>
|
||||
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.
|
||||
|
||||
### Fixed sidebars
|
||||
|
||||
Keep app shortcuts or widgets in a sidebar while the main board scrolls. Enable a sidebar and choose its width in board settings.
|
||||
|
||||
<figure style={{ margin: "1.5rem 0" }}>
|
||||
<video
|
||||
controls
|
||||
muted
|
||||
playsInline
|
||||
preload="none"
|
||||
poster={require("./img/sidebar-settings-poster.webp").default.src}
|
||||
width={1600}
|
||||
height={700}
|
||||
style={{ width: "100%", height: "auto", borderRadius: "0.5rem" }}
|
||||
aria-label="Enable the right sidebar and set its width."
|
||||
>
|
||||
<source src="https://media.homarr.dev/videos/homarr-2.0/sidebar-settings.mp4" type="video/mp4" />
|
||||
<track kind="captions" src="/media/homarr-2.0/sidebar-settings.vtt" srcLang="en" label="English" />
|
||||
</video>
|
||||
<figcaption>Enable the right sidebar and set its width.</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure style={{ margin: "1.5rem 0" }}>
|
||||
<video
|
||||
controls
|
||||
muted
|
||||
playsInline
|
||||
preload="none"
|
||||
poster={require("./img/fixed-sidebar-poster.webp").default.src}
|
||||
width={1580}
|
||||
height={640}
|
||||
style={{ width: "100%", height: "auto", borderRadius: "0.5rem" }}
|
||||
aria-label="The board scrolls; the app shortcuts stay in place."
|
||||
>
|
||||
<source src="https://media.homarr.dev/videos/homarr-2.0/fixed-sidebar.mp4" type="video/mp4" />
|
||||
<track kind="captions" src="/media/homarr-2.0/fixed-sidebar.vtt" srcLang="en" label="English" />
|
||||
</video>
|
||||
<figcaption>The board scrolls; the app shortcuts stay in place.</figcaption>
|
||||
</figure>
|
||||
|
||||
## Homarr Assistant
|
||||
|
||||
### Work with live Homarr data
|
||||
@@ -220,70 +85,17 @@ Open it with <kbd>Shift</kbd> + <kbd>A</kbd>. <kbd>Cmd</kbd>/<kbd>Ctrl</kbd> + <
|
||||
|
||||
### The Homarr provider
|
||||
|
||||
<Alert type="note">
|
||||
:::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.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
### 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 most powerful one gets its own section.
|
||||
|
||||
## The front door to your homelab
|
||||
|
||||
Homarr already knows how to reach your services: their URLs, API keys and auth headers. v2 adds one API endpoint that
|
||||
lets you use them without ever seeing them.
|
||||
|
||||
`POST /api/integrations/request` calls **any HTTP endpoint** on a saved integration. It is also available as tRPC
|
||||
`integration.request` and MCP `integration_request`. You send the integration, a method and a path, authenticated with
|
||||
your Homarr API key or session. Homarr checks your access, adds the credentials it stores and hands back the service's
|
||||
answer:
|
||||
|
||||

|
||||
|
||||
The secrets stay in Homarr. API keys, passwords and tokens are stored encrypted, no API returns them, and Homarr redacts
|
||||
them from responses. If a service's API can do something, you can do it through Homarr, even when Homarr has no button
|
||||
for it:
|
||||
|
||||
- **Custom Widgets** bind a saved integration as a [source](/docs/management/integrations#beyond-native-widgets) and call
|
||||
its endpoints, so you never paste credentials into widget code.
|
||||
- **Agents** act on your behalf through MCP. The agent reads the service's official API docs, tells you which integration
|
||||
it will use and what the call changes, then reports the result.
|
||||
|
||||
It stays inside the lines:
|
||||
|
||||
- Paths stay within the saved integration URL, and redirects are rejected.
|
||||
- Every request needs **full integration access**. `DELETE` needs explicit confirmation, and in Assistant every request,
|
||||
even a `GET`, waits for your approval.
|
||||
|
||||
### One prompt, seven services
|
||||
|
||||
We asked a local model, through Homarr's MCP server:
|
||||
|
||||
> Could you install a notification provider and then set it up everywhere it's supported in my installed integrations?
|
||||
|
||||
It checked the Docker endpoint, asked which provider to use (it recommended ntfy), deployed ntfy and reported back:
|
||||
|
||||
| Service | How | Verified |
|
||||
| ----------- | ------------------------------------------------------------------------- | ------------------------------------------ |
|
||||
| Sonarr | Native ntfy connection for grabs, downloads, upgrades and health | Test fired ✓ |
|
||||
| Radarr | Native ntfy connection, plus movie added | Test fired ✓ |
|
||||
| Seerr | Webhook with a JSON payload for readable titles | Fires on request, approve, available, fail |
|
||||
| SABnzbd | 5.1.3 has no ntfy support, so a completion script on the default category | Dry run ✓ |
|
||||
| qBittorrent | `[AutoRun]` completion hook in `qBittorrent.conf` | Persisted after restart |
|
||||
| Bazarr | ntfy provider enabled for subtitle events | Restarted ✓ |
|
||||
| Beszel | Webhook with status, CPU, memory and disk alerts on all eight systems | 29 alerts created |
|
||||
|
||||
Two test notifications later, the phone buzzed. It cost $0: Qwen3.8-27B ran locally through MLX. The only side effect
|
||||
was warm feet.
|
||||
|
||||
See [authenticated integration requests](/docs/management/mcp#authenticated-integration-requests) and the
|
||||
[API contract](/docs/management/api#call-any-integration-api-endpoint) for selectors, limits and the services that
|
||||
cannot use it.
|
||||
|
||||
## The rest (there is a lot)
|
||||
|
||||
@@ -297,23 +109,7 @@ A six-stage setup studio handles the first admin, core settings, Docker discover
|
||||
|
||||
Boards now live in a searchable gallery with keyboard navigation. Open it from the header or press <kbd>Shift</kbd> + <kbd>C</kbd>.
|
||||
|
||||
<figure style={{ margin: "1.5rem 0" }}>
|
||||
<video
|
||||
controls
|
||||
muted
|
||||
playsInline
|
||||
preload="none"
|
||||
poster={require("./img/board-switcher-poster.webp").default.src}
|
||||
width={1920}
|
||||
height={778}
|
||||
style={{ width: "100%", height: "auto", borderRadius: "0.5rem" }}
|
||||
aria-label="Open the switcher, filter by name and press Enter to open a board."
|
||||
>
|
||||
<source src="https://media.homarr.dev/videos/homarr-2.0/board-switcher.mp4" type="video/mp4" />
|
||||
<track kind="captions" src="/media/homarr-2.0/board-switcher.vtt" srcLang="en" label="English" />
|
||||
</video>
|
||||
<figcaption>Open the switcher, filter by name and press Enter to open a board.</figcaption>
|
||||
</figure>
|
||||

|
||||
|
||||
### Command menu and keybinds
|
||||
|
||||
@@ -322,17 +118,10 @@ keybinds cover Assistant, editing, selection and advanced widget views.
|
||||
|
||||
### Customizable header
|
||||
|
||||
Add direct links to boards and shortcuts to features such as search, Docker and settings. Put them on the left, center or right, and change their order.
|
||||
|
||||

|
||||
|
||||
<details>
|
||||
<summary>Arrange it in the Header studio</summary>
|
||||
Pick which buttons appear, put them on the left, center or right, and change their order.
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
### 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.
|
||||
@@ -341,65 +130,7 @@ Change the name, logo, favicon, colors, corner styles and login page. Global CSS
|
||||
|
||||
### Widgets
|
||||
|
||||
Add content without leaving the board. Settings have live previews. Weather and Clock were redesigned; Air Quality, Countdown and Timer are new.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
<figure style={{ margin: "1.5rem 0" }}>
|
||||
<video
|
||||
controls
|
||||
muted
|
||||
playsInline
|
||||
preload="none"
|
||||
poster={require("./img/timer-poster.webp").default.src}
|
||||
width={950}
|
||||
height={500}
|
||||
style={{ width: "100%", height: "auto", borderRadius: "0.5rem" }}
|
||||
aria-label="Start, pause and reset Timer without leaving the board."
|
||||
>
|
||||
<source src="https://media.homarr.dev/videos/homarr-2.0/timer.mp4" type="video/mp4" />
|
||||
<track kind="captions" src="/media/homarr-2.0/timer.vtt" srcLang="en" label="English" />
|
||||
</video>
|
||||
<figcaption>Start, pause and reset Timer without leaving the board.</figcaption>
|
||||
</figure>
|
||||
|
||||
Supported widgets also have **advanced views**. Hover over a widget and hold <kbd>Shift</kbd> for **half a second** to open one.
|
||||
|
||||
<figure style={{ margin: "1.5rem 0" }}>
|
||||
<video
|
||||
controls
|
||||
muted
|
||||
playsInline
|
||||
preload="none"
|
||||
poster={require("./img/advanced-widget-poster.webp").default.src}
|
||||
width={864}
|
||||
height={600}
|
||||
style={{ width: "100%", height: "auto", borderRadius: "0.5rem" }}
|
||||
aria-label="Hold Shift for half a second over Downloads to open its advanced view."
|
||||
>
|
||||
<source src="https://media.homarr.dev/videos/homarr-2.0/advanced-widget.mp4" type="video/mp4" />
|
||||
<track kind="captions" src="/media/homarr-2.0/advanced-widget.vtt" srcLang="en" label="English" />
|
||||
</video>
|
||||
<figcaption>Hold Shift for half a second over Downloads to open its advanced view.</figcaption>
|
||||
</figure>
|
||||
|
||||
### Statistics and 31 new integrations
|
||||
|
||||
The new [Statistics widget](/docs/widgets/stats) combines fields from different services in one grid: Sonarr shows beside Paperless-ngx documents, Navidrome songs or Homebox inventory. Choose cards, compact rows or tables grouped by integration. Click a metric for related fields, or open **Advanced view** from the context menu to see every available field from the selected sources.
|
||||
|
||||
Add your services in **Management → Integrations**, then select them and their metrics in Statistics settings. Labels, order and abbreviated values are configurable; the widget's local view controls let you try a different arrangement without changing the shared board. The demo board includes mocked cards, rows and tables.
|
||||
|
||||
This release adds:
|
||||
|
||||
- **Media and libraries:** Autobrr, FileFlows, Jackett, Jellystat, Komga, Maintainerr, RomM, Stash, Tube Archivist, Unmanic, xTeVe and Your Spotify.
|
||||
- **Monitoring and networking:** Caddy, Changedetection.io, Frigate, Gatus, Healthchecks, NetAlertX, Netdata, Prometheus, Scrutiny and Syncthing Relay.
|
||||
- **Collections and home:** Homebox, Karakeep, Linkwarden, Mealie, Miniflux, Plant-it, Spoolman, Tandoor and Trilium.
|
||||
|
||||
The [integration and metric reference](/docs/widgets/stats#new-service-integrations-in-v2) links to each setup guide. Jackett also joins the Indexer Manager widget. Existing supported integrations reuse their Homarr clients.
|
||||
|
||||
Statistics keeps saved snapshots until you refresh them. Multiple metrics from one source share retrieval, and dashboard loads read cached data instead of refetching every service. **Right-click → Refresh** retrieves new source data and shows the oldest saved snapshot's age; unsuccessful refreshes keep the previous values. These are on-demand snapshots, not live monitoring.
|
||||
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
|
||||
|
||||
@@ -417,21 +148,17 @@ Groups use a new Viewer, Editor and Admin matrix. The API, Assistant and MCP fol
|
||||
|
||||
Board startup work runs in parallel, heavy screens load when opened, and identical integration requests share work instead of doing it twice.
|
||||
|
||||
## Deploy on Hostinger
|
||||
|
||||
[Hostinger now offers a one-click Docker deployment of Homarr](https://www.hostg.xyz/aff_c?offer_id=815&aff_id=244277&url_id=6861) on its VPS plans. They maintain the deployment template, and you manage the resulting VPS and Homarr instance. Our new [Hostinger installation guide](/docs/getting-started/installation/hostinger) walks through deployment and first setup.
|
||||
|
||||
We deploy the Homarr Workshop and the compiled documentation website on the VPS provided by Hostinger. Thank you for hosting this part of Homarr's infrastructure. We have an affiliate partnership as well: purchases through a referral link may support Homarr.
|
||||
|
||||
## Compatibility notes
|
||||
## 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 [live demo](https://demo.homarr.dev/).
|
||||
- See [installation and update instructions](/docs/getting-started/installation/docker).
|
||||
- Browse [Workshop](https://homarr.dev/workshop).
|
||||
- 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).
|
||||
@@ -1,8 +1,8 @@
|
||||
# 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 `bun run dev:cli -- ...`; no shell-specific setup script is required. It is separate from the recovery CLI included in production images.
|
||||
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 `bun run 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.
|
||||
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
|
||||
|
||||
@@ -14,7 +14,7 @@ To install an optional `homarr` binary, run `bun run dev:cli:install` and make y
|
||||
## Interactive Development Browser
|
||||
|
||||
```bash
|
||||
bun run dev:cli --
|
||||
pnpm dev:cli --
|
||||
# or with global binary:
|
||||
homarr
|
||||
```
|
||||
@@ -66,7 +66,7 @@ Pulls and builds run asynchronously in the background. Press `t` to open the tas
|
||||
```bash
|
||||
homarr dash
|
||||
# or
|
||||
bun run dev:cli -- dash
|
||||
pnpm dev:cli -- dash
|
||||
```
|
||||
|
||||
The instance dashboard displays all running and stopped Homarr containers, published ports, health states, and resource volumes.
|
||||
@@ -81,7 +81,7 @@ The instance dashboard displays all running and stopped Homarr containers, publi
|
||||
|
||||
## CLI Commands
|
||||
|
||||
The commands below use the installed `homarr` binary. Without global installation, prefix each command with `bun run dev:cli --` (for example, `bun run dev:cli -- list`).
|
||||
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
|
||||
|
||||
@@ -90,7 +90,7 @@ homarr run dev
|
||||
homarr run --detach dev
|
||||
homarr run --pr 6441
|
||||
homarr run --pr 6441 --demo
|
||||
homarr --pr 6441 --env WORKSHOP_WEB_URL=https://workshop.example.com/workshop
|
||||
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>`).
|
||||
@@ -133,7 +133,7 @@ homarr ci 6441 --watch
|
||||
homarr shell homarr-dev
|
||||
|
||||
# Run specific command in container
|
||||
homarr shell homarr-dev bun run db:migration:sqlite:run
|
||||
homarr shell homarr-dev pnpm db:migration:sqlite:run
|
||||
```
|
||||
|
||||
### Image & Volume Management
|
||||
@@ -148,18 +148,13 @@ homarr images rm feature
|
||||
# List data volumes with owner PR/tag and in-use status
|
||||
homarr data
|
||||
|
||||
# Preview deleting a data volume and its owning container
|
||||
# Delete data volume (safely stops and removes owning container first)
|
||||
homarr data rm homarr_feature_data
|
||||
|
||||
# Permanently delete the data after reviewing the preview
|
||||
homarr data rm homarr_feature_data --force
|
||||
|
||||
# Remove stopped containers (prompts without --force)
|
||||
homarr prune --force
|
||||
```
|
||||
|
||||
Forced data deletion removes the derived owner container only when it mounts the exact selected volume. A same-named container that does not own that volume stops the operation.
|
||||
|
||||
### Instance Operations & Diagnostics
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Fix usernames
|
||||
|
||||
<Alert type="info">
|
||||
:::info
|
||||
|
||||
This guide requires that you understand [how to access the CLI](../).
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
If you somehow get into a situation where you have users with usernames that contain uppercase characters and you are no longer able to log in, you can use the following command to fix the usernames.
|
||||
|
||||
@@ -14,4 +14,4 @@ homarr fix-usernames
|
||||
|
||||
This will convert all credential usernames to lowercase and update the database accordingly.
|
||||
|
||||

|
||||

|
||||
@@ -4,7 +4,6 @@ The Homarr image includes a recovery CLI for operations that cannot be completed
|
||||
application API; use the [API](/docs/management/api) or [MCP endpoint](/docs/management/mcp) for automation.
|
||||
|
||||
Back up the database and stop Homarr before running a command that changes data.
|
||||
Failed commands print an error and return a non-zero exit code, so shell scripts can stop safely.
|
||||
|
||||
## Run the recovery CLI
|
||||
|
||||
@@ -50,20 +49,20 @@ The repository's host-side CLI can build the current checkout and run it as a na
|
||||

|
||||
|
||||
```sh
|
||||
bun run cli harness setup feature-board
|
||||
pnpm cli harness setup feature-board
|
||||
```
|
||||
|
||||
This creates `homarr:feature-board`, starts it with persistent demo data, and prints the assigned URL. Query the port
|
||||
again with:
|
||||
|
||||
```sh
|
||||
bun run cli harness port feature-board
|
||||
pnpm cli harness port feature-board
|
||||
```
|
||||
|
||||
Pass environment overrides with repeated `--env` options:
|
||||
|
||||
```sh
|
||||
bun run cli harness setup feature-board \
|
||||
pnpm cli harness setup feature-board \
|
||||
--env WORKSHOP_WEB_URL=https://example.test \
|
||||
--env FEATURE_FLAG=true
|
||||
```
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Integrations
|
||||
|
||||
<Alert type="info">
|
||||
:::info
|
||||
|
||||
This guide requires that you understand [how to access the CLI](../).
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
Using these commands, you can manage the integrations from the CLI:
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
# Password Recovery
|
||||
|
||||
<Alert type="info">
|
||||
:::info
|
||||
|
||||
This guide requires that you understand [how to access the CLI](../).
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
Using this command, you can forcefully reset the password of a user.
|
||||
It will be set to a random value and the hash for the user will be updated.
|
||||
All sessions for that user will be terminated and the user must re-login.
|
||||
|
||||

|
||||

|
||||
|
||||
Using the `--username` parameter, you can specify who you want to reset the password for.
|
||||
Executing the command will create a new random password, update the database and display the new password.
|
||||
All sessions for this user will be terminated and they must re-login.
|
||||
|
||||

|
||||

|
||||
@@ -1,10 +1,10 @@
|
||||
# Users
|
||||
|
||||
<Alert type="info">
|
||||
:::info
|
||||
|
||||
This guide requires that you understand [how to access the CLI](../).
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
Using these commands, you can manage the users from the CLI:
|
||||
|
||||
|
||||
@@ -12,32 +12,28 @@ sidebar_position: 1
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Node.js 24.18.0**: application and tooling runtime, pinned in `mise.toml`.
|
||||
- **Bun 1.4.2**: Install the version pinned in `mise.toml` from the [official website](https://bun.sh/docs/installation).
|
||||
- **Node.js 24.18 or newer**: You can download it from the [official website](https://nodejs.org/).
|
||||
- **Corepack**: Run `corepack enable` so the repository selects its pinned pnpm 11 version.
|
||||
- **Git**: You can download it from the [official website](https://git-scm.com/).
|
||||
- **Docker or Docker Desktop**: Required for the Redis service and the developer Docker CLI.
|
||||
- **Go 1.25+**: Required only for the developer Docker CLI. You can download it from the [official website](https://go.dev/).
|
||||
- **GitHub CLI**: Required only for pull-request image features. Install it from the [official website](https://cli.github.com/), then authenticate with `gh auth login`.
|
||||
|
||||
Install dependencies once at the repository root. Bun uses a hoisted workspace installation, so compatible dependencies
|
||||
are shared across packages. Catalog versions, patches and overrides live in the root `package.json`.
|
||||
The versions in the root `package.json` are the supported baseline. Let Corepack select the repository version instead
|
||||
of installing an unrelated global pnpm:
|
||||
|
||||
```sh
|
||||
bun install --frozen-lockfile
|
||||
corepack enable
|
||||
corepack install
|
||||
pnpm install --frozen-lockfile
|
||||
```
|
||||
|
||||
Next.js, operational commands and SQLite run on Node.js. Bun installs dependencies and dispatches package scripts.
|
||||
|
||||
### Run the Homarr application
|
||||
|
||||
1. Copy `.env.example` to `.env`.
|
||||
2. Run `openssl rand -hex 32` twice. Set `AUTH_SECRET` and `SECRET_ENCRYPTION_KEY` to the two different generated
|
||||
values in `.env`; the example leaves both empty. Keep this file private and preserve `SECRET_ENCRYPTION_KEY`
|
||||
when reusing a database.
|
||||
3. Set `DB_URL` to an absolute writable SQLite file path. Migration and application commands run from different
|
||||
workspace directories, so a relative path can point them at different databases.
|
||||
4. Keep Redis and the optional local databases running in one terminal.
|
||||
5. Apply migrations and start Next.js in another terminal.
|
||||
2. Set `DB_URL` to an absolute writable SQLite file path.
|
||||
3. Keep Redis and the optional local databases running in one terminal.
|
||||
4. Apply migrations and start Next.js in another terminal.
|
||||
|
||||
```sh
|
||||
cp .env.example .env
|
||||
@@ -46,98 +42,43 @@ cp .env.example .env
|
||||
Terminal 1:
|
||||
|
||||
```sh
|
||||
bun run docker:dev
|
||||
pnpm docker:dev
|
||||
```
|
||||
|
||||
Terminal 2:
|
||||
|
||||
```sh
|
||||
bun run db:migration:sqlite:run
|
||||
bun run dev
|
||||
pnpm db:migration:sqlite:run
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
The infrastructure command remains in the foreground. Open `http://127.0.0.1:3000`; the first user created through
|
||||
Homarr becomes the instance administrator.
|
||||
Use `bun run docker:dev:up` instead when you only need Redis in the background. Running the migration also seeds the
|
||||
default data; use `bun run db:seed` to seed it explicitly later.
|
||||
|
||||
`bun run dev` starts only Next.js. When your work needs scheduled tasks or live subscriptions, run the relevant service
|
||||
in a separate terminal alongside it:
|
||||
|
||||
```sh
|
||||
bun run --filter @homarr/tasks dev
|
||||
```
|
||||
|
||||
```sh
|
||||
bun run --filter @homarr/websocket dev
|
||||
```
|
||||
|
||||
The WebSocket service listens on port 3001. Production startup embeds both services, so these extra commands are
|
||||
only needed for development.
|
||||
Use `pnpm docker:dev:up` instead when you only need Redis in the background. Running the migration also seeds the
|
||||
default data; use `pnpm db:seed` to seed it explicitly later.
|
||||
|
||||
Workshop contributors can set up PocketBase and Fumadocs with the dedicated
|
||||
[Workshop development guide](/docs/advanced/development/workshop).
|
||||
|
||||
### Useful commands
|
||||
|
||||
- `bun run dev` — Next.js on port 3000.
|
||||
- `bun run dev:benchmark` — authenticated-board benchmark; readiness-only `--smoke` runs are not performance claims.
|
||||
- `bun run dev:cli -- dev` — browse and run local images and remote pull-request images.
|
||||
- `bun run dev:cli -- build <name>` — build the current checkout as `homarr:<name>`.
|
||||
- `bun run dev:cli -- build --pr <number>` — build a pull request from a temporary checkout.
|
||||
- `bun run dev:cli -- rebuild <name>` — rebuild an image from its recorded checkout or pull request.
|
||||
- `bun run dev:cli:install` — optionally install the developer CLI as a `homarr` binary.
|
||||
- `bun run dev:docs` — Fumadocs on port 3003.
|
||||
- `bun run docker:dev:up` — Redis development service in the background.
|
||||
- `bun run cli` — Homarr operations CLI.
|
||||
- `bun run db:migration:sqlite:run` — create or update a SQLite database.
|
||||
- `bun run db:seed` — seed the default database data explicitly.
|
||||
- `bun run db:studio` — inspect the database.
|
||||
- `bun run format` / `bun run format:fix` — check or apply oxfmt.
|
||||
- `bun run lint` / `bun run lint:fix` — check or apply oxlint.
|
||||
- `bun run typecheck` — typecheck all workspaces.
|
||||
- `bun run test` — unit and contract suites.
|
||||
- `bun run build` — production monorepo build.
|
||||
- `docker build -t homarr:local .` — build a production Docker image.
|
||||
- `docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY='your_64_character_hex_string' homarr:local` — run it.
|
||||
|
||||
## CI and focused validation
|
||||
|
||||
The main CI workflow validates SQLite and PostgreSQL migrations before lint, typechecking, affected workspace builds, workspace configuration, OpenAPI, and Custom Widget architecture and bundle checks. Manual container validation builds an amd64 image and exercises MySQL conversion, authentication, and restart persistence. Preview publication requires the Fast gate and both architecture builds.
|
||||
|
||||
Workshop CI runs when its workflow or Docker build inputs change. Pull requests exercise production images on amd64 and arm64; release branches publish them after Compose validation. Workspace typechecks run in the main Fast gate.
|
||||
|
||||
Choose the smallest relevant suite when changing behavior:
|
||||
|
||||
| Command | Scope | Requirements |
|
||||
| ------------------------------------------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| `bun run test <file>` | Focused unit or contract tests | Workspace dependencies |
|
||||
| `bun run test` | All unit and contract tests, without Docker integration suites | Workspace dependencies |
|
||||
| `bun run test:coverage` | Unit and contract tests with coverage | Workspace dependencies |
|
||||
| `bun run test:integration <file>` | Docker-backed service and database compatibility tests | Docker |
|
||||
| `bun run test:e2e <file>` | Browser and container scenarios | Docker; Playwright Chromium and system dependencies for browser tests |
|
||||
| `bun run test:docs-screenshots` | Regenerate Assistant documentation screenshots | Same browser prerequisites |
|
||||
| `bun run test:workshop` / `bun run test:workshop-image` | Workshop integration / production-image validation | Docker |
|
||||
|
||||
Omit `<file>` to run all tests in that command's scope. E2E and screenshot commands use a local image tagged `homarr-e2e`, or an image selected with `HOMARR_E2E_IMAGE`. The default commands run once without automatic retries; coverage is opt-in. The screenshot generator is excluded from both unit and E2E commands.
|
||||
|
||||
Keep behavioral coverage for authorization, migrations, persistence, and widget execution. Avoid tests that only search source code for particular component names, JSX, CSS, or documentation wording.
|
||||
|
||||
## Documentation development
|
||||
|
||||
Run `bun run dev:docs` to open the Next.js and Fumadocs site at `http://127.0.0.1:3003`. Content lives in
|
||||
`apps/docs/docs/`; the nearest `meta.json` controls navigation. Keep integration and widget setup metadata in their
|
||||
typed `index.ts` files alongside the page.
|
||||
|
||||
After changing content, navigation, or shared MDX components, run:
|
||||
|
||||
```sh
|
||||
bun run --filter @homarr/docs build
|
||||
bun run --filter @homarr/docs validate:links
|
||||
bun run --filter @homarr/docs verify:search
|
||||
bun run --filter @homarr/docs verify:seo
|
||||
```
|
||||
|
||||
The build writes a static site to `apps/docs/out/`. Check heading links, search results, and the page's Markdown export
|
||||
when changing shared components. The [Fumadocs documentation](https://fumadocs.dev/docs) describes supported components
|
||||
and source APIs; match examples to the versions installed in this repository.
|
||||
- `pnpm dev` — Next.js on port 3000.
|
||||
- `pnpm dev:benchmark` — authenticated-board benchmark; readiness-only `--smoke` runs are not performance claims.
|
||||
- `pnpm dev:cli -- dev` — browse and run local images and remote pull-request images.
|
||||
- `pnpm dev:cli -- build <name>` — build the current checkout as `homarr:<name>`.
|
||||
- `pnpm dev:cli -- build --pr <number>` — build a pull request from a temporary checkout.
|
||||
- `pnpm dev:cli -- rebuild <name>` — rebuild an image from its recorded checkout or pull request.
|
||||
- `pnpm dev:cli:install` — optionally install the developer CLI as a `homarr` binary.
|
||||
- `pnpm dev:docs` — Fumadocs on port 3003.
|
||||
- `pnpm docker:dev:up` — Redis development service in the background.
|
||||
- `pnpm cli` — Homarr operations CLI.
|
||||
- `pnpm db:migration:sqlite:run` — create or update a SQLite database.
|
||||
- `pnpm db:seed` — seed the default database data explicitly.
|
||||
- `pnpm db:studio` — inspect the database.
|
||||
- `pnpm format` / `pnpm format:fix` — check or apply oxfmt.
|
||||
- `pnpm lint` / `pnpm lint:fix` — check or apply oxlint.
|
||||
- `pnpm typecheck` — typecheck all workspaces.
|
||||
- `pnpm test` — unit and contract suites.
|
||||
- `pnpm build` — production monorepo build.
|
||||
- `docker build -t homarr .` — build a production Docker image.
|
||||
- `docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY='your_64_character_hex_string' homarr:<name>` — run it.
|
||||
@@ -16,8 +16,8 @@ A new feature is available when its typed metadata and lazy loader are registere
|
||||
Keep the UI, options, helpers, and tests together in `packages/widgets/src/<widget>/`. The usual folder only needs an
|
||||
`index.ts` definition and a lazily imported `component.tsx`; complex widgets can add local files as needed.
|
||||
|
||||
1. Add the stable kind to `packages/definitions/src/widget.ts` and its documentation slug or `null` to the direct typed
|
||||
map in `packages/definitions/src/docs/widget-doc-slugs.ts`.
|
||||
1. Add the stable kind to `packages/definitions/src/widget.ts` and its documentation slug to the direct typed map in
|
||||
`packages/definitions/src/docs/widget-doc-slugs.ts`.
|
||||
2. Export `definition` and `componentLoader` from `packages/widgets/src/<widget>/index.ts` with
|
||||
`createWidgetDefinition`. The local definition owns its icon, query keys, refresh interval, options, matchers, and
|
||||
errors. Keep the component behind `withDynamicImport` so adding a widget does not load it on every dashboard.
|
||||
@@ -33,8 +33,7 @@ Keep the UI, options, helpers, and tests together in `packages/widgets/src/<widg
|
||||
5. If the widget needs server data, add its tRPC router under `packages/api/src/router/widgets/` and add its explicit
|
||||
lazy import to `packages/api/src/router/widgets/index.ts`. Keep upstream requests and Redis response caching in
|
||||
`@homarr/request-handler`. Use the widget integration middleware for integration-backed procedures.
|
||||
6. If advanced users need non-obvious setup or behavior explained, add a concise page in `apps/docs/docs/widgets/<slug>/`;
|
||||
otherwise leave the widget's docs slug as `null`.
|
||||
6. Add canonical documentation in `apps/docs/docs/widgets/<slug>/`.
|
||||
|
||||
Weather is the small integration-free example. Downloads shows integration selection, polling, server caching,
|
||||
mutations, partial upstream failures, and a larger options surface.
|
||||
@@ -43,21 +42,20 @@ mutations, partial upstream failures, and a larger options surface.
|
||||
|
||||
Keep the HTTP client and its capability-specific types in `packages/integrations/src/<integration>/`.
|
||||
|
||||
1. Add the typed definition to `packages/definitions/src/integration.ts`, including credential alternatives, categories,
|
||||
ports, Docker aliases, and onboarding metadata when relevant. Set `documentationSlug` to a page slug or `null`.
|
||||
1. Add the typed definition to `packages/definitions/src/integration.ts`, including credential alternatives,
|
||||
categories, documentation slug, ports, Docker aliases, and onboarding metadata when relevant.
|
||||
2. Add an explicit lazy creator to `packages/integrations/src/base/creator.ts`. The literal import keeps the
|
||||
implementation out of the initial server bundle and makes the registration easy to find.
|
||||
3. Implement only the interfaces required by the widgets that use the service.
|
||||
4. When advanced users need non-obvious setup or behavior explained, add a concise page in
|
||||
`apps/docs/docs/integrations/<slug>/`. Its `index.ts` must satisfy `IntegrationDefinition`; use a type-only import
|
||||
and `satisfies IntegrationDefinition` rather than a cast.
|
||||
4. Add canonical documentation in `apps/docs/docs/integrations/<slug>/`. Its `index.ts` must satisfy
|
||||
`IntegrationDefinition` so invalid metadata fails type checking without widening the object. Use a type-only import
|
||||
and `satisfies IntegrationDefinition`; do not cast the object.
|
||||
|
||||
Beszel is a compact example with several widgets sharing one integration client.
|
||||
|
||||
## User documentation
|
||||
## Write user documentation
|
||||
|
||||
Add a page only when a smart, advanced user cannot infer setup or behavior from the UI or generated schema. Keep it short
|
||||
and operational.
|
||||
Write for technically capable self-hosters. Keep each page short and operational.
|
||||
|
||||
- Start with one sentence describing what the integration or widget adds to Homarr.
|
||||
- Include prerequisites, credentials, permissions, destructive actions, and non-obvious limits only when they matter.
|
||||
|
||||
@@ -29,26 +29,11 @@ Workshop uses PocketBase. The production image serves the Workshop website and A
|
||||
| `HOMARR_AI_DAILY_REQUEST_LIMIT` | Per-user daily request allowance | `50` |
|
||||
| `HOMARR_AI_GLOBAL_DAILY_REQUEST_LIMIT` | Shared daily request ceiling | `10000` |
|
||||
| `HOMARR_AI_OPENROUTER_BASE_URL` | OpenRouter-compatible upstream | `https://openrouter.ai/api/v1` |
|
||||
| `HOMARR_AI_OPENROUTER_MODEL` | Upstream model behind `homarr/model` | `openai/gpt-6-luna` |
|
||||
| `HOMARR_AI_OPENROUTER_MODEL` | Upstream model behind `homarr/model` | Selected by the Homarr team |
|
||||
|
||||
Public URL variables accept HTTP(S) origins without embedded credentials, query strings, or fragments. Restart the
|
||||
container after changing runtime configuration.
|
||||
|
||||
## Documentation search and AI
|
||||
|
||||
The documentation uses Fumadocs search with an index generated during the website build. Search runs in the browser
|
||||
and needs no hosted search credentials or crawler.
|
||||
|
||||
The **Ask AI** launcher connects to Kapa separately. `KAPA_WEBSITE_ID` is a public website integration ID, read when
|
||||
building the documentation. The default uses Homarr's integration. For a private deployment, pass your own ID or
|
||||
an empty value to disable it: `docker build --build-arg KAPA_WEBSITE_ID= -f apps/workshop/Dockerfile .`.
|
||||
Changing this value requires rebuilding the image; setting it on an already-built container has no effect.
|
||||
|
||||
For your own Kapa integration, enable the deployment domain in Kapa and configure a Website Crawl source for your
|
||||
published docs. Preview the `main` content selector and verify extracted headings, code, and integration details.
|
||||
After a migration, refresh the source and check answer citations. `/llms.txt` alone does not configure Kapa ingestion.
|
||||
See the [Kapa website widget guide](https://docs.kapa.ai/integrations/website-widget/quickstart).
|
||||
|
||||
## Authentication and email
|
||||
|
||||
Configure both GitHub OAuth variables and use `https://<workshop-host>/api/oauth2-redirect` as the callback. Supplying
|
||||
@@ -59,24 +44,11 @@ can use a comma-separated allowlist.
|
||||
|
||||
## Homarr provider
|
||||
|
||||
`OPENROUTER_API_KEY` enables the [Homarr provider](/docs/workshop/homarr-provider). The default is `openai/gpt-6-luna`
|
||||
with Max reasoning and a 32K combined reasoning/output budget. Remove an old `HOMARR_AI_OPENROUTER_MODEL` override or set
|
||||
it explicitly to this model when upgrading. OpenRouter selects an eligible provider for the model; Workshop does not
|
||||
pin a provider. Provider fallbacks remain disabled.
|
||||
`OPENROUTER_API_KEY` enables the [Homarr provider](/docs/workshop/homarr-provider). The Workshop server chooses the upstream model and
|
||||
enforces per-user and global daily limits. It requests zero-data-retention routing, disables data-collecting providers,
|
||||
and does not retain prompts or responses.
|
||||
|
||||
For [BYOK](https://openrouter.ai/docs/guides/overview/auth/byok), configure your OpenAI key as a prioritized key in the
|
||||
same OpenRouter workspace as `OPENROUTER_API_KEY`. Set **Shared capacity fallback** to **Never use shared capacity for
|
||||
any model on this provider**. The request's `allow_fallbacks: false` prevents retrying another provider, but does not
|
||||
pin the initial provider or guarantee OpenAI BYOK billing. Use OpenRouter provider restrictions if required. Store the
|
||||
OpenAI key in OpenRouter, not in Workshop or Homarr clients.
|
||||
|
||||
Workshop retains zero-data-retention routing and denies data collection. Your BYOK key must be eligible under those
|
||||
policies; configure any provider-agreement declaration only if it reflects your actual OpenAI agreement. An ineligible
|
||||
or unavailable key cannot make a non-ZDR route eligible. Other eligible providers may serve the model.
|
||||
|
||||
Workshop does not retain prompts or responses and enforces per-user and global daily request limits. Keep spending
|
||||
controls on both the OpenAI account and OpenRouter workspace: request limits are not monetary limits, and OpenRouter
|
||||
fees can still apply to BYOK requests. Streaming responses include upstream token usage when reported.
|
||||
Keep an OpenRouter account-level credit limit as the monetary backstop; request limits are not cost limits.
|
||||
|
||||
## Deployment
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ docker compose --env-file apps/workshop/.env -f apps/workshop/docker-compose.yml
|
||||
Start Fumadocs in another terminal:
|
||||
|
||||
```sh
|
||||
WORKSHOP_API_URL=http://127.0.0.1:8090 bun run dev:docs
|
||||
WORKSHOP_API_URL=http://127.0.0.1:8090 pnpm dev:docs
|
||||
```
|
||||
|
||||
PocketBase runs at `http://127.0.0.1:8090`; create the first superuser under `/_/`. Workshop is available at
|
||||
|
||||
@@ -14,20 +14,16 @@ tags:
|
||||
|
||||
Homarr offers a few environment variables, which can be used to configure the container.
|
||||
|
||||
<Alert type="info">
|
||||
|
||||
:::info
|
||||
With docker you can add the `_FILE` suffix to any environment variable, which will read the value from a file.
|
||||
This is especially useful for [docker secrets](https://docs.docker.com/engine/swarm/secrets/#use-secrets-in-compose).
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
## General
|
||||
|
||||
<Alert type="warning">
|
||||
|
||||
:::warning
|
||||
Using the `PUID` and `PGID` will require you to set the correct permissions on the mounted volumes and if used the docker socket. See more in the [Running as a different user](/docs/advanced/running-as-different-user) documentation.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
| Environment Variable | Description | Possible values | Default |
|
||||
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------- |
|
||||
@@ -36,7 +32,7 @@ Using the `PUID` and `PGID` will require you to set the correct permissions on t
|
||||
| `LOG_LEVEL` | Log level to use | `debug` / `info` / `warn` / `error` | `info` |
|
||||
| `BASE_URL` | Public Homarr origin used by MCP OAuth metadata and redirects. Set this when a reverse proxy does not preserve the public host and protocol. | HTTP(S) origin without a path | - |
|
||||
| `NO_EXTERNAL_CONNECTION` | Disables Homarr's internet-backed requests, including update checks, weather, location search, and RSS feeds | `true` or `false` | `false` |
|
||||
| `ENABLE_DNS_CACHING` | Opt-in DNS cache. Keep disabled for Docker service names and `extra_hosts`; cached addresses can survive container recreation and DNS-first lookup can override hosts-file entries. | `true` or `false` | `false` |
|
||||
| `ENABLE_DNS_CACHING` | Enables dns caching. Enabled by default, but if you experience issues with IPv6 or static IPs, see [#4006](https://github.com/homarr-labs/homarr/issues/4006) or disable this flag | `true` or `false` | `true` |
|
||||
| `HOMARR_DISABLE_IPV6` | Disables the IPv6 listener of the internal nginx proxy, even when the host supports IPv6. IPv6 is normally disabled automatically when the host has no IPv6 configured. See [#4596](https://github.com/homarr-labs/homarr/issues/4596) | `true` or `false` | `false` |
|
||||
| `HOMARR_WEBSITE_URL` | Main Homarr documentation/site base used by runtime links. Must be an absolute HTTP(S) URL. | HTTP(S) URL | `https://homarr.dev` |
|
||||
| `WORKSHOP_API_URL` | PocketBase API used by Homarr and standalone documentation. | HTTP(S) URL | `HOMARR_WEBSITE_URL` |
|
||||
@@ -45,10 +41,6 @@ Using the `PUID` and `PGID` will require you to set the correct permissions on t
|
||||
Workshop service operators can find its server-only configuration in the
|
||||
[Workshop operator guide](/docs/advanced/development/workshop-operator#environment-variables).
|
||||
|
||||
### Demo mode
|
||||
|
||||
`DEMO_MODE=true` seeds demo data and makes Assistant return a fixed response without calling a model provider. `DEMO_READ_ONLY` defaults to `true`, blocking dashboard and configuration changes. Authenticated users can still save and manage their own Assistant conversations so canned chats work. Set `DEMO_READ_ONLY=false` for editable preview deployments.
|
||||
|
||||
### Workshop service
|
||||
|
||||
These variables configure the separate Workshop service, not the Homarr application container.
|
||||
@@ -66,7 +58,7 @@ These variables configure the separate Workshop service, not the Homarr applicat
|
||||
| `HOMARR_AI_DAILY_REQUEST_LIMIT` | Daily Assistant request units per user | `50` |
|
||||
| `HOMARR_AI_GLOBAL_DAILY_REQUEST_LIMIT` | Daily Assistant request units shared by all users | `10000` |
|
||||
| `HOMARR_AI_OPENROUTER_BASE_URL` | OpenRouter-compatible provider endpoint | `https://openrouter.ai/api/v1` |
|
||||
| `HOMARR_AI_OPENROUTER_MODEL` | OpenRouter model exposed through `homarr/model` | `openai/gpt-6-luna` |
|
||||
| `HOMARR_AI_OPENROUTER_MODEL` | Upstream model exposed through the stable `homarr/model` alias | Selected by the Homarr team |
|
||||
| `HOMARR_AI_ALLOW_INSECURE_UPSTREAM` | Allows an HTTP upstream for local provider tests; never use in production | `false` |
|
||||
|
||||
## Authentication
|
||||
@@ -81,11 +73,9 @@ The `SECRET_ENCRYPTION_KEY` is required. If none is specified before starting th
|
||||
| ----------------------- | ------------------------------------------- | ----------------------- | ------- |
|
||||
| `SECRET_ENCRYPTION_KEY` | Secret used to encrypt secrets in database. | 64 character hex string | - |
|
||||
|
||||
<Alert type="info">
|
||||
|
||||
:::info
|
||||
A random secret can be generated by using the following command: `openssl rand -hex 32`
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
## Docker
|
||||
|
||||
@@ -98,18 +88,16 @@ A random secret can be generated by using the following command: `openssl rand -
|
||||
|
||||
## Database
|
||||
|
||||
SQLite is the default; PostgreSQL is also supported. Existing MySQL installations must [convert to SQLite before upgrading to v2](/docs/advanced/mysql-to-sqlite).
|
||||
|
||||
| Environment Variable | Description | Possible values | Default |
|
||||
| -------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------- | ----------------------- |
|
||||
| `DB_DRIVER` | Database driver to use. Use `better-sqlite3` for SQLite or `node-postgres` for PostgreSQL | `better-sqlite3` / `node-postgres` | `better-sqlite3` |
|
||||
| `DB_DIALECT` | Database dialect to use. | `sqlite` / `postgresql` | `sqlite` |
|
||||
| `DB_URL` | Database URL to connect to. | Any valid database URL | `/appdata/db/db.sqlite` |
|
||||
| `DB_HOST` | Database host to connect to. | Any valid database host | - |
|
||||
| `DB_PORT` | Database port to connect to. | Any valid database port | - |
|
||||
| `DB_NAME` | Database name to connect to. | Any valid database name | - |
|
||||
| `DB_USER` | Database user to connect with. | Any valid database user | - |
|
||||
| `DB_PASSWORD` | Database password to connect with. | Any valid database password | - |
|
||||
| Environment Variable | Description | Possible values | Default |
|
||||
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | ----------------------- |
|
||||
| `DB_DRIVER` | Database driver to use. Currently `better-sqlite3` is used for sqlite, `mysql2` for mysql and `node-postgres` for postgresql | `better-sqlite3` / `mysql2` / `node-postgres` | `better-sqlite3` |
|
||||
| `DB_DIALECT` | Database dialect to use. | `sqlite` / `mysql` / `postgresql` | `sqlite` |
|
||||
| `DB_URL` | Database URL to connect to. | Any valid database URL | `/appdata/db/db.sqlite` |
|
||||
| `DB_HOST` | Database host to connect to. | Any valid database host | - |
|
||||
| `DB_PORT` | Database port to connect to. | Any valid database port | - |
|
||||
| `DB_NAME` | Database name to connect to. | Any valid database name | - |
|
||||
| `DB_USER` | Database user to connect with. | Any valid database user | - |
|
||||
| `DB_PASSWORD` | Database password to connect with. | Any valid database password | - |
|
||||
|
||||
You can either use the url or host, port, name and credentials combined. The URL will be prioritized over the other values.
|
||||
|
||||
@@ -118,11 +106,9 @@ You can either use the url or host, port, name and credentials combined. The URL
|
||||
By default Redis is running within the installation. (for example in Docker).
|
||||
However for example for K8s it can be useful to use an external Redis instance.
|
||||
|
||||
<Alert type="note">
|
||||
|
||||
:::note
|
||||
External redis is currently not supported with Proxmox Community Scripts
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
| Environment Variable | Description | Possible values | Default |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------ | ------- |
|
||||
@@ -144,11 +130,9 @@ External redis is currently not supported with Proxmox Community Scripts
|
||||
|
||||
## Advanced deployments
|
||||
|
||||
<Alert type="warning">
|
||||
|
||||
:::warning
|
||||
The advanced deployments environment variables should only be used if you know what you are doing.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
| Environment Variable | Description |
|
||||
| ------------------------ | --------------------------------------------------- |
|
||||
|
||||
@@ -23,8 +23,6 @@ Icons in Homarr are automatically requested from multiple sources:
|
||||
Using these icon sources, Homarr provides a total collection of over 11,000 icons at your fingertips.
|
||||
The image picker searches this collection after a short typing delay. When a name is available, such as an app or search engine name, the picker uses it as the initial search. Results show your locally uploaded images first, followed by SVG icons and then other image formats.
|
||||
|
||||
GitHub icon indexes are refreshed on startup and weekly. A GitHub rate limit can temporarily prevent Dashboard Icons, selfh.st, and Simple Icons from refreshing; previously cached icons remain available. Invalid or incomplete GitHub tree responses also leave the cached provider icons intact. The server log reports the HTTP status and, when GitHub provides them, remaining request quota and reset time (Unix seconds). After the limit resets, an administrator can rerun **Icons Updater** under **Manage → Tools → Tasks**. Direct image URLs and local uploads remain usable while indexing is unavailable.
|
||||
|
||||
### Finding Missing Icons
|
||||
|
||||
If you can't find the icon you're looking for in Homarr's built-in picker, we strongly recommend checking [Dashboard Icons](https://dashboardicons.com/). It offers a modern, user-friendly interface and a curated collection of high-quality icons specifically designed for dashboards and app directories.
|
||||
|
||||
@@ -17,10 +17,6 @@ import {
|
||||
|
||||
These guides cover changes that affect how Homarr is deployed, secured, operated, or extended.
|
||||
|
||||
## Dashboard data cache
|
||||
|
||||
Homarr keeps eligible integration responses in Redis. It also retains a bounded, board-scoped snapshot of selected integration widget queries for up to three days. Authorized viewers receive the last snapshot while the server refreshes those queries and streams updated values to the browser. Embedded images and large results are excluded. The browser keeps TanStack Query data in memory only; dashboard query data is not saved in browser storage.
|
||||
|
||||
## Deploy and secure
|
||||
|
||||
<Cards>
|
||||
|
||||
@@ -53,16 +53,30 @@ Commands at a grid boundary or at the tile's minimum size are ignored and announ
|
||||
|
||||
On touch screens, press and hold a tile before moving it; a quick tap does not start a drag. Resize handles accept direct touch input. During a pointer or touch move or resize, `Esc` cancels the interaction and restores the layout from before it started.
|
||||
|
||||
<Alert type="tip">
|
||||
## Board editing
|
||||
|
||||
When a tile is focused in edit mode:
|
||||
|
||||
| Shortcut | Description |
|
||||
| -------------------- | --------------------------------------------------------------------------- |
|
||||
| `Enter` or `Space` | Starts or stops keyboard editing |
|
||||
| Arrow keys | Moves the focused tile within its current canvas, rail, or Container |
|
||||
| `Shift` + Arrow keys | Resizes from the tile's lower-right corner: Left/Up shrink; Right/Down grow |
|
||||
| `Esc` | Stops keyboard editing without undoing its changes |
|
||||
|
||||
Keyboard moves and resizes apply immediately. To move an item between a canvas, rail, or Container, drag it there or use **Move / resize item** for precise numeric placement.
|
||||
Commands at a grid boundary or at the tile's minimum size are ignored and announced instead of moving or shrinking the tile.
|
||||
|
||||
:::tip
|
||||
|
||||
Here `MOD` refers to the modifier key, which is `Ctrl` on Windows and `Cmd` on macOS.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
<Alert type="note">
|
||||
:::note
|
||||
|
||||
The focus of your browser must be set on Homarr. If you click outside of Homarr, you might need to click once somewhere in Homarr to send all keystrokes again to Homarr.
|
||||
|
||||
If the shortcuts are not working, ensure that you have selected the correct keyboard layout.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
@@ -5,7 +5,6 @@
|
||||
"index",
|
||||
"---Deploy and secure---",
|
||||
"environment-variables",
|
||||
"mysql-to-sqlite",
|
||||
"single-sign-on",
|
||||
"proxy",
|
||||
"running-as-different-user",
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
title: Move from MySQL to SQLite
|
||||
---
|
||||
|
||||
# Move from MySQL to SQLite
|
||||
|
||||
Homarr v2 supports SQLite (the default) and PostgreSQL. MySQL and MariaDB are no longer supported; migrate before upgrading to v2. Changing the database connection settings alone does not move your existing data.
|
||||
|
||||
## Choose a database
|
||||
|
||||
Use SQLite for an instance with its own persistent database volume. Do not synchronize a running SQLite database between Swarm nodes with tools such as Syncthing.
|
||||
|
||||
For a shared database in a multi-node deployment, use PostgreSQL. The converter below targets **SQLite only**; Homarr does not currently provide a MySQL-to-PostgreSQL converter. PostgreSQL connection settings are documented under [environment variables](/docs/advanced/environment-variables#database), but those settings do not import your MySQL data. Keep your previous Homarr image and database available until you have a validated migration plan.
|
||||
|
||||
The standalone converter is tested against the **Homarr v1.77.1 database schema** and writes its corresponding SQLite database. Homarr v2 applies its normal migrations when it first opens that file. The converter does not modify the source database.
|
||||
|
||||
## Before converting
|
||||
|
||||
1. Back up the MySQL database, `/appdata`, your deployment configuration, and `SECRET_ENCRYPTION_KEY`. Keep the previous Homarr image available for rollback.
|
||||
2. Upgrade to the latest stable v1 release (**v1.77.1** at the tested baseline) first and let its migrations finish successfully. Back up that database before continuing.
|
||||
3. Stop every Homarr instance that writes to this database. Leave MySQL running and keep Homarr stopped throughout conversion and cutover.
|
||||
|
||||
The converter validates the schema and migration journal against v1.77.1; it does not identify the application binary version. Older releases with identical schemas are compatible. Different schemas, incomplete migrations, and existing output files are rejected.
|
||||
|
||||
## Convert
|
||||
|
||||
From a checkout containing `tools/mysql-to-sqlite`, build the converter:
|
||||
|
||||
```sh
|
||||
docker build -t homarr-mysql-to-sqlite tools/mysql-to-sqlite
|
||||
mkdir -p converted
|
||||
```
|
||||
|
||||
Create `mysql-conversion.env` with credentials for the source database:
|
||||
|
||||
```dotenv
|
||||
MYSQL_HOST=mysql.example.com
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_USER=homarr
|
||||
MYSQL_PASSWORD=replace-with-your-password
|
||||
MYSQL_DATABASE=homarr
|
||||
```
|
||||
|
||||
`MYSQL_PORT` defaults to `3306`. Use a host reachable from the converter container; `localhost` refers to the container itself. Keep this credentials file out of version control.
|
||||
|
||||
Run the converter after stopping Homarr:
|
||||
|
||||
```sh
|
||||
docker run --rm --user "$(id -u):$(id -g)" --env-file mysql-conversion.env \
|
||||
-v "$(pwd)/converted:/output" \
|
||||
homarr-mysql-to-sqlite --output /output/db.sqlite --homarr-stopped
|
||||
```
|
||||
|
||||
Running as your user keeps the output file readable by you. The required `--homarr-stopped` flag confirms that you stopped Homarr; it does not stop your deployment. Wait for the converter to exit successfully before using `converted/db.sqlite`.
|
||||
|
||||
For a TLS connection, add `MYSQL_SSL_CA=/certs/mysql-ca.pem` to the env file and mount the PEM certificate with `-v "$(pwd)/mysql-ca.pem:/certs/mysql-ca.pem:ro"`. The certificate file must be readable by the container user.
|
||||
|
||||
## Start Homarr v2
|
||||
|
||||
1. Keep a separate copy of the converted file before the first v2 startup.
|
||||
2. Place `db.sqlite` at `/appdata/db/db.sqlite` in the appdata volume used by Homarr. Ensure the Homarr process can write to the file and its directory.
|
||||
3. Set `DB_DRIVER=better-sqlite3` and `DB_URL=/appdata/db/db.sqlite`. Remove the previous MySQL connection settings; if you explicitly set `DB_DIALECT`, change it to `sqlite`.
|
||||
4. Preserve the original `/appdata` files and **the same `SECRET_ENCRYPTION_KEY`**. The converter copies encrypted values without re-encrypting them; a different key will make those secrets unreadable.
|
||||
5. Start Homarr v2 and wait for its database migrations to finish. Check the startup logs, sign in, and verify your boards, users, integrations, and uploaded files.
|
||||
|
||||
The converter output is a raw SQLite database. Do not upload it to the backup ZIP import page.
|
||||
|
||||
## Roll back
|
||||
|
||||
Stop Homarr v2, then restore your previous deployment configuration, image, appdata backup, and encryption key, pointing it at the preserved MySQL database. Do not open the v2 SQLite database with the older Homarr image.
|
||||
|
||||
Changes made after switching to SQLite are not synchronized back to MySQL. Keep the original database and backups until you have verified the new installation.
|
||||
@@ -16,13 +16,13 @@ import { TabItem } from "@/components/mdx/legacy-tabs";
|
||||
|
||||
Some users may come across a barrier, where they're unable to receive a 200 response from the Ping widget for some apps, while using self-signed certificates or a local certificate authory.
|
||||
|
||||
<Alert type="note" title="What's going on?">
|
||||
:::note[What's going on?]
|
||||
|
||||
Homarr is trying to communicate to your apps via the integrations.
|
||||
It usually doesn't matter if Homarr is running on `http` or `https`.
|
||||
Your apps have a self-signed certificate - Homarr will recognize that the certificate was signed by an unknown authority and requests will be blocked.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
To allow self-signed certificates, you can configure them on the certificates management page. You can find more informations on the dedicated page: [Certificates](/docs/management/certificates/)
|
||||
|
||||
@@ -106,11 +106,9 @@ networks:
|
||||
external: true
|
||||
```
|
||||
|
||||
<Alert type="tip">
|
||||
|
||||
:::tip
|
||||
Of particular note here is that both configurations explicitly define which network they are using, in this case "proxy", but it can be named anything. It just has to be the same across all apps for which Traefik is serving as a proxy. These are marked as external because the proxy network was manually created by running: `docker network create proxy` but this might be unnecessary depending on HOW exactly you are running Traefik. For example, if running [Traefik with Portainer](https://docs.portainer.io/advanced/reverse-proxy/traefik#deploying-in-a-docker-standalone-scenario), you can follow their official docs on how to set up Traefik and Portainer together, and you can just focus on the Homarr docker labels instead.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
## Securing Homarr with Caddy
|
||||
|
||||
@@ -144,8 +142,7 @@ networks:
|
||||
external: true
|
||||
```
|
||||
|
||||
<Alert type="warning" title="Important">
|
||||
|
||||
:::important
|
||||
Homarr and Caddy must share the same Docker network:
|
||||
|
||||
```yaml
|
||||
@@ -155,7 +152,7 @@ homarr:
|
||||
- proxy
|
||||
```
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
Next, create a file named `Caddyfile` at the same level as your `docker-compose.yml` file with the following content:
|
||||
|
||||
|
||||
@@ -12,12 +12,9 @@ By default the container is running with user `root` and group `root`.
|
||||
You can change the user and group by using the `PUID` and `PGID` environment variables.
|
||||
Important to note is, that you'll also need to change the permissions for the mounted directories accordingly.
|
||||
|
||||
<Alert type="info">
|
||||
|
||||
At startup, Homarr updates ownership of its data directories and runtime caches for the selected user and group.
|
||||
Startup time depends on the amount of persistent data; application code and dependencies do not need ownership changes.
|
||||
|
||||
</Alert>
|
||||
:::info
|
||||
When starting up an updated version of the container it will have about **10 seconds longer to start** as it has to change the permissions of the app directories
|
||||
:::
|
||||
|
||||
## Without mounted Docker socket
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ tags:
|
||||
|
||||
Homarr supports multiple authentication options, from internal userbase (credentials), to LDAP (with Active directory support), and OIDC.
|
||||
|
||||
## Common configuration
|
||||
### Auth configuration common variables
|
||||
|
||||
| Environment Variable | Description | Default Value |
|
||||
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
||||
@@ -23,191 +23,198 @@ Homarr supports multiple authentication options, from internal userbase (credent
|
||||
| AUTH_SESSION_EXPIRY_TIME | Time for the session to time out. Can be set as pure number, which will automatically be used in seconds, or followed by s, m, h or d for seconds, minutes, hours or days. (ex: "30m") | "30d" |
|
||||
| AUTH_COOKIE_PREFIX | Prefix used for all authentication cookies (session, csrf, callback, pkce, state, nonce). Change this if you run another Auth.js/NextAuth app on the same hostname as Homarr (browsers scope cookies by host, not by port) to avoid cookie name collisions. Only letters, numbers, hyphens and underscores are allowed. | `homarr` |
|
||||
|
||||
## Credentials provider
|
||||
<Tabs>
|
||||
<TabItem value="credentials" label="Credentials Provider" default>
|
||||
|
||||
This is the default provider.
|
||||
This is the default provider.
|
||||
|
||||
First user is created using the onboarding process and the rest can be created by this user (see [user management](/docs/management/users))
|
||||
First user is created using the onboarding process and the rest can be created by this user (see [user management](/docs/management/users))
|
||||
|
||||
## LDAP provider
|
||||
</TabItem>
|
||||
<TabItem value="ldap" label="LDAP provider">
|
||||
|
||||
This provider authenticates against an LDAP server.
|
||||
This provider authenticates against an LDAP server.
|
||||
|
||||
Any user in LDAP server that signs in gets created in Homarr database.
|
||||
Any user in LDAP server that signs in gets created in Homarr database.
|
||||
|
||||
Roles are fetched from LDAP groups. Groups with the same name of Homarr will be used to synchronize them.
|
||||
Roles are fetched from LDAP groups. Groups with the same name of Homarr will be used to synchronize them.
|
||||
|
||||
LDAP users whose distinguished names contain non-ASCII characters can sign in and have their groups resolved.
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Example Setup
|
||||
</summary>
|
||||
<div>
|
||||
|
||||
In this setup we are using [lldap](https://github.com/lldap/lldap). Install your server using docker or [as a service](https://github.com/lldap/lldap/blob/main/example_configs/lldap.service).
|
||||
|
||||
Minimal configuration requires 4 env variables, LDAP URI, base, user and password for querying data.
|
||||
<details>
|
||||
<summary>
|
||||
Example Setup
|
||||
</summary>
|
||||
<div>
|
||||
|
||||
There's more variables, but all have defaults corresponding to lldap defaults. These might need to be changed if you have different LDAP provider.
|
||||
```
|
||||
docker run ...
|
||||
AUTH_PROVIDERS="ldap"
|
||||
AUTH_LDAP_URI="ldap://example.com:3890"
|
||||
AUTH_LDAP_BASE="dc=example,dc=com" // Same as LLDAP_LDAP_BASE_DN
|
||||
AUTH_LDAP_BIND_DN="uid=admin,ou=People,dc=example,dc=com"
|
||||
AUTH_LDAP_BIND_PASSWORD="adminpass" // Same as LLDAP_LDAP_USER_PASS
|
||||
```
|
||||
```
|
||||
#Docker compose
|
||||
version: x
|
||||
services:
|
||||
homarr:
|
||||
environment:
|
||||
AUTH_PROVIDERS: ldap
|
||||
AUTH_LDAP_URI: ldap://example.com:3890
|
||||
AUTH_LDAP_BASE: dc=example,dc=com #Same as LLDAP_LDAP_BASE_DN
|
||||
AUTH_LDAP_BIND_DN: uid=admin,ou=People,dc=example,dc=com
|
||||
AUTH_LDAP_BIND_PASSWORD: adminpass #Same as LLDAP_LDAP_USER_PASS
|
||||
```
|
||||
In this setup we are using [lldap](https://github.com/lldap/lldap). Install your server using docker or [as a service](https://github.com/lldap/lldap/blob/main/example_configs/lldap.service).
|
||||
|
||||
In lldap, create a user and admin group, assign this user to the external admin group configured during onboarding. You can log in using this user and he will be in the group.
|
||||
Minimal configuration requires 4 env variables, LDAP URI, base, user and password for querying data.
|
||||
|
||||
Here is another example for Active Directory:
|
||||
There's more variables, but all have defaults corresponding to lldap defaults. These might need to be changed if you have different LDAP provider.
|
||||
```
|
||||
docker run ...
|
||||
AUTH_PROVIDERS="ldap"
|
||||
AUTH_LDAP_URI="ldap://example.com:3890"
|
||||
AUTH_LDAP_BASE="dc=example,dc=com" // Same as LLDAP_LDAP_BASE_DN
|
||||
AUTH_LDAP_BIND_DN="uid=admin,ou=People,dc=example,dc=com"
|
||||
AUTH_LDAP_BIND_PASSWORD="adminpass" // Same as LLDAP_LDAP_USER_PASS
|
||||
```
|
||||
```
|
||||
#Docker compose
|
||||
version: x
|
||||
services:
|
||||
homarr:
|
||||
environment:
|
||||
AUTH_PROVIDERS: ldap
|
||||
AUTH_LDAP_URI: ldap://example.com:3890
|
||||
AUTH_LDAP_BASE: dc=example,dc=com #Same as LLDAP_LDAP_BASE_DN
|
||||
AUTH_LDAP_BIND_DN: uid=admin,ou=People,dc=example,dc=com
|
||||
AUTH_LDAP_BIND_PASSWORD: adminpass #Same as LLDAP_LDAP_USER_PASS
|
||||
```
|
||||
|
||||
```
|
||||
AUTH_LDAP_URI="ldap://ldap.abc.xyz:389
|
||||
AUTH_LDAP_BASE="DC=abc,DC=xyz"
|
||||
AUTH_LDAP_BIND_DN="CN=Administrator,CN=Users,DC=abc,DC=xyz"
|
||||
AUTH_LDAP_BIND_PASSWORD="YourAdministratorPassword"
|
||||
AUTH_LDAP_USERNAME_ATTRIBUTE="sAMAccountName"
|
||||
AUTH_LDAP_USER_MAIL_ATTRIBUTE="userPrincipalName"
|
||||
AUTH_LDAP_GROUP_CLASS="group"
|
||||
AUTH_LDAP_GROUP_MEMBER_ATTRIBUTE="member"
|
||||
AUTH_LDAP_GROUP_MEMBER_USER_ATTRIBUTE="dn"
|
||||
AUTH_LDAP_SEARCH_SCOPE="sub"
|
||||
AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG="(sAMAccountType=805306368)"
|
||||
```
|
||||
User mail attribute is set to userPrincipalName as it follows the right schema, but it is recommended to use real emails and the default 'mail' value.
|
||||
In lldap, create a user and admin group, assign this user to the external admin group configured during onboarding. You can log in using this user and he will be in the group.
|
||||
|
||||
</div>
|
||||
</details>
|
||||
Here is another example for Active Directory:
|
||||
|
||||
### LDAP configuration
|
||||
```
|
||||
AUTH_LDAP_URI="ldap://ldap.abc.xyz:389
|
||||
AUTH_LDAP_BASE="DC=abc,DC=xyz"
|
||||
AUTH_LDAP_BIND_DN="CN=Administrator,CN=Users,DC=abc,DC=xyz"
|
||||
AUTH_LDAP_BIND_PASSWORD="YourAdministratorPassword"
|
||||
AUTH_LDAP_USERNAME_ATTRIBUTE="sAMAccountName"
|
||||
AUTH_LDAP_USER_MAIL_ATTRIBUTE="userPrincipalName"
|
||||
AUTH_LDAP_GROUP_CLASS="group"
|
||||
AUTH_LDAP_GROUP_MEMBER_ATTRIBUTE="member"
|
||||
AUTH_LDAP_GROUP_MEMBER_USER_ATTRIBUTE="dn"
|
||||
AUTH_LDAP_SEARCH_SCOPE="sub"
|
||||
AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG="(sAMAccountType=805306368)"
|
||||
```
|
||||
User mail attribute is set to userPrincipalName as it follows the right schema, but it is recommended to use real emails and the default 'mail' value.
|
||||
|
||||
| Environment Variable | Description | Default value |
|
||||
| --------------------------------------- | --------------------------------------------------------- | ------------------ |
|
||||
| `AUTH_LDAP_URI` | URI of your LDAP server | --- |
|
||||
| `AUTH_LDAP_BASE` | Base dn of your LDAP server | --- |
|
||||
| `AUTH_LDAP_BIND_DN` | User used for finding users and groups | --- |
|
||||
| `AUTH_LDAP_BIND_PASSWORD` | Password for bind user | --- |
|
||||
| `AUTH_LDAP_USERNAME_ATTRIBUTE` | Attribute used for username | uid |
|
||||
| `AUTH_LDAP_USER_MAIL_ATTRIBUTE` | Attribute used for mail field | mail |
|
||||
| `AUTH_LDAP_GROUP_CLASS` | Class used for querying groups | groupOfUniqueNames |
|
||||
| `AUTH_LDAP_GROUP_MEMBER_ATTRIBUTE` | Attribute used for querying group member | member |
|
||||
| `AUTH_LDAP_GROUP_MEMBER_USER_ATTRIBUTE` | User attribute used for comparing with group member | dn |
|
||||
| `AUTH_LDAP_SEARCH_SCOPE` | Serach scopes between base, one and sub | base |
|
||||
| `AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG` | Extra arguments for user search filter (& based) | --- |
|
||||
| `AUTH_LDAP_GROUP_FILTER_EXTRA_ARG` | Extra arguments for user's groups search filter (& based) | --- |
|
||||
</div>
|
||||
</details>
|
||||
|
||||
## OIDC provider
|
||||
|
||||
This provider authenticates using OIDC protocol.
|
||||
### Configuration
|
||||
|
||||
Users signed in using OIDC are created in Homarr.
|
||||
| Environment Variable | Description | Default value |
|
||||
|-------------------------------------------|-----------------------------------------------------------|---------------------|
|
||||
| ``AUTH_LDAP_URI`` | URI of your LDAP server | --- |
|
||||
| ``AUTH_LDAP_BASE`` | Base dn of your LDAP server | --- |
|
||||
| ``AUTH_LDAP_BIND_DN`` | User used for finding users and groups | --- |
|
||||
| ``AUTH_LDAP_BIND_PASSWORD`` | Password for bind user | --- |
|
||||
| ``AUTH_LDAP_USERNAME_ATTRIBUTE`` | Attribute used for username | uid |
|
||||
| ``AUTH_LDAP_USER_MAIL_ATTRIBUTE`` | Attribute used for mail field | mail |
|
||||
| ``AUTH_LDAP_GROUP_CLASS`` | Class used for querying groups | groupOfUniqueNames |
|
||||
| ``AUTH_LDAP_GROUP_MEMBER_ATTRIBUTE`` | Attribute used for querying group member | member |
|
||||
| ``AUTH_LDAP_GROUP_MEMBER_USER_ATTRIBUTE`` | User attribute used for comparing with group member | dn |
|
||||
| ``AUTH_LDAP_SEARCH_SCOPE`` | Serach scopes between base, one and sub | base |
|
||||
| ``AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG`` | Extra arguments for user search filter (& based) | --- |
|
||||
| ``AUTH_LDAP_GROUP_FILTER_EXTRA_ARG`` | Extra arguments for user's groups search filter (& based) | --- |
|
||||
|
||||
When signing out, Homarr completes the redirect to the configured logout URL after clearing its local session.
|
||||
</TabItem>
|
||||
<TabItem value="oidc" label="OIDC provider">
|
||||
|
||||
Roles are fetched from group claims. This can also be changed to roles for example added to a azure app registration by using the `AUTH_OIDC_GROUPS_ATTRIBUTE`.
|
||||
This provider authenticates using OIDC protocol.
|
||||
|
||||
If you'd rather manage group memberships for OIDC users locally (instead of via the IdP), set `AUTH_OIDC_GROUPS_LOCAL_MANAGEMENT=true`. While enabled, Homarr stops syncing OIDC users' group memberships from the groups claim, and admins can add/remove OIDC users on a group's members page. If you turn this back to `false`, the next login of each user resumes the regular sync and reconciles their groups with the IdP's claim again, which may remove memberships that were added manually while local management was enabled.
|
||||
Users signed in using OIDC are created in Homarr.
|
||||
|
||||
To let an existing credentials user also sign in with OIDC, set `AUTH_OIDC_ENABLE_DANGEROUS_CREDENTIALS_LINKING=true`. On the first OIDC sign-in, Homarr links a credentials user with the same email address instead of creating a second user. The user keeps their password login, local username and profile, groups, permissions, boards, settings, and API keys. OIDC profile and group claims do not overwrite the credentials-owned data. Homarr requires the OIDC profile to contain `email_verified: true` while this option is enabled. Only enable this when your OIDC provider verifies email addresses, because a matching email could otherwise be used to take over an existing account.
|
||||
Roles are fetched from group claims. This can also be changed to roles for example added to a azure app registration by using the `AUTH_OIDC_GROUPS_ATTRIBUTE`.
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Example Setup
|
||||
</summary>
|
||||
<div>
|
||||
If you'd rather manage group memberships for OIDC users locally (instead of via the IdP), set `AUTH_OIDC_GROUPS_LOCAL_MANAGEMENT=true`. While enabled, Homarr stops syncing OIDC users' group memberships from the groups claim, and admins can add/remove OIDC users on a group's members page. If you turn this back to `false`, the next login of each user resumes the regular sync and reconciles their groups with the IdP's claim again, which may remove memberships that were added manually while local management was enabled.
|
||||
|
||||
In this example we will be using [Authelia](https://github.com/authelia/authelia).
|
||||
To let an existing credentials user also sign in with OIDC, set `AUTH_OIDC_ENABLE_DANGEROUS_CREDENTIALS_LINKING=true`. On the first OIDC sign-in, Homarr links a credentials user with the same email address instead of creating a second user. The user keeps their password login, local username and profile, groups, permissions, boards, settings, and API keys. OIDC profile and group claims do not overwrite the credentials-owned data. Homarr requires the OIDC profile to contain `email_verified: true` while this option is enabled. Only enable this when your OIDC provider verifies email addresses, because a matching email could otherwise be used to take over an existing account.
|
||||
|
||||
You can use any setup, but the simplest is [local](https://github.com/authelia/authelia/blob/master/examples/compose/local/authelia/configuration.yml).
|
||||
<details>
|
||||
<summary>
|
||||
Example Setup
|
||||
</summary>
|
||||
<div>
|
||||
|
||||
You also have to [enable OIDC in Authelia](https://www.authelia.com/configuration/identity-providers/open-id-connect/).
|
||||
In this example we will be using [Authelia](https://github.com/authelia/authelia).
|
||||
|
||||
Create a client for homarr. To generate client secret you can [use authelia](https://www.authelia.com/integration/openid-connect/frequently-asked-questions/#how-do-i-generate-client-secrets). This is an example config:
|
||||
You can use any setup, but the simplest is [local](https://github.com/authelia/authelia/blob/master/examples/compose/local/authelia/configuration.yml).
|
||||
|
||||
```yaml
|
||||
identity_providers:
|
||||
oidc:
|
||||
...
|
||||
clients:
|
||||
- id: homarr
|
||||
secret: <secret_hash>
|
||||
public: false
|
||||
authorization_policy: one_factor
|
||||
redirect_uris:
|
||||
- https://example.com/api/auth/callback/oidc
|
||||
- http://localhost:3000/api/auth/callback/oidc
|
||||
scopes:
|
||||
- openid
|
||||
- groups
|
||||
- profile
|
||||
- email
|
||||
userinfo_signing_algorithm: none
|
||||
consent_mode: implicit # self hosted
|
||||
```
|
||||
You also have to [enable OIDC in Authelia](https://www.authelia.com/configuration/identity-providers/open-id-connect/).
|
||||
|
||||
In Homarr use following env variables.
|
||||
Create a client for homarr. To generate client secret you can [use authelia](https://www.authelia.com/integration/openid-connect/frequently-asked-questions/#how-do-i-generate-client-secrets). This is an example config:
|
||||
|
||||
```
|
||||
AUTH_PROVIDERS="oidc"
|
||||
AUTH_OIDC_ISSUER="https://auth.example.com"
|
||||
AUTH_OIDC_CLIENT_SECRET="client_secret"
|
||||
AUTH_OIDC_CLIENT_ID="homarr"
|
||||
AUTH_OIDC_CLIENT_NAME="Authelia"
|
||||
AUTH_OIDC_FORCE_USERINFO="true" --> from v4.39 of authelia and above this is required
|
||||
```
|
||||
```yaml
|
||||
identity_providers:
|
||||
oidc:
|
||||
...
|
||||
clients:
|
||||
- id: homarr
|
||||
secret: <secret_hash>
|
||||
public: false
|
||||
authorization_policy: one_factor
|
||||
redirect_uris:
|
||||
- https://example.com/api/auth/callback/oidc
|
||||
- http://localhost:3000/api/auth/callback/oidc
|
||||
scopes:
|
||||
- openid
|
||||
- groups
|
||||
- profile
|
||||
- email
|
||||
userinfo_signing_algorithm: none
|
||||
consent_mode: implicit # self hosted
|
||||
```
|
||||
|
||||
For an azure app registration the setup could look like this:
|
||||
In Homarr use following env variables.
|
||||
|
||||
```
|
||||
AUTH_PROVIDERS="oidc"
|
||||
AUTH_OIDC_ISSUER="https://login.microsoftonline.com/<teanant-id>/v2.0"
|
||||
AUTH_OIDC_CLIENT_SECRET="<client-secret>"
|
||||
AUTH_OIDC_CLIENT_ID="<client-id>"
|
||||
AUTH_OIDC_CLIENT_NAME="Azure"
|
||||
AUTH_OIDC_SCOPE_OVERWRITE="openid email profile" # Groups scope does not exist in azure
|
||||
AUTH_OIDC_GROUPS_ATTRIBUTE="roles" # We use the roles of the app registration so that we don't need to use uuids as our groups
|
||||
```
|
||||
```
|
||||
AUTH_PROVIDERS="oidc"
|
||||
AUTH_OIDC_ISSUER="https://auth.example.com"
|
||||
AUTH_OIDC_CLIENT_SECRET="client_secret"
|
||||
AUTH_OIDC_CLIENT_ID="homarr"
|
||||
AUTH_OIDC_CLIENT_NAME="Authelia"
|
||||
AUTH_OIDC_FORCE_USERINFO="true" --> from v4.39 of authelia and above this is required
|
||||
```
|
||||
|
||||
</div>
|
||||
</details>
|
||||
For an azure app registration the setup could look like this:
|
||||
|
||||
### OIDC configuration
|
||||
```
|
||||
AUTH_PROVIDERS="oidc"
|
||||
AUTH_OIDC_ISSUER="https://login.microsoftonline.com/<teanant-id>/v2.0"
|
||||
AUTH_OIDC_CLIENT_SECRET="<client-secret>"
|
||||
AUTH_OIDC_CLIENT_ID="<client-id>"
|
||||
AUTH_OIDC_CLIENT_NAME="Azure"
|
||||
AUTH_OIDC_SCOPE_OVERWRITE="openid email profile" # Groups scope does not exist in azure
|
||||
AUTH_OIDC_GROUPS_ATTRIBUTE="roles" # We use the roles of the app registration so that we don't need to use uuids as our groups
|
||||
```
|
||||
|
||||
| Environment Variable | Description | Default value |
|
||||
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
|
||||
| `AUTH_OIDC_ISSUER` | Issuer URI of OIDC provider. This has generally to be **without trailing slash** except for **Authentik** | --- |
|
||||
| `AUTH_OIDC_CLIENT_ID` | ID of OIDC client (application) | --- |
|
||||
| `AUTH_OIDC_CLIENT_SECRET` | Secret of OIDC client (application) | --- |
|
||||
| `AUTH_OIDC_CLIENT_NAME` | Display name of provider (in login screen) | OIDC |
|
||||
| `AUTH_OIDC_AUTO_LOGIN` | Automatically redirect to OIDC login | false |
|
||||
| `AUTH_OIDC_SCOPE_OVERWRITE` | Overwrite default scopes (openid, profile, email) | openid email profile groups |
|
||||
| `AUTH_OIDC_GROUPS_ATTRIBUTE` | Attribute used for groups (roles) claim. Dot-separated paths such as `resource_access.homarr.roles` are supported. | groups |
|
||||
| `AUTH_OIDC_GROUPS_LOCAL_MANAGEMENT` | Stop syncing group memberships of OIDC users from the groups claim and manage them locally via the group members page instead | false |
|
||||
| `AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE` | Overwrite name attribute. Dot-separated paths are supported. By default it will use preferred_username if it does not contain a `@` and otherwise name. | --- |
|
||||
| `AUTH_OIDC_FORCE_USERINFO` | Force userinfo endpoint to be used for user information. | false |
|
||||
| `AUTH_OIDC_ENABLE_DANGEROUS_ACCOUNT_LINKING` | Enable account linking for OIDC provider. This will link the OIDC accounts by email. Be sure that you have verified emails to prevent stealing accounts. | false |
|
||||
| `AUTH_OIDC_ENABLE_DANGEROUS_CREDENTIALS_LINKING` | Link an OIDC identity to an existing credentials user with the same email. The credentials login and existing user data are preserved. Requires the OIDC profile to contain `email_verified: true`. | false |
|
||||
| `AUTH_OIDC_TOKEN_ENDPOINT_AUTH_METHOD` | Override method for the token endpoint authentication. Supported values are `client_secret_basic`, `client_secret_post`, `client_secret_jwt` and `none`. | client_secret_basic |
|
||||
</div>
|
||||
</details>
|
||||
|
||||
### OIDC permissions
|
||||
|
||||
To give a user special permissions, first create a new group in homarr and assign the permission desired.
|
||||
For example if the group on homarr is called `homarr-admins` a new group on the oidc provider, for example authentik needs to be created with the same name, that would be `homarr-admins`.
|
||||
After assigning the group on the oidc provider to the user, and logging again into homarr, the user should automatically get placed in that group and inherit the permissions defined.
|
||||
### Configuration
|
||||
|
||||
| Environment Variable | Description | Default value |
|
||||
|-----------------------------|------------------------------------------------------------|---------------|
|
||||
| ``AUTH_OIDC_ISSUER`` | Issuer URI of OIDC provider. This has generally to be **without trailing slash** except for **Authentik** | --- |
|
||||
| ``AUTH_OIDC_CLIENT_ID`` | ID of OIDC client (application) | --- |
|
||||
| ``AUTH_OIDC_CLIENT_SECRET`` | Secret of OIDC client (application) | --- |
|
||||
| ``AUTH_OIDC_CLIENT_NAME`` | Display name of provider (in login screen) | OIDC |
|
||||
| ``AUTH_OIDC_AUTO_LOGIN`` | Automatically redirect to OIDC login | false |
|
||||
| ``AUTH_OIDC_SCOPE_OVERWRITE`` | Overwrite default scopes (openid, profile, email) | openid email profile groups |
|
||||
| ``AUTH_OIDC_GROUPS_ATTRIBUTE`` | Attribute used for groups (roles) claim. Dot-separated paths such as `resource_access.homarr.roles` are supported. | groups |
|
||||
| ``AUTH_OIDC_GROUPS_LOCAL_MANAGEMENT`` | Stop syncing group memberships of OIDC users from the groups claim and manage them locally via the group members page instead | false |
|
||||
| ``AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE`` | Overwrite name attribute. Dot-separated paths are supported. By default it will use preferred_username if it does not contain a `@` and otherwise name. | --- |
|
||||
| ``AUTH_OIDC_FORCE_USERINFO`` | Force userinfo endpoint to be used for user information. | false |
|
||||
| ``AUTH_OIDC_ENABLE_DANGEROUS_ACCOUNT_LINKING`` | Enable account linking for OIDC provider. This will link the OIDC accounts by email. Be sure that you have verified emails to prevent stealing accounts. | false |
|
||||
| ``AUTH_OIDC_ENABLE_DANGEROUS_CREDENTIALS_LINKING`` | Link an OIDC identity to an existing credentials user with the same email. The credentials login and existing user data are preserved. Requires the OIDC profile to contain `email_verified: true`. | false |
|
||||
| ``AUTH_OIDC_TOKEN_ENDPOINT_AUTH_METHOD`` | Override method for the token endpoint authentication. Supported values are `client_secret_basic`, `client_secret_post`, `client_secret_jwt` and `none`. | client_secret_basic |
|
||||
|
||||
### Permission System
|
||||
|
||||
To give a user special permissions, first create a new group in homarr and assign the permission desired.
|
||||
For example if the group on homarr is called `homarr-admins` a new group on the oidc provider, for example authentik needs to be created with the same name, that would be `homarr-admins`.
|
||||
After assigning the group on the oidc provider to the user, and logging again into homarr, the user should automatically get placed in that group and inherit the permissions defined.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Example setups
|
||||
|
||||
@@ -288,7 +295,7 @@ After assigning the group on the oidc provider to the user, and logging again in
|
||||
</div>
|
||||
</details>
|
||||
|
||||
See [OIDC configuration](#oidc-configuration) for additional options.
|
||||
Additional OIDC configuration options are described in the OIDC tab above.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="google" label="Google (OIDC) Example" default>
|
||||
@@ -354,7 +361,7 @@ After assigning the group on the oidc provider to the user, and logging again in
|
||||
</div>
|
||||
</details>
|
||||
|
||||
See [OIDC configuration](#oidc-configuration) for additional options.
|
||||
Additional OIDC configuration options are described in the OIDC tab above.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -7,12 +7,12 @@ Homarr uses Mantine and supports custom CSS at two scopes:
|
||||
|
||||
Both editors accept direct CSS and Workshop imports.
|
||||
|
||||
<Alert type="warning">
|
||||
:::warning
|
||||
|
||||
Custom CSS can break after updates and is not an authorization boundary. Hidden controls remain accessible to users who
|
||||
have permission to use them.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
## Target elements
|
||||
|
||||
|
||||