mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Compare commits
1697 Commits
v2.5.0-alp
...
v3.7.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8888e33ccc | ||
|
|
250f9f2d89 | ||
|
|
9fd864df0b | ||
|
|
361d987f47 | ||
|
|
6017db260a | ||
|
|
f9c9b3d981 | ||
|
|
e7b876f4e6 | ||
|
|
554b948775 | ||
|
|
9bdbb59401 | ||
|
|
377861c5f1 | ||
|
|
26c2aaf567 | ||
|
|
62b54ceaaf | ||
|
|
625eba76c7 | ||
|
|
e12bda3f42 | ||
|
|
0f0301ae5c | ||
|
|
8d1776af98 | ||
|
|
c597738915 | ||
|
|
639329dbe4 | ||
|
|
92b0525b6e | ||
|
|
06306aabd5 | ||
|
|
225905fcdb | ||
|
|
8ca2b3c641 | ||
|
|
ac1737492b | ||
|
|
a63075eb4c | ||
|
|
97e9dfad67 | ||
|
|
17c4f13568 | ||
|
|
3b5243689b | ||
|
|
4d28adf4a7 | ||
|
|
1b14bb07b1 | ||
|
|
3a43aa4d41 | ||
|
|
2340b4f601 | ||
|
|
664886eddf | ||
|
|
d3570dac0b | ||
|
|
1854cc2f19 | ||
|
|
bff4bd12ae | ||
|
|
549c6605f0 | ||
|
|
f50263d2d9 | ||
|
|
c80ec54508 | ||
|
|
a91a560651 | ||
|
|
a931c60230 | ||
|
|
479a331008 | ||
|
|
5d99b1aef0 | ||
|
|
17e0c9176e | ||
|
|
48d7388bdc | ||
|
|
aa01c034db | ||
|
|
9035161b65 | ||
|
|
df57ca5edb | ||
|
|
0511c73cc8 | ||
|
|
a3554eaf74 | ||
|
|
cb0bac55d2 | ||
|
|
bd5d3f6897 | ||
|
|
5e6e386c5a | ||
|
|
e783bdc456 | ||
|
|
316b88cea6 | ||
|
|
c03eea789c | ||
|
|
bd5d3cb6fa | ||
|
|
e1f2dcc136 | ||
|
|
5e6ebfa5a9 | ||
|
|
a7ed14a1c9 | ||
|
|
f88d57b2b6 | ||
|
|
9dbd15bc0c | ||
|
|
0edb3e5c2c | ||
|
|
7501eca71e | ||
|
|
b145d106b0 | ||
|
|
b4386a3508 | ||
|
|
36e2a9387d | ||
|
|
14c68b4bbe | ||
|
|
d6fedaf926 | ||
|
|
8d35c1dde2 | ||
|
|
85b5f760e5 | ||
|
|
1a4a107952 | ||
|
|
e431395a12 | ||
|
|
cfc3d54122 | ||
|
|
d9d6d5bd9c | ||
|
|
32f465f7a6 | ||
|
|
4cddb573a0 | ||
|
|
2cb8486bb3 | ||
|
|
2a782ab60b | ||
|
|
5bde273d59 | ||
|
|
41e322fd47 | ||
|
|
55885b0f25 | ||
|
|
d419b9d62d | ||
|
|
3bdc05fbc4 | ||
|
|
57053d66a4 | ||
|
|
9d8ea0a4f6 | ||
|
|
46354baae9 | ||
|
|
27c48414da | ||
|
|
25b1138000 | ||
|
|
4cf3bc08f9 | ||
|
|
7e093a3fd8 | ||
|
|
abecf3be02 | ||
|
|
3d9b48c5fd | ||
|
|
ba4a66f772 | ||
|
|
ec839183e8 | ||
|
|
b0ec0601c1 | ||
|
|
637d8334f4 | ||
|
|
6db46b50e9 | ||
|
|
f6b1cc7556 | ||
|
|
65a0a90a51 | ||
|
|
38a7872fbf | ||
|
|
5839cc5926 | ||
|
|
49bd230474 | ||
|
|
074f2135f6 | ||
|
|
ef073d59ca | ||
|
|
a3b2f94339 | ||
|
|
b17c576a30 | ||
|
|
bc4156ca3c | ||
|
|
b747b8448e | ||
|
|
aa4b9fc27e | ||
|
|
3f3c2c3d1e | ||
|
|
4b49f8fb7f | ||
|
|
550f3b28ea | ||
|
|
6d4c232345 | ||
|
|
2d085ad6f4 | ||
|
|
3ea83f5cc3 | ||
|
|
b47bd0fc7a | ||
|
|
27e90c4c26 | ||
|
|
ad1a846d8e | ||
|
|
78f75a06df | ||
|
|
262d714751 | ||
|
|
f71c3bba5c | ||
|
|
8b495064b2 | ||
|
|
57a36204db | ||
|
|
7cc1722858 | ||
|
|
af50887361 | ||
|
|
99c8c6c8d4 | ||
|
|
1d7c9fd553 | ||
|
|
274c454fa4 | ||
|
|
453fcc4675 | ||
|
|
42427bfd74 | ||
|
|
33eedd2270 | ||
|
|
edaf9e34f4 | ||
|
|
965325aa7c | ||
|
|
bdf2bbd0f1 | ||
|
|
dc4aab2cab | ||
|
|
3b1f6c69de | ||
|
|
cdf5082cff | ||
|
|
61686ed6ea | ||
|
|
f63b96fd05 | ||
|
|
228bd83b75 | ||
|
|
a21dcb852a | ||
|
|
6558c37b9a | ||
|
|
e6720cce12 | ||
|
|
22d039c550 | ||
|
|
a21fcd72ce | ||
|
|
058391cf06 | ||
|
|
24e6acc6e8 | ||
|
|
8e3508f28d | ||
|
|
e72b424968 | ||
|
|
56d299a7dc | ||
|
|
575e6bf707 | ||
|
|
e50c21440f | ||
|
|
7cff11080d | ||
|
|
72381f9844 | ||
|
|
7c649e7497 | ||
|
|
eff308cd43 | ||
|
|
bd3745118e | ||
|
|
602ebef02a | ||
|
|
9040937376 | ||
|
|
a11be5b2ff | ||
|
|
6351d0b87d | ||
|
|
fae3434393 | ||
|
|
4013ae87dd | ||
|
|
cb4ba58b08 | ||
|
|
8c94452348 | ||
|
|
ea96a00b12 | ||
|
|
be26dd58c3 | ||
|
|
eaba5edf7f | ||
|
|
12da04ef37 | ||
|
|
8b03532ddb | ||
|
|
112b54ec7d | ||
|
|
ee6a8ede0a | ||
|
|
07ce5f05a2 | ||
|
|
7b04016ca2 | ||
|
|
b6792d3fb8 | ||
|
|
2f0d658432 | ||
|
|
8c1865c3ec | ||
|
|
096d2098d6 | ||
|
|
882d64ae11 | ||
|
|
3352bcd186 | ||
|
|
9743c1624d | ||
|
|
e85d59c5a4 | ||
|
|
ac0ff8fb94 | ||
|
|
90f93ee4ec | ||
|
|
794d926e3a | ||
|
|
bd41ebef9f | ||
|
|
725370704f | ||
|
|
f03b7689ce | ||
|
|
fb31e2a5e4 | ||
|
|
d688c6da7e | ||
|
|
618784dc3b | ||
|
|
9d64d4ed1d | ||
|
|
7f02243c6c | ||
|
|
3916c68126 | ||
|
|
a6f0c66331 | ||
|
|
bdfb220126 | ||
|
|
abcf2f86be | ||
|
|
a4d8ebdfc9 | ||
|
|
b51c149c30 | ||
|
|
39095a3098 | ||
|
|
765199727e | ||
|
|
c7043af853 | ||
|
|
02a8ef7dd9 | ||
|
|
6bb8c1b2f5 | ||
|
|
3cf253fd0f | ||
|
|
0365728337 | ||
|
|
b8143a5bb4 | ||
|
|
531a19dde9 | ||
|
|
69ff7de481 | ||
|
|
ffc0555c7c | ||
|
|
84a7981dfa | ||
|
|
2573c2bf98 | ||
|
|
3b4c1501f3 | ||
|
|
e836bedecc | ||
|
|
a797b74a70 | ||
|
|
ab497403ca | ||
|
|
d4dea9a1d2 | ||
|
|
28d93ea5e0 | ||
|
|
e6a31b16ed | ||
|
|
9553192281 | ||
|
|
74069f2d24 | ||
|
|
b4b00a57c1 | ||
|
|
a516ef691d | ||
|
|
e80b7448f5 | ||
|
|
f129544f83 | ||
|
|
9fa291a32f | ||
|
|
d06e92ffc2 | ||
|
|
1b83344995 | ||
|
|
cf49393ef2 | ||
|
|
f2ecbceae9 | ||
|
|
c582eba753 | ||
|
|
de86b62cdd | ||
|
|
73150471e9 | ||
|
|
ec751f4ac2 | ||
|
|
e652166289 | ||
|
|
a671d555cb | ||
|
|
6240554f4c | ||
|
|
4ee9c9bbe3 | ||
|
|
c830439085 | ||
|
|
f2196583c8 | ||
|
|
3dbca2115c | ||
|
|
b45d8de27d | ||
|
|
3ba46bbbfa | ||
|
|
06f3f0c86c | ||
|
|
06f07e3e40 | ||
|
|
740740b8c6 | ||
|
|
b6ed39b18b | ||
|
|
958c4704f8 | ||
|
|
ef075fb0ce | ||
|
|
556520583a | ||
|
|
399c56a097 | ||
|
|
f078d95588 | ||
|
|
33911afcd6 | ||
|
|
ae8d31e83f | ||
|
|
72c4052012 | ||
|
|
f713a1fa7e | ||
|
|
62488ac4e5 | ||
|
|
ab4c6e5fca | ||
|
|
26f4a969c9 | ||
|
|
703965915d | ||
|
|
24e38a3cf9 | ||
|
|
b12cfaedf3 | ||
|
|
71807e698c | ||
|
|
1d155298c1 | ||
|
|
4dfc5ae681 | ||
|
|
26f237069c | ||
|
|
b6e1c65c4c | ||
|
|
11f94b8306 | ||
|
|
01bcedef7a | ||
|
|
e51384fcc0 | ||
|
|
83c53c8b2e | ||
|
|
1afe08caed | ||
|
|
7289833928 | ||
|
|
f4d10df0f3 | ||
|
|
652b0df054 | ||
|
|
0e9ea5027c | ||
|
|
658303d375 | ||
|
|
ccc3a4b584 | ||
|
|
ef5ac86e0a | ||
|
|
91b90b276a | ||
|
|
85c32c3c9a | ||
|
|
40838255a7 | ||
|
|
a67ccb384f | ||
|
|
cb31e5a581 | ||
|
|
3c12a55872 | ||
|
|
6da8b11674 | ||
|
|
552489611f | ||
|
|
e48d0f4f0c | ||
|
|
49b6063501 | ||
|
|
dd049feb40 | ||
|
|
76a86c452e | ||
|
|
41aec15fab | ||
|
|
245cb0e35d | ||
|
|
7a0b1e8494 | ||
|
|
70c1c9f018 | ||
|
|
97e965157b | ||
|
|
04bbd471ff | ||
|
|
650a286982 | ||
|
|
ad44a8441a | ||
|
|
b339cf2429 | ||
|
|
9cd97c2f1e | ||
|
|
a7f6b60cba | ||
|
|
0d7dc50670 | ||
|
|
4bc5b9261f | ||
|
|
fb572d5abb | ||
|
|
8fa4219b30 | ||
|
|
a52d0cd419 | ||
|
|
0080ab5132 | ||
|
|
8afa582aa5 | ||
|
|
d847c7648e | ||
|
|
c140db16d1 | ||
|
|
adbf7c6f5e | ||
|
|
5cec697be3 | ||
|
|
587bbfdd73 | ||
|
|
b3a2ceedea | ||
|
|
621f18bf40 | ||
|
|
99c1a59dd4 | ||
|
|
3a149c9edc | ||
|
|
fdaf5fb2f3 | ||
|
|
2f83e90c8b | ||
|
|
05acd4ae88 | ||
|
|
87007677ed | ||
|
|
4ee0032c2a | ||
|
|
06583a0bc1 | ||
|
|
024c9c1a7a | ||
|
|
f3855dbc6f | ||
|
|
758dac47c3 | ||
|
|
81393a76b4 | ||
|
|
9949bb654d | ||
|
|
b0b9902f40 | ||
|
|
5aa8de11f4 | ||
|
|
b18c9e495f | ||
|
|
d3590234a3 | ||
|
|
39adef8ab8 | ||
|
|
13e443880a | ||
|
|
45961144b9 | ||
|
|
34129b8d24 | ||
|
|
48bd97fe41 | ||
|
|
b1b67c497e | ||
|
|
237fb95b4b | ||
|
|
c1b7c6ba6c | ||
|
|
d8add9291f | ||
|
|
a93edf158e | ||
|
|
fdadf3ba07 | ||
|
|
3e26f1113d | ||
|
|
822652cac3 | ||
|
|
1447687ebe | ||
|
|
12150f775d | ||
|
|
5f2f179581 | ||
|
|
407134bab1 | ||
|
|
de5b895fad | ||
|
|
80e3f01562 | ||
|
|
6904dcfed0 | ||
|
|
21863e8de6 | ||
|
|
d75be372cb | ||
|
|
edaf999bf5 | ||
|
|
3e98485c8b | ||
|
|
cc292886a6 | ||
|
|
0c1b36d0d4 | ||
|
|
a06957e9fa | ||
|
|
390bc59d99 | ||
|
|
85464f0fbb | ||
|
|
42f7a68ba5 | ||
|
|
e3397a7c90 | ||
|
|
46b4a21617 | ||
|
|
fc0aba6311 | ||
|
|
0b96a79c41 | ||
|
|
a5929ebb29 | ||
|
|
ce9ec0d738 | ||
|
|
961178fd82 | ||
|
|
49c73a9590 | ||
|
|
92c80e7833 | ||
|
|
6d5bce0078 | ||
|
|
112cbb9039 | ||
|
|
812c5f4993 | ||
|
|
921f303404 | ||
|
|
e0a9f8120c | ||
|
|
8ecc241a4b | ||
|
|
30e34151ed | ||
|
|
d734578f74 | ||
|
|
37c8328eed | ||
|
|
e71f6bb528 | ||
|
|
f7ae52f86e | ||
|
|
067d1cc41c | ||
|
|
b97af7efb9 | ||
|
|
fd0ecc05b2 | ||
|
|
5b934c3f9a | ||
|
|
c7a2f499e0 | ||
|
|
713f7e7bc9 | ||
|
|
09078e4c6a | ||
|
|
1f66ec2af5 | ||
|
|
936e5b3b86 | ||
|
|
99f28b569b | ||
|
|
0c83dea8b7 | ||
|
|
30edfdbdc5 | ||
|
|
60ef98b836 | ||
|
|
73c8b53882 | ||
|
|
425d8f0a3f | ||
|
|
92a83b82a0 | ||
|
|
d1ec15febf | ||
|
|
dd345c82ea | ||
|
|
2bf3e6a13b | ||
|
|
0b04476c99 | ||
|
|
229dc93132 | ||
|
|
0952c488be | ||
|
|
c4f28b3a32 | ||
|
|
201f25e0ad | ||
|
|
0c3523c34a | ||
|
|
0d7a0ee9ea | ||
|
|
931bdb0cd7 | ||
|
|
8807a78463 | ||
|
|
d832133410 | ||
|
|
cdde59b543 | ||
|
|
463dfe9729 | ||
|
|
805c8c87ba | ||
|
|
7ba2cfc010 | ||
|
|
40794c476f | ||
|
|
c3ab871366 | ||
|
|
42a5296f93 | ||
|
|
183db4ff80 | ||
|
|
0bc9bd9281 | ||
|
|
9bed7ef156 | ||
|
|
8f68e4b9f5 | ||
|
|
6589c8fce6 | ||
|
|
38b313a25d | ||
|
|
dab0ebeb99 | ||
|
|
27bf7220b9 | ||
|
|
e68ef87c66 | ||
|
|
29b747c192 | ||
|
|
2047d6b772 | ||
|
|
71e7938b7a | ||
|
|
6bce219eb3 | ||
|
|
dfcac525bc | ||
|
|
da307aee0a | ||
|
|
edf2b5b4c2 | ||
|
|
f41d947cf7 | ||
|
|
54bc169525 | ||
|
|
05d55c4000 | ||
|
|
739f5eb421 | ||
|
|
0aab1bdc4e | ||
|
|
47f99cf6cc | ||
|
|
55c9773a02 | ||
|
|
4b66aaba5c | ||
|
|
4223408090 | ||
|
|
58e6b0b683 | ||
|
|
891438c672 | ||
|
|
910864eaaf | ||
|
|
598c0757be | ||
|
|
01e0a95e14 | ||
|
|
f459a99e7e | ||
|
|
85e18a4754 | ||
|
|
1650499a38 | ||
|
|
51f243995a | ||
|
|
aeafb244d9 | ||
|
|
142417dda1 | ||
|
|
da658185c3 | ||
|
|
ef82158368 | ||
|
|
083ccd36b7 | ||
|
|
d61c79da84 | ||
|
|
8f76c3e202 | ||
|
|
23aa7a015c | ||
|
|
674a4416cf | ||
|
|
db85915c2f | ||
|
|
dfc8e8d74e | ||
|
|
b2b424a4ed | ||
|
|
3433899577 | ||
|
|
b1f814e118 | ||
|
|
7aa6afeb30 | ||
|
|
d414496a3c | ||
|
|
d4684fd01f | ||
|
|
bb444a02fe | ||
|
|
e980a8d121 | ||
|
|
f493baaf2b | ||
|
|
28f26920dd | ||
|
|
69e994c067 | ||
|
|
656083cb6f | ||
|
|
ab9ea887d2 | ||
|
|
9ac6a50e66 | ||
|
|
acc9cb94b5 | ||
|
|
01829c82ee | ||
|
|
9c02ea8799 | ||
|
|
d202538581 | ||
|
|
a84b642ba5 | ||
|
|
74176c298f | ||
|
|
91e21441f7 | ||
|
|
896b7f2d73 | ||
|
|
66ed152358 | ||
|
|
257134cd80 | ||
|
|
a4373aee91 | ||
|
|
7442905873 | ||
|
|
d3af51f684 | ||
|
|
04419a7242 | ||
|
|
a45d6e6b44 | ||
|
|
37b1306eb3 | ||
|
|
cff6573767 | ||
|
|
a2f34e02ad | ||
|
|
796543d194 | ||
|
|
3b25fb27fe | ||
|
|
3b20f955ff | ||
|
|
c81ae9c40d | ||
|
|
7ceae7af87 | ||
|
|
5e02cfe375 | ||
|
|
6e836b5fd9 | ||
|
|
8753e3a77f | ||
|
|
6a2227efc5 | ||
|
|
1fbcea7a06 | ||
|
|
168c839cf1 | ||
|
|
162e913cc4 | ||
|
|
5aaf50d68e | ||
|
|
d2f5be1d18 | ||
|
|
36ab455a49 | ||
|
|
ee8cab8455 | ||
|
|
bd884e85d4 | ||
|
|
5ceb6fb740 | ||
|
|
0d6155e8bc | ||
|
|
a78c59c11a | ||
|
|
173420c608 | ||
|
|
10b0ec301b | ||
|
|
1706a869d9 | ||
|
|
d0393799d2 | ||
|
|
739433ba8b | ||
|
|
a15e9c29c8 | ||
|
|
d58f89aa26 | ||
|
|
b7671f70da | ||
|
|
52366b9dd4 | ||
|
|
32417e40cb | ||
|
|
4cb44be9a0 | ||
|
|
a484455b0b | ||
|
|
4b3ed2b7ba | ||
|
|
e2986a7b4c | ||
|
|
82e04800aa | ||
|
|
5d367da626 | ||
|
|
59de5a5f55 | ||
|
|
0855104068 | ||
|
|
8c6f97c4e2 | ||
|
|
2d16856582 | ||
|
|
41e903cf26 | ||
|
|
4872bd3a92 | ||
|
|
8b675f55cc | ||
|
|
acda7f02c6 | ||
|
|
184ff90b9f | ||
|
|
d8be3c28cb | ||
|
|
3d358ab046 | ||
|
|
960bdfc232 | ||
|
|
101b4daff4 | ||
|
|
13431ff8cf | ||
|
|
4cdcad29df | ||
|
|
a4c34ff7be | ||
|
|
2b7b5e9a8f | ||
|
|
58db902084 | ||
|
|
983e3c9eaa | ||
|
|
dbe35cf567 | ||
|
|
8298f9d491 | ||
|
|
16a951b938 | ||
|
|
51fcbfb3c2 | ||
|
|
e01e370d16 | ||
|
|
736ac8ba90 | ||
|
|
d07104b8d9 | ||
|
|
cad53e397a | ||
|
|
3608a6d068 | ||
|
|
92ddd2eebe | ||
|
|
bf0b58b344 | ||
|
|
ff543b151c | ||
|
|
d842025835 | ||
|
|
230e56370a | ||
|
|
a8514a9ae4 | ||
|
|
148f7a9cfe | ||
|
|
29d50cabc2 | ||
|
|
a8f8297131 | ||
|
|
cd4b632d75 | ||
|
|
843754b7e7 | ||
|
|
847cc2bc50 | ||
|
|
751bd15785 | ||
|
|
c12db7567e | ||
|
|
e8069a10ba | ||
|
|
9742bf13e4 | ||
|
|
6441707c76 | ||
|
|
23bcba4fd9 | ||
|
|
9049a205b7 | ||
|
|
8cfa0b595c | ||
|
|
4b958e8b87 | ||
|
|
bcd5d2848d | ||
|
|
b59cbeceac | ||
|
|
46f948a584 | ||
|
|
14bf3a134b | ||
|
|
1557438fdf | ||
|
|
27b680e0cd | ||
|
|
14314ef939 | ||
|
|
bf5c168d7d | ||
|
|
1e0791416d | ||
|
|
ab8d42b609 | ||
|
|
96dbdbe7c9 | ||
|
|
6f135ad6ab | ||
|
|
5b9a1e1978 | ||
|
|
4ba3522e79 | ||
|
|
d3faa22b78 | ||
|
|
1daad334a5 | ||
|
|
3dda49dab4 | ||
|
|
c6c4e5580b | ||
|
|
3f808e3813 | ||
|
|
e5107c40f9 | ||
|
|
0871ca884e | ||
|
|
62ce9311bf | ||
|
|
70b15a7ab0 | ||
|
|
708bff20f0 | ||
|
|
369628ee95 | ||
|
|
0c6f8f1136 | ||
|
|
9f9d011d46 | ||
|
|
e28b73c130 | ||
|
|
56f953ab2f | ||
|
|
3ad8be175c | ||
|
|
f5f990511c | ||
|
|
1e3ccba503 | ||
|
|
a842b5b7cd | ||
|
|
909e42b0be | ||
|
|
c8acb5de68 | ||
|
|
53b9e3ddc1 | ||
|
|
68e1c61e7f | ||
|
|
8605b35b57 | ||
|
|
36680e82aa | ||
|
|
83b7d5a5f1 | ||
|
|
fe41e9d573 | ||
|
|
d76e6647d2 | ||
|
|
6f17f70137 | ||
|
|
ef01754ad5 | ||
|
|
eab9347522 | ||
|
|
59bcd62717 | ||
|
|
3f01fad12f | ||
|
|
c7f0d14c1b | ||
|
|
2408829627 | ||
|
|
8d244c8d34 | ||
|
|
42af057316 | ||
|
|
8f68078835 | ||
|
|
0c34032fd3 | ||
|
|
20f457a3e9 | ||
|
|
39693ca1fe | ||
|
|
784908420e | ||
|
|
9685929824 | ||
|
|
fe4b2c4ae4 | ||
|
|
5f87bb13f8 | ||
|
|
a87f6c6709 | ||
|
|
da3ee6b65e | ||
|
|
c5eda37bda | ||
|
|
1966367caf | ||
|
|
eed7b6e565 | ||
|
|
0e54ed691d | ||
|
|
997289da02 | ||
|
|
c841e57db5 | ||
|
|
f5138385be | ||
|
|
63ceba199d | ||
|
|
e6ee4ceae2 | ||
|
|
19a9d815eb | ||
|
|
5b78b363f0 | ||
|
|
b078c00492 | ||
|
|
e712efd008 | ||
|
|
ab27c0ce53 | ||
|
|
d97cabbe79 | ||
|
|
c3c7ffad25 | ||
|
|
fe4329d730 | ||
|
|
c53ba7b119 | ||
|
|
025eec6c70 | ||
|
|
40e1670314 | ||
|
|
2bca260627 | ||
|
|
463d8e8950 | ||
|
|
e2eed8a728 | ||
|
|
f97effcfe0 | ||
|
|
2cf21ab3bd | ||
|
|
7daa602630 | ||
|
|
7b637d6a61 | ||
|
|
a4f979be08 | ||
|
|
8852739111 | ||
|
|
2099ea16ec | ||
|
|
a739eb6d60 | ||
|
|
529ddacafe | ||
|
|
f71c95b74a | ||
|
|
8260a0843b | ||
|
|
bfbeb7b1fb | ||
|
|
df70810aa6 | ||
|
|
aca5804f98 | ||
|
|
b7f7288a4b | ||
|
|
d54a2bde0f | ||
|
|
679bb8d357 | ||
|
|
ca515998e4 | ||
|
|
c5b6d203f5 | ||
|
|
86159c5d86 | ||
|
|
846802c003 | ||
|
|
e9ec32b3c3 | ||
|
|
4882bec118 | ||
|
|
89ff259be0 | ||
|
|
60ece7fbf7 | ||
|
|
0c110f574a | ||
|
|
dbca5b2a7e | ||
|
|
3088298e6b | ||
|
|
a9c6a12182 | ||
|
|
fa5b512629 | ||
|
|
5c2061a6e6 | ||
|
|
cf0fc956c9 | ||
|
|
a0517dfbeb | ||
|
|
39c71638e6 | ||
|
|
672b728379 | ||
|
|
750a546faf | ||
|
|
a41835573b | ||
|
|
2650cb89b5 | ||
|
|
4a122e0209 | ||
|
|
ce4bf62d75 | ||
|
|
40bbcb3250 | ||
|
|
905f51fbd0 | ||
|
|
cd4fe4362b | ||
|
|
ed7be6eb99 | ||
|
|
555007ab16 | ||
|
|
bd31b99324 | ||
|
|
60237c3c0b | ||
|
|
eb21833d94 | ||
|
|
763002ae14 | ||
|
|
ae2dc39a78 | ||
|
|
fe4ced2709 | ||
|
|
9075d68b7c | ||
|
|
759c0ea957 | ||
|
|
67b393d4a3 | ||
|
|
de71821759 | ||
|
|
0c2bcaee34 | ||
|
|
1613975e0e | ||
|
|
be82204df2 | ||
|
|
14c2ff5545 | ||
|
|
d7d0e11f2c | ||
|
|
6654f45cb8 | ||
|
|
23f92179ad | ||
|
|
7377917642 | ||
|
|
0f796859f2 | ||
|
|
6383230678 | ||
|
|
51536f8746 | ||
|
|
e3b6c061c4 | ||
|
|
4bd3fa74d1 | ||
|
|
71553988d5 | ||
|
|
761b24e614 | ||
|
|
10974902b5 | ||
|
|
474407dbc2 | ||
|
|
95d84f354d | ||
|
|
db47a9a253 | ||
|
|
709a4639b3 | ||
|
|
28b9cd02ef | ||
|
|
af9ea13933 | ||
|
|
bd2cd18916 | ||
|
|
23138dc0b4 | ||
|
|
a2f9742cfc | ||
|
|
6378e614b0 | ||
|
|
b116a57aa7 | ||
|
|
a03f32f521 | ||
|
|
b9180be685 | ||
|
|
334aee64ad | ||
|
|
16bd368a58 | ||
|
|
3266a0f85c | ||
|
|
4629f1b03f | ||
|
|
fbd0c6cbea | ||
|
|
8260051c30 | ||
|
|
c061c9c3ff | ||
|
|
8961191b2e | ||
|
|
fc0d99be41 | ||
|
|
6834e72c4a | ||
|
|
efe655f880 | ||
|
|
3d5ddce621 | ||
|
|
a3de3e15cb | ||
|
|
619999d4f8 | ||
|
|
7acf27dd38 | ||
|
|
ba517eeeb5 | ||
|
|
fdd3e24967 | ||
|
|
a570ce202a | ||
|
|
0a220bbc7a | ||
|
|
e0e511f56d | ||
|
|
d375dece0e | ||
|
|
f801f265ed | ||
|
|
1b4fc89b07 | ||
|
|
3ac2b77bf0 | ||
|
|
b2ca4ad66b | ||
|
|
25a7c7bc7f | ||
|
|
6b009a4de4 | ||
|
|
0b80a86e88 | ||
|
|
b03f24d59a | ||
|
|
78ea13d366 | ||
|
|
8c2bdfba1c | ||
|
|
3289968a93 | ||
|
|
73ae754aa7 | ||
|
|
20a6e7e210 | ||
|
|
4cf433a994 | ||
|
|
e36c4d397c | ||
|
|
26037327f9 | ||
|
|
da6aa1d697 | ||
|
|
dada6aa3d1 | ||
|
|
fa5ebb1677 | ||
|
|
f071df325d | ||
|
|
3c042c4011 | ||
|
|
7e8109caa3 | ||
|
|
d3add6d8e4 | ||
|
|
1b089749c0 | ||
|
|
791d6b7e57 | ||
|
|
233bb603cf | ||
|
|
db8a816524 | ||
|
|
eff50ca202 | ||
|
|
ceabb5ab2c | ||
|
|
122c2fd5e6 | ||
|
|
cd27a72982 | ||
|
|
19b0f0d7dc | ||
|
|
6ce2049935 | ||
|
|
53b937be63 | ||
|
|
71c8f99dab | ||
|
|
9eb3fca726 | ||
|
|
019bd4dec8 | ||
|
|
be1ce06c00 | ||
|
|
074bfe3db2 | ||
|
|
34e72b42dc | ||
|
|
97d24d76d8 | ||
|
|
4d1af867a4 | ||
|
|
fc6b4c12b2 | ||
|
|
405c6de591 | ||
|
|
47bfa5fcc0 | ||
|
|
67d91d5fc5 | ||
|
|
f832c56adb | ||
|
|
1aa9ae680e | ||
|
|
c4b30db82d | ||
|
|
abd9f3c6be | ||
|
|
3de3594282 | ||
|
|
ed5816d464 | ||
|
|
3d43bdbb49 | ||
|
|
1ab492ce5b | ||
|
|
de30c6ad79 | ||
|
|
f5a48ff98d | ||
|
|
8493ee5b83 | ||
|
|
52a6d55e5d | ||
|
|
7142295aa5 | ||
|
|
8b65be26a6 | ||
|
|
60f5deb494 | ||
|
|
0fc09e6dd3 | ||
|
|
0c4ccf4e3e | ||
|
|
a0e79bf446 | ||
|
|
aa356ad7c7 | ||
|
|
9e0e384d46 | ||
|
|
a20b99e643 | ||
|
|
fe4237b2b1 | ||
|
|
4146835f6f | ||
|
|
5201ea4516 | ||
|
|
fba7b36245 | ||
|
|
353ac0fc0c | ||
|
|
00002b1e24 | ||
|
|
aa32830671 | ||
|
|
12b8100d89 | ||
|
|
72e56d271d | ||
|
|
2b69f5eff4 | ||
|
|
f224bb98c4 | ||
|
|
ec17eb3fbc | ||
|
|
358b600713 | ||
|
|
aacb5c39ba | ||
|
|
9ae8804095 | ||
|
|
2d51bd895d | ||
|
|
18f656fed2 | ||
|
|
eea76999b2 | ||
|
|
bd495adf22 | ||
|
|
e8c7bee924 | ||
|
|
24b06c24dc | ||
|
|
6fdd764a35 | ||
|
|
2400d1f265 | ||
|
|
cdef430b0b | ||
|
|
6074e4ae2c | ||
|
|
6ada704bc3 | ||
|
|
e8f7daac6f | ||
|
|
404455928e | ||
|
|
bca3e62ced | ||
|
|
e79391907a | ||
|
|
54a23cc7fa | ||
|
|
e8ebe77923 | ||
|
|
68e0aa1ea9 | ||
|
|
ceaf64c9b3 | ||
|
|
e015b1bec5 | ||
|
|
ca9726c997 | ||
|
|
85c79abd30 | ||
|
|
54c1c7ad84 | ||
|
|
af15e31b42 | ||
|
|
99b4f9f4dd | ||
|
|
9a0e5ea626 | ||
|
|
984205e02c | ||
|
|
e11bf6ac67 | ||
|
|
e029d91544 | ||
|
|
011856cbfa | ||
|
|
9ce8164013 | ||
|
|
dca6b7bbf5 | ||
|
|
2511ec3395 | ||
|
|
9f6ffc7186 | ||
|
|
622f0217fa | ||
|
|
09eba49bab | ||
|
|
c20c3801a8 | ||
|
|
f8208b7288 | ||
|
|
3db8a65ddb | ||
|
|
0495b6f298 | ||
|
|
52c55f385d | ||
|
|
fe28d6fba0 | ||
|
|
0294e49d4a | ||
|
|
cbaf51d3ef | ||
|
|
efb604a381 | ||
|
|
da87f05126 | ||
|
|
21534b78cb | ||
|
|
ab65593f7d | ||
|
|
3a82691503 | ||
|
|
430e2ff0b5 | ||
|
|
7d572e7988 | ||
|
|
1449e24b66 | ||
|
|
6a671760da | ||
|
|
613bdd82b0 | ||
|
|
01bae358f9 | ||
|
|
3a8e560dc5 | ||
|
|
a0c538d9ee | ||
|
|
07c30f86e9 | ||
|
|
c22e38f9a0 | ||
|
|
4d082a87a1 | ||
|
|
1b6512fc8d | ||
|
|
7502e8a1e4 | ||
|
|
5a25211371 | ||
|
|
6983f9b2bf | ||
|
|
5affe64b61 | ||
|
|
c52f3c396b | ||
|
|
e1ebbaa52f | ||
|
|
7abe6af5ec | ||
|
|
c98ea6055b | ||
|
|
3e3d255edf | ||
|
|
816d4851e7 | ||
|
|
9e32886f60 | ||
|
|
79af315b9f | ||
|
|
4110c702c0 | ||
|
|
57eb29c3c8 | ||
|
|
5267c4eee6 | ||
|
|
1453ebe8ca | ||
|
|
3b5cea5768 | ||
|
|
a89f67348d | ||
|
|
af1ae0399c | ||
|
|
5bf84144e7 | ||
|
|
d9adff5749 | ||
|
|
a1cd8baf3e | ||
|
|
87cadce0ac | ||
|
|
f22f50afa2 | ||
|
|
d0fa6f7e03 | ||
|
|
d29c58dce5 | ||
|
|
9ea8f3b590 | ||
|
|
ffffc68144 | ||
|
|
086de9f2a0 | ||
|
|
1b731aa4a3 | ||
|
|
12913f6b90 | ||
|
|
1d4186392c | ||
|
|
48d3ab0cb4 | ||
|
|
a9d04729eb | ||
|
|
7adf2292bd | ||
|
|
c8f55f9c05 | ||
|
|
93de62f81d | ||
|
|
9766231d41 | ||
|
|
9df80e5bec | ||
|
|
81b00e4302 | ||
|
|
25ac7c31c4 | ||
|
|
11c6c9a2b8 | ||
|
|
066268765a | ||
|
|
356b33ced0 | ||
|
|
77088d55f9 | ||
|
|
8d965655a8 | ||
|
|
3ace4952e6 | ||
|
|
299f9f3a10 | ||
|
|
0e6790cdab | ||
|
|
1f90af3aec | ||
|
|
cdb18a5baf | ||
|
|
8adb6596fb | ||
|
|
dd6bcd68cc | ||
|
|
1c9ce0ede0 | ||
|
|
6b409b06cc | ||
|
|
361e864415 | ||
|
|
d5606114cd | ||
|
|
afca66d655 | ||
|
|
33a6c9fda7 | ||
|
|
a198484ab6 | ||
|
|
dd5bce9694 | ||
|
|
bef17ff76d | ||
|
|
7202605fc8 | ||
|
|
747dac531d | ||
|
|
07d24f70e1 | ||
|
|
d0546a2ea2 | ||
|
|
9e9621e7b2 | ||
|
|
d39f3b4265 | ||
|
|
223339cd61 | ||
|
|
dc430bae10 | ||
|
|
661b07e12f | ||
|
|
0727c7bdfb | ||
|
|
b4c52116b4 | ||
|
|
89dc6108b7 | ||
|
|
492eba3a8b | ||
|
|
77fee1390f | ||
|
|
bfb54189b8 | ||
|
|
fb43885d85 | ||
|
|
7c1f705e64 | ||
|
|
fe1fa7689a | ||
|
|
8b79eee081 | ||
|
|
4a3e04ced9 | ||
|
|
61618250ec | ||
|
|
60c0ca3ae5 | ||
|
|
3436405c55 | ||
|
|
d43c3ec865 | ||
|
|
545da96d15 | ||
|
|
f53305c404 | ||
|
|
199d976e3d | ||
|
|
a1366f0ef1 | ||
|
|
91192a8a8f | ||
|
|
c6f16b0558 | ||
|
|
0d37eb65c9 | ||
|
|
f7e3a74794 | ||
|
|
976b07cd61 | ||
|
|
cff9cd5fb8 | ||
|
|
f0fd947046 | ||
|
|
7f4ada9c4b | ||
|
|
db4ef328b1 | ||
|
|
b438802e71 | ||
|
|
632a19ceeb | ||
|
|
629f8ece7a | ||
|
|
9215f9beb5 | ||
|
|
154431e86c | ||
|
|
8cfb14198b | ||
|
|
2ed475fc76 | ||
|
|
34512da2fb | ||
|
|
5b3c6f7752 | ||
|
|
f4dd27ca3f | ||
|
|
ce47d8c591 | ||
|
|
b941ee9aa9 | ||
|
|
45de3f0128 | ||
|
|
41d3826c1a | ||
|
|
f2ff6e6a70 | ||
|
|
e47c90c645 | ||
|
|
d2fef20239 | ||
|
|
3b359cfc4f | ||
|
|
acfc5a4676 | ||
|
|
a6b1647f27 | ||
|
|
d5851bbb6a | ||
|
|
397bbe2655 | ||
|
|
95d8d3ef72 | ||
|
|
1b51a1aedd | ||
|
|
ff75b3cd1f | ||
|
|
2abcaf02e2 | ||
|
|
d0c9f5a776 | ||
|
|
a46d510e2b | ||
|
|
2d861b4077 | ||
|
|
470c62d993 | ||
|
|
364734094b | ||
|
|
a667c957ee | ||
|
|
dc648b0142 | ||
|
|
1d14140986 | ||
|
|
2b693dc492 | ||
|
|
38bea6c086 | ||
|
|
cbdc5f121e | ||
|
|
562763199a | ||
|
|
107c09604a | ||
|
|
2b0567b368 | ||
|
|
d00a56bec0 | ||
|
|
ead8cc6cec | ||
|
|
806ff9daaf | ||
|
|
b3863a3e10 | ||
|
|
5559107776 | ||
|
|
af1cf806f0 | ||
|
|
0cf33c6119 | ||
|
|
b38b8a3365 | ||
|
|
e998c8434d | ||
|
|
4b90cf93d3 | ||
|
|
3dd4e33758 | ||
|
|
6caeb11598 | ||
|
|
11a383b7e5 | ||
|
|
fd2a2530b1 | ||
|
|
2f42b2d072 | ||
|
|
775f58f972 | ||
|
|
5c0abb3d96 | ||
|
|
3dc46bc5a3 | ||
|
|
e2871fc048 | ||
|
|
44e3f9b49f | ||
|
|
d3f4263639 | ||
|
|
9dec05f62d | ||
|
|
0447aa07fa | ||
|
|
0e25af790d | ||
|
|
d8484e777f | ||
|
|
665a3dbcbf | ||
|
|
103af2e2c1 | ||
|
|
d0daeb0078 | ||
|
|
a9aa49c2e5 | ||
|
|
aad24389a7 | ||
|
|
4b4f6f3201 | ||
|
|
81f82d3b73 | ||
|
|
4103ccf791 | ||
|
|
e2ed11f960 | ||
|
|
8e1cd67a3d | ||
|
|
18b4caa55e | ||
|
|
30207ce0c2 | ||
|
|
0f37f1cb23 | ||
|
|
99a905cdee | ||
|
|
6eac079440 | ||
|
|
4a1e7b8d5a | ||
|
|
5e781a9010 | ||
|
|
277d817429 | ||
|
|
970f202757 | ||
|
|
2c6b2b47a4 | ||
|
|
5c8be4b21f | ||
|
|
558a4a2b30 | ||
|
|
203ad7d594 | ||
|
|
04cf1aed9c | ||
|
|
8c14526f85 | ||
|
|
1ff241411b | ||
|
|
351e36b273 | ||
|
|
ca06040efc | ||
|
|
2102cc4e9a | ||
|
|
14d3493db8 | ||
|
|
d082d208e1 | ||
|
|
959efd17c8 | ||
|
|
30a7c55e84 | ||
|
|
2aec759857 | ||
|
|
54dcaaac0c | ||
|
|
8e5730f374 | ||
|
|
cb1705b6e8 | ||
|
|
0bcefda60d | ||
|
|
966620f840 | ||
|
|
bd3bb67949 | ||
|
|
69f4daa9d2 | ||
|
|
ec11155c9c | ||
|
|
c48a3a24f7 | ||
|
|
1b8bc9f0ff | ||
|
|
5bf25c7eca | ||
|
|
5a7f0ad340 | ||
|
|
8a93922da0 | ||
|
|
295de5be8e | ||
|
|
5c114b584d | ||
|
|
dad58b7610 | ||
|
|
23c1dadb4c | ||
|
|
05332e0606 | ||
|
|
453842246f | ||
|
|
38280b9027 | ||
|
|
7d7c0011bb | ||
|
|
de2d87a6d9 | ||
|
|
537152450f | ||
|
|
87f6b19207 | ||
|
|
064fef5d8a | ||
|
|
6aebaa94af | ||
|
|
a1a6ac9c08 | ||
|
|
0d936425c2 | ||
|
|
00f867c6ee | ||
|
|
bfea3f35f0 | ||
|
|
3f3cf5e20e | ||
|
|
0786a962e7 | ||
|
|
f7c0486101 | ||
|
|
2e3798b282 | ||
|
|
181740c3e9 | ||
|
|
23c51a1d5f | ||
|
|
15ce529c09 | ||
|
|
0fd941401b | ||
|
|
808ae6a442 | ||
|
|
ada8d18fa1 | ||
|
|
9752abff19 | ||
|
|
ee43e68a57 | ||
|
|
348ac78f5a | ||
|
|
0b22fb933c | ||
|
|
cbd4b4849f | ||
|
|
f8a48c16f0 | ||
|
|
8cc4e4bfca | ||
|
|
6c01bb87bf | ||
|
|
02d5a6f15e | ||
|
|
f361403888 | ||
|
|
3a4550fe24 | ||
|
|
5e722c830f | ||
|
|
5a56982ad9 | ||
|
|
13d70cd12a | ||
|
|
bb87bdce8a | ||
|
|
cc7ded1be7 | ||
|
|
d8735e6a91 | ||
|
|
813ae71aad | ||
|
|
1a75bd87d8 | ||
|
|
bdc89e2056 | ||
|
|
bf09990f9c | ||
|
|
81ba8dc518 | ||
|
|
e519aaaf1e | ||
|
|
5a90a5e6c7 | ||
|
|
b855ea92a9 | ||
|
|
7e74d06cdd | ||
|
|
1e7602bc36 | ||
|
|
7477e672fe | ||
|
|
4a4522dfa3 | ||
|
|
8afe058cfc | ||
|
|
e13103a925 | ||
|
|
8e167ff174 | ||
|
|
c0dcd31819 | ||
|
|
a83ae996db | ||
|
|
a66ddabe8a | ||
|
|
cdab1e9cae | ||
|
|
f2140d1eb0 | ||
|
|
4a3fa85b5f | ||
|
|
ade23a9650 | ||
|
|
fc547e6c47 | ||
|
|
f532b5edee | ||
|
|
da76eb5cf4 | ||
|
|
ac89f352ce | ||
|
|
198b2a2ace | ||
|
|
93a3d78111 | ||
|
|
494997d021 | ||
|
|
eb1345e5c3 | ||
|
|
dc8f5d002d | ||
|
|
9030e5e6eb | ||
|
|
2b827baeed | ||
|
|
cc6fa312f0 | ||
|
|
b8eba5b643 | ||
|
|
0c3f98cc37 | ||
|
|
cd35770d26 | ||
|
|
bd3a59e9ca | ||
|
|
ff50d601b3 | ||
|
|
a4fde0f9e6 | ||
|
|
c7a621cb31 | ||
|
|
6f62d62909 | ||
|
|
c3aa2df4e9 | ||
|
|
f4fbf2da75 | ||
|
|
288de8acaa | ||
|
|
750e6cff45 | ||
|
|
f49e94052d | ||
|
|
ceb642b745 | ||
|
|
ed1d886f01 | ||
|
|
db98d811b0 | ||
|
|
14ba11e1ab | ||
|
|
483bdddb18 | ||
|
|
d16fda3f16 | ||
|
|
c718b94937 | ||
|
|
bb21ab073f | ||
|
|
3ffe530461 | ||
|
|
95cf0d86bc | ||
|
|
9899a5fc56 | ||
|
|
952648185c | ||
|
|
69740493b7 | ||
|
|
994a28f31d | ||
|
|
b0a8a03f0a | ||
|
|
36e740f4cc | ||
|
|
a5874a3f80 | ||
|
|
0c95b978b4 | ||
|
|
e21f9dd1fb | ||
|
|
74a08b86ce | ||
|
|
8212d3c540 | ||
|
|
1ed6458b39 | ||
|
|
60c8f23a63 | ||
|
|
5e627641ea | ||
|
|
abc954aa47 | ||
|
|
3bfa27e682 | ||
|
|
4b7e122254 | ||
|
|
cfd3c13751 | ||
|
|
6a1ee2b894 | ||
|
|
cbec4a4e81 | ||
|
|
c7d8c1cab5 | ||
|
|
c39e1e649d | ||
|
|
95ab64ba77 | ||
|
|
1f8fcdb0f3 | ||
|
|
fb26a8ae50 | ||
|
|
42dbeaa1a5 | ||
|
|
3305f4c03a | ||
|
|
f5beec70c8 | ||
|
|
c2cd21c15e | ||
|
|
633e389275 | ||
|
|
f5aaf298e1 | ||
|
|
7db6136436 | ||
|
|
ce7296eebb | ||
|
|
c6226547f7 | ||
|
|
e7ec8f0489 | ||
|
|
dc466f238b | ||
|
|
f164656874 | ||
|
|
ef3c59152f | ||
|
|
14c6ead389 | ||
|
|
b0d33f3a85 | ||
|
|
066cc20ee6 | ||
|
|
0f51b5b1ce | ||
|
|
ebcf3e0119 | ||
|
|
6ee248d656 | ||
|
|
8a46b410f6 | ||
|
|
cd06cea153 | ||
|
|
8393d44c50 | ||
|
|
3e8ce80f80 | ||
|
|
80e033c91d | ||
|
|
a7dd441333 | ||
|
|
48efaa9fd7 | ||
|
|
92e831175f | ||
|
|
9b75e076b5 | ||
|
|
2c5d6d1651 | ||
|
|
c5cf116f33 | ||
|
|
66a4a9bce6 | ||
|
|
7e7ef64c79 | ||
|
|
9a31f53d4d | ||
|
|
60d6314262 | ||
|
|
eff7445804 | ||
|
|
2bfe21aef4 | ||
|
|
6ae0c7dcfc | ||
|
|
883ac1007a | ||
|
|
ff6747dab5 | ||
|
|
555e6f804c | ||
|
|
98c5c7990f | ||
|
|
27dcdb517d | ||
|
|
916d75d161 | ||
|
|
57bd16f4f5 | ||
|
|
444a1756a2 | ||
|
|
0c97c8f04f | ||
|
|
de81e81ecb | ||
|
|
40a7d5a112 | ||
|
|
7c06633a67 | ||
|
|
9abf611d63 | ||
|
|
565fe4c348 | ||
|
|
7b43403b1c | ||
|
|
9f25979b4c | ||
|
|
20f70b8b07 | ||
|
|
8fbf05acd4 | ||
|
|
6860855c71 | ||
|
|
b78660c685 | ||
|
|
6a40bbdcb5 | ||
|
|
5a1a4f5594 | ||
|
|
2ac7be4363 | ||
|
|
4ef5ab7a90 | ||
|
|
192308a6a3 | ||
|
|
c9ce111643 | ||
|
|
32fda231ad | ||
|
|
e4d4686717 | ||
|
|
ca6c6a1b6e | ||
|
|
806c4ce168 | ||
|
|
9d14597be0 | ||
|
|
dc68bb907c | ||
|
|
e4598c058a | ||
|
|
377562cdff | ||
|
|
969fcccc1f | ||
|
|
4c00764146 | ||
|
|
e98ee64695 | ||
|
|
51c7d0ed3e | ||
|
|
db93498ee6 | ||
|
|
b3af493758 | ||
|
|
7b9ebbbb5f | ||
|
|
ea735fc0a0 | ||
|
|
06227e97d0 | ||
|
|
dbc50b6d4f | ||
|
|
8b75ad1e7f | ||
|
|
8f3c740b57 | ||
|
|
24a6a0c23f | ||
|
|
267d72a76c | ||
|
|
021cecc216 | ||
|
|
4642480bbb | ||
|
|
4d48482d1e | ||
|
|
2054784a4a | ||
|
|
57d816b862 | ||
|
|
32bb66c42b | ||
|
|
e4d15e64b6 | ||
|
|
b11daeae7d | ||
|
|
8e78f8527e | ||
|
|
bc385744db | ||
|
|
02fd28edec | ||
|
|
95770ac104 | ||
|
|
2eeb762cd9 | ||
|
|
de5666d262 | ||
|
|
934d266880 | ||
|
|
b8fa234dbb | ||
|
|
bceea090ed | ||
|
|
8efda2ef56 | ||
|
|
f86cda6730 | ||
|
|
43f59a19fb | ||
|
|
eabaa94e06 | ||
|
|
20575a2b0f | ||
|
|
0096a778ac | ||
|
|
050f83e3bb | ||
|
|
32f7b4fbf6 | ||
|
|
265e8cdeaf | ||
|
|
fe5460e0c5 | ||
|
|
d4cad337fc | ||
|
|
0af6ecbd34 | ||
|
|
6466045363 | ||
|
|
67c7e503d9 | ||
|
|
01e06f0503 | ||
|
|
d6e155d874 | ||
|
|
5a2350bac1 | ||
|
|
2b97f9ac9e | ||
|
|
090b215179 | ||
|
|
32f483c333 | ||
|
|
655652f94f | ||
|
|
53b84c147c | ||
|
|
d8b4827601 | ||
|
|
5737acf759 | ||
|
|
58262e8604 | ||
|
|
b9e5eaf689 | ||
|
|
6d78cd59e9 | ||
|
|
aa00e33b6d | ||
|
|
4ef04c641e | ||
|
|
bfcccd8c33 | ||
|
|
f7d3c73c4a | ||
|
|
bfdece1c23 | ||
|
|
a68f450d36 | ||
|
|
d1df225e47 | ||
|
|
9c6ba4bc61 | ||
|
|
c01121d61a | ||
|
|
e0f0bb5523 | ||
|
|
a3b7742992 | ||
|
|
4b5fa6ddad | ||
|
|
b6aca82da6 | ||
|
|
8ee3bb4d58 | ||
|
|
46bea241e6 | ||
|
|
5e6d2881bc | ||
|
|
cd19876419 | ||
|
|
f82e103bc5 | ||
|
|
3ff4ac84c4 | ||
|
|
bf0c679a48 | ||
|
|
96c5bc164a | ||
|
|
73aead01b4 | ||
|
|
e19b3cc45d | ||
|
|
a1cab4ca7d | ||
|
|
4b89687e45 | ||
|
|
1c861fecfc | ||
|
|
a12c9f54c4 | ||
|
|
69d72e754f | ||
|
|
db3c5a3031 | ||
|
|
6327ed814a | ||
|
|
9fa32b5b6b | ||
|
|
663ae9f6bb | ||
|
|
70c73bc5d6 | ||
|
|
05d73552cf | ||
|
|
70384237c1 | ||
|
|
36e4a0dd32 | ||
|
|
3ef02d46c0 | ||
|
|
e6ce84aa14 | ||
|
|
348e34d52e | ||
|
|
2e6b3c0561 | ||
|
|
7280b48cdc | ||
|
|
2803f65479 | ||
|
|
3535688c3b | ||
|
|
9cff570c45 | ||
|
|
15c028e151 | ||
|
|
f0dc32b6e5 | ||
|
|
d423d18249 | ||
|
|
8a8e3c3fc8 | ||
|
|
111fc3c37d | ||
|
|
e9a5284dde | ||
|
|
b277f431c2 | ||
|
|
c8b8947b1f | ||
|
|
9a78d1585d | ||
|
|
09414a8834 | ||
|
|
1efa973507 | ||
|
|
e23cd6a856 | ||
|
|
743bb7a6ee | ||
|
|
086efe3c63 | ||
|
|
d751df70fd | ||
|
|
334d95c843 | ||
|
|
5ddac7866b | ||
|
|
a896fbbb90 | ||
|
|
a478699fe8 | ||
|
|
6387e50276 | ||
|
|
28c06de685 | ||
|
|
823573cea7 | ||
|
|
5d0f306bcc | ||
|
|
f30e922074 | ||
|
|
96ff0bffda | ||
|
|
d9b675fc8b | ||
|
|
104b7390da | ||
|
|
7b4456e73f | ||
|
|
86230d9bf3 | ||
|
|
1f8994ca8b | ||
|
|
f7b6f4ba19 | ||
|
|
74ea5969be | ||
|
|
c4718e7523 | ||
|
|
5de706e0fe | ||
|
|
77d06b4b03 | ||
|
|
b4d137375a | ||
|
|
0621ae7735 | ||
|
|
3a6a1f25a7 | ||
|
|
731dc64f44 | ||
|
|
d72b9cf646 | ||
|
|
0f0c4dc549 | ||
|
|
ea0820d881 | ||
|
|
312f20d2f1 | ||
|
|
f8289fa4be | ||
|
|
554209d644 | ||
|
|
4ce44034cb | ||
|
|
221b62ea57 | ||
|
|
0a5c265b12 | ||
|
|
9b55389538 | ||
|
|
60a8d6e986 | ||
|
|
d7523bcb57 | ||
|
|
562110fac4 | ||
|
|
d104265f04 | ||
|
|
4439685403 | ||
|
|
9feb72235a | ||
|
|
c372a498cc | ||
|
|
be35349350 | ||
|
|
771fd74978 | ||
|
|
3ba9ee7bf1 | ||
|
|
a0062a62e8 | ||
|
|
0f2bd77ebc | ||
|
|
744b368cc1 | ||
|
|
a03c4519c9 | ||
|
|
66327881d5 | ||
|
|
b93b14986e | ||
|
|
1ade4e9917 | ||
|
|
65f2ab6720 | ||
|
|
7d38ba12bd | ||
|
|
8128e85b6b | ||
|
|
3d99819be4 | ||
|
|
a2ca1618ea | ||
|
|
b244c56862 | ||
|
|
c931874bac | ||
|
|
1b168ac3d2 | ||
|
|
0fc123b249 | ||
|
|
c622804950 | ||
|
|
e093480a5b | ||
|
|
1498b72966 | ||
|
|
51e1c7cd50 | ||
|
|
40040b627f | ||
|
|
b752f8a357 | ||
|
|
5b93cf42b1 | ||
|
|
fa8145df9f | ||
|
|
5ddb6a7f99 | ||
|
|
8ad34357d3 | ||
|
|
81eb2684bf | ||
|
|
d2c0745efa | ||
|
|
3f2d38a86a | ||
|
|
4df4db05f4 | ||
|
|
6526fda960 | ||
|
|
32fdb257a3 | ||
|
|
9247cd874e | ||
|
|
4ceb42b88d | ||
|
|
3d99fc1708 | ||
|
|
656346bd04 | ||
|
|
6b5217ece2 | ||
|
|
0a2b388f6b | ||
|
|
25b3de84ec | ||
|
|
5c4e348078 | ||
|
|
2ac84ac8c4 | ||
|
|
f0249346b0 | ||
|
|
86651d7992 | ||
|
|
5cd5fb0071 | ||
|
|
e3bf046c30 | ||
|
|
d9af0a5547 | ||
|
|
ede7551856 | ||
|
|
a2030b6c27 | ||
|
|
9a951589dc | ||
|
|
c766cfad89 | ||
|
|
bd25572e08 | ||
|
|
4e00c950cc | ||
|
|
d932f0a204 | ||
|
|
08507272ed | ||
|
|
9c48e6578d | ||
|
|
db6c83eefd | ||
|
|
cc68cb944f | ||
|
|
a0d8d237d7 | ||
|
|
7d6d8e3a68 | ||
|
|
cc42f037c7 | ||
|
|
f4623ed028 | ||
|
|
bec467c703 | ||
|
|
bd09b1571a | ||
|
|
3ed6953d0b | ||
|
|
2fc6fb0a9a | ||
|
|
4b73bd907e | ||
|
|
a18486cc2e | ||
|
|
82f4e22f69 | ||
|
|
3497165ebd | ||
|
|
d5794a3dcb | ||
|
|
bd9ec53e7b | ||
|
|
5d98316c4e | ||
|
|
dcf2526aad | ||
|
|
85dd023c0e | ||
|
|
1fbcb44136 | ||
|
|
ab9042db24 | ||
|
|
b01487ac91 | ||
|
|
5278d7668c | ||
|
|
f7215fc2c5 | ||
|
|
d2e0413a48 | ||
|
|
0e165bc7e0 | ||
|
|
55e7f7ccb0 | ||
|
|
15061d3e0d | ||
|
|
36be44e7b5 | ||
|
|
1b4e62b823 | ||
|
|
12b07bb0ac | ||
|
|
f5666c9451 | ||
|
|
23cffce861 | ||
|
|
a1fb44f2a6 | ||
|
|
4d5dd8c8de | ||
|
|
1c492881e1 | ||
|
|
d310c53cce | ||
|
|
2289eebd91 | ||
|
|
e259bd62ab | ||
|
|
9f512705fa | ||
|
|
383fde488b | ||
|
|
b54350674c | ||
|
|
1e2b88496c | ||
|
|
919b567d46 | ||
|
|
da65c05c4f | ||
|
|
2f8ca6cf11 | ||
|
|
7121ff340a | ||
|
|
37f4d18d34 | ||
|
|
a8b424003c | ||
|
|
de4e677f18 | ||
|
|
bc632fd644 | ||
|
|
d386e1405c | ||
|
|
608742c2e2 | ||
|
|
71e91a541f | ||
|
|
ed18f10b94 | ||
|
|
4330a09793 | ||
|
|
16873201e9 | ||
|
|
2640c7b43c | ||
|
|
7824dddef7 | ||
|
|
7192480751 | ||
|
|
1da66f272c | ||
|
|
0ef1e347e7 | ||
|
|
bfba05c863 | ||
|
|
71f85350da | ||
|
|
8b54098299 | ||
|
|
356e68ca3e | ||
|
|
430c407106 | ||
|
|
31a7212a71 | ||
|
|
36a172308a | ||
|
|
e20df481a4 | ||
|
|
8fa0c95f5a | ||
|
|
4b4a859722 | ||
|
|
9cddedcdba | ||
|
|
01aebd90d5 | ||
|
|
c00105607c | ||
|
|
79ff12a1b0 | ||
|
|
e30c9c86ff | ||
|
|
c61e1e1c0e | ||
|
|
85bc5ce595 | ||
|
|
ef12e90ae7 | ||
|
|
6b0f6e4541 | ||
|
|
cdfd14e63f | ||
|
|
977058d219 | ||
|
|
c58fa586e1 | ||
|
|
1cd6fdeede | ||
|
|
09f25d78b7 | ||
|
|
82091c7951 | ||
|
|
1081211439 | ||
|
|
7e90fd45a7 | ||
|
|
08f4a424e6 | ||
|
|
063ee9c195 | ||
|
|
3f2bd657e1 | ||
|
|
903ef8e249 | ||
|
|
07b12fac3c | ||
|
|
56a2253535 | ||
|
|
752ded2f80 | ||
|
|
248d5e2fe5 | ||
|
|
158e981970 | ||
|
|
96d07c4b4e | ||
|
|
28afb8944f | ||
|
|
0d59ebfe55 | ||
|
|
bc38ecf6de | ||
|
|
755a52b78e | ||
|
|
4c008f1672 | ||
|
|
eb268fb856 | ||
|
|
b0e390ced1 | ||
|
|
741e8f625c | ||
|
|
17eb6dc4ce | ||
|
|
db997fe6f5 | ||
|
|
3b1fc2a27d | ||
|
|
0cbd965010 | ||
|
|
f3c3979eec | ||
|
|
bf26f5baa1 | ||
|
|
164462d536 | ||
|
|
239af85dd4 | ||
|
|
f585cfe02e | ||
|
|
ef54caab85 | ||
|
|
aaaa3010ab | ||
|
|
cfdf866c3f | ||
|
|
2a1a386b9c | ||
|
|
a4de8cee7d | ||
|
|
a9d6c463ed | ||
|
|
b8a725d60c | ||
|
|
927fb51313 | ||
|
|
76aa6502db | ||
|
|
7d908b6545 | ||
|
|
83a29d6ed0 | ||
|
|
55ddc4ae75 | ||
|
|
088e361228 | ||
|
|
80012b8ee8 | ||
|
|
a61235a5d1 | ||
|
|
823242a6c2 | ||
|
|
0670a4dc3c | ||
|
|
f57303f8c0 | ||
|
|
2eff9929d8 | ||
|
|
92d7dc2595 | ||
|
|
4a5cc9a986 | ||
|
|
da9896a28b | ||
|
|
b5b3a50bb2 | ||
|
|
ea99b88c44 | ||
|
|
45d162e71a | ||
|
|
8132113ed9 | ||
|
|
eef49478fc | ||
|
|
60cdd8b198 | ||
|
|
47d86b58a3 | ||
|
|
e6663aeb20 | ||
|
|
b321af6d03 | ||
|
|
78038b3141 | ||
|
|
89fd782dd3 | ||
|
|
37c68c39b0 | ||
|
|
1309290a2f | ||
|
|
3e2701f136 | ||
|
|
5ad1a12457 | ||
|
|
2e8f5202d0 | ||
|
|
6b6d751d54 | ||
|
|
a9704c6e2f | ||
|
|
e3ff447152 | ||
|
|
c5fc8fbf00 | ||
|
|
d945c28a75 | ||
|
|
7bb40c74c1 |
@@ -1,3 +1,4 @@
|
|||||||
|
bin/rr
|
||||||
config/autoload/*local*
|
config/autoload/*local*
|
||||||
data/infra
|
data/infra
|
||||||
data/cache/*
|
data/cache/*
|
||||||
@@ -5,10 +6,11 @@ data/log/*
|
|||||||
data/locks/*
|
data/locks/*
|
||||||
data/proxies/*
|
data/proxies/*
|
||||||
data/migrations_template.txt
|
data/migrations_template.txt
|
||||||
data/GeoLite2-City.*
|
data/GeoLite2-City*
|
||||||
data/database.sqlite
|
data/database.sqlite
|
||||||
data/shlink-tests.db
|
data/shlink-tests.db
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
|
CONTRIBUTING.md
|
||||||
UPGRADE.md
|
UPGRADE.md
|
||||||
composer.lock
|
composer.lock
|
||||||
vendor
|
vendor
|
||||||
@@ -21,3 +23,4 @@ infection*
|
|||||||
**/test*
|
**/test*
|
||||||
build*
|
build*
|
||||||
**/.*
|
**/.*
|
||||||
|
!config/roadrunner/.rr.yml
|
||||||
|
|||||||
51
.github/DISCUSSION_TEMPLATE/help-wanted.yml
vendored
Normal file
51
.github/DISCUSSION_TEMPLATE/help-wanted.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
title: 'Help wanted'
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Shlink version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: PHP version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: How do you serve Shlink
|
||||||
|
options:
|
||||||
|
- Self-hosted Apache
|
||||||
|
- Self-hosted nginx
|
||||||
|
- Self-hosted openswoole
|
||||||
|
- Self-hosted RoadRunner
|
||||||
|
- Openswoole Docker image
|
||||||
|
- RoadRunner Docker image
|
||||||
|
- Other (explain in summary)
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database engine
|
||||||
|
options:
|
||||||
|
- MySQL
|
||||||
|
- MariaDB
|
||||||
|
- PostgreSQL
|
||||||
|
- MicrosoftSQL
|
||||||
|
- SQLite
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
value: '<!-- Describe your issue, question or request here. -->'
|
||||||
|
|
||||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
||||||
I'm always happy to help and provide support, but some understanding will be expected.
|
I'm always happy to help and provide support, but some understanding will be expected.
|
||||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personally if an issue gets eventually closed.
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
You may also be asked to provide tests or ways to reproduce reported bugs.
|
||||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
||||||
-->
|
-->
|
||||||
|
|||||||
38
.github/ISSUE_TEMPLATE/Bug.md
vendored
38
.github/ISSUE_TEMPLATE/Bug.md
vendored
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Something on shlink is broken or not working as documented?
|
|
||||||
labels: bug
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
|
||||||
I'm always happy to help and provide support, but some understanding will be expected.
|
|
||||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
|
||||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
|
||||||
|
|
||||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
|
||||||
-->
|
|
||||||
|
|
||||||
#### How Shlink is set-up
|
|
||||||
|
|
||||||
* Shlink Version: x.y.z
|
|
||||||
* PHP Version: x.y.z
|
|
||||||
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted swoole|Docker image
|
|
||||||
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
|
||||||
|
|
||||||
#### Summary
|
|
||||||
|
|
||||||
<!-- Provide a summary describing the problem you are experiencing. -->
|
|
||||||
|
|
||||||
#### Current behavior
|
|
||||||
|
|
||||||
<!-- How is it actually behaving (and it shouldn't)? -->
|
|
||||||
|
|
||||||
#### Expected behavior
|
|
||||||
|
|
||||||
<!-- How did you expected to behave? -->
|
|
||||||
|
|
||||||
#### How to reproduce
|
|
||||||
|
|
||||||
<!-- Provide steps to reproduce the bug. -->
|
|
||||||
64
.github/ISSUE_TEMPLATE/Bug.yml
vendored
Normal file
64
.github/ISSUE_TEMPLATE/Bug.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Something on Shlink is broken or not working as documented?
|
||||||
|
labels: ['bug']
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Shlink version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: PHP version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: How do you serve Shlink
|
||||||
|
options:
|
||||||
|
- Self-hosted Apache
|
||||||
|
- Self-hosted nginx
|
||||||
|
- Self-hosted openswoole
|
||||||
|
- Self-hosted RoadRunner
|
||||||
|
- Openswoole Docker image
|
||||||
|
- RoadRunner Docker image
|
||||||
|
- Other (explain in summary)
|
||||||
|
- type: dropdown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database engine
|
||||||
|
options:
|
||||||
|
- MySQL
|
||||||
|
- MariaDB
|
||||||
|
- PostgreSQL
|
||||||
|
- MicrosoftSQL
|
||||||
|
- SQLite
|
||||||
|
- type: input
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Database version
|
||||||
|
placeholder: x.y.z
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Current behavior
|
||||||
|
value: '<!-- How is it actually behaving (and it should not)? -->'
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
value: '<!-- How did you expect it to behave? -->'
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: How to reproduce
|
||||||
|
value: '<!-- Provide steps to reproduce the bug. -->'
|
||||||
19
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
19
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Do you find shlink is missing some important feature that would make it more useful?
|
|
||||||
labels: feature
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
|
||||||
I'm always happy to help and provide support, but some understanding will be expected.
|
|
||||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
|
||||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
|
||||||
|
|
||||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
|
||||||
-->
|
|
||||||
|
|
||||||
#### Summary
|
|
||||||
|
|
||||||
<!-- Describe the new feature you would like to request. -->
|
|
||||||
16
.github/ISSUE_TEMPLATE/Feature_Request.yml
vendored
Normal file
16
.github/ISSUE_TEMPLATE/Feature_Request.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Do you find Shlink is missing some important feature that would make it more useful?
|
||||||
|
labels: ['feature']
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
value: '<!-- Describe the new feature you would like to request. -->'
|
||||||
|
- type: textarea
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
attributes:
|
||||||
|
label: Use case
|
||||||
|
value: '<!-- Explain why do you think this feature would be useful, and what problems would it help to solve. -->'
|
||||||
26
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
26
.github/ISSUE_TEMPLATE/Question_Support.md
vendored
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
name: Question - Support
|
|
||||||
about: Do you have a problem setting up or using shlink?
|
|
||||||
labels: question
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Before opening an issue, just take into account that this is a completely free of charge and open source project.
|
|
||||||
I'm always happy to help and provide support, but some understanding will be expected.
|
|
||||||
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
|
|
||||||
You may also be asked to provide tests or ways to reproduce reported bugs.
|
|
||||||
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.
|
|
||||||
|
|
||||||
With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
|
|
||||||
-->
|
|
||||||
|
|
||||||
#### How Shlink is set-up
|
|
||||||
|
|
||||||
* Shlink Version: x.y.z
|
|
||||||
* PHP Version: x.y.z
|
|
||||||
* How do you serve Shlink: Self-hosted Apache|Self-hosted nginx|Self-hosted swoole|Docker image
|
|
||||||
* Database engine used: MySQL|MariaDB|PostgreSQL|MicrosoftSQL|SQLite (x.y.z)
|
|
||||||
|
|
||||||
#### Summary
|
|
||||||
|
|
||||||
<!-- Describe the issue you are facing here. -->
|
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Question - Support
|
||||||
|
about: Do you need help setting up or using Shlink?
|
||||||
|
url: https://github.com/shlinkio/shlink/discussions/new?category=help-wanted
|
||||||
47
.github/actions/ci-setup/action.yml
vendored
Normal file
47
.github/actions/ci-setup/action.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: CI setup
|
||||||
|
description: 'Sets up the environment to run CI actions for Shlink'
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
install-deps:
|
||||||
|
description: 'Tells if dependencies should be installed with composer. Default value is "yes"'
|
||||||
|
required: true
|
||||||
|
default: 'yes'
|
||||||
|
php-version:
|
||||||
|
description: 'The PHP version to be setup'
|
||||||
|
required: true
|
||||||
|
php-extensions:
|
||||||
|
description: 'The PHP extensions to install'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
extensions-cache-key:
|
||||||
|
description: 'The key used to cache PHP extensions. If empty value is provided, extension caching is disabled'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Setup cache environment
|
||||||
|
id: extcache
|
||||||
|
uses: shivammathur/cache-extensions@v1
|
||||||
|
with:
|
||||||
|
php-version: ${{ inputs.php-version }}
|
||||||
|
extensions: ${{ inputs.php-extensions }}
|
||||||
|
key: ${{ inputs.extensions-cache-key }}
|
||||||
|
- name: Cache extensions
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.extcache.outputs.dir }}
|
||||||
|
key: ${{ steps.extcache.outputs.key }}
|
||||||
|
restore-keys: ${{ steps.extcache.outputs.key }}
|
||||||
|
- name: Use PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ inputs.php-version }}
|
||||||
|
tools: composer
|
||||||
|
extensions: ${{ inputs.php-extensions }}
|
||||||
|
coverage: pcov
|
||||||
|
ini-values: pcov.directory=module
|
||||||
|
- name: Install dependencies
|
||||||
|
if: ${{ inputs.install-deps == 'yes' }}
|
||||||
|
run: composer install --no-interaction --prefer-dist ${{ inputs.php-version == '8.3' && '--ignore-platform-reqs' || '' }}
|
||||||
|
shell: bash
|
||||||
45
.github/workflows/ci-db-tests.yml
vendored
Normal file
45
.github/workflows/ci-db-tests.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Database tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
platform:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: One of sqlite:ci, mysql, maria, postgres or ms
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
db-tests:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2', '8.3']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.3' }}
|
||||||
|
env:
|
||||||
|
LC_ALL: C
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install MSSQL ODBC
|
||||||
|
if: ${{ inputs.platform == 'ms' }}
|
||||||
|
run: sudo ./data/infra/ci/install-ms-odbc.sh
|
||||||
|
- name: Start database server
|
||||||
|
if: ${{ inputs.platform != 'sqlite:ci' }}
|
||||||
|
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_${{ inputs.platform }}
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-22.1.0, pdo_sqlsrv-5.11.1
|
||||||
|
extensions-cache-key: db-tests-extensions-${{ matrix.php-version }}-${{ inputs.platform }}
|
||||||
|
- name: Create test database
|
||||||
|
if: ${{ inputs.platform == 'ms' }}
|
||||||
|
run: docker-compose exec -T shlink_db_ms /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
|
||||||
|
- name: Run tests
|
||||||
|
run: composer test:db:${{ inputs.platform }}
|
||||||
|
- name: Upload code coverage
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ matrix.php-version == '8.2' && inputs.platform == 'sqlite:ci' }}
|
||||||
|
with:
|
||||||
|
name: coverage-db
|
||||||
|
path: |
|
||||||
|
build/coverage-db
|
||||||
|
build/coverage-db.cov
|
||||||
14
.github/workflows/ci-docker-image-build.yml
vendored
Normal file
14
.github/workflows/ci-docker-image-build.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name: Build docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'Dockerfile'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-docker-image:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- run: docker build -t shlink-docker-image:temp .
|
||||||
46
.github/workflows/ci-mutation-tests.yml
vendored
Normal file
46
.github/workflows/ci-mutation-tests.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: Mutation tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
test-group:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: One of unit, db, api or cli
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mutation-tests:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2', '8.3']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.3' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-22.1.0
|
||||||
|
extensions-cache-key: mutation-tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }}
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: coverage-${{ inputs.test-group }}
|
||||||
|
path: build
|
||||||
|
- name: Resolve infection args
|
||||||
|
id: infection_args
|
||||||
|
run: echo "args=--logger-github=false" >> $GITHUB_OUTPUT
|
||||||
|
# TODO Try to filter mutation tests to improve execution times. Investigate why --git-diff-lines --git-diff-base=develop does not work
|
||||||
|
# run: |
|
||||||
|
# BRANCH="${GITHUB_REF#refs/heads/}" |
|
||||||
|
# if [[ $BRANCH == 'main' || $BRANCH == 'develop' ]]; then
|
||||||
|
# echo "args=--logger-github=false" >> $GITHUB_OUTPUT
|
||||||
|
# else
|
||||||
|
# echo "args=--logger-github=false --git-diff-lines --git-diff-base=develop" >> $GITHUB_OUTPUT
|
||||||
|
# fi;
|
||||||
|
shell: bash
|
||||||
|
- if: ${{ inputs.test-group == 'unit' }}
|
||||||
|
run: composer infect:ci:unit -- ${{ steps.infection_args.outputs.args }}
|
||||||
|
env:
|
||||||
|
INFECTION_BADGE_API_KEY: ${{ secrets.INFECTION_BADGE_API_KEY }}
|
||||||
|
- if: ${{ inputs.test-group != 'unit' }}
|
||||||
|
run: composer infect:ci:${{ inputs.test-group }} -- ${{ steps.infection_args.outputs.args }}
|
||||||
38
.github/workflows/ci-tests.yml
vendored
Normal file
38
.github/workflows/ci-tests.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
test-group:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: One of unit, api or cli
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2', '8.3']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.3' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Start postgres database server
|
||||||
|
if: ${{ inputs.test-group == 'api' }}
|
||||||
|
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
||||||
|
- name: Start maria database server
|
||||||
|
if: ${{ inputs.test-group == 'cli' }}
|
||||||
|
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_maria
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-22.1.0
|
||||||
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ inputs.test-group }}
|
||||||
|
- run: composer test:${{ inputs.test-group }}:ci
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ matrix.php-version == '8.2' }}
|
||||||
|
with:
|
||||||
|
name: coverage-${{ inputs.test-group }}
|
||||||
|
path: |
|
||||||
|
build/coverage-${{ inputs.test-group }}
|
||||||
|
build/coverage-${{ inputs.test-group }}.cov
|
||||||
372
.github/workflows/ci.yml
vendored
372
.github/workflows/ci.yml
vendored
@@ -1,291 +1,160 @@
|
|||||||
name: Continuous integration
|
name: Continuous integration
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request: null
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- 'LICENSE'
|
||||||
|
- '.*'
|
||||||
|
- '*.md'
|
||||||
|
- '*.xml'
|
||||||
|
- '*.yml*'
|
||||||
|
- '*.json5'
|
||||||
|
- '*.neon'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- develop
|
- develop
|
||||||
|
- 2.x
|
||||||
|
paths-ignore:
|
||||||
|
- 'LICENSE'
|
||||||
|
- '.*'
|
||||||
|
- '*.md'
|
||||||
|
- '*.xml'
|
||||||
|
- '*.yml*'
|
||||||
|
- '*.json5'
|
||||||
|
- '*.neon'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version: ['7.4']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.5.9
|
|
||||||
coverage: none
|
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer cs
|
|
||||||
|
|
||||||
static-analysis:
|
static-analysis:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['7.4']
|
php-version: ['8.2']
|
||||||
|
command: ['cs', 'stan', 'swagger:validate']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
- uses: './.github/actions/ci-setup'
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
tools: composer
|
php-extensions: openswoole-22.1.0
|
||||||
extensions: swoole-4.5.9
|
extensions-cache-key: tests-extensions-${{ matrix.php-version }}-${{ matrix.command }}
|
||||||
coverage: none
|
- run: composer ${{ matrix.command }}
|
||||||
- run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer stan
|
|
||||||
|
|
||||||
unit-tests:
|
unit-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
continue-on-error: ${{ matrix.php-version == '8.0' }}
|
with:
|
||||||
|
test-group: unit
|
||||||
|
|
||||||
|
cli-tests:
|
||||||
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: cli
|
||||||
|
|
||||||
|
openswoole-api-tests:
|
||||||
|
uses: './.github/workflows/ci-tests.yml'
|
||||||
|
with:
|
||||||
|
test-group: api
|
||||||
|
|
||||||
|
roadrunner-api-tests:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['7.4', '8.0']
|
php-version: ['8.2', '8.3']
|
||||||
|
continue-on-error: ${{ matrix.php-version == '8.3' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # rr get-binary picks this env automatically
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
- run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
||||||
- name: Use PHP
|
- uses: shivammathur/setup-php@v2
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
tools: composer
|
tools: composer
|
||||||
extensions: swoole-4.5.9
|
- run: composer install --no-interaction --prefer-dist --ignore-platform-req=ext-openswoole ${{ matrix.php-version == '8.3' && '--ignore-platform-reqs' || '' }}
|
||||||
coverage: pcov
|
- run: ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr
|
||||||
ini-values: pcov.directory=module
|
- run: composer test:api:rr
|
||||||
- if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
|
||||||
- if: ${{ matrix.php-version != '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:unit:ci
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.php-version == '7.4' }}
|
|
||||||
with:
|
|
||||||
name: coverage-unit
|
|
||||||
path: |
|
|
||||||
build/coverage-unit
|
|
||||||
build/coverage-unit.cov
|
|
||||||
|
|
||||||
db-tests-sqlite:
|
sqlite-db-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
platform: 'sqlite:ci'
|
||||||
php-version: ['7.4', '8.0']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.5.9
|
|
||||||
coverage: pcov
|
|
||||||
ini-values: pcov.directory=module
|
|
||||||
- if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
|
||||||
- if: ${{ matrix.php-version != '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:db:sqlite:ci
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.php-version == '7.4' }}
|
|
||||||
with:
|
|
||||||
name: coverage-db
|
|
||||||
path: |
|
|
||||||
build/coverage-db
|
|
||||||
build/coverage-db.cov
|
|
||||||
|
|
||||||
db-tests-mysql:
|
mysql-db-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
platform: 'mysql'
|
||||||
php-version: ['7.4', '8.0']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Start database server
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.5.9
|
|
||||||
coverage: none
|
|
||||||
- if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
|
||||||
- if: ${{ matrix.php-version != '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:db:mysql
|
|
||||||
|
|
||||||
db-tests-maria:
|
maria-db-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
platform: 'maria'
|
||||||
php-version: ['7.4', '8.0']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Start database server
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_maria
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.5.9
|
|
||||||
coverage: none
|
|
||||||
- if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
|
||||||
- if: ${{ matrix.php-version != '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:db:maria
|
|
||||||
|
|
||||||
db-tests-postgres:
|
postgres-db-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
platform: 'postgres'
|
||||||
php-version: ['7.4', '8.0']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Start database server
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_postgres
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.5.9
|
|
||||||
coverage: none
|
|
||||||
- if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
|
||||||
- if: ${{ matrix.php-version != '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: composer test:db:postgres
|
|
||||||
|
|
||||||
db-tests-ms:
|
ms-db-tests:
|
||||||
runs-on: ubuntu-20.04
|
uses: './.github/workflows/ci-db-tests.yml'
|
||||||
strategy:
|
with:
|
||||||
matrix:
|
platform: 'ms'
|
||||||
php-version: ['7.4', '8.0']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Install MSSQL ODBC
|
|
||||||
run: sudo ./data/infra/ci/install-ms-odbc.sh
|
|
||||||
- name: Start database server
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db_ms
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.5.9, pdo_sqlsrv-5.9.0beta2
|
|
||||||
coverage: none
|
|
||||||
- if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
|
||||||
- if: ${{ matrix.php-version != '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist
|
|
||||||
- name: Create test database
|
|
||||||
run: docker-compose exec -T shlink_db_ms /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Passw0rd!' -Q "CREATE DATABASE shlink_test;"
|
|
||||||
- run: composer test:db:ms
|
|
||||||
|
|
||||||
api-tests:
|
unit-mutation-tests:
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
continue-on-error: ${{ matrix.php-version == '8.0' }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version: ['7.4', '8.0']
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Start database server
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d shlink_db
|
|
||||||
- name: Use PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer
|
|
||||||
extensions: swoole-4.5.9
|
|
||||||
coverage: pcov
|
|
||||||
ini-values: pcov.directory=module
|
|
||||||
- if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
|
||||||
- if: ${{ matrix.php-version != '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist
|
|
||||||
- run: bin/test/run-api-tests.sh
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.php-version == '7.4' }}
|
|
||||||
with:
|
|
||||||
name: coverage-api
|
|
||||||
path: |
|
|
||||||
build/coverage-api
|
|
||||||
build/coverage-api.cov
|
|
||||||
|
|
||||||
mutation-tests:
|
|
||||||
needs:
|
needs:
|
||||||
- unit-tests
|
- unit-tests
|
||||||
- db-tests-sqlite
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
- api-tests
|
with:
|
||||||
runs-on: ubuntu-20.04
|
test-group: unit
|
||||||
strategy:
|
|
||||||
matrix:
|
db-mutation-tests:
|
||||||
php-version: ['7.4', '8.0']
|
needs:
|
||||||
steps:
|
- sqlite-db-tests
|
||||||
- name: Checkout code
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
uses: actions/checkout@v2
|
with:
|
||||||
- name: Use PHP
|
test-group: db
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
api-mutation-tests:
|
||||||
php-version: ${{ matrix.php-version }}
|
needs:
|
||||||
tools: composer
|
- openswoole-api-tests
|
||||||
extensions: swoole-4.5.9
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
coverage: pcov
|
with:
|
||||||
ini-values: pcov.directory=module
|
test-group: api
|
||||||
- if: ${{ matrix.php-version == '8.0' }}
|
|
||||||
run: composer install --no-interaction --prefer-dist --ignore-platform-req=php
|
cli-mutation-tests:
|
||||||
- if: ${{ matrix.php-version != '8.0' }}
|
needs:
|
||||||
run: composer install --no-interaction --prefer-dist
|
- cli-tests
|
||||||
- uses: actions/download-artifact@v2
|
uses: './.github/workflows/ci-mutation-tests.yml'
|
||||||
with:
|
with:
|
||||||
path: build
|
test-group: cli
|
||||||
- run: composer infect:ci
|
|
||||||
|
|
||||||
upload-coverage:
|
upload-coverage:
|
||||||
needs:
|
needs:
|
||||||
- unit-tests
|
- unit-tests
|
||||||
- db-tests-sqlite
|
- openswoole-api-tests
|
||||||
- api-tests
|
- cli-tests
|
||||||
runs-on: ubuntu-20.04
|
- sqlite-db-tests
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['7.4']
|
php-version: ['8.2']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
- name: Use PHP
|
- name: Use PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
coverage: pcov
|
coverage: pcov
|
||||||
ini-values: pcov.directory=module
|
ini-values: pcov.directory=module
|
||||||
- uses: actions/download-artifact@v2
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: build
|
path: build
|
||||||
- run: mv build/coverage-unit/coverage-unit.cov build/coverage-unit.cov
|
- run: mv build/coverage-unit/coverage-unit.cov build/coverage-unit.cov
|
||||||
- run: mv build/coverage-db/coverage-db.cov build/coverage-db.cov
|
- run: mv build/coverage-db/coverage-db.cov build/coverage-db.cov
|
||||||
- run: mv build/coverage-api/coverage-api.cov build/coverage-api.cov
|
- run: mv build/coverage-api/coverage-api.cov build/coverage-api.cov
|
||||||
- run: wget https://phar.phpunit.de/phpcov-8.2.0.phar
|
- run: mv build/coverage-cli/coverage-cli.cov build/coverage-cli.cov
|
||||||
- run: php phpcov-8.2.0.phar merge build --clover build/clover.xml
|
- run: wget https://phar.phpunit.de/phpcov-9.0.0.phar
|
||||||
|
- run: php phpcov-9.0.0.phar merge build --clover build/clover.xml
|
||||||
- name: Publish coverage
|
- name: Publish coverage
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v1
|
||||||
with:
|
with:
|
||||||
@@ -293,27 +162,14 @@ jobs:
|
|||||||
|
|
||||||
delete-artifacts:
|
delete-artifacts:
|
||||||
needs:
|
needs:
|
||||||
- mutation-tests
|
- unit-mutation-tests
|
||||||
|
- db-mutation-tests
|
||||||
|
- api-mutation-tests
|
||||||
|
- cli-mutation-tests
|
||||||
- upload-coverage
|
- upload-coverage
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: geekyeggo/delete-artifact@v1
|
- uses: geekyeggo/delete-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: |
|
name: |
|
||||||
coverage-unit
|
coverage-*
|
||||||
coverage-db
|
|
||||||
coverage-api
|
|
||||||
|
|
||||||
build-docker-image:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- uses: marceloprado/has-changed-path@v1
|
|
||||||
id: changed-dockerfile
|
|
||||||
with:
|
|
||||||
paths: ./Dockerfile
|
|
||||||
- if: ${{ steps.changed-dockerfile.outputs.changed == 'true' }}
|
|
||||||
run: docker build -t shlink-docker-image:temp .
|
|
||||||
- if: ${{ steps.changed-dockerfile.outputs.changed != 'true' }}
|
|
||||||
run: echo "Dockerfile didn't change. Skipped"
|
|
||||||
|
|||||||
28
.github/workflows/docker-image-build.yml
vendored
28
.github/workflows/docker-image-build.yml
vendored
@@ -1,28 +0,0 @@
|
|||||||
name: Build docker image
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
- name: Login to docker hub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
- name: Build the image
|
|
||||||
run: bash ./docker/build
|
|
||||||
34
.github/workflows/publish-docker-image.yml
vendored
Normal file
34
.github/workflows/publish-docker-image.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
name: Build and publish docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-image:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- runtime: 'rr'
|
||||||
|
platforms: 'linux/arm64/v8,linux/amd64'
|
||||||
|
- runtime: 'rr'
|
||||||
|
tag-suffix: 'roadrunner'
|
||||||
|
platforms: 'linux/arm64/v8,linux/amd64'
|
||||||
|
- runtime: 'openswoole'
|
||||||
|
tag-suffix: 'openswoole'
|
||||||
|
platforms: 'linux/arm/v7,linux/arm64/v8,linux/amd64'
|
||||||
|
- runtime: 'rr'
|
||||||
|
tag-suffix: 'non-root'
|
||||||
|
platforms: 'linux/arm64/v8,linux/amd64'
|
||||||
|
user-id: '1001'
|
||||||
|
uses: shlinkio/github-actions/.github/workflows/docker-build-and-publish.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
image-name: shlinkio/shlink
|
||||||
|
version-arg-name: SHLINK_VERSION
|
||||||
|
platforms: ${{ matrix.platforms }}
|
||||||
|
tags-suffix: ${{ matrix.tag-suffix }}
|
||||||
|
extra-build-args: |
|
||||||
|
SHLINK_RUNTIME=${{ matrix.runtime }}
|
||||||
|
SHLINK_USER_ID=${{ matrix.user-id && matrix.user-id || 'root' }}
|
||||||
47
.github/workflows/publish-release.yml
vendored
47
.github/workflows/publish-release.yml
vendored
@@ -7,24 +7,49 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2', '8.3']
|
||||||
|
swoole: ['yes', 'no']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v2
|
- uses: './.github/actions/ci-setup'
|
||||||
- name: Use PHP 7.4
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
with:
|
||||||
php-version: '7.4' # Publish release with lowest supported PHP version
|
php-version: ${{ matrix.php-version }}
|
||||||
tools: composer
|
php-extensions: openswoole-22.1.0
|
||||||
extensions: swoole-4.5.9
|
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||||
- name: Generate release assets
|
install-deps: 'no'
|
||||||
|
- if: ${{ matrix.swoole == 'yes' }}
|
||||||
run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
run: ./build.sh ${GITHUB_REF#refs/tags/v}
|
||||||
|
- if: ${{ matrix.swoole == 'no' }}
|
||||||
|
run: ./build.sh ${GITHUB_REF#refs/tags/v} --no-swoole
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist-files-${{ matrix.php-version }}-${{ matrix.swoole }}
|
||||||
|
path: build
|
||||||
|
|
||||||
|
publish:
|
||||||
|
needs: ['build']
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: build
|
||||||
- name: Publish release with assets
|
- name: Publish release with assets
|
||||||
uses: docker://antonyurchenko/git-release:latest
|
uses: docker://antonyurchenko/git-release:latest
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
ALLOW_TAG_PREFIX: "true"
|
|
||||||
ALLOW_EMPTY_CHANGELOG: "true"
|
ALLOW_EMPTY_CHANGELOG: "true"
|
||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
build/shlink_*_dist.zip
|
build/*/shlink*_dist.zip
|
||||||
|
|
||||||
|
delete-artifacts:
|
||||||
|
needs: ['publish']
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: geekyeggo/delete-artifact@v2
|
||||||
|
with:
|
||||||
|
name: dist-files-*
|
||||||
|
|||||||
36
.github/workflows/publish-swagger-spec.yml
vendored
Normal file
36
.github/workflows/publish-swagger-spec.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Publish swagger spec
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.2']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Determine version
|
||||||
|
id: determine_version
|
||||||
|
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
- uses: './.github/actions/ci-setup'
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
php-extensions: openswoole-22.1.0
|
||||||
|
extensions-cache-key: publish-swagger-spec-extensions-${{ matrix.php-version }}
|
||||||
|
- run: composer swagger:inline
|
||||||
|
- run: mkdir ${{ steps.determine_version.outputs.version }}
|
||||||
|
- run: mv docs/swagger/swagger-inlined.json ${{ steps.determine_version.outputs.version }}/open-api-spec.json
|
||||||
|
- name: Publish spec
|
||||||
|
uses: JamesIves/github-pages-deploy-action@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.OAS_PUBLISH_TOKEN }}
|
||||||
|
repository-name: 'shlinkio/shlink-open-api-specs'
|
||||||
|
branch: main
|
||||||
|
folder: ${{ steps.determine_version.outputs.version }}
|
||||||
|
target-folder: specs/${{ steps.determine_version.outputs.version }}
|
||||||
|
clean: false
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,4 +1,6 @@
|
|||||||
.idea
|
.idea
|
||||||
|
bin/rr
|
||||||
|
config/roadrunner/.pid
|
||||||
build
|
build
|
||||||
!docker/build
|
!docker/build
|
||||||
composer.lock
|
composer.lock
|
||||||
@@ -6,8 +8,10 @@ composer.phar
|
|||||||
vendor/
|
vendor/
|
||||||
data/database.sqlite
|
data/database.sqlite
|
||||||
data/shlink-tests.db
|
data/shlink-tests.db
|
||||||
data/GeoLite2-City.mmdb
|
data/GeoLite2-City.*
|
||||||
data/GeoLite2-City.mmdb.*
|
data/infra/matomo
|
||||||
docs/swagger-ui*
|
docs/swagger-ui*
|
||||||
|
docs/mercure.html
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
|
docs/swagger/swagger-inlined.json
|
||||||
|
|||||||
1178
CHANGELOG.md
1178
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
102
CONTRIBUTING.md
102
CONTRIBUTING.md
@@ -6,9 +6,9 @@ You will also see how to ensure the code fulfills the expected code checks, and
|
|||||||
|
|
||||||
## System dependencies
|
## System dependencies
|
||||||
|
|
||||||
The project provides all its dependencies as docker containers through a docker-compose configuration.
|
The project provides all its dependencies as docker containers through a `docker compose` configuration.
|
||||||
|
|
||||||
Because of this, the only actual dependencies are [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/).
|
Because of this, the only actual dependencies are [docker](https://docs.docker.com/get-docker/) and [docker compose](https://docs.docker.com/compose/install/).
|
||||||
|
|
||||||
## Setting up the project
|
## Setting up the project
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ Then you will have to follow these steps:
|
|||||||
For example the `common.local.php.dist` file should be copied as `common.local.php`.
|
For example the `common.local.php.dist` file should be copied as `common.local.php`.
|
||||||
|
|
||||||
* Copy the file `docker-compose.override.yml.dist` by also removing the `dist` extension.
|
* Copy the file `docker-compose.override.yml.dist` by also removing the `dist` extension.
|
||||||
* Start-up the project by running `docker-compose up`.
|
* Start-up the project by running `docker compose up`.
|
||||||
|
|
||||||
The first time this command is run, it will create several containers that are used during development, so it may take some time.
|
The first time this command is run, it will create several containers that are used during development, so it may take some time.
|
||||||
|
|
||||||
@@ -31,9 +31,9 @@ Then you will have to follow these steps:
|
|||||||
* Run `./indocker bin/cli db:migrate` to get database migrations up to date.
|
* Run `./indocker bin/cli db:migrate` to get database migrations up to date.
|
||||||
* Run `./indocker bin/cli api-key:generate` to get your first API key generated.
|
* Run `./indocker bin/cli api-key:generate` to get your first API key generated.
|
||||||
|
|
||||||
Once you finish this, you will have the project exposed in ports `8000` through nginx+php-fpm and `8080` through swoole.
|
Once you finish this, you will have the project exposed in ports `8800` through RoadRunner, `8080` through openswoole and `8000` through nginx+php-fpm.
|
||||||
|
|
||||||
> Note: The `indocker` shell script is a helper used to run commands inside the main docker container.
|
> Note: The `indocker` shell script is a helper tool used to run commands inside the main docker container.
|
||||||
|
|
||||||
## Project structure
|
## Project structure
|
||||||
|
|
||||||
@@ -46,40 +46,41 @@ This is a simplified version of the project structure:
|
|||||||
```
|
```
|
||||||
shlink
|
shlink
|
||||||
├── bin
|
├── bin
|
||||||
│ ├── cli
|
│ ├── cli
|
||||||
│ ├── install
|
│ └── [...]
|
||||||
│ └── update
|
|
||||||
├── config
|
├── config
|
||||||
│ ├── autoload
|
│ ├── autoload
|
||||||
│ ├── params
|
│ ├── params
|
||||||
│ ├── config.php
|
│ ├── config.php
|
||||||
│ └── container.php
|
│ ├── container.php
|
||||||
|
│ └── [...]
|
||||||
├── data
|
├── data
|
||||||
│ ├── cache
|
│ ├── cache
|
||||||
│ ├── locks
|
│ ├── locks
|
||||||
│ ├── log
|
│ ├── log
|
||||||
│ ├── migrations
|
│ └── proxies
|
||||||
│ └── proxies
|
|
||||||
├── docs
|
├── docs
|
||||||
│ ├── async-api
|
│ ├── adr
|
||||||
│ └── swagger
|
│ ├── async-api
|
||||||
|
│ └── swagger
|
||||||
├── module
|
├── module
|
||||||
│ ├── CLI
|
│ ├── CLI
|
||||||
│ ├── Core
|
│ ├── Core
|
||||||
│ └── Rest
|
│ └── Rest
|
||||||
├── public
|
├── public
|
||||||
|
│ └── [...]
|
||||||
├── composer.json
|
├── composer.json
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
The purposes of every folder are:
|
The purposes of every folder are:
|
||||||
|
|
||||||
* `bin`: It contains the CLI tools. The `cli` one is the main entry point to run shlink from the command line, while `install` and `update` are helper tools used to install and update shlink when not using the docker image.
|
* `bin`: It contains the CLI tools. The `cli` one is the main entry point to run Shlink from the command line.
|
||||||
* `config`: Contains application-wide configurations, which are later merged with the ones provided by every module.
|
* `config`: Contains application-wide configurations, which are later merged with the ones provided by every module.
|
||||||
* `data`: Common runtime-generated git-ignored assets, like logs, caches, etc.
|
* `data`: Common git-ignored assets, like logs, caches, lock files, GeoLite DB files, etc. It's the only location where Shlink may need to write at runtime.
|
||||||
* `docs`: Any project documentation is stored here, like API spec definitions.
|
* `docs`: Any project documentation is stored here, like API spec definitions or architectural decision records.
|
||||||
* `module`: Contains a subfolder for every module in the project. Modules contain the source code, tests and configurations for every context in the project.
|
* `module`: Contains a sub-folder for every module in the project. Modules contain the source code, tests and configurations for every context in the project.
|
||||||
* `public`: Few assets (like `favicon.ico` or `robots.txt`) and the web entry point are stored here. This web entry point is not used when serving the app with swoole.
|
* `public`: Few assets (like `favicon.ico` or `robots.txt`) and the web entry point are stored here. This web entry point is not used when serving the app with RoadRunner or openswoole.
|
||||||
|
|
||||||
## Project tests
|
## Project tests
|
||||||
|
|
||||||
@@ -87,21 +88,25 @@ In order to ensure stability and no regressions are introduced while developing
|
|||||||
|
|
||||||
* **Unit tests**: These are the simplest to run, and usually test individual pieces of code, replacing any external dependency by mocks.
|
* **Unit tests**: These are the simplest to run, and usually test individual pieces of code, replacing any external dependency by mocks.
|
||||||
|
|
||||||
The code coverage of unit tests is pretty high, and only entity repositories are excluded because of their nature.
|
The code coverage of unit tests is pretty high, and only components which work closer to the database, like entity repositories, are excluded because of their nature.
|
||||||
|
|
||||||
* **Database tests**: These are integration tests that run against a real database, and only cover entity repositories.
|
* **Database tests**: These are integration tests that run against a real database, and only cover components which work closer to the database.
|
||||||
|
|
||||||
Its purpose is to verify all the database queries behave as expected and return what's expected.
|
Its purpose is to verify all the database queries behave as expected and return what's expected.
|
||||||
|
|
||||||
The project provides some tooling to run them against any of the supported database engines.
|
The project provides some tooling to run them against any of the supported database engines.
|
||||||
|
|
||||||
* **API tests**: These are E2E tests that spin up an instance of the app and test it from the outside, by interacting with the REST API.
|
* **API tests**: These are E2E tests that spin up an instance of the app with RoadRunner or openswoole, and test it from the outside by interacting with the REST API.
|
||||||
|
|
||||||
These are the best tests to catch regressions, and to verify everything interacts as expected.
|
These are the best tests to catch regressions, and to verify everything behaves as expected.
|
||||||
|
|
||||||
They use MySQL as the database engine, and include some fixtures that ensure the same data exists at the beginning of the execution.
|
They use Postgres as the database engine, and include some fixtures that ensure the same data exists at the beginning of the execution.
|
||||||
|
|
||||||
* **CLI tests**: *TBD. Once included, its purpose will be the same as API tests, but running through the command line*
|
Since the app instance is run on a process different from the one running the tests, when a test fails it might not be obvious why. To help debugging that, the app will dump all its logs inside `data/log/api-tests`, where you will find the `shlink.log` and `access.log` files.
|
||||||
|
|
||||||
|
* **CLI tests**: These are E2E tests too, but they test console commands instead of REST endpoints.
|
||||||
|
|
||||||
|
They use Maria DB as the database engine, and include the same fixtures as the API tests, that ensure the same data exists at the beginning of the execution.
|
||||||
|
|
||||||
Depending on the kind of contribution, maybe not all kinds of tests are needed, but the more you provide, the better.
|
Depending on the kind of contribution, maybe not all kinds of tests are needed, but the more you provide, the better.
|
||||||
|
|
||||||
@@ -113,24 +118,35 @@ Depending on the kind of contribution, maybe not all kinds of tests are needed,
|
|||||||
* Run `./indocker composer test:unit` to run the unit tests.
|
* Run `./indocker composer test:unit` to run the unit tests.
|
||||||
* Run `./indocker composer test:db` to run the database integration tests.
|
* Run `./indocker composer test:db` to run the database integration tests.
|
||||||
|
|
||||||
This command runs the same test suite against all supported database engines. If you just want to run one of them, you can add one of `:sqlite`, `:mysql`, `:maria`, `:postgres`, `:mssql` at the end of the command.
|
This command runs the same test suite against all supported database engines in parallel. If you just want to run one of them, you can add one of `:sqlite`, `:mysql`, `:maria`, `:postgres`, `:mssql` at the end of the command.
|
||||||
|
|
||||||
For example, `test:db:postgres`.
|
For example, `test:db:postgres`.
|
||||||
|
|
||||||
* Run `./indocker composer test:api` to run API E2E tests. For these, the MySQL database engine is used.
|
* Run `./indocker composer test:api` to run API E2E tests. For these, the Postgres database engine is used.
|
||||||
* Run `./indocker composer infect:test` ti run both unit and database tests (over sqlite) and then apply mutations to them with [infection](https://infection.github.io/).
|
* Run `./indocker composer test:cli` to run CLI E2E tests. For these, the Maria DB database engine is used.
|
||||||
* Run `./indocker composer ci` to run all previous commands together. This command is run during the project's continuous integration.
|
* Run `./indocker composer infect:test` to run both unit and database tests (over sqlite) and then apply mutations to them with [infection](https://infection.github.io/).
|
||||||
|
* Run `./indocker composer ci` to run all previous commands together, parallelizing non-conflicting tasks as much as possible.
|
||||||
|
|
||||||
> Note: Due to some limitations in the tooling used by shlink, the testing databases need to exist beforehand, both for db and api tests (except sqlite).
|
## Testing endpoints
|
||||||
>
|
|
||||||
> However, they just need to be created empty, with no tables. Also, once created, they are automatically reset before every new execution.
|
The project provides a Swagger UI container for dev envs, which can be accessed in http://localhost:8005.
|
||||||
>
|
|
||||||
> The testing database is always called `shlink_test`. You can create it using the database client of your choice. [DBeaver](https://dbeaver.io/) is a good multi-platform desktop database client which supports all the engines supported by shlink.
|
It will automatically load the contents of `docs/swagger`, so you can make any updates and they will get reflected.
|
||||||
|
|
||||||
## Pull request process
|
## Pull request process
|
||||||
|
|
||||||
In order to provide pull requests to this project, you should always start by creating a new branch, where you will make all desired changes.
|
**Important!**: Before starting to work on a pull request, make sure you always [open an issue](https://github.com/shlinkio/shlink/issues/new/choose) first.
|
||||||
|
|
||||||
|
This is important because any contribution needs to be discussed first. Maybe there's someone else already working on something similar, or there are other considerations to have in mind.
|
||||||
|
|
||||||
|
Once everything is clear, to provide a pull request to this project, you should always start by creating a new branch, where you will make all desired changes.
|
||||||
|
|
||||||
The base branch should always be `develop`, and the target branch for the pull request should also be `develop`.
|
The base branch should always be `develop`, and the target branch for the pull request should also be `develop`.
|
||||||
|
|
||||||
Before your branch can be merged, all the checks described in [Running code checks](#running-code-checks) have to be passing. You can verify that manually by running `./indocker composer ci`, or wait for the build to be run automatically after the pull request is created.
|
Before your branch can be merged, all the checks described in [Running code checks](#running-code-checks) have to be passing. You can verify that manually by running `./indocker composer ci`, or wait for the build to be run automatically after the pull request is created.
|
||||||
|
|
||||||
|
## Architectural Decision Records
|
||||||
|
|
||||||
|
The project includes logs for some architectural decisions, using the [adr](https://adr.github.io/) proposal.
|
||||||
|
|
||||||
|
If you are curious or want to understand why something has been built in some specific way, [take a look at them](docs/adr).
|
||||||
|
|||||||
90
Dockerfile
90
Dockerfile
@@ -1,56 +1,60 @@
|
|||||||
FROM php:7.4.11-alpine3.12 as base
|
FROM php:8.2-alpine3.17 as base
|
||||||
|
|
||||||
ARG SHLINK_VERSION=2.4.0
|
ARG SHLINK_VERSION=latest
|
||||||
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
||||||
ENV SWOOLE_VERSION 4.5.9
|
ARG SHLINK_RUNTIME=rr
|
||||||
ENV LC_ALL "C"
|
ENV SHLINK_RUNTIME ${SHLINK_RUNTIME}
|
||||||
|
ARG SHLINK_USER_ID='root'
|
||||||
|
ENV SHLINK_USER_ID ${SHLINK_USER_ID}
|
||||||
|
|
||||||
|
ENV OPENSWOOLE_VERSION 22.1.0
|
||||||
|
ENV PDO_SQLSRV_VERSION 5.11.1
|
||||||
|
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
|
||||||
|
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
|
||||||
|
ENV LC_ALL 'C'
|
||||||
|
|
||||||
WORKDIR /etc/shlink
|
WORKDIR /etc/shlink
|
||||||
|
|
||||||
# Install required PHP extensions
|
# Install required PHP extensions
|
||||||
RUN \
|
RUN \
|
||||||
# Install mysql and calendar
|
# Temp install dev dependencies needed to compile the extensions
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_mysql calendar && \
|
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev linux-headers && \
|
||||||
# Install sqlite
|
docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip gd && \
|
||||||
apk add --no-cache sqlite-libs sqlite-dev && \
|
apk add --no-cache sqlite-libs && \
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
||||||
# Install postgres
|
# Remove temp dev extensions, and install prod equivalents that are required at runtime
|
||||||
apk add --no-cache postgresql-dev && \
|
apk del .dev-deps && \
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_pgsql && \
|
apk add --no-cache postgresql icu libzip libpng
|
||||||
# Install intl
|
|
||||||
apk add --no-cache icu-dev && \
|
|
||||||
docker-php-ext-install -j"$(nproc)" intl && \
|
|
||||||
# Install zip and gd
|
|
||||||
apk add --no-cache libzip-dev zlib-dev libpng-dev && \
|
|
||||||
docker-php-ext-install -j"$(nproc)" zip gd && \
|
|
||||||
# Install gmp
|
|
||||||
apk add --no-cache gmp-dev && \
|
|
||||||
docker-php-ext-install -j"$(nproc)" gmp
|
|
||||||
|
|
||||||
# Install sqlsrv driver
|
# Install openswoole and sqlsrv driver for x86_64 builds
|
||||||
RUN if [ $(uname -m) == "x86_64" ]; then \
|
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
|
||||||
wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \
|
if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
|
||||||
apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \
|
# Openswoole is deprecated. Remove in v4.0.0
|
||||||
apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} unixodbc-dev && \
|
pecl install openswoole-${OPENSWOOLE_VERSION} && \
|
||||||
pecl install pdo_sqlsrv && \
|
docker-php-ext-enable openswoole ; \
|
||||||
|
fi; \
|
||||||
|
if [ $(uname -m) == "x86_64" ]; then \
|
||||||
|
wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
|
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
|
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} && \
|
||||||
docker-php-ext-enable pdo_sqlsrv && \
|
docker-php-ext-enable pdo_sqlsrv && \
|
||||||
apk del .phpize-deps && \
|
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk ; \
|
||||||
rm msodbcsql17_17.5.1.1-1_amd64.apk ; \
|
fi; \
|
||||||
fi
|
|
||||||
|
|
||||||
# Install swoole
|
|
||||||
RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} && \
|
|
||||||
pecl install swoole-${SWOOLE_VERSION} && \
|
|
||||||
docker-php-ext-enable swoole && \
|
|
||||||
apk del .phpize-deps
|
apk del .phpize-deps
|
||||||
|
|
||||||
|
|
||||||
# Install shlink
|
# Install shlink
|
||||||
FROM base as builder
|
FROM base as builder
|
||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=composer:2 /usr/bin/composer ./composer.phar
|
COPY --from=composer:2 /usr/bin/composer ./composer.phar
|
||||||
RUN apk add --no-cache git && \
|
RUN apk add --no-cache git && \
|
||||||
php composer.phar install --no-dev --optimize-autoloader --prefer-dist --no-progress --no-interaction && \
|
# FIXME Ignoring ext-openswoole platform req, as it makes install fail with roadrunner, even though it's a dev dependency and we are passing --no-dev
|
||||||
|
php composer.phar install --no-dev --prefer-dist --optimize-autoloader --no-progress --no-interaction --ignore-platform-req=ext-openswoole && \
|
||||||
|
if [ "$SHLINK_RUNTIME" == 'openswoole' ]; then \
|
||||||
|
# Openswoole is deprecated. Remove in v4.0.0
|
||||||
|
php composer.phar remove spiral/roadrunner spiral/roadrunner-jobs spiral/roadrunner-cli spiral/roadrunner-http --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interaction ; \
|
||||||
|
elif [ "$SHLINK_RUNTIME" == 'rr' ]; then \
|
||||||
|
php composer.phar remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev --optimize-autoloader --no-progress --no-interaction --ignore-platform-req=ext-openswoole ; \
|
||||||
|
fi; \
|
||||||
php composer.phar clear-cache && \
|
php composer.phar clear-cache && \
|
||||||
rm -r docker composer.* && \
|
rm -r docker composer.* && \
|
||||||
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
|
sed -i "s/%SHLINK_VERSION%/${SHLINK_VERSION}/g" config/autoload/app_options.global.php
|
||||||
@@ -60,18 +64,20 @@ RUN apk add --no-cache git && \
|
|||||||
FROM base
|
FROM base
|
||||||
LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
|
LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
|
||||||
|
|
||||||
COPY --from=builder /etc/shlink .
|
COPY --from=builder --chown=${SHLINK_USER_ID} /etc/shlink .
|
||||||
RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink
|
RUN ln -s /etc/shlink/bin/cli /usr/local/bin/shlink && \
|
||||||
|
if [ "$SHLINK_RUNTIME" == 'rr' ]; then \
|
||||||
|
php ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr ; \
|
||||||
|
fi;
|
||||||
|
|
||||||
# Expose default swoole port
|
# Expose default port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Expose params config dir, since the user is expected to provide custom config from there
|
|
||||||
VOLUME /etc/shlink/config/params
|
|
||||||
|
|
||||||
# Copy config specific for the image
|
# Copy config specific for the image
|
||||||
COPY docker/docker-entrypoint.sh docker-entrypoint.sh
|
COPY docker/docker-entrypoint.sh docker-entrypoint.sh
|
||||||
COPY docker/config/shlink_in_docker.local.php config/autoload/shlink_in_docker.local.php
|
COPY docker/config/shlink_in_docker.local.php config/autoload/shlink_in_docker.local.php
|
||||||
COPY docker/config/php.ini ${PHP_INI_DIR}/conf.d/
|
COPY docker/config/php.ini ${PHP_INI_DIR}/conf.d/
|
||||||
|
|
||||||
|
USER ${SHLINK_USER_ID}
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/sh", "./docker-entrypoint.sh"]
|
ENTRYPOINT ["/bin/sh", "./docker-entrypoint.sh"]
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016-2020 Alejandro Celaya
|
Copyright (c) 2016-2023 Alejandro Celaya
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
341
README.md
341
README.md
@@ -1,46 +1,49 @@
|
|||||||

|

|
||||||
|
|
||||||
[](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Continuous+integration%22)
|
[](https://github.com/shlinkio/shlink/actions/workflows/ci.yml?query=workflow%3A%22Continuous+integration%22)
|
||||||
[](https://app.codecov.io/gh/shlinkio/shlink)
|
[](https://app.codecov.io/gh/shlinkio/shlink)
|
||||||
|
[](https://dashboard.stryker-mutator.io/reports/github.com/shlinkio/shlink/develop)
|
||||||
[](https://packagist.org/packages/shlinkio/shlink)
|
[](https://packagist.org/packages/shlinkio/shlink)
|
||||||
[](https://hub.docker.com/r/shlinkio/shlink/)
|
[](https://hub.docker.com/r/shlinkio/shlink/)
|
||||||
[](https://github.com/shlinkio/shlink/blob/main/LICENSE)
|
[](https://github.com/shlinkio/shlink/blob/main/LICENSE)
|
||||||
|
[](https://twitter.com/shlinkio)
|
||||||
|
[](https://fosstodon.org/@shlinkio)
|
||||||
[](https://slnk.to/donate)
|
[](https://slnk.to/donate)
|
||||||
|
|
||||||
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own custom domain.
|
A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own domain.
|
||||||
|
|
||||||
> This document references Shlink 2.x. If you are using an older version and want to upgrade, follow the [UPGRADE](UPGRADE.md) doc.
|
|
||||||
|
|
||||||
> If you are trying to find out how to run the project in development mode or how to provide contributions, read the [CONTRIBUTING](CONTRIBUTING.md) doc.
|
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Installation](#installation)
|
- [Full documentation](#full-documentation)
|
||||||
|
- [Docker image](#docker-image)
|
||||||
|
- [Self-hosted](#self-hosted)
|
||||||
- [Download](#download)
|
- [Download](#download)
|
||||||
- [Configure](#configure)
|
- [Configure](#configure)
|
||||||
- [Serve](#serve)
|
|
||||||
- [Bonus](#bonus)
|
|
||||||
- [Update to new version](#update-to-new-version)
|
|
||||||
- [Update a configuration option](#update-a-configuration-option)
|
|
||||||
- [Using a docker image](#using-a-docker-image)
|
|
||||||
- [Using shlink](#using-shlink)
|
- [Using shlink](#using-shlink)
|
||||||
- [Shlink CLI Help](#shlink-cli-help)
|
- [Contributing](#contributing)
|
||||||
- [Multiple domains](#multiple-domains)
|
|
||||||
- [Management](#management)
|
|
||||||
- [Visits](#visits)
|
|
||||||
- [Special redirects](#special-redirects)
|
|
||||||
|
|
||||||
## Installation
|
## Full documentation
|
||||||
|
|
||||||
> These are the steps needed to install Shlink if you plan to manually host it.
|
This document contains the very basics to get started with Shlink. If you want to learn everything you can do with it, visit the [full searchable documentation](https://shlink.io/documentation/).
|
||||||
>
|
|
||||||
> Alternatively, you can use the official docker image. If that's your intention, jump directly to [Using a docker image](#using-a-docker-image)
|
## Docker image
|
||||||
|
|
||||||
|
You can learn how to use the official docker image by reading [the docs](https://shlink.io/documentation/install-docker-image/).
|
||||||
|
|
||||||
|
The idea is that you can just generate a container using the image and provide the custom config via env vars.
|
||||||
|
|
||||||
|
## Self-hosted
|
||||||
|
|
||||||
First, make sure the host where you are going to run shlink fulfills these requirements:
|
First, make sure the host where you are going to run shlink fulfills these requirements:
|
||||||
|
|
||||||
* PHP 7.4 or greater with JSON, curl, PDO, intl and gd extensions enabled.
|
* PHP 8.2 or 8.3
|
||||||
* MySQL, MariaDB, PostgreSQL, Microsoft SQL Server or SQLite.
|
* The next PHP extensions: json, curl, pdo, intl, gd and gmp/bcmath.
|
||||||
* The web server of your choice with PHP integration (Apache or Nginx recommended).
|
* apcu extension is recommended if you don't plan to use openswoole.
|
||||||
|
* xml extension is required if you want to generate QR codes in svg format.
|
||||||
|
* sockets and bcmath extensions are required if you want to integrate with a RabbitMQ instance.
|
||||||
|
* MySQL, MariaDB, PostgreSQL, MicrosoftSQL or SQLite.
|
||||||
|
* You will also need the corresponding pdo variation for the database you are planning to use: `pdo_mysql`, `pdo_pgsql`, `pdo_sqlsrv` or `pdo_sqlite`.
|
||||||
|
* The [openswoole](https://openswoole.com/) PHP extension (if you plan to serve Shlink with openswoole) or the web server of your choice with PHP integration (like Apache or Nginx).
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
@@ -50,7 +53,7 @@ In order to run Shlink, you will need a built version of the project. There are
|
|||||||
|
|
||||||
The easiest way to install shlink is by using one of the pre-bundled distributable packages.
|
The easiest way to install shlink is by using one of the pre-bundled distributable packages.
|
||||||
|
|
||||||
Go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink_x.x.x_dist.zip` file you will find there.
|
Go to the [latest version](https://github.com/shlinkio/shlink/releases/latest) and download the `shlink*_dist.zip` file that suits your needs. You will find one for every supported PHP version and with/without openswoole integration.
|
||||||
|
|
||||||
Finally, decompress the file in the location of your choice.
|
Finally, decompress the file in the location of your choice.
|
||||||
|
|
||||||
@@ -60,11 +63,13 @@ In order to run Shlink, you will need a built version of the project. There are
|
|||||||
|
|
||||||
* Clone the project with git (`git clone https://github.com/shlinkio/shlink.git`), or download it by clicking the **Clone or download** green button.
|
* Clone the project with git (`git clone https://github.com/shlinkio/shlink.git`), or download it by clicking the **Clone or download** green button.
|
||||||
* Download the [Composer](https://getcomposer.org/download/) PHP package manager inside the project folder.
|
* Download the [Composer](https://getcomposer.org/download/) PHP package manager inside the project folder.
|
||||||
* Run `./build.sh 1.0.0`, replacing the version with the version number you are going to build (the version number is only used for the generated dist file).
|
* Run `./build.sh 3.0.0`, replacing the version with the version number you are going to build (the version number is used as part of the generated dist file name, and to set the value returned when running `shlink -V` from the command line).
|
||||||
|
|
||||||
After that, you will have a `shlink_x.x.x_dist.zip` dist file inside the `build` directory, that you need to decompress in the location fo your choice.
|
After that, you will have a dist file inside the `build` directory, that you need to decompress in the location of your choice.
|
||||||
|
|
||||||
> This is the process used when releasing new shlink versions. After tagging the new version with git, the Github release is automatically created by [travis](https://travis-ci.com/shlinkio/shlink), attaching the generated dist file to it.
|
> **Note**
|
||||||
|
>
|
||||||
|
> This is the process used when releasing new Shlink versions. After tagging the new version with git, the GitHub release is automatically created by a [GitHub workflow](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Publish+release%22), attaching the generated dist file to it.
|
||||||
|
|
||||||
### Configure
|
### Configure
|
||||||
|
|
||||||
@@ -72,290 +77,28 @@ Despite how you built the project, you now need to configure it, by following th
|
|||||||
|
|
||||||
* If you are going to use MySQL, MariaDB, PostgreSQL or Microsoft SQL Server, create an empty database with the name of your choice.
|
* If you are going to use MySQL, MariaDB, PostgreSQL or Microsoft SQL Server, create an empty database with the name of your choice.
|
||||||
* Recursively grant write permissions to the `data` directory. Shlink uses it to cache some information.
|
* Recursively grant write permissions to the `data` directory. Shlink uses it to cache some information.
|
||||||
* Setup the application by running the `bin/install` script. It is a command line tool that will guide you through the installation process. **Take into account that this tool has to be run directly on the server where you plan to host Shlink. Do not run it before uploading/moving it there.**
|
* Set up the application by running the `vendor/bin/shlink-installer install` script. It is a command line tool that will guide you through the installation process. **Take into account that this tool has to be run directly on the server where you plan to host Shlink. Do not run it before uploading/moving it there.**
|
||||||
* Generate your first API key by running `bin/cli api-key:generate`. You will need the key in order to interact with shlink's API.
|
* Generate your first API key by running `bin/cli api-key:generate`. You will need the key in order to interact with Shlink's API.
|
||||||
|
|
||||||
### Serve
|
|
||||||
|
|
||||||
Once Shlink is configured, you need to expose it to the web, either by using a traditional web server + fast CGI approach, or by using a [swoole](https://www.swoole.co.uk/) non-blocking server.
|
|
||||||
|
|
||||||
* **Using a web server:**
|
|
||||||
|
|
||||||
For example, assuming your domain is doma.in and shlink is in the `/path/to/shlink` folder, these would be the basic configurations for Nginx and Apache.
|
|
||||||
|
|
||||||
*Nginx:*
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
server {
|
|
||||||
server_name doma.in;
|
|
||||||
listen 80;
|
|
||||||
root /path/to/shlink/public;
|
|
||||||
index index.php;
|
|
||||||
charset utf-8;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.php$is_args$args;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ \.php$ {
|
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
|
||||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
|
||||||
fastcgi_index index.php;
|
|
||||||
include fastcgi.conf;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ /\.ht {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*Apache:*
|
|
||||||
|
|
||||||
```apache
|
|
||||||
<VirtualHost *:80>
|
|
||||||
ServerName doma.in
|
|
||||||
DocumentRoot "/path/to/shlink/public"
|
|
||||||
|
|
||||||
<Directory "/path/to/shlink/public">
|
|
||||||
Options FollowSymLinks Includes ExecCGI
|
|
||||||
AllowOverride all
|
|
||||||
Order allow,deny
|
|
||||||
Allow from all
|
|
||||||
</Directory>
|
|
||||||
</VirtualHost>
|
|
||||||
```
|
|
||||||
|
|
||||||
* **Using swoole:**
|
|
||||||
|
|
||||||
First you need to install the swoole PHP extension with [pecl](https://pecl.php.net/package/swoole), `pecl install swoole`.
|
|
||||||
|
|
||||||
Once installed, it's actually pretty easy to get shlink up and running with swoole. Run `./vendor/bin/mezzio-swoole start -d` and you will get shlink running on port 8080.
|
|
||||||
|
|
||||||
However, by doing it this way, you are loosing all the access logs, and the service won't be automatically run if the server has to be restarted.
|
|
||||||
|
|
||||||
For that reason, you should create a daemon script, in `/etc/init.d/shlink_swoole`, like this one, replacing `/path/to/shlink` by the path to your shlink installation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
### BEGIN INIT INFO
|
|
||||||
# Provides: shlink_swoole
|
|
||||||
# Required-Start: $local_fs $network $named $time $syslog
|
|
||||||
# Required-Stop: $local_fs $network $named $time $syslog
|
|
||||||
# Default-Start: 2 3 4 5
|
|
||||||
# Default-Stop: 0 1 6
|
|
||||||
# Description: Shlink non-blocking server with swoole
|
|
||||||
### END INIT INFO
|
|
||||||
|
|
||||||
SCRIPT=/path/to/shlink/vendor/bin/mezzio-swoole\ start
|
|
||||||
RUNAS=root
|
|
||||||
|
|
||||||
PIDFILE=/var/run/shlink_swoole.pid
|
|
||||||
LOGDIR=/var/log/shlink
|
|
||||||
LOGFILE=${LOGDIR}/shlink_swoole.log
|
|
||||||
|
|
||||||
start() {
|
|
||||||
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
|
||||||
echo 'Shlink with swoole already running' >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo 'Starting shlink with swoole' >&2
|
|
||||||
mkdir -p "$LOGDIR"
|
|
||||||
touch "$LOGFILE"
|
|
||||||
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
|
||||||
su -c "$CMD" $RUNAS > "$PIDFILE"
|
|
||||||
echo 'Shlink started' >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
|
||||||
echo 'Shlink with swoole not running' >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo 'Stopping shlink with swoole' >&2
|
|
||||||
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
|
||||||
echo 'Shlink stopped' >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
start)
|
|
||||||
start
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
stop
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
stop
|
|
||||||
start
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $0 {start|stop|restart}"
|
|
||||||
esac
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run these commands to enable the service and start it:
|
|
||||||
|
|
||||||
* `sudo chmod +x /etc/init.d/shlink_swoole`
|
|
||||||
* `sudo update-rc.d shlink_swoole defaults`
|
|
||||||
* `sudo update-rc.d shlink_swoole enable`
|
|
||||||
* `/etc/init.d/shlink_swoole start`
|
|
||||||
|
|
||||||
Now again, you can access shlink on port 8080, but this time the service will be automatically run at system start-up, and all access logs will be written in `/var/log/shlink/shlink_swoole.log` (you will probably want to [rotate those logs](https://www.digitalocean.com/community/tutorials/how-to-manage-logfiles-with-logrotate-on-ubuntu-16-04). You can find an example logrotate config file [here](data/infra/examples/shlink-daemon-logrotate.conf)).
|
|
||||||
|
|
||||||
Finally access to [https://app.shlink.io](https://app.shlink.io) and configure your server to start creating short URLs.
|
|
||||||
|
|
||||||
### Bonus
|
|
||||||
|
|
||||||
Geo-locating visits to your short links is a time-consuming task. When serving Shlink with swoole, the geo-location task is automatically run asynchronously just after a visit to a short URL happens.
|
|
||||||
|
|
||||||
However, if you are not serving Shlink with swoole, you will have to schedule the geo-location task to be run regularly in the background (for example, using cron jobs):
|
|
||||||
|
|
||||||
The command you need to run is `/path/to/shlink/bin/cli visit:locate`, and you can optionally provide the `-q` flag to remove any output and avoid your cron logs to be polluted.
|
|
||||||
|
|
||||||
## Update to new version
|
|
||||||
|
|
||||||
When a new Shlink version is available, you don't need to repeat the entire process. Instead, follow these steps:
|
|
||||||
|
|
||||||
1. Rename your existing Shlink directory to something else (ie. `shlink` ---> `shlink-old`).
|
|
||||||
2. Download and extract the new version of Shlink, and set the directory name to that of the old version (ie. `shlink`).
|
|
||||||
3. Run the `bin/update` script in the new version's directory to migrate your configuration over. You will be asked to provide the path to the old instance (ie. `shlink-old`).
|
|
||||||
4. If you are using shlink with swoole, restart the service by running `/etc/init.d/shlink_swoole restart`.
|
|
||||||
|
|
||||||
The `bin/update` will use the location from previous shlink version to import the configuration. It will then update the database and generate some assets shlink needs to work.
|
|
||||||
|
|
||||||
**Important!** It is recommended that you don't skip any version when using this process. The update tool gets better on every version, but older versions might make assumptions.
|
|
||||||
|
|
||||||
### Update a configuration option
|
|
||||||
|
|
||||||
Sometimes you need to update the configuration on your shlink instance. Maybe you want to change the GeoLite2 license key, or move from http to https.
|
|
||||||
|
|
||||||
In order to do that, run `bin/set-option` and follow the instructions. You will be asked to select the option to change, and then you will be asked to provide the new value.
|
|
||||||
|
|
||||||
This script will take care of updating that value without changing anything else, and it will also delete the configuration cache so that the new value is applied.
|
|
||||||
|
|
||||||
> This script will fail if you didn't run `bin/install` at least once.
|
|
||||||
|
|
||||||
## Using a docker image
|
|
||||||
|
|
||||||
Starting with version 1.15.0, an official docker image is provided. You can learn how to use it by reading [the docs](docker/README.md).
|
|
||||||
|
|
||||||
The idea is that you can just generate a container using the image and provide custom config via env vars.
|
|
||||||
|
|
||||||
## Using shlink
|
## Using shlink
|
||||||
|
|
||||||
Once shlink is installed, there are two main ways to interact with it:
|
Once shlink is installed, there are two main ways to interact with it:
|
||||||
|
|
||||||
* **The command line**. Try running `bin/cli` and see all the [available commands](#shlink-cli-help).
|
* **The command line**: Try running `bin/cli` to see all the available commands.
|
||||||
|
|
||||||
All of those commands can be run with the `--help`/`-h` flag in order to see how to use them and all the available options.
|
All of them can be run with the `--help`/`-h` flag in order to see how to use them and all the available options.
|
||||||
|
|
||||||
It is probably a good idea to symlink the CLI entry point (`bin/cli`) to somewhere in your path, so that you can run shlink from any directory.
|
It is probably a good idea to symlink the CLI entry point (`bin/cli`) to somewhere in your path, so that you can run shlink from any directory.
|
||||||
|
|
||||||
* **The REST API**. The complete docs on how to use the API can be found [here](https://shlink.io/documentation/api-docs), and a sandbox which also documents every endpoint can be found in the [API Spec](https://api-spec.shlink.io/) portal.
|
* **The REST API**: The complete docs on how to use the API can be found [here](https://shlink.io/documentation/api-docs), and a sandbox which also documents every endpoint can be found in the [API Spec](https://api-spec.shlink.io/) portal.
|
||||||
|
|
||||||
However, you probably don't want to consume the raw API yourself. That's why a nice [web client](https://github.com/shlinkio/shlink-web-client) is provided that can be directly used from [https://app.shlink.io](https://app.shlink.io), or you can host it yourself too.
|
However, you probably don't want to consume the raw API yourself. That's why a nice [web client](https://github.com/shlinkio/shlink-web-client) is provided that can be directly used from [https://app.shlink.io](https://app.shlink.io), or hosted by yourself.
|
||||||
|
|
||||||
Both the API and CLI allow you to do the same operations, except for API key management, which can be done from the command line interface only.
|
Both the API and CLI allow you to do mostly the same operations, except for API key management, which can be done from the command line interface only.
|
||||||
|
|
||||||
### Shlink CLI Help
|
## Contributing
|
||||||
|
|
||||||
```
|
If you are trying to find out how to run the project in development mode or how to provide contributions, read the [CONTRIBUTING](CONTRIBUTING.md) doc.
|
||||||
Usage:
|
|
||||||
command [options] [arguments]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help Display this help message
|
|
||||||
-q, --quiet Do not output any message
|
|
||||||
-V, --version Display this application version
|
|
||||||
--ansi Force ANSI output
|
|
||||||
--no-ansi Disable ANSI output
|
|
||||||
-n, --no-interaction Do not ask any interactive question
|
|
||||||
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
|
|
||||||
|
|
||||||
Available commands:
|
|
||||||
help Displays help for a command
|
|
||||||
list Lists commands
|
|
||||||
api-key
|
|
||||||
api-key:disable Disables an API key.
|
|
||||||
api-key:generate Generates a new valid API key.
|
|
||||||
api-key:list Lists all the available API keys.
|
|
||||||
db
|
|
||||||
db:create Creates the database needed for shlink to work. It will do nothing if the database already exists
|
|
||||||
db:migrate Runs database migrations, which will ensure the shlink database is up to date.
|
|
||||||
short-url
|
|
||||||
short-url:delete Deletes a short URL
|
|
||||||
short-url:generate Generates a short URL for provided long URL and returns it
|
|
||||||
short-url:list List all short URLs
|
|
||||||
short-url:parse Returns the long URL behind a short code
|
|
||||||
short-url:visits Returns the detailed visits information for provided short code
|
|
||||||
tag
|
|
||||||
tag:create Creates one or more tags.
|
|
||||||
tag:delete Deletes one or more tags.
|
|
||||||
tag:list Lists existing tags.
|
|
||||||
tag:rename Renames one existing tag.
|
|
||||||
visit
|
|
||||||
visit:locate Resolves visits origin locations.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multiple domains
|
|
||||||
|
|
||||||
While in many cases you will just have one short domain and you'll want all your short URLs to be served from it, there are some cases in which you might want to have multiple short domains served from the same Shlink instance.
|
|
||||||
|
|
||||||
If that's the case, you need to understand how Shlink will behave when managing your short URLs or any of them is visited.
|
|
||||||
|
|
||||||
### Management
|
|
||||||
|
|
||||||
When you create a short URL it is possible to optionally pass a `domain` param. If you don't pass it, the short URL will be created for the default domain (the one provided during Shlink's installation or in the `SHORT_DOMAIN_HOST` env var when using the docker image).
|
|
||||||
|
|
||||||
However, if you pass it, the short URL will be "linked" to that domain.
|
|
||||||
|
|
||||||
> Note that, if the default domain is passed, Shlink will ignore it and will behave as if no `domain` param was provided.
|
|
||||||
|
|
||||||
The main benefit of being able to pass the domain is that Shlink will allow the same custom slug to be used in multiple short URLs, as long as the domain is different (like `example.com/my-compaign`, `another.com/my-compaign` and `foo.com/my-compaign`).
|
|
||||||
|
|
||||||
Then, each short URL will be tracked separately and you will be able to define specific tags and metadata for each one of them.
|
|
||||||
|
|
||||||
However, this has a side effect. When you try to interact with an existing short URL (editing tags, editing meta, resolving it or deleting it), either from the REST API or the CLI tool, you will have to provide the domain appropriately.
|
|
||||||
|
|
||||||
Let's imagine this situation. Shlink's default domain is `example.com`, and you have the next short URLs:
|
|
||||||
|
|
||||||
* `https://example.com/abc123` -> a regular short URL where no domain was provided.
|
|
||||||
* `https://example.com/my-campaign` -> a regular short URL where no domain was provided, but it has a custom slug.
|
|
||||||
* `https://another.com/my-campaign` -> a short URL where the `another.com` domain was provided, and it has a custom slug.
|
|
||||||
* `https://another.com/def456` -> a short URL where the `another.com` domain was provided.
|
|
||||||
|
|
||||||
These are some of the results you will get when trying to interact with them, depending on the params you provide:
|
|
||||||
|
|
||||||
* Providing just the `abc123` short code -> the first URL will be matched.
|
|
||||||
* Providing just the `my-campaign` short code -> the second URL will be matched, since you did not specify a domain, therefor, Shlink looks for the one with the short code/slug `my-campaign` which is also linked to default domain (or not linked to any domain, to be more accurate).
|
|
||||||
* Providing the `my-campaign` short code and the `another.com` domain -> The third one will be matched.
|
|
||||||
* Providing just the `def456` short code -> Shlink will fail/not find any short URL, since there's none with the short code `def456` linked to default domain.
|
|
||||||
* Providing the `def456` short code and the `another.com` domain -> The fourth short URL will be matched.
|
|
||||||
* Providing any short code and the `foo.com` domain -> Again, no short URL will be found, as there's none linked to `foo.com` domain.
|
|
||||||
|
|
||||||
### Visits
|
|
||||||
|
|
||||||
Before adding support for multiple domains, you could point as many domains as you wanted to Shlink, and they would have always worked for existing short codes/slugs.
|
|
||||||
|
|
||||||
In order to keep backwards compatibility, Shlink's behavior when a short URL is visited is slightly different, getting to fallback in some cases.
|
|
||||||
|
|
||||||
Let's continue with previous example, and also consider we have three domains that will resolve to our Shlink instance, which are `example.com`, `another.com` and `foo.com`.
|
|
||||||
|
|
||||||
With that in mind, this is how Shlink will behave when the next short URLs are visited:
|
|
||||||
|
|
||||||
* `https://another.com/abc123` -> There was no short URL specifically defined for domain `another.com` and short code `abc123`, but it exists for default domain (`example.com`), so it will fall back to it and redirect to where `example.com/abc123` is configured to redirect.
|
|
||||||
* `https://example.com/def456` -> The fall-back does not happen from default domain to specific ones, only the other way around (like in previous case). Because of that, this one will result in a not-found URL, even though the `def456` short code exists for `another.com` domain.
|
|
||||||
* `https://foo.com/abc123` -> This will also fall-back to `example.com/abc123`, like in the first case.
|
|
||||||
* `https://another.com/non-existing` -> The combination of `another.com` domain with the `non-existing` slug does not exist, so Shlink will try to fall-back to the same but for default domain (`example.com`). However, since that combination does not exist either, it will result in a not-found URL.
|
|
||||||
* Any other short URL visited exactly as it was configured will, of course, resolve as expected.
|
|
||||||
|
|
||||||
### Special redirects
|
|
||||||
|
|
||||||
It is currently possible to configure some special redirects when the base domain is visited, a URL does not match, or an invalid/disabled short URL is visited.
|
|
||||||
|
|
||||||
Those are configured during Shlink's installation or via env vars when using the docker image.
|
|
||||||
|
|
||||||
Currently those are all shared for all domains serving the same Shlink instance, but the plan is to update that and allow specific ones for every existing domain.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
50
UPGRADE.md
50
UPGRADE.md
@@ -1,5 +1,53 @@
|
|||||||
# Upgrading
|
# Upgrading
|
||||||
|
|
||||||
|
## From v2.x to v3.x
|
||||||
|
|
||||||
|
### Changes in REST API
|
||||||
|
|
||||||
|
* The `type` property returned when trying to delete a URL that reached the visits threshold, when using the `DELETE /short-urls/{shortCode}` endpoint, is now `INVALID_SHORT_URL_DELETION` instead of `INVALID_SHORTCODE_DELETION`.
|
||||||
|
* The `INVALID_AUTHORIZATION` error no longer includes the `expectedTypes` property. Use `expectedHeaders` one instead.
|
||||||
|
* The `GET /rest/v2/short-urls` endpoint no longer allows ordering by `visitsCount`, `visitCount` or `originalUrl`. Use `visits` instead of the first two, and `longUrl` instead of the last one.
|
||||||
|
* The `GET /rest/v2/short-urls` endpoint no longer allows providing the ordering params with array notation, as in `/shortUrls?orderBy[longUrl]=DESC`. Instead, use the following notation `/shortUrls?orderBy=longUrl-DESC`.
|
||||||
|
* The `GET /rest/v2/short-urls` endpoint now has a default ordering of newest-to-oldest. Use `/shortUrls?orderBy=dateCreated-ASC` in order to keep the oldest-to-newest behavior.
|
||||||
|
* Requests expecting a body no longer support url-encoded payloads. Instead, always use JSON bodies with `Content-Type: application/json`.
|
||||||
|
* The next endpoints have been removed:
|
||||||
|
* `PUT /rest/v2/short-urls/{shortCode}/tags`: Use the `PATCH /rest/v2/short-urls/{shortCode}` endpoint to set the short URL tags.
|
||||||
|
* `POST /rest/v2/tags`: Use `POST /rest/v2/short-urls` or `PATCH /rest/v2/short-urls/{shortCodes}` to create new tags already attached to a short URL. Creating orphan tags makes no sense.
|
||||||
|
|
||||||
|
### Changes in CLI
|
||||||
|
|
||||||
|
* The next commands have been removed:
|
||||||
|
* `short-url:generate`: Use `short-url:create` instead.
|
||||||
|
* `tag:create`: Creating orphan tags makes no sense.
|
||||||
|
* Params in camelCase format are no longer supported. They all have an equivalent kebab-case replacement. (for example, from `--startDate` to `--start-date`).
|
||||||
|
* The `short-url:create` command no longer accepts the `--no-validate-url` flag. Now URLs are never validated, unless `--validate-url` is passed.
|
||||||
|
* The CLI installer tool entry-points have changed.
|
||||||
|
* `bin/install`: replaced by `vendor/bin/shlink-installer install`
|
||||||
|
* `bin/update`: replaced by `vendor/bin/shlink-installer update`
|
||||||
|
* `bin/set-option`: replaced by `vendor/bin/shlink-installer set-option`
|
||||||
|
|
||||||
|
### Changes in config
|
||||||
|
|
||||||
|
* The next env vars have been removed:
|
||||||
|
* `INVALID_SHORT_URL_REDIRECT_TO`: Replaced by `DEFAULT_INVALID_SHORT_URL_REDIRECT`.
|
||||||
|
* `REGULAR_404_REDIRECT_TO`: Replaced by `DEFAULT_REGULAR_404_REDIRECT`.
|
||||||
|
* `BASE_URL_REDIRECT_TO`: Replaced by `DEFAULT_BASE_URL_REDIRECT`.
|
||||||
|
* `SHORT_DOMAIN_HOST`: Replaced by `DEFAULT_DOMAIN`.
|
||||||
|
* `SHORT_DOMAIN_SCHEMA`: Replaced by `IS_HTTPS_ENABLED`.
|
||||||
|
* `USE_HTTPS`: Replaced by `IS_HTTPS_ENABLED`.
|
||||||
|
* `VALIDATE_URLS`: There's no replacement. URLs are not validated, unless explicitly requested during creation or edition.
|
||||||
|
* The next env vars behavior has changed:
|
||||||
|
* `DELETE_SHORT_URL_THRESHOLD`: Now, if this env var is not provided, the "visits threshold" won't be checked at all when deleting short URLs. Make sure you explicitly provide a value if you want to enable this feature.
|
||||||
|
* Environment variables now have precedence over configuration set via the installer tool.
|
||||||
|
|
||||||
|
### Other changes
|
||||||
|
|
||||||
|
* A default GeoLite2 license key is no longer provided. If you don't provide your own as explained in [the docs](https://shlink.io/documentation/geolite-license-key/), Shlink will not try to update the file anymore.
|
||||||
|
* The docker image no longer accepts providing configuration via json files mounted in the `config/params` folder. Only env vars are supported now.
|
||||||
|
* If you were manually serving Shlink with swoole, the entry script has to be changed from `/path/to/shlink/vendor/bin/mezzio-swoole start` to `/path/to/shlink/vendor/bin/laminas mezzio:swoole:start`
|
||||||
|
* The `GET /{shortCode}/qr-code/{size}` url has been removed. Use `GET /{shortCode}/qr-code?size={size}` instead.
|
||||||
|
* Regular swoole extension is no longer supported. Use openswoole instead, as a direct replacement. In most of the cases you just need to uninstall one and install the other, the rest is transparent.
|
||||||
|
|
||||||
## From v1.x to v2.x
|
## From v1.x to v2.x
|
||||||
|
|
||||||
### PHP 7.4 required
|
### PHP 7.4 required
|
||||||
@@ -28,7 +76,7 @@ These routes have been removed, but have a direct replacement:
|
|||||||
* `/qr/{shortCode}[/{size}]` -> `/{shortCode}/qr-code[/{size}]`
|
* `/qr/{shortCode}[/{size}]` -> `/{shortCode}/qr-code[/{size}]`
|
||||||
* `PUT /rest/v{version}/short-urls/{shortCode}` -> `PATCH /rest/v{version}/short-urls/{shortCode}`
|
* `PUT /rest/v{version}/short-urls/{shortCode}` -> `PATCH /rest/v{version}/short-urls/{shortCode}`
|
||||||
|
|
||||||
When using the old ones, a 404 status will me returned now.
|
When using the old ones, a 404 status will be returned now.
|
||||||
|
|
||||||
### Removed command and route aliases
|
### Removed command and route aliases
|
||||||
|
|
||||||
|
|||||||
7
bin/cli
7
bin/cli
@@ -3,5 +3,8 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
$run = require __DIR__ . '/../config/run.php';
|
use Symfony\Component\Console\Application;
|
||||||
$run(true);
|
|
||||||
|
/** @var Application $app */
|
||||||
|
$app = require __DIR__ . '/../config/cli-app.php';
|
||||||
|
$app->run();
|
||||||
|
|||||||
12
bin/doctrine
Executable file
12
bin/doctrine
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||||
|
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||||
|
|
||||||
|
/** @var EntityManager $app */
|
||||||
|
$em = require __DIR__ . '/../config/entity-manager.php';
|
||||||
|
ConsoleRunner::run(new SingleManagerProvider($em));
|
||||||
12
bin/install
12
bin/install
@@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
|
||||||
|
|
||||||
use function chdir;
|
|
||||||
use function dirname;
|
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
|
||||||
[$install] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
|
||||||
$install();
|
|
||||||
32
bin/roadrunner-worker.php
Normal file
32
bin/roadrunner-worker.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Mezzio\Application;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Shlinkio\Shlink\EventDispatcher\RoadRunner\RoadRunnerTaskConsumerToListener;
|
||||||
|
use Spiral\RoadRunner\Http\PSR7Worker;
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
|
||||||
|
(static function (): void {
|
||||||
|
/** @var ContainerInterface $container */
|
||||||
|
$container = include __DIR__ . '/../config/container.php';
|
||||||
|
$rrMode = env('RR_MODE');
|
||||||
|
|
||||||
|
if ($rrMode === 'http') {
|
||||||
|
// This was spin-up as a web worker
|
||||||
|
$app = $container->get(Application::class);
|
||||||
|
$worker = $container->get(PSR7Worker::class);
|
||||||
|
|
||||||
|
while ($req = $worker->waitRequest()) {
|
||||||
|
try {
|
||||||
|
$worker->respond($app->handle($req));
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$worker->getWorker()->error((string) $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$container->get(RoadRunnerTaskConsumerToListener::class)->listenForTasks();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\Installer\Command\SetOptionCommand;
|
|
||||||
|
|
||||||
use function chdir;
|
|
||||||
use function dirname;
|
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
|
||||||
[,, $installer] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
|
||||||
$installer(SetOptionCommand::NAME);
|
|
||||||
@@ -1,19 +1,38 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
export APP_ENV=test
|
export APP_ENV=test
|
||||||
export DB_DRIVER=mysql
|
|
||||||
export TEST_ENV=api
|
export TEST_ENV=api
|
||||||
|
export TEST_RUNTIME="${TEST_RUNTIME:-"openswoole"}" # Openswoole is deprecated. Remove in v4.0.0
|
||||||
|
export DB_DRIVER="${DB_DRIVER:-"postgres"}"
|
||||||
|
export GENERATE_COVERAGE="${GENERATE_COVERAGE:-"no"}"
|
||||||
|
|
||||||
|
# Reset logs
|
||||||
|
OUTPUT_LOGS=data/log/api-tests/output.log
|
||||||
|
rm -rf data/log/api-tests
|
||||||
|
mkdir data/log/api-tests
|
||||||
|
touch $OUTPUT_LOGS
|
||||||
|
|
||||||
# Try to stop server just in case it hanged in last execution
|
# Try to stop server just in case it hanged in last execution
|
||||||
vendor/bin/mezzio-swoole stop
|
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:stop
|
||||||
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -f
|
||||||
|
|
||||||
echo 'Starting server...'
|
echo 'Starting server...'
|
||||||
vendor/bin/mezzio-swoole start -d
|
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:start -d
|
||||||
sleep 2
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr serve -p -c=config/roadrunner/.rr.dev.yml \
|
||||||
|
-o=http.address=0.0.0.0:9999 \
|
||||||
|
-o=logs.encoding=json \
|
||||||
|
-o=logs.channels.http.encoding=json \
|
||||||
|
-o=logs.channels.server.encoding=json \
|
||||||
|
-o=logs.output="${PWD}/${OUTPUT_LOGS}" \
|
||||||
|
-o=logs.channels.http.output="${PWD}/${OUTPUT_LOGS}" \
|
||||||
|
-o=logs.channels.server.output="${PWD}/${OUTPUT_LOGS}" &
|
||||||
|
sleep 2 # Let's give the server a couple of seconds to start
|
||||||
|
|
||||||
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always --log-junit=build/coverage-api/junit.xml $*
|
vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always --log-junit=build/coverage-api/junit.xml $*
|
||||||
testsExitCode=$?
|
testsExitCode=$?
|
||||||
|
|
||||||
vendor/bin/mezzio-swoole stop
|
[ "$TEST_RUNTIME" = 'openswoole' ] && vendor/bin/laminas mezzio:swoole:stop
|
||||||
|
[ "$TEST_RUNTIME" = 'rr' ] && bin/rr stop -c config/roadrunner/.rr.dev.yml -o=http.address=0.0.0.0:9999
|
||||||
|
|
||||||
# Exit this script with the same code as the tests. If tests failed, this script has to fail
|
# Exit this script with the same code as the tests. If tests failed, this script has to fail
|
||||||
exit $testsExitCode
|
exit $testsExitCode
|
||||||
|
|||||||
12
bin/update
12
bin/update
@@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
|
||||||
|
|
||||||
use function chdir;
|
|
||||||
use function dirname;
|
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
|
||||||
[, $update] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
|
||||||
$update();
|
|
||||||
41
build.sh
41
build.sh
@@ -1,46 +1,63 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ "$#" -ne 1 ]]; then
|
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || ([ "$#" == 2 ] && [ "$2" != "--no-swoole" ]); then
|
||||||
echo "Usage:" >&2
|
echo "Usage:" >&2
|
||||||
echo " $0 {version}" >&2
|
echo " $0 {version} [--no-swoole]" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
version=$1
|
version=$1
|
||||||
builtcontent="./build/shlink_${version}_dist"
|
noSwoole=$2
|
||||||
|
phpVersion=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
|
||||||
|
# Openswoole is deprecated. Remove in v4.0.0
|
||||||
|
[[ $noSwoole ]] && swooleSuffix="" || swooleSuffix="_openswoole"
|
||||||
|
distId="shlink${version}_php${phpVersion}${swooleSuffix}_dist"
|
||||||
|
builtContent="./build/${distId}"
|
||||||
projectdir=$(pwd)
|
projectdir=$(pwd)
|
||||||
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
|
[[ -f ./composer.phar ]] && composerBin='./composer.phar' || composerBin='composer'
|
||||||
|
|
||||||
# Copy project content to temp dir
|
# Copy project content to temp dir
|
||||||
echo 'Copying project files...'
|
echo 'Copying project files...'
|
||||||
rm -rf "${builtcontent}"
|
rm -rf "${builtContent}"
|
||||||
mkdir -p "${builtcontent}"
|
mkdir -p "${builtContent}"
|
||||||
rsync -av * "${builtcontent}" \
|
rsync -av * "${builtContent}" \
|
||||||
--exclude=*docker* \
|
--exclude=*docker* \
|
||||||
--exclude=Dockerfile \
|
--exclude=Dockerfile \
|
||||||
--include=.htaccess \
|
--include=.htaccess \
|
||||||
|
--include=config/roadrunner/.rr.yml \
|
||||||
--exclude-from=./.dockerignore
|
--exclude-from=./.dockerignore
|
||||||
cd "${builtcontent}"
|
cd "${builtContent}"
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
echo "Installing dependencies with $composerBin..."
|
echo "Installing dependencies with $composerBin..."
|
||||||
|
# Deprecated. Do not ignore PHP platform req for Shlink v4.0.0
|
||||||
|
composerFlags="--optimize-autoloader --no-progress --no-interaction --ignore-platform-req=php+"
|
||||||
${composerBin} self-update
|
${composerBin} self-update
|
||||||
${composerBin} install --no-dev --optimize-autoloader --prefer-dist --no-progress --no-interaction
|
${composerBin} install --no-dev --prefer-dist $composerFlags
|
||||||
|
|
||||||
|
if [[ $noSwoole ]]; then
|
||||||
|
# If generating a dist not for openswoole, uninstall mezzio-swoole
|
||||||
|
${composerBin} remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev $composerFlags
|
||||||
|
else
|
||||||
|
# Deprecated. Remove in Shlink v4.0.0
|
||||||
|
# If generating a dist for openswoole, uninstall RoadRunner
|
||||||
|
${composerBin} remove spiral/roadrunner spiral/roadrunner-jobs spiral/roadrunner-cli spiral/roadrunner-http --with-all-dependencies --update-no-dev $composerFlags
|
||||||
|
fi
|
||||||
|
|
||||||
# Delete development files
|
# Delete development files
|
||||||
echo 'Deleting dev files...'
|
echo 'Deleting dev files...'
|
||||||
rm composer.*
|
rm composer.*
|
||||||
|
|
||||||
# Update shlink version in config
|
# Update Shlink version in config
|
||||||
sed -i "s/%SHLINK_VERSION%/${version}/g" config/autoload/app_options.global.php
|
sed -i "s/%SHLINK_VERSION%/${version}/g" config/autoload/app_options.global.php
|
||||||
|
|
||||||
# Compressing file
|
# Compressing file
|
||||||
echo 'Compressing files...'
|
echo 'Compressing files...'
|
||||||
cd "${projectdir}"/build
|
cd "${projectdir}"/build
|
||||||
rm -f ./shlink_${version}_dist.zip
|
rm -f ./${distId}.zip
|
||||||
zip -ry ./shlink_${version}_dist.zip ./shlink_${version}_dist
|
zip -ry ./${distId}.zip ./${distId}
|
||||||
cd "${projectdir}"
|
cd "${projectdir}"
|
||||||
rm -rf "${builtcontent}"
|
rm -rf "${builtContent}"
|
||||||
|
|
||||||
echo 'Done!'
|
echo 'Done!'
|
||||||
|
|||||||
215
composer.json
215
composer.json
@@ -12,68 +12,75 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.4",
|
"php": "^8.2",
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-gd": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"akrabat/ip-address-middleware": "^2.0",
|
"akrabat/ip-address-middleware": "^2.1",
|
||||||
"cakephp/chronos": "^2.0",
|
"cakephp/chronos": "^3.0.2",
|
||||||
"cocur/slugify": "^4.0",
|
"doctrine/migrations": "^3.6",
|
||||||
"doctrine/cache": "^1.9",
|
"doctrine/orm": "^2.16",
|
||||||
"doctrine/migrations": "^3.0.2",
|
"endroid/qr-code": "^4.8",
|
||||||
"doctrine/orm": "^2.8",
|
"friendsofphp/proxy-manager-lts": "^1.0",
|
||||||
"endroid/qr-code": "^3.6",
|
"geoip2/geoip2": "^3.0",
|
||||||
"geoip2/geoip2": "^2.9",
|
"guzzlehttp/guzzle": "^7.5",
|
||||||
"guzzlehttp/guzzle": "^7.0",
|
"happyr/doctrine-specification": "^2.0",
|
||||||
"happyr/doctrine-specification": "2.0.x-dev#cb116d3 as 2.0",
|
"jaybizzle/crawler-detect": "^1.2.116",
|
||||||
"laminas/laminas-config": "^3.3",
|
"laminas/laminas-config": "^3.8",
|
||||||
"laminas/laminas-config-aggregator": "^1.1",
|
"laminas/laminas-config-aggregator": "^1.13",
|
||||||
"laminas/laminas-diactoros": "^2.1.3",
|
"laminas/laminas-diactoros": "^3.3",
|
||||||
"laminas/laminas-inputfilter": "^2.10",
|
"laminas/laminas-inputfilter": "^2.27",
|
||||||
"laminas/laminas-paginator": "^2.8",
|
"laminas/laminas-servicemanager": "^3.21",
|
||||||
"laminas/laminas-servicemanager": "^3.6",
|
"laminas/laminas-stdlib": "^3.17",
|
||||||
"laminas/laminas-stdlib": "^3.2",
|
"league/uri": "^6.8",
|
||||||
"lcobucci/jwt": "^4.0",
|
"matomo/matomo-php-tracker": "^3.2",
|
||||||
"league/uri": "^6.2",
|
"mezzio/mezzio": "^3.17",
|
||||||
"lstrojny/functional-php": "^1.15",
|
"mezzio/mezzio-fastroute": "^3.10",
|
||||||
"mezzio/mezzio": "^3.2",
|
"mezzio/mezzio-problem-details": "^1.13",
|
||||||
"mezzio/mezzio-fastroute": "^3.1",
|
"mezzio/mezzio-swoole": "^4.7",
|
||||||
"mezzio/mezzio-helpers": "^5.3",
|
"mlocati/ip-lib": "^1.18",
|
||||||
"mezzio/mezzio-problem-details": "^1.1",
|
"mobiledetect/mobiledetectlib": "^4.8",
|
||||||
"mezzio/mezzio-swoole": "^2.6.4",
|
"pagerfanta/core": "^3.8",
|
||||||
"monolog/monolog": "^2.0",
|
|
||||||
"nikolaposa/monolog-factory": "^3.1",
|
|
||||||
"ocramius/proxy-manager": "^2.11",
|
|
||||||
"php-middleware/request-id": "^4.1",
|
"php-middleware/request-id": "^4.1",
|
||||||
"predis/predis": "^1.1",
|
"pugx/shortid-php": "^1.1",
|
||||||
"pugx/shortid-php": "^0.7",
|
"ramsey/uuid": "^4.7",
|
||||||
"ramsey/uuid": "^3.9",
|
"shlinkio/shlink-common": "^5.7.1",
|
||||||
"shlinkio/shlink-common": "dev-main#1311861 as 3.4",
|
"shlinkio/shlink-config": "^2.5",
|
||||||
"shlinkio/shlink-config": "^1.0",
|
"shlinkio/shlink-event-dispatcher": "^3.1",
|
||||||
"shlinkio/shlink-event-dispatcher": "^1.6",
|
"shlinkio/shlink-importer": "^5.2.1",
|
||||||
"shlinkio/shlink-importer": "^2.1",
|
"shlinkio/shlink-installer": "^8.7",
|
||||||
"shlinkio/shlink-installer": "^5.3",
|
"shlinkio/shlink-ip-geolocation": "^4.0",
|
||||||
"shlinkio/shlink-ip-geolocation": "^1.5",
|
"shlinkio/shlink-json": "^1.1",
|
||||||
"symfony/console": "^5.1",
|
"spiral/roadrunner": "^2023.2",
|
||||||
"symfony/filesystem": "^5.1",
|
"spiral/roadrunner-cli": "^2.5",
|
||||||
"symfony/lock": "^5.1",
|
"spiral/roadrunner-http": "^3.1",
|
||||||
"symfony/mercure": "^0.4.1",
|
"spiral/roadrunner-jobs": "^4.0",
|
||||||
"symfony/process": "^5.1",
|
"symfony/console": "^6.3",
|
||||||
"symfony/string": "^5.1"
|
"symfony/filesystem": "^6.3",
|
||||||
|
"symfony/lock": "^6.3",
|
||||||
|
"symfony/process": "^6.3",
|
||||||
|
"symfony/string": "^6.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
"devizzent/cebe-php-openapi": "^1.0.1",
|
||||||
"devster/ubench": "^2.1",
|
"devster/ubench": "^2.1",
|
||||||
"dms/phpunit-arraysubset-asserts": "^0.2.1",
|
"infection/infection": "^0.27",
|
||||||
"eaglewu/swoole-ide-helper": "dev-master",
|
"openswoole/ide-helper": "~22.0.0",
|
||||||
"infection/infection": "^0.20.2",
|
"phpstan/phpstan": "^1.10",
|
||||||
"phpspec/prophecy-phpunit": "^2.0",
|
"phpstan/phpstan-doctrine": "^1.3",
|
||||||
"phpstan/phpstan": "^0.12.64",
|
"phpstan/phpstan-phpunit": "^1.3",
|
||||||
"phpunit/php-code-coverage": "^9.2",
|
"phpstan/phpstan-symfony": "^1.3",
|
||||||
"phpunit/phpunit": "^9.5",
|
"phpunit/php-code-coverage": "^10.1",
|
||||||
|
"phpunit/phpunit": "^10.4",
|
||||||
"roave/security-advisories": "dev-master",
|
"roave/security-advisories": "dev-master",
|
||||||
"shlinkio/php-coding-standard": "~2.1.1",
|
"shlinkio/php-coding-standard": "~2.3.0",
|
||||||
"shlinkio/shlink-test-utils": "^1.7",
|
"shlinkio/shlink-test-utils": "^3.8.1",
|
||||||
"symfony/var-dumper": "^5.2",
|
"symfony/var-dumper": "^6.3",
|
||||||
"veewee/composer-run-parallel": "^0.1.0"
|
"veewee/composer-run-parallel": "^1.3"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/var-exporter": ">=6.3.9,<=6.4.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -82,18 +89,21 @@
|
|||||||
"Shlinkio\\Shlink\\Core\\": "module/Core/src"
|
"Shlinkio\\Shlink\\Core\\": "module/Core/src"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
"config/constants.php",
|
||||||
|
"module/Core/functions/array-utils.php",
|
||||||
"module/Core/functions/functions.php"
|
"module/Core/functions/functions.php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
|
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
|
||||||
|
"ShlinkioCliTest\\Shlink\\CLI\\": "module/CLI/test-cli",
|
||||||
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
|
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
|
||||||
"ShlinkioApiTest\\Shlink\\Rest\\": "module/Rest/test-api",
|
"ShlinkioApiTest\\Shlink\\Rest\\": "module/Rest/test-api",
|
||||||
"ShlinkioTest\\Shlink\\Core\\": [
|
"ShlinkioDbTest\\Shlink\\Rest\\": "module/Rest/test-db",
|
||||||
"module/Core/test",
|
"ShlinkioTest\\Shlink\\Core\\": "module/Core/test",
|
||||||
"module/Core/test-db"
|
"ShlinkioDbTest\\Shlink\\Core\\": "module/Core/test-db",
|
||||||
]
|
"ShlinkioApiTest\\Shlink\\Core\\": "module/Core/test-api"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/test/constants.php"
|
"config/test/constants.php"
|
||||||
@@ -101,30 +111,19 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ci": [
|
"ci": [
|
||||||
"@cs",
|
"@parallel cs stan swagger:validate test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
||||||
"@stan",
|
"@parallel infect:test:api infect:test:cli infect:ci:unit infect:ci:db"
|
||||||
"@test:ci",
|
|
||||||
"@infect:ci"
|
|
||||||
],
|
],
|
||||||
"ci:parallel": [
|
"cs": "phpcs -s",
|
||||||
"@parallel cs stan test:unit:ci test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
|
||||||
"@parallel test:api infect:ci:unit infect:ci:db"
|
|
||||||
],
|
|
||||||
"cs": "phpcs",
|
|
||||||
"cs:fix": "phpcbf",
|
"cs:fix": "phpcbf",
|
||||||
"stan": "phpstan analyse module/*/src/ module/*/config config docker/config data/migrations --level=6",
|
"stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/test* module/*/config module/*/migrations config docker/config --level=8",
|
||||||
"test": [
|
"test": [
|
||||||
"@test:unit",
|
"@parallel test:unit test:db",
|
||||||
"@test:db",
|
"@parallel test:api test:cli"
|
||||||
"@test:api"
|
|
||||||
],
|
],
|
||||||
"test:ci": [
|
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --testdox",
|
||||||
"@test:unit:ci",
|
"test:unit:ci": "@test:unit --coverage-php=build/coverage-unit.cov --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
|
||||||
"@test:db",
|
"test:unit:pretty": "@test:unit --coverage-html build/coverage-unit/coverage-html",
|
||||||
"@test:api"
|
|
||||||
],
|
|
||||||
"test:unit": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-php build/coverage-unit.cov --testdox",
|
|
||||||
"test:unit:ci": "@test:unit --coverage-xml=build/coverage-unit/coverage-xml --log-junit=build/coverage-unit/junit.xml",
|
|
||||||
"test:db": "@parallel test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
"test:db": "@parallel test:db:sqlite:ci test:db:mysql test:db:maria test:db:postgres test:db:ms",
|
||||||
"test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml",
|
"test:db:sqlite": "APP_ENV=test php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-db.xml",
|
||||||
"test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml",
|
"test:db:sqlite:ci": "@test:db:sqlite --coverage-php build/coverage-db.cov --coverage-xml=build/coverage-db/coverage-xml --log-junit=build/coverage-db/junit.xml",
|
||||||
@@ -133,44 +132,80 @@
|
|||||||
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
|
"test:db:postgres": "DB_DRIVER=postgres composer test:db:sqlite",
|
||||||
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
|
"test:db:ms": "DB_DRIVER=mssql composer test:db:sqlite",
|
||||||
"test:api": "bin/test/run-api-tests.sh",
|
"test:api": "bin/test/run-api-tests.sh",
|
||||||
"test:unit:pretty": "@php vendor/bin/phpunit --order-by=random --colors=always --coverage-html build/coverage-unit-html",
|
"test:api:rr": "TEST_RUNTIME=rr bin/test/run-api-tests.sh",
|
||||||
"infect:ci:base": "infection --threads=4 --log-verbosity=default --only-covered --skip-initial-tests",
|
"test:api:ci": "GENERATE_COVERAGE=yes composer test:api",
|
||||||
|
"test:api:pretty": "GENERATE_COVERAGE=pretty composer test:api",
|
||||||
|
"test:cli": "APP_ENV=test DB_DRIVER=maria TEST_ENV=cli php vendor/bin/phpunit --order-by=random --colors=always --testdox -c phpunit-cli.xml --log-junit=build/coverage-cli/junit.xml",
|
||||||
|
"test:cli:ci": "GENERATE_COVERAGE=yes composer test:cli",
|
||||||
|
"test:cli:pretty": "GENERATE_COVERAGE=pretty composer test:cli",
|
||||||
|
"infect:ci:base": "infection --threads=max --only-covered --skip-initial-tests",
|
||||||
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
|
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
|
||||||
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json",
|
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json5",
|
||||||
"infect:ci": "@parallel infect:ci:unit infect:ci:db",
|
"infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=95 --configuration=infection-api.json5",
|
||||||
|
"infect:ci:cli": "@infect:ci:base --coverage=build/coverage-cli --min-msi=90 --configuration=infection-cli.json5",
|
||||||
|
"infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api infect:ci:cli",
|
||||||
"infect:test": [
|
"infect:test": [
|
||||||
"@parallel test:unit:ci test:db:sqlite:ci",
|
"@parallel test:unit:ci test:db:sqlite:ci test:api:ci",
|
||||||
"@infect:ci"
|
"@infect:ci"
|
||||||
],
|
],
|
||||||
|
"infect:test:unit": [
|
||||||
|
"@test:unit:ci",
|
||||||
|
"@infect:ci:unit"
|
||||||
|
],
|
||||||
|
"infect:test:db": [
|
||||||
|
"@test:db:sqlite:ci",
|
||||||
|
"@infect:ci:db"
|
||||||
|
],
|
||||||
|
"infect:test:api": [
|
||||||
|
"@test:api:ci",
|
||||||
|
"@infect:ci:api"
|
||||||
|
],
|
||||||
|
"infect:test:cli": [
|
||||||
|
"@test:cli:ci",
|
||||||
|
"@infect:ci:cli"
|
||||||
|
],
|
||||||
|
"swagger:validate": "php-openapi validate docs/swagger/swagger.json",
|
||||||
|
"swagger:inline": "php-openapi inline docs/swagger/swagger.json docs/swagger/swagger-inlined.json",
|
||||||
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
"clean:dev": "rm -f data/database.sqlite && rm -f config/params/generated_config.php"
|
||||||
},
|
},
|
||||||
"scripts-descriptions": {
|
"scripts-descriptions": {
|
||||||
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"test:ci\" and \"infect:ci\"</>",
|
"ci": "<fg=blue;options=bold>Alias for \"cs\", \"stan\", \"swagger:validate\", \"test:ci\" and \"infect:ci\"</>",
|
||||||
"ci:parallel": "<fg=blue;options=bold>Same as \"ci\", but parallelizing tasks as much as possible</>",
|
|
||||||
"cs": "<fg=blue;options=bold>Checks coding styles</>",
|
"cs": "<fg=blue;options=bold>Checks coding styles</>",
|
||||||
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",
|
"cs:fix": "<fg=blue;options=bold>Fixes coding styles, when possible</>",
|
||||||
"stan": "<fg=blue;options=bold>Inspects code with phpstan</>",
|
"stan": "<fg=blue;options=bold>Inspects code with phpstan</>",
|
||||||
"test": "<fg=blue;options=bold>Runs all test suites</>",
|
"test": "<fg=blue;options=bold>Runs all test suites</>",
|
||||||
"test:ci": "<fg=blue;options=bold>Runs all test suites, generating all needed reports and logs for CI envs</>",
|
|
||||||
"test:unit": "<fg=blue;options=bold>Runs unit test suites</>",
|
"test:unit": "<fg=blue;options=bold>Runs unit test suites</>",
|
||||||
"test:unit:ci": "<fg=blue;options=bold>Runs unit test suites, generating all needed reports and logs for CI envs</>",
|
"test:unit:ci": "<fg=blue;options=bold>Runs unit test suites, generating all needed reports and logs for CI envs</>",
|
||||||
|
"test:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
||||||
"test:db": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL, MariaDB, PostgreSQL and MsSQL</>",
|
"test:db": "<fg=blue;options=bold>Runs database test suites on a SQLite, MySQL, MariaDB, PostgreSQL and MsSQL</>",
|
||||||
"test:db:sqlite": "<fg=blue;options=bold>Runs database test suites on a SQLite database</>",
|
"test:db:sqlite": "<fg=blue;options=bold>Runs database test suites on a SQLite database</>",
|
||||||
"test:db:sqlite:ci": "<fg=blue;options=bold>Runs database test suites on a SQLite database, generating all needed reports and logs for CI envs</>",
|
"test:db:sqlite:ci": "<fg=blue;options=bold>Runs database test suites on a SQLite database, generating all needed reports and logs for CI envs</>",
|
||||||
"test:db:mysql": "<fg=blue;options=bold>Runs database test suites on a MySQL database</>",
|
"test:db:mysql": "<fg=blue;options=bold>Runs database test suites on a MySQL database</>",
|
||||||
"test:db:maria": "<fg=blue;options=bold>Runs database test suites on a MariaDB database</>",
|
"test:db:maria": "<fg=blue;options=bold>Runs database test suites on a MariaDB database</>",
|
||||||
"test:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
|
"test:db:postgres": "<fg=blue;options=bold>Runs database test suites on a PostgreSQL database</>",
|
||||||
"test:db:ms": "<fg=blue;options=bold>Runs database test suites on a Miscrosoft SQL Server database</>",
|
"test:db:ms": "<fg=blue;options=bold>Runs database test suites on a Microsoft SQL Server database</>",
|
||||||
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
|
"test:api": "<fg=blue;options=bold>Runs API test suites</>",
|
||||||
"test:unit:pretty": "<fg=blue;options=bold>Runs unit test suites and generates an HTML code coverage report</>",
|
"test:api:ci": "<fg=blue;options=bold>Runs API test suites, and generates code coverage for CI</>",
|
||||||
|
"test:api:pretty": "<fg=blue;options=bold>Runs API test suites, and generates code coverage in HTML format</>",
|
||||||
|
"test:cli": "<fg=blue;options=bold>Runs CLI test suites</>",
|
||||||
|
"test:cli:ci": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage for CI</>",
|
||||||
|
"test:cli:pretty": "<fg=blue;options=bold>Runs CLI test suites, and generates code coverage in HTML format</>",
|
||||||
"infect:ci": "<fg=blue;options=bold>Checks unit and db tests quality applying mutation testing with existing reports and logs</>",
|
"infect:ci": "<fg=blue;options=bold>Checks unit and db tests quality applying mutation testing with existing reports and logs</>",
|
||||||
"infect:ci:unit": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
|
"infect:ci:unit": "<fg=blue;options=bold>Checks unit tests quality applying mutation testing with existing reports and logs</>",
|
||||||
"infect:ci:db": "<fg=blue;options=bold>Checks db tests quality applying mutation testing with existing reports and logs</>",
|
"infect:ci:db": "<fg=blue;options=bold>Checks db tests quality applying mutation testing with existing reports and logs</>",
|
||||||
"infect:test": "<fg=blue;options=bold>Runs unit and db tests, then checks tests quality applying mutation testing</>",
|
"infect:test": "<fg=blue;options=bold>Runs unit and db tests, then checks tests quality applying mutation testing</>",
|
||||||
|
"swagger:validate": "<fg=blue;options=bold>Validates the swagger docs, making sure they fulfil the spec</>",
|
||||||
|
"swagger:inline": "<fg=blue;options=bold>Inlines swagger docs in a single file</>",
|
||||||
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
|
"clean:dev": "<fg=blue;options=bold>Deletes artifacts which are gitignored and could affect dev env</>"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"platform-check": false
|
"platform-check": false,
|
||||||
|
"allow-plugins": {
|
||||||
|
"composer/package-versions-deprecated": true,
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||||
|
"infection/extension-installer": true,
|
||||||
|
"veewee/composer-run-parallel": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ return [
|
|||||||
'app_options' => [
|
'app_options' => [
|
||||||
'name' => 'Shlink',
|
'name' => 'Shlink',
|
||||||
'version' => '%SHLINK_VERSION%',
|
'version' => '%SHLINK_VERSION%',
|
||||||
'disable_track_param' => null,
|
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
11
config/autoload/app_options.local.php.dist
Normal file
11
config/autoload/app_options.local.php.dist
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'app_options' => [
|
||||||
|
'version' => 'latest',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
25
config/autoload/cache.global.php
Normal file
25
config/autoload/cache.global.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return (static function (): array {
|
||||||
|
$redisServers = EnvVars::REDIS_SERVERS->loadFromEnv();
|
||||||
|
$redis = ['pub_sub_enabled' => $redisServers !== null && EnvVars::REDIS_PUB_SUB_ENABLED->loadFromEnv(false)];
|
||||||
|
$cacheRedisBlock = $redisServers === null ? [] : [
|
||||||
|
'redis' => [
|
||||||
|
'servers' => $redisServers,
|
||||||
|
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(),
|
||||||
|
'decode_credentials' => (bool) EnvVars::REDIS_DECODE_CREDENTIALS->loadFromEnv(false),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'cache' => [
|
||||||
|
'namespace' => EnvVars::CACHE_NAMESPACE->loadFromEnv('Shlink'),
|
||||||
|
...$cacheRedisBlock,
|
||||||
|
],
|
||||||
|
'redis' => $redis,
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -8,8 +8,8 @@ return [
|
|||||||
|
|
||||||
'debug' => false,
|
'debug' => false,
|
||||||
|
|
||||||
// Disabling config cache for cli, ensures it's never used for swoole and also that console commands don't generate
|
// Disabling config cache for cli, ensures it's never used for openswoole/RoadRunner, and also that console
|
||||||
// a cache file that's then used by non-swoole web executions
|
// commands don't generate a cache file that's then used by php-fpm web executions
|
||||||
ConfigAggregator::ENABLE_CACHE => PHP_SAPI !== 'cli',
|
ConfigAggregator::ENABLE_CACHE => PHP_SAPI !== 'cli',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,11 +4,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
return [
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
'delete_short_urls' => [
|
return (static function (): array {
|
||||||
'visits_threshold' => 15,
|
$threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD->loadFromEnv();
|
||||||
'check_visits_threshold' => true,
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
return [
|
||||||
|
|
||||||
|
'delete_short_urls' => [
|
||||||
|
'check_visits_threshold' => $threshold !== null,
|
||||||
|
'visits_threshold' => (int) ($threshold ?? DEFAULT_DELETE_SHORT_URL_THRESHOLD),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -3,14 +3,25 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
|
use Mezzio\Application;
|
||||||
use Mezzio\Container;
|
use Mezzio\Container;
|
||||||
use Psr\Http\Client\ClientInterface;
|
use Psr\Http\Client\ClientInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestFactoryInterface;
|
||||||
|
use Psr\Http\Message\StreamFactoryInterface;
|
||||||
|
use Psr\Http\Message\UploadedFileFactoryInterface;
|
||||||
|
use Spiral\RoadRunner\Http\PSR7Worker;
|
||||||
|
use Spiral\RoadRunner\WorkerInterface;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
|
'factories' => [
|
||||||
|
PSR7Worker::class => ConfigAbstractFactory::class,
|
||||||
|
],
|
||||||
|
|
||||||
'delegators' => [
|
'delegators' => [
|
||||||
Mezzio\Application::class => [
|
Application::class => [
|
||||||
Container\ApplicationConfigInjectionDelegator::class,
|
Container\ApplicationConfigInjectionDelegator::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -26,4 +37,13 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
ConfigAbstractFactory::class => [
|
||||||
|
PSR7Worker::class => [
|
||||||
|
WorkerInterface::class,
|
||||||
|
ServerRequestFactoryInterface::class,
|
||||||
|
StreamFactoryInterface::class,
|
||||||
|
UploadedFileFactoryInterface::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,24 +2,62 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Common;
|
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
use Happyr\DoctrineSpecification\EntitySpecificationRepository;
|
use function Shlinkio\Shlink\Core\ArrayUtils\contains;
|
||||||
|
|
||||||
return [
|
return (static function (): array {
|
||||||
|
$driver = EnvVars::DB_DRIVER->loadFromEnv();
|
||||||
|
$isMysqlCompatible = contains($driver, ['maria', 'mysql']);
|
||||||
|
|
||||||
'entity_manager' => [
|
$resolveDriver = static fn () => match ($driver) {
|
||||||
'orm' => [
|
'postgres' => 'pdo_pgsql',
|
||||||
'proxies_dir' => 'data/proxies',
|
'mssql' => 'pdo_sqlsrv',
|
||||||
'load_mappings_using_functional_style' => true,
|
default => 'pdo_mysql',
|
||||||
'default_repository_classname' => EntitySpecificationRepository::class,
|
};
|
||||||
|
$resolveDefaultPort = static fn () => match ($driver) {
|
||||||
|
'postgres' => '5432',
|
||||||
|
'mssql' => '1433',
|
||||||
|
default => '3306',
|
||||||
|
};
|
||||||
|
$resolveCharset = static fn () => match ($driver) {
|
||||||
|
// This does not determine charsets or collations in tables or columns, but the charset used in the data
|
||||||
|
// flowing in the connection, so it has to match what has been set in the database.
|
||||||
|
'maria', 'mysql' => 'utf8mb4',
|
||||||
|
'postgres' => 'utf8',
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
$resolveConnection = static fn () => match ($driver) {
|
||||||
|
null, 'sqlite' => [
|
||||||
|
'driver' => 'pdo_sqlite',
|
||||||
|
'path' => 'data/database.sqlite',
|
||||||
],
|
],
|
||||||
'connection' => [
|
default => [
|
||||||
'user' => '',
|
'driver' => $resolveDriver(),
|
||||||
'password' => '',
|
'dbname' => EnvVars::DB_NAME->loadFromEnv('shlink'),
|
||||||
'dbname' => 'shlink',
|
'user' => EnvVars::DB_USER->loadFromEnv(),
|
||||||
'charset' => 'utf8',
|
'password' => EnvVars::DB_PASSWORD->loadFromEnv(),
|
||||||
|
'host' => EnvVars::DB_HOST->loadFromEnv(EnvVars::DB_UNIX_SOCKET->loadFromEnv()),
|
||||||
|
'port' => EnvVars::DB_PORT->loadFromEnv($resolveDefaultPort()),
|
||||||
|
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET->loadFromEnv() : null,
|
||||||
|
'charset' => $resolveCharset(),
|
||||||
|
'driverOptions' => $driver !== 'mssql' ? [] : [
|
||||||
|
'TrustServerCertificate' => 'true',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
};
|
||||||
|
|
||||||
];
|
return [
|
||||||
|
|
||||||
|
'entity_manager' => [
|
||||||
|
'orm' => [
|
||||||
|
'proxies_dir' => 'data/proxies',
|
||||||
|
'load_mappings_using_functional_style' => true,
|
||||||
|
'default_repository_classname' => EntitySpecificationRepository::class,
|
||||||
|
],
|
||||||
|
'connection' => $resolveConnection(),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -2,23 +2,44 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
// When running tests, any mysql-specific option can interfere with other drivers
|
|
||||||
$driverOptions = env('APP_ENV') === 'test' ? [] : [
|
|
||||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
|
||||||
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'entity_manager' => [
|
'entity_manager' => [
|
||||||
'connection' => [
|
'connection' => [
|
||||||
|
// MySQL
|
||||||
'user' => 'root',
|
'user' => 'root',
|
||||||
'password' => 'root',
|
'password' => 'root',
|
||||||
'driver' => 'pdo_mysql',
|
'driver' => 'pdo_mysql',
|
||||||
'host' => 'shlink_db',
|
'host' => 'shlink_db_mysql',
|
||||||
'driverOptions' => $driverOptions,
|
'dbname' => 'shlink',
|
||||||
|
// 'dbname' => 'shlink_foo',
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
|
||||||
|
// MariaDB
|
||||||
|
// 'user' => 'root',
|
||||||
|
// 'password' => 'root',
|
||||||
|
// 'driver' => 'pdo_mysql',
|
||||||
|
// 'host' => 'shlink_db_maria',
|
||||||
|
// 'dbname' => 'shlink_foo',
|
||||||
|
// 'charset' => 'utf8mb4',
|
||||||
|
|
||||||
|
// Postgres
|
||||||
|
// 'user' => 'postgres',
|
||||||
|
// 'password' => 'root',
|
||||||
|
// 'driver' => 'pdo_pgsql',
|
||||||
|
// 'host' => 'shlink_db_postgres',
|
||||||
|
// 'dbname' => 'shlink_foo',
|
||||||
|
// 'charset' => 'utf8',
|
||||||
|
|
||||||
|
// MSSQL
|
||||||
|
// 'user' => 'sa',
|
||||||
|
// 'password' => 'Passw0rd!',
|
||||||
|
// 'driver' => 'pdo_sqlsrv',
|
||||||
|
// 'host' => 'shlink_db_ms',
|
||||||
|
// 'dbname' => 'shlink_foo',
|
||||||
|
// 'driverOptions' => [
|
||||||
|
// 'TrustServerCertificate' => 'true',
|
||||||
|
// ],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ use Laminas\Stratigility\Middleware\ErrorHandler;
|
|||||||
use Mezzio\ProblemDetails\ProblemDetailsMiddleware;
|
use Mezzio\ProblemDetails\ProblemDetailsMiddleware;
|
||||||
use Shlinkio\Shlink\Common\Logger;
|
use Shlinkio\Shlink\Common\Logger;
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Core\toProblemDetailsType;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'problem-details' => [
|
'problem-details' => [
|
||||||
'default_types_map' => [
|
'default_types_map' => [
|
||||||
404 => 'NOT_FOUND',
|
404 => toProblemDetailsType('not-found'),
|
||||||
500 => 'INTERNAL_SERVER_ERROR',
|
500 => toProblemDetailsType('internal-server-error'),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'geolite2' => [
|
'geolite2' => [
|
||||||
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
||||||
'temp_dir' => __DIR__ . '/../../data',
|
'temp_dir' => __DIR__ . '/../../data',
|
||||||
'license_key' => 'G4Lm0C60yJsnkdPi', // Deprecated. Remove hardcoded license on v3
|
'license_key' => EnvVars::GEOLITE_LICENSE_KEY->loadFromEnv(),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI;
|
||||||
|
|
||||||
use Shlinkio\Shlink\Installer\Config\Option;
|
use Shlinkio\Shlink\Installer\Config\Option;
|
||||||
|
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'installer' => [
|
'installer' => [
|
||||||
'enabled_options' => [
|
'enabled_options' => [
|
||||||
|
Option\Server\RuntimeConfigOption::class,
|
||||||
Option\Database\DatabaseDriverConfigOption::class,
|
Option\Database\DatabaseDriverConfigOption::class,
|
||||||
Option\Database\DatabaseNameConfigOption::class,
|
Option\Database\DatabaseNameConfigOption::class,
|
||||||
Option\Database\DatabaseHostConfigOption::class,
|
Option\Database\DatabaseHostConfigOption::class,
|
||||||
@@ -15,39 +19,84 @@ return [
|
|||||||
Option\Database\DatabaseUserConfigOption::class,
|
Option\Database\DatabaseUserConfigOption::class,
|
||||||
Option\Database\DatabasePasswordConfigOption::class,
|
Option\Database\DatabasePasswordConfigOption::class,
|
||||||
Option\Database\DatabaseUnixSocketConfigOption::class,
|
Option\Database\DatabaseUnixSocketConfigOption::class,
|
||||||
Option\Database\DatabaseSqlitePathConfigOption::class,
|
|
||||||
Option\Database\DatabaseMySqlOptionsConfigOption::class,
|
|
||||||
Option\UrlShortener\ShortDomainHostConfigOption::class,
|
Option\UrlShortener\ShortDomainHostConfigOption::class,
|
||||||
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
|
Option\UrlShortener\ShortDomainSchemaConfigOption::class,
|
||||||
Option\UrlShortener\ValidateUrlConfigOption::class,
|
|
||||||
Option\Visit\VisitsWebhooksConfigOption::class,
|
Option\Visit\VisitsWebhooksConfigOption::class,
|
||||||
|
Option\Visit\OrphanVisitsWebhooksConfigOption::class,
|
||||||
Option\Redirect\BaseUrlRedirectConfigOption::class,
|
Option\Redirect\BaseUrlRedirectConfigOption::class,
|
||||||
Option\Redirect\InvalidShortUrlRedirectConfigOption::class,
|
Option\Redirect\InvalidShortUrlRedirectConfigOption::class,
|
||||||
Option\Redirect\Regular404RedirectConfigOption::class,
|
Option\Redirect\Regular404RedirectConfigOption::class,
|
||||||
Option\DisableTrackParamConfigOption::class,
|
|
||||||
Option\Visit\CheckVisitsThresholdConfigOption::class,
|
|
||||||
Option\Visit\VisitsThresholdConfigOption::class,
|
Option\Visit\VisitsThresholdConfigOption::class,
|
||||||
Option\BasePathConfigOption::class,
|
Option\BasePathConfigOption::class,
|
||||||
|
Option\TimezoneConfigOption::class,
|
||||||
|
Option\Cache\CacheNamespaceConfigOption::class,
|
||||||
Option\Worker\TaskWorkerNumConfigOption::class,
|
Option\Worker\TaskWorkerNumConfigOption::class,
|
||||||
Option\Worker\WebWorkerNumConfigOption::class,
|
Option\Worker\WebWorkerNumConfigOption::class,
|
||||||
Option\RedisServersConfigOption::class,
|
Option\Redis\RedisServersConfigOption::class,
|
||||||
|
Option\Redis\RedisDecodeCredentialsConfigOption::class,
|
||||||
|
Option\Redis\RedisSentinelServiceConfigOption::class,
|
||||||
|
Option\Redis\RedisPubSubConfigOption::class,
|
||||||
Option\UrlShortener\ShortCodeLengthOption::class,
|
Option\UrlShortener\ShortCodeLengthOption::class,
|
||||||
Option\Mercure\EnableMercureConfigOption::class,
|
Option\Mercure\EnableMercureConfigOption::class,
|
||||||
Option\Mercure\MercurePublicUrlConfigOption::class,
|
Option\Mercure\MercurePublicUrlConfigOption::class,
|
||||||
Option\Mercure\MercureInternalUrlConfigOption::class,
|
Option\Mercure\MercureInternalUrlConfigOption::class,
|
||||||
Option\Mercure\MercureJwtSecretConfigOption::class,
|
Option\Mercure\MercureJwtSecretConfigOption::class,
|
||||||
Option\UrlShortener\GeoLiteLicenseKeyConfigOption::class,
|
Option\UrlShortener\GeoLiteLicenseKeyConfigOption::class,
|
||||||
Option\UrlShortener\IpAnonymizationConfigOption::class,
|
|
||||||
Option\UrlShortener\RedirectStatusCodeConfigOption::class,
|
Option\UrlShortener\RedirectStatusCodeConfigOption::class,
|
||||||
Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class,
|
Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class,
|
||||||
|
Option\UrlShortener\AutoResolveTitlesConfigOption::class,
|
||||||
|
Option\UrlShortener\AppendExtraPathConfigOption::class,
|
||||||
|
Option\UrlShortener\EnableMultiSegmentSlugsConfigOption::class,
|
||||||
|
Option\UrlShortener\EnableTrailingSlashConfigOption::class,
|
||||||
|
Option\UrlShortener\ShortUrlModeConfigOption::class,
|
||||||
|
Option\Tracking\IpAnonymizationConfigOption::class,
|
||||||
|
Option\Tracking\OrphanVisitsTrackingConfigOption::class,
|
||||||
|
Option\Tracking\DisableTrackParamConfigOption::class,
|
||||||
|
Option\Tracking\DisableTrackingFromConfigOption::class,
|
||||||
|
Option\Tracking\DisableTrackingConfigOption::class,
|
||||||
|
Option\Tracking\DisableIpTrackingConfigOption::class,
|
||||||
|
Option\Tracking\DisableReferrerTrackingConfigOption::class,
|
||||||
|
Option\Tracking\DisableUaTrackingConfigOption::class,
|
||||||
|
Option\QrCode\DefaultSizeConfigOption::class,
|
||||||
|
Option\QrCode\DefaultMarginConfigOption::class,
|
||||||
|
Option\QrCode\DefaultFormatConfigOption::class,
|
||||||
|
Option\QrCode\DefaultErrorCorrectionConfigOption::class,
|
||||||
|
Option\QrCode\DefaultRoundBlockSizeConfigOption::class,
|
||||||
|
Option\QrCode\EnabledForDisabledShortUrlsConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqEnabledConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqHostConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqUseSslConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqPortConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqUserConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqPasswordConfigOption::class,
|
||||||
|
Option\RabbitMq\RabbitMqVhostConfigOption::class,
|
||||||
|
Option\Matomo\MatomoEnabledConfigOption::class,
|
||||||
|
Option\Matomo\MatomoBaseUrlConfigOption::class,
|
||||||
|
Option\Matomo\MatomoSiteIdConfigOption::class,
|
||||||
|
Option\Matomo\MatomoApiTokenConfigOption::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'installation_commands' => [
|
'installation_commands' => [
|
||||||
'db_create_schema' => [
|
InstallationCommand::DB_CREATE_SCHEMA->value => [
|
||||||
'command' => 'bin/cli db:create',
|
'command' => 'bin/cli ' . Command\Db\CreateDatabaseCommand::NAME,
|
||||||
],
|
],
|
||||||
'db_migrate' => [
|
InstallationCommand::DB_MIGRATE->value => [
|
||||||
'command' => 'bin/cli db:migrate',
|
'command' => 'bin/cli ' . Command\Db\MigrateDatabaseCommand::NAME,
|
||||||
|
],
|
||||||
|
InstallationCommand::ORM_PROXIES->value => [
|
||||||
|
'command' => 'bin/doctrine orm:generate-proxies',
|
||||||
|
],
|
||||||
|
InstallationCommand::ORM_CLEAR_CACHE->value => [
|
||||||
|
'command' => 'bin/doctrine orm:clear-cache:metadata',
|
||||||
|
],
|
||||||
|
InstallationCommand::GEOLITE_DOWNLOAD_DB->value => [
|
||||||
|
'command' => 'bin/cli ' . Command\Visit\DownloadGeoLiteDbCommand::NAME,
|
||||||
|
],
|
||||||
|
InstallationCommand::API_KEY_GENERATE->value => [
|
||||||
|
'command' => 'bin/cli ' . Command\Api\GenerateKeyCommand::NAME,
|
||||||
|
],
|
||||||
|
InstallationCommand::API_KEY_CREATE->value => [
|
||||||
|
'command' => 'bin/cli ' . Command\Api\InitialApiKeyCommand::NAME,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||||
use Shlinkio\Shlink\Common\Lock\RetryLockStoreDelegatorFactory;
|
use Shlinkio\Shlink\Common\Lock\NamespacedStore;
|
||||||
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Symfony\Component\Lock;
|
use Symfony\Component\Lock;
|
||||||
|
|
||||||
use const Shlinkio\Shlink\Core\LOCAL_LOCK_FACTORY;
|
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
@@ -22,18 +23,15 @@ return [
|
|||||||
Lock\Store\RedisStore::class => ConfigAbstractFactory::class,
|
Lock\Store\RedisStore::class => ConfigAbstractFactory::class,
|
||||||
Lock\LockFactory::class => ConfigAbstractFactory::class,
|
Lock\LockFactory::class => ConfigAbstractFactory::class,
|
||||||
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
||||||
|
NamespacedStore::class => ConfigAbstractFactory::class,
|
||||||
],
|
],
|
||||||
'aliases' => [
|
'aliases' => [
|
||||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
'lock_store' => EnvVars::REDIS_SERVERS->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
|
||||||
'lock_store' => 'local_lock_store',
|
|
||||||
|
|
||||||
'redis_lock_store' => Lock\Store\RedisStore::class,
|
'redis_lock_store' => NamespacedStore::class,
|
||||||
'local_lock_store' => Lock\Store\FlockStore::class,
|
'local_lock_store' => Lock\Store\FlockStore::class,
|
||||||
],
|
],
|
||||||
'delegators' => [
|
'delegators' => [
|
||||||
Lock\Store\RedisStore::class => [
|
|
||||||
RetryLockStoreDelegatorFactory::class,
|
|
||||||
],
|
|
||||||
Lock\LockFactory::class => [
|
Lock\LockFactory::class => [
|
||||||
LoggerAwareDelegatorFactory::class,
|
LoggerAwareDelegatorFactory::class,
|
||||||
],
|
],
|
||||||
@@ -43,6 +41,8 @@ return [
|
|||||||
ConfigAbstractFactory::class => [
|
ConfigAbstractFactory::class => [
|
||||||
Lock\Store\FlockStore::class => ['config.locks.locks_dir'],
|
Lock\Store\FlockStore::class => ['config.locks.locks_dir'],
|
||||||
Lock\Store\RedisStore::class => [RedisFactory::SERVICE_NAME],
|
Lock\Store\RedisStore::class => [RedisFactory::SERVICE_NAME],
|
||||||
|
NamespacedStore::class => [Lock\Store\RedisStore::class, 'config.cache.namespace'],
|
||||||
|
|
||||||
Lock\LockFactory::class => ['lock_store'],
|
Lock\LockFactory::class => ['lock_store'],
|
||||||
LOCAL_LOCK_FACTORY => ['local_lock_store'],
|
LOCAL_LOCK_FACTORY => ['local_lock_store'],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,87 +4,64 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Monolog\Formatter;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
use Monolog\Handler;
|
use Monolog\Level;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Monolog\Processor;
|
|
||||||
use MonologFactory\DiContainerLoggerFactory;
|
|
||||||
use PhpMiddleware\RequestId;
|
use PhpMiddleware\RequestId;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerFactory;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\AccessLogMiddleware;
|
||||||
|
|
||||||
use const PHP_EOL;
|
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||||
|
|
||||||
$processors = [
|
return (static function (): array {
|
||||||
'exception_with_new_line' => [
|
$common = [
|
||||||
'name' => Common\Logger\Processor\ExceptionWithNewLineProcessor::class,
|
'level' => Level::Info->value,
|
||||||
],
|
'processors' => [RequestId\MonologProcessor::class],
|
||||||
'psr3' => [
|
'line_format' => '[%datetime%] [%extra.request_id%] %channel%.%level_name% - %message%',
|
||||||
'name' => Processor\PsrLogMessageProcessor::class,
|
];
|
||||||
],
|
|
||||||
'request_id' => RequestId\MonologProcessor::class,
|
|
||||||
];
|
|
||||||
$formatter = [
|
|
||||||
'name' => Formatter\LineFormatter::class,
|
|
||||||
'params' => [
|
|
||||||
'format' => '[%datetime%] [%extra.request_id%] %channel%.%level_name% - %message%' . PHP_EOL,
|
|
||||||
'allow_inline_line_breaks' => true,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => [
|
'Shlink' => [
|
||||||
'name' => 'Shlink',
|
'type' => LoggerType::FILE->value,
|
||||||
'handlers' => [
|
...$common,
|
||||||
'shlink_handler' => [
|
],
|
||||||
'name' => Handler\RotatingFileHandler::class,
|
'Access' => [
|
||||||
'params' => [
|
'type' => LoggerType::STREAM->value,
|
||||||
'level' => Logger::INFO,
|
'destination' => 'php://stderr',
|
||||||
'filename' => 'data/log/shlink_log.log',
|
'add_new_line' => ! runningInRoadRunner(),
|
||||||
'max_files' => 30,
|
...$common,
|
||||||
'file_permission' => 0666,
|
],
|
||||||
],
|
],
|
||||||
'formatter' => $formatter,
|
|
||||||
|
'dependencies' => [
|
||||||
|
'factories' => [
|
||||||
|
'Logger_Shlink' => [LoggerFactory::class, 'Shlink'],
|
||||||
|
'Logger_Access' => [LoggerFactory::class, 'Access'],
|
||||||
|
NullLogger::class => InvokableFactory::class,
|
||||||
|
],
|
||||||
|
'aliases' => [
|
||||||
|
'logger' => 'Logger_Shlink',
|
||||||
|
Logger::class => 'Logger_Shlink',
|
||||||
|
LoggerInterface::class => 'Logger_Shlink',
|
||||||
|
AccessLogMiddleware::LOGGER_SERVICE_NAME => 'Logger_Access',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// Deprecated. Remove in Shlink 4.0.0
|
||||||
|
'mezzio-swoole' => [
|
||||||
|
'swoole-http-server' => [
|
||||||
|
'logger' => [
|
||||||
|
// Let's disable mezio-swoole access logging, so that we can provide our own implementation,
|
||||||
|
// consistent for roadrunner and openswoole
|
||||||
|
'logger-name' => NullLogger::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'processors' => $processors,
|
|
||||||
],
|
],
|
||||||
'Access' => [
|
|
||||||
'name' => 'Access',
|
|
||||||
'handlers' => [
|
|
||||||
'access_handler' => [
|
|
||||||
'name' => Handler\StreamHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::INFO,
|
|
||||||
'stream' => 'php://stdout',
|
|
||||||
],
|
|
||||||
'formatter' => $formatter,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'processors' => $processors,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
];
|
||||||
'factories' => [
|
})();
|
||||||
'Logger_Shlink' => [DiContainerLoggerFactory::class, 'Shlink'],
|
|
||||||
'Logger_Access' => [DiContainerLoggerFactory::class, 'Access'],
|
|
||||||
],
|
|
||||||
'aliases' => [
|
|
||||||
'logger' => 'Logger_Shlink',
|
|
||||||
Logger::class => 'Logger_Shlink',
|
|
||||||
LoggerInterface::class => 'Logger_Shlink',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
|
||||||
'swoole-http-server' => [
|
|
||||||
'logger' => [
|
|
||||||
'logger-name' => 'Logger_Access',
|
|
||||||
'format' => '%h %l %u "%r" %>s %b',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -2,33 +2,16 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Monolog\Handler\StreamHandler;
|
use Monolog\Level;
|
||||||
use Monolog\Logger;
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
|
|
||||||
$isSwoole = extension_loaded('swoole');
|
|
||||||
|
|
||||||
// For swoole, send logs to standard output
|
|
||||||
$handler = $isSwoole
|
|
||||||
? [
|
|
||||||
'name' => StreamHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::DEBUG,
|
|
||||||
'stream' => 'php://stdout',
|
|
||||||
],
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::DEBUG,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => [
|
'Shlink' => [
|
||||||
'handlers' => [
|
'type' => LoggerType::STREAM->value,
|
||||||
'shlink_handler' => $handler,
|
'destination' => 'php://stderr',
|
||||||
],
|
'level' => Level::Debug->value,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
16
config/autoload/matomo.global.php
Normal file
16
config/autoload/matomo.global.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'matomo' => [
|
||||||
|
'enabled' => (bool) EnvVars::MATOMO_ENABLED->loadFromEnv(false),
|
||||||
|
'base_url' => EnvVars::MATOMO_BASE_URL->loadFromEnv(),
|
||||||
|
'site_id' => EnvVars::MATOMO_SITE_ID->loadFromEnv(),
|
||||||
|
'api_token' => EnvVars::MATOMO_API_TOKEN->loadFromEnv(),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
26
config/autoload/matomo.local.php.dist
Normal file
26
config/autoload/matomo.local.php.dist
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dev matomo instance needs to be manually configured once before enabling the configuration below.
|
||||||
|
*
|
||||||
|
* 1. Go to http://localhost:8003 and follow the installation instructions.
|
||||||
|
* 2. Open data/infra/matomo/config/config.ini.php and replace `trusted_hosts[] = "localhost"` with
|
||||||
|
* `trusted_hosts[] = "localhost:8003"` (see https://github.com/matomo-org/matomo/issues/9549)
|
||||||
|
* 3. Go to http://localhost:8003/index.php?module=SitesManager&action=index and paste the ID for the site you just
|
||||||
|
* created into the `site_id` field below.
|
||||||
|
* 4. Go to http://localhost:8003/index.php?module=UsersManager&action=userSecurity, scroll down, click
|
||||||
|
* "Create new token" and once generated, paste the token into the `api_token` field below.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'matomo' => [
|
||||||
|
// 'enabled' => true,
|
||||||
|
// 'base_url' => 'http://shlink_matomo',
|
||||||
|
// 'site_id' => '...',
|
||||||
|
// 'api_token' => '...',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -4,33 +4,38 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
|
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
|
||||||
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
|
use Shlinkio\Shlink\Common\Mercure\LcobucciJwtProvider;
|
||||||
use Symfony\Component\Mercure\Publisher;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Symfony\Component\Mercure\PublisherInterface;
|
use Symfony\Component\Mercure\Hub;
|
||||||
|
use Symfony\Component\Mercure\HubInterface;
|
||||||
|
|
||||||
return [
|
return (static function (): array {
|
||||||
|
$publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL->loadFromEnv();
|
||||||
|
|
||||||
'mercure' => [
|
return [
|
||||||
'public_hub_url' => null,
|
|
||||||
'internal_hub_url' => null,
|
|
||||||
'jwt_secret' => null,
|
|
||||||
'jwt_issuer' => 'Shlink',
|
|
||||||
],
|
|
||||||
|
|
||||||
'dependencies' => [
|
'mercure' => [
|
||||||
'delegators' => [
|
'public_hub_url' => $publicUrl,
|
||||||
LcobucciJwtProvider::class => [
|
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL->loadFromEnv($publicUrl),
|
||||||
LazyServiceFactory::class,
|
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET->loadFromEnv(),
|
||||||
|
'jwt_issuer' => 'Shlink',
|
||||||
|
],
|
||||||
|
|
||||||
|
'dependencies' => [
|
||||||
|
'delegators' => [
|
||||||
|
LcobucciJwtProvider::class => [
|
||||||
|
LazyServiceFactory::class,
|
||||||
|
],
|
||||||
|
Hub::class => [
|
||||||
|
LazyServiceFactory::class,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
Publisher::class => [
|
'lazy_services' => [
|
||||||
LazyServiceFactory::class,
|
'class_map' => [
|
||||||
|
LcobucciJwtProvider::class => LcobucciJwtProvider::class,
|
||||||
|
Hub::class => HubInterface::class,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'lazy_services' => [
|
|
||||||
'class_map' => [
|
|
||||||
LcobucciJwtProvider::class => LcobucciJwtProvider::class,
|
|
||||||
Publisher::class => PublisherInterface::class,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ return [
|
|||||||
'mercure' => [
|
'mercure' => [
|
||||||
'public_hub_url' => 'http://localhost:8001',
|
'public_hub_url' => 'http://localhost:8001',
|
||||||
'internal_hub_url' => 'http://shlink_mercure_proxy',
|
'internal_hub_url' => 'http://shlink_mercure_proxy',
|
||||||
'jwt_secret' => 'mercure_jwt_key',
|
'jwt_secret' => 'mercure_jwt_key_long_enough_to_avoid_error',
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -5,25 +5,28 @@ declare(strict_types=1);
|
|||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Laminas\Stratigility\Middleware\ErrorHandler;
|
use Laminas\Stratigility\Middleware\ErrorHandler;
|
||||||
use Mezzio\Helper;
|
|
||||||
use Mezzio\ProblemDetails;
|
use Mezzio\ProblemDetails;
|
||||||
use Mezzio\Router;
|
use Mezzio\Router;
|
||||||
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
use PhpMiddleware\RequestId\RequestIdMiddleware;
|
||||||
|
use RKA\Middleware\IpAddress;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\AccessLogMiddleware;
|
||||||
|
use Shlinkio\Shlink\Common\Middleware\ContentLengthMiddleware;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'middleware_pipeline' => [
|
'middleware_pipeline' => [
|
||||||
'error-handler' => [
|
'error-handler' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Helper\ContentLengthMiddleware::class,
|
AccessLogMiddleware::class,
|
||||||
|
ContentLengthMiddleware::class,
|
||||||
|
RequestIdMiddleware::class,
|
||||||
ErrorHandler::class,
|
ErrorHandler::class,
|
||||||
|
Rest\Middleware\CrossDomainMiddleware::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'error-handler-rest' => [
|
'error-handler-rest' => [
|
||||||
'path' => '/rest',
|
'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
Rest\Middleware\CrossDomainMiddleware::class,
|
|
||||||
RequestIdMiddleware::class,
|
|
||||||
ProblemDetails\ProblemDetailsMiddleware::class,
|
ProblemDetails\ProblemDetailsMiddleware::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -44,6 +47,7 @@ return [
|
|||||||
'rest' => [
|
'rest' => [
|
||||||
'path' => '/rest',
|
'path' => '/rest',
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
|
Rest\Middleware\ErrorHandler\BackwardsCompatibleProblemDetailsHandler::class,
|
||||||
Router\Middleware\ImplicitOptionsMiddleware::class,
|
Router\Middleware\ImplicitOptionsMiddleware::class,
|
||||||
Rest\Middleware\BodyParserMiddleware::class,
|
Rest\Middleware\BodyParserMiddleware::class,
|
||||||
Rest\Middleware\AuthenticationMiddleware::class,
|
Rest\Middleware\AuthenticationMiddleware::class,
|
||||||
@@ -64,6 +68,11 @@ return [
|
|||||||
],
|
],
|
||||||
'not-found' => [
|
'not-found' => [
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
|
// This middleware is in front of tracking actions explicitly. Putting here for orphan visits tracking
|
||||||
|
IpAddress::class,
|
||||||
|
Core\ErrorHandler\NotFoundTypeResolverMiddleware::class,
|
||||||
|
Core\ShortUrl\Middleware\ExtraPathRedirectMiddleware::class,
|
||||||
|
Core\ErrorHandler\NotFoundTrackerMiddleware::class,
|
||||||
Core\ErrorHandler\NotFoundRedirectHandler::class,
|
Core\ErrorHandler\NotFoundRedirectHandler::class,
|
||||||
Core\ErrorHandler\NotFoundTemplateHandler::class,
|
Core\ErrorHandler\NotFoundTemplateHandler::class,
|
||||||
],
|
],
|
||||||
|
|||||||
31
config/autoload/qr-codes.global.php
Normal file
31
config/autoload/qr-codes.global.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ERROR_CORRECTION;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_FORMAT;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_MARGIN;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_ROUND_BLOCK_SIZE;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_QR_CODE_SIZE;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'qr_codes' => [
|
||||||
|
'size' => (int) EnvVars::DEFAULT_QR_CODE_SIZE->loadFromEnv(DEFAULT_QR_CODE_SIZE),
|
||||||
|
'margin' => (int) EnvVars::DEFAULT_QR_CODE_MARGIN->loadFromEnv(DEFAULT_QR_CODE_MARGIN),
|
||||||
|
'format' => EnvVars::DEFAULT_QR_CODE_FORMAT->loadFromEnv(DEFAULT_QR_CODE_FORMAT),
|
||||||
|
'error_correction' => EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION->loadFromEnv(
|
||||||
|
DEFAULT_QR_CODE_ERROR_CORRECTION,
|
||||||
|
),
|
||||||
|
'round_block_size' => (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE->loadFromEnv(
|
||||||
|
DEFAULT_QR_CODE_ROUND_BLOCK_SIZE,
|
||||||
|
),
|
||||||
|
'enabled_for_disabled_short_urls' => (bool) EnvVars::QR_CODE_FOR_DISABLED_SHORT_URLS->loadFromEnv(
|
||||||
|
DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
22
config/autoload/rabbit.global.php
Normal file
22
config/autoload/rabbit.global.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'rabbitmq' => [
|
||||||
|
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED->loadFromEnv(false),
|
||||||
|
'host' => EnvVars::RABBITMQ_HOST->loadFromEnv(),
|
||||||
|
'use_ssl' => (bool) EnvVars::RABBITMQ_USE_SSL->loadFromEnv(false),
|
||||||
|
'port' => (int) EnvVars::RABBITMQ_PORT->loadFromEnv('5672'),
|
||||||
|
'user' => EnvVars::RABBITMQ_USER->loadFromEnv(),
|
||||||
|
'password' => EnvVars::RABBITMQ_PASSWORD->loadFromEnv(),
|
||||||
|
'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv('/'),
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
'legacy_visits_publishing' => (bool) EnvVars::RABBITMQ_LEGACY_VISITS_PUBLISHING->loadFromEnv(false),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
14
config/autoload/rabbit.local.php.dist
Normal file
14
config/autoload/rabbit.local.php.dist
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'rabbitmq' => [
|
||||||
|
'enabled' => true,
|
||||||
|
'host' => 'shlink_rabbitmq',
|
||||||
|
'user' => 'rabbit',
|
||||||
|
'password' => 'rabbit',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -2,12 +2,24 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
||||||
|
use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'not_found_redirects' => [
|
'not_found_redirects' => [
|
||||||
'invalid_short_url' => null,
|
'invalid_short_url' => EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT->loadFromEnv(),
|
||||||
'regular_404' => null,
|
'regular_404' => EnvVars::DEFAULT_REGULAR_404_REDIRECT->loadFromEnv(),
|
||||||
'base_url' => null,
|
'base_url' => EnvVars::DEFAULT_BASE_URL_REDIRECT->loadFromEnv(),
|
||||||
|
],
|
||||||
|
|
||||||
|
'redirects' => [
|
||||||
|
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE->value),
|
||||||
|
'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME->loadFromEnv(
|
||||||
|
DEFAULT_REDIRECT_CACHE_LIFETIME,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ return [
|
|||||||
'cache' => [
|
'cache' => [
|
||||||
'redis' => [
|
'redis' => [
|
||||||
'servers' => 'tcp://shlink_redis:6379',
|
'servers' => 'tcp://shlink_redis:6379',
|
||||||
// 'servers' => [
|
// 'servers' => 'tcp://barbar@shlink_redis_acl:6379',
|
||||||
// 'tcp://shlink_redis:6379',
|
// 'servers' => 'tcp://foo:bar@shlink_redis_acl:6379',
|
||||||
// ],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
'pub_sub_enabled' => true,
|
||||||
|
],
|
||||||
|
|
||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
'aliases' => [
|
'aliases' => [
|
||||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
use PhpMiddleware\RequestId;
|
use PhpMiddleware\RequestId;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\Processor\BackwardsCompatibleMonologProcessorDelegator;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
@@ -20,6 +21,11 @@ return [
|
|||||||
RequestId\RequestIdMiddleware::class => ConfigAbstractFactory::class,
|
RequestId\RequestIdMiddleware::class => ConfigAbstractFactory::class,
|
||||||
RequestId\MonologProcessor::class => ConfigAbstractFactory::class,
|
RequestId\MonologProcessor::class => ConfigAbstractFactory::class,
|
||||||
],
|
],
|
||||||
|
'delegators' => [
|
||||||
|
RequestId\MonologProcessor::class => [
|
||||||
|
BackwardsCompatibleMonologProcessorDelegator::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
ConfigAbstractFactory::class => [
|
ConfigAbstractFactory::class => [
|
||||||
|
|||||||
@@ -3,14 +3,17 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Mezzio\Router\FastRouteRouter;
|
use Mezzio\Router\FastRouteRouter;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'router' => [
|
'router' => [
|
||||||
'base_path' => '',
|
'base_path' => EnvVars::BASE_PATH->loadFromEnv(''),
|
||||||
|
|
||||||
'fastroute' => [
|
'fastroute' => [
|
||||||
FastRouteRouter::CONFIG_CACHE_ENABLED => true,
|
// Disabling config cache for cli, ensures it's never used for openswoole/RoadRunner, and also that console
|
||||||
|
// commands don't generate a cache file that's then used by php-fpm web executions
|
||||||
|
FastRouteRouter::CONFIG_CACHE_ENABLED => PHP_SAPI !== 'cli',
|
||||||
FastRouteRouter::CONFIG_CACHE_FILE => 'data/cache/fastroute_cached_routes.php',
|
FastRouteRouter::CONFIG_CACHE_FILE => 'data/cache/fastroute_cached_routes.php',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
115
config/autoload/routes.config.php
Normal file
115
config/autoload/routes.config.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
|
use Fig\Http\Message\RequestMethodInterface;
|
||||||
|
use RKA\Middleware\IpAddress;
|
||||||
|
use Shlinkio\Shlink\Core\Action as CoreAction;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Middleware\TrimTrailingSlashMiddleware;
|
||||||
|
use Shlinkio\Shlink\Rest\Action;
|
||||||
|
use Shlinkio\Shlink\Rest\ConfigProvider;
|
||||||
|
use Shlinkio\Shlink\Rest\Middleware;
|
||||||
|
use Shlinkio\Shlink\Rest\Middleware\Mercure\NotConfiguredMercureErrorHandler;
|
||||||
|
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
|
return (static function (): array {
|
||||||
|
$contentNegotiationMiddleware = Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class;
|
||||||
|
$dropDomainMiddleware = Middleware\ShortUrl\DropDefaultDomainFromRequestMiddleware::class;
|
||||||
|
$overrideDomainMiddleware = Middleware\ShortUrl\OverrideDomainMiddleware::class;
|
||||||
|
|
||||||
|
// TODO This should be based on config, not the env var
|
||||||
|
$shortUrlRouteSuffix = EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(false) ? '[/]' : '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
// The order of the routes defined here matters. Changing it might cause path conflicts
|
||||||
|
'routes' => [
|
||||||
|
// Rest
|
||||||
|
...ConfigProvider::applyRoutesPrefix([
|
||||||
|
Action\HealthAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Visits.
|
||||||
|
// These routes must go first, as they have a more specific path, otherwise, when multi-segment slugs
|
||||||
|
// are enabled, routes with a less-specific path might match first
|
||||||
|
Action\Visit\ShortUrlVisitsAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\DeleteShortUrlVisitsAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\Visit\TagVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\DomainVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\GlobalVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\OrphanVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\DeleteOrphanVisitsAction::getRouteDef(),
|
||||||
|
Action\Visit\NonOrphanVisitsAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Short URLs
|
||||||
|
Action\ShortUrl\CreateShortUrlAction::getRouteDef([
|
||||||
|
$contentNegotiationMiddleware,
|
||||||
|
$dropDomainMiddleware,
|
||||||
|
$overrideDomainMiddleware,
|
||||||
|
Middleware\ShortUrl\DefaultShortCodesLengthMiddleware::class,
|
||||||
|
]),
|
||||||
|
Action\ShortUrl\SingleStepCreateShortUrlAction::getRouteDef([
|
||||||
|
$contentNegotiationMiddleware,
|
||||||
|
$overrideDomainMiddleware,
|
||||||
|
]),
|
||||||
|
Action\ShortUrl\EditShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\DeleteShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\ResolveShortUrlAction::getRouteDef([$dropDomainMiddleware]),
|
||||||
|
Action\ShortUrl\ListShortUrlsAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
Action\Tag\ListTagsAction::getRouteDef(),
|
||||||
|
Action\Tag\TagsStatsAction::getRouteDef(),
|
||||||
|
Action\Tag\DeleteTagsAction::getRouteDef(),
|
||||||
|
Action\Tag\UpdateTagAction::getRouteDef(),
|
||||||
|
|
||||||
|
// Domains
|
||||||
|
Action\Domain\ListDomainsAction::getRouteDef(),
|
||||||
|
Action\Domain\DomainRedirectsAction::getRouteDef(),
|
||||||
|
|
||||||
|
Action\MercureInfoAction::getRouteDef([NotConfiguredMercureErrorHandler::class]),
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Non-rest
|
||||||
|
[
|
||||||
|
'name' => CoreAction\RobotsAction::class,
|
||||||
|
'path' => '/robots.txt',
|
||||||
|
'middleware' => [
|
||||||
|
CoreAction\RobotsAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\PixelAction::class,
|
||||||
|
'path' => '/{shortCode}/track',
|
||||||
|
'middleware' => [
|
||||||
|
IpAddress::class,
|
||||||
|
CoreAction\PixelAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\QrCodeAction::class,
|
||||||
|
'path' => '/{shortCode}/qr-code',
|
||||||
|
'middleware' => [
|
||||||
|
CoreAction\QrCodeAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => CoreAction\RedirectAction::class,
|
||||||
|
'path' => sprintf('/{shortCode}%s', $shortUrlRouteSuffix),
|
||||||
|
'middleware' => [
|
||||||
|
IpAddress::class,
|
||||||
|
TrimTrailingSlashMiddleware::class,
|
||||||
|
CoreAction\RedirectAction::class,
|
||||||
|
],
|
||||||
|
'allowed_methods' => [RequestMethodInterface::METHOD_GET],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -2,21 +2,33 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
use function Shlinkio\Shlink\Config\getOpenswooleConfigFromEnv;
|
||||||
// Setting this to true can have unexpected behaviors when running several concurrent slow DB queries
|
|
||||||
'enable_coroutine' => false,
|
|
||||||
|
|
||||||
'swoole-http-server' => [
|
use const Shlinkio\Shlink\MIN_TASK_WORKERS;
|
||||||
'host' => '0.0.0.0',
|
|
||||||
'process-name' => 'shlink',
|
|
||||||
|
|
||||||
'options' => [
|
return (static function (): array {
|
||||||
'worker_num' => 16,
|
$taskWorkers = (int) EnvVars::TASK_WORKER_NUM->loadFromEnv(16);
|
||||||
'task_worker_num' => 16,
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'mezzio-swoole' => [
|
||||||
|
// Setting this to true can have unexpected behaviors when running several concurrent slow DB queries
|
||||||
|
'enable_coroutine' => false,
|
||||||
|
|
||||||
|
'swoole-http-server' => [
|
||||||
|
'host' => '0.0.0.0',
|
||||||
|
'port' => (int) EnvVars::PORT->loadFromEnv(8080),
|
||||||
|
'process-name' => 'shlink',
|
||||||
|
|
||||||
|
'options' => [
|
||||||
|
...getOpenswooleConfigFromEnv(),
|
||||||
|
'worker_num' => (int) EnvVars::WEB_WORKER_NUM->loadFromEnv(16),
|
||||||
|
'task_worker_num' => max($taskWorkers, MIN_TASK_WORKERS),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
})();
|
||||||
|
|||||||
43
config/autoload/tracking.global.php
Normal file
43
config/autoload/tracking.global.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
return (static function (): array {
|
||||||
|
/** @var string|null $disableTrackingFrom */
|
||||||
|
$disableTrackingFrom = EnvVars::DISABLE_TRACKING_FROM->loadFromEnv();
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'tracking' => [
|
||||||
|
// Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
|
||||||
|
// This applies only if IP address tracking is enabled
|
||||||
|
'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR->loadFromEnv(true),
|
||||||
|
|
||||||
|
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
|
||||||
|
'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS->loadFromEnv(true),
|
||||||
|
|
||||||
|
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
|
||||||
|
'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM->loadFromEnv(),
|
||||||
|
|
||||||
|
// If true, visits will not be tracked at all
|
||||||
|
'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING->loadFromEnv(false),
|
||||||
|
|
||||||
|
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
|
||||||
|
'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING->loadFromEnv(false),
|
||||||
|
|
||||||
|
// If true, the referrer will not be tracked
|
||||||
|
'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING->loadFromEnv(false),
|
||||||
|
|
||||||
|
// If true, the user agent will not be tracked
|
||||||
|
'disable_ua_tracking' => (bool) EnvVars::DISABLE_UA_TRACKING->loadFromEnv(false),
|
||||||
|
|
||||||
|
// A list of IP addresses, patterns or CIDR blocks from which tracking is disabled by default
|
||||||
|
'disable_tracking_from' => $disableTrackingFrom === null
|
||||||
|
? []
|
||||||
|
: array_map(trim(...), explode(',', $disableTrackingFrom)),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
@@ -2,23 +2,34 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_STATUS_CODE;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode;
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_SHORT_CODES_LENGTH;
|
|
||||||
|
|
||||||
return [
|
use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
|
||||||
|
use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
|
||||||
|
|
||||||
'url_shortener' => [
|
return (static function (): array {
|
||||||
'domain' => [
|
$shortCodesLength = max(
|
||||||
'schema' => 'https',
|
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
|
||||||
'hostname' => '',
|
MIN_SHORT_CODES_LENGTH,
|
||||||
|
);
|
||||||
|
$modeFromEnv = EnvVars::SHORT_URL_MODE->loadFromEnv(ShortUrlMode::STRICT->value);
|
||||||
|
$mode = ShortUrlMode::tryDeprecated($modeFromEnv) ?? ShortUrlMode::STRICT;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'url_shortener' => [
|
||||||
|
'domain' => [ // TODO Refactor this structure to url_shortener.schema and url_shortener.default_domain
|
||||||
|
'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv(true)) ? 'https' : 'http',
|
||||||
|
'hostname' => EnvVars::DEFAULT_DOMAIN->loadFromEnv(''),
|
||||||
|
],
|
||||||
|
'default_short_codes_length' => $shortCodesLength,
|
||||||
|
'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(false),
|
||||||
|
'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv(false),
|
||||||
|
'multi_segment_slugs_enabled' => (bool) EnvVars::MULTI_SEGMENT_SLUGS_ENABLED->loadFromEnv(false),
|
||||||
|
'trailing_slash_enabled' => (bool) EnvVars::SHORT_URL_TRAILING_SLASH->loadFromEnv(false),
|
||||||
|
'mode' => $mode,
|
||||||
],
|
],
|
||||||
'validate_url' => false,
|
|
||||||
'anonymize_remote_addr' => true,
|
|
||||||
'visits_webhooks' => [],
|
|
||||||
'default_short_codes_length' => DEFAULT_SHORT_CODES_LENGTH,
|
|
||||||
'redirect_status_code' => DEFAULT_REDIRECT_STATUS_CODE,
|
|
||||||
'redirect_cache_lifetime' => DEFAULT_REDIRECT_CACHE_LIFETIME,
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
})();
|
||||||
|
|||||||
@@ -2,13 +2,23 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Config\runningInOpenswoole;
|
||||||
|
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'url_shortener' => [
|
'url_shortener' => [
|
||||||
'domain' => [
|
'domain' => [
|
||||||
'schema' => 'http',
|
'schema' => 'http',
|
||||||
'hostname' => 'localhost:8080',
|
'hostname' => sprintf('localhost:%s', match (true) {
|
||||||
|
runningInRoadRunner() => '8800',
|
||||||
|
runningInOpenswoole() => '8080',
|
||||||
|
default => '8000',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
|
'auto_resolve_titles' => true,
|
||||||
|
// 'multi_segment_slugs_enabled' => true,
|
||||||
|
// 'trailing_slash_enabled' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
20
config/autoload/webhooks.global.php
Normal file
20
config/autoload/webhooks.global.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
|
|
||||||
|
// Deprecated. Webhooks are no longer supported. To be removed in Shlink 4.0.0
|
||||||
|
return (static function (): array {
|
||||||
|
$webhooks = EnvVars::VISITS_WEBHOOKS->loadFromEnv();
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'visits_webhooks' => [
|
||||||
|
'webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
|
||||||
|
'notify_orphan_visits_to_webhooks' =>
|
||||||
|
(bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS->loadFromEnv(false),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
})();
|
||||||
12
config/cli-app.php
Normal file
12
config/cli-app.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Symfony\Component\Console\Application as CliApp;
|
||||||
|
|
||||||
|
return (static function (): CliApp {
|
||||||
|
/** @var ContainerInterface $container */
|
||||||
|
$container = include __DIR__ . '/container.php';
|
||||||
|
return $container->get(CliApp::class);
|
||||||
|
})();
|
||||||
@@ -2,14 +2,26 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\Migrations\Configuration\EntityManager\ExistingEntityManager;
|
||||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
use Doctrine\Migrations\Configuration\Migration\ConfigurationArray;
|
||||||
use Psr\Container\ContainerInterface;
|
use Doctrine\Migrations\DependencyFactory;
|
||||||
|
|
||||||
return (function () {
|
// This file is currently used by doctrine migrations only
|
||||||
/** @var ContainerInterface $container */
|
|
||||||
$container = include __DIR__ . '/container.php';
|
|
||||||
$em = $container->get(EntityManager::class);
|
|
||||||
|
|
||||||
return ConsoleRunner::createHelperSet($em);
|
return (static function () {
|
||||||
|
$migrationsConfig = [
|
||||||
|
'migrations_paths' => [
|
||||||
|
'ShlinkMigrations' => 'module/Core/migrations',
|
||||||
|
],
|
||||||
|
'table_storage' => [
|
||||||
|
'table_name' => 'migrations',
|
||||||
|
],
|
||||||
|
'custom_template' => 'data/migrations_template.txt',
|
||||||
|
];
|
||||||
|
$em = include __DIR__ . '/entity-manager.php';
|
||||||
|
|
||||||
|
return DependencyFactory::fromEntityManager(
|
||||||
|
new ConfigurationArray($migrationsConfig),
|
||||||
|
new ExistingEntityManager($em),
|
||||||
|
);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -8,30 +8,53 @@ use Laminas\ConfigAggregator;
|
|||||||
use Laminas\Diactoros;
|
use Laminas\Diactoros;
|
||||||
use Mezzio;
|
use Mezzio;
|
||||||
use Mezzio\ProblemDetails;
|
use Mezzio\ProblemDetails;
|
||||||
|
use Mezzio\Swoole;
|
||||||
|
use Shlinkio\Shlink\Config\ConfigAggregator\EnvVarLoaderProvider;
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use function class_exists;
|
||||||
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
use function Shlinkio\Shlink\Config\openswooleIsInstalled;
|
||||||
|
use function Shlinkio\Shlink\Config\runningInRoadRunner;
|
||||||
|
use function Shlinkio\Shlink\Core\enumValues;
|
||||||
|
|
||||||
return (new ConfigAggregator\ConfigAggregator([
|
use const PHP_SAPI;
|
||||||
Mezzio\ConfigProvider::class,
|
|
||||||
Mezzio\Router\ConfigProvider::class,
|
$isTestEnv = env('APP_ENV') === 'test';
|
||||||
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
$enableSwoole = PHP_SAPI === 'cli' && openswooleIsInstalled() && ! runningInRoadRunner();
|
||||||
Mezzio\Swoole\ConfigProvider::class,
|
|
||||||
ProblemDetails\ConfigProvider::class,
|
return (new ConfigAggregator\ConfigAggregator(
|
||||||
Diactoros\ConfigProvider::class,
|
providers: [
|
||||||
Common\ConfigProvider::class,
|
! $isTestEnv
|
||||||
Config\ConfigProvider::class,
|
? new EnvVarLoaderProvider('config/params/generated_config.php', enumValues(Core\Config\EnvVars::class))
|
||||||
Importer\ConfigProvider::class,
|
: new ConfigAggregator\ArrayProvider([]),
|
||||||
IpGeolocation\ConfigProvider::class,
|
Mezzio\ConfigProvider::class,
|
||||||
EventDispatcher\ConfigProvider::class,
|
Mezzio\Router\ConfigProvider::class,
|
||||||
Core\ConfigProvider::class,
|
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
|
||||||
CLI\ConfigProvider::class,
|
$enableSwoole && class_exists(Swoole\ConfigProvider::class)
|
||||||
Rest\ConfigProvider::class,
|
? Swoole\ConfigProvider::class
|
||||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
: new ConfigAggregator\ArrayProvider([]),
|
||||||
env('APP_ENV') === 'test'
|
ProblemDetails\ConfigProvider::class,
|
||||||
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
Diactoros\ConfigProvider::class,
|
||||||
: new ConfigAggregator\LaminasConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
Common\ConfigProvider::class,
|
||||||
], 'data/cache/app_config.php', [
|
Config\ConfigProvider::class,
|
||||||
Core\Config\SimplifiedConfigParser::class,
|
Importer\ConfigProvider::class,
|
||||||
Core\Config\BasePathPrefixer::class,
|
IpGeolocation\ConfigProvider::class,
|
||||||
Core\Config\DeprecatedConfigParser::class,
|
EventDispatcher\ConfigProvider::class,
|
||||||
]))->getMergedConfig();
|
Core\ConfigProvider::class,
|
||||||
|
CLI\ConfigProvider::class,
|
||||||
|
Rest\ConfigProvider::class,
|
||||||
|
new ConfigAggregator\PhpFileProvider('config/autoload/{,*.}global.php'),
|
||||||
|
// Local config should not be loaded during tests, whereas test config should be loaded ONLY during tests
|
||||||
|
new ConfigAggregator\PhpFileProvider(
|
||||||
|
$isTestEnv ? 'config/test/*.global.php' : 'config/autoload/{,*.}local.php',
|
||||||
|
),
|
||||||
|
// Routes have to be loaded last
|
||||||
|
new ConfigAggregator\PhpFileProvider('config/autoload/routes.config.php'),
|
||||||
|
],
|
||||||
|
cachedConfigFile: 'data/cache/app_config.php',
|
||||||
|
postProcessors: [
|
||||||
|
Core\Config\PostProcessor\BasePathPrefixer::class,
|
||||||
|
Core\Config\PostProcessor\MultiSegmentSlugProcessor::class,
|
||||||
|
Core\Config\PostProcessor\ShortUrlMethodsProcessor::class,
|
||||||
|
],
|
||||||
|
))->getMergedConfig();
|
||||||
|
|||||||
24
config/constants.php
Normal file
24
config/constants.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Util\RedirectStatus;
|
||||||
|
|
||||||
|
const DEFAULT_DELETE_SHORT_URL_THRESHOLD = 15;
|
||||||
|
const DEFAULT_SHORT_CODES_LENGTH = 5;
|
||||||
|
const MIN_SHORT_CODES_LENGTH = 4;
|
||||||
|
const DEFAULT_REDIRECT_STATUS_CODE = RedirectStatus::STATUS_302; // Deprecated. Default to 307 for Shlink v4
|
||||||
|
const DEFAULT_REDIRECT_CACHE_LIFETIME = 30;
|
||||||
|
const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory';
|
||||||
|
const TITLE_TAG_VALUE = '/<title[^>]*>(.*?)<\/title>/i'; // Matches the value inside a html title tag
|
||||||
|
const LOOSE_URI_MATCHER = '/(.+)\:(.+)/i'; // Matches anything starting with a schema.
|
||||||
|
const DEFAULT_QR_CODE_SIZE = 300;
|
||||||
|
const DEFAULT_QR_CODE_MARGIN = 0;
|
||||||
|
const DEFAULT_QR_CODE_FORMAT = 'png';
|
||||||
|
const DEFAULT_QR_CODE_ERROR_CORRECTION = 'l';
|
||||||
|
const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = true;
|
||||||
|
// Deprecated. Shlink 4.0.0 should change default value to `true`
|
||||||
|
const DEFAULT_QR_CODE_ENABLED_FOR_DISABLED_SHORT_URLS = false;
|
||||||
|
const MIN_TASK_WORKERS = 4;
|
||||||
@@ -3,22 +3,36 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Laminas\ServiceManager\ServiceManager;
|
use Laminas\ServiceManager\ServiceManager;
|
||||||
|
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||||
use Symfony\Component\Lock;
|
use Symfony\Component\Lock;
|
||||||
|
|
||||||
use const Shlinkio\Shlink\Core\LOCAL_LOCK_FACTORY;
|
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
chdir(dirname(__DIR__));
|
||||||
|
|
||||||
require 'vendor/autoload.php';
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
|
// Workaround to make this compatible with both openswoole 22 and earlier versions.
|
||||||
|
// Openswoole support is deprecated. Remove in v4.0.0
|
||||||
|
if (! function_exists('swoole_set_process_name')) {
|
||||||
|
// phpcs:disable
|
||||||
|
function swoole_set_process_name(string $name): void
|
||||||
|
{
|
||||||
|
OpenSwoole\Util::setProcessName($name);
|
||||||
|
}
|
||||||
|
// phpcs:enable
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is one of the first files loaded. Configure the timezone here
|
||||||
|
date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv(date_default_timezone_get()));
|
||||||
|
|
||||||
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
|
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
|
||||||
// It needs to be placed here as individual config files will not be loaded once config is cached
|
// It needs to be placed here as individual config files will not be loaded once config is cached
|
||||||
if (! class_exists(LOCAL_LOCK_FACTORY)) {
|
if (! class_exists(LOCAL_LOCK_FACTORY)) {
|
||||||
class_alias(Lock\LockFactory::class, LOCAL_LOCK_FACTORY);
|
class_alias(Lock\LockFactory::class, LOCAL_LOCK_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build container
|
return (static function (): ServiceManager {
|
||||||
return (function () {
|
|
||||||
$config = require __DIR__ . '/config.php';
|
$config = require __DIR__ . '/config.php';
|
||||||
$container = new ServiceManager($config['dependencies']);
|
$container = new ServiceManager($config['dependencies']);
|
||||||
$container->setService('config', $config);
|
$container->setService('config', $config);
|
||||||
|
|||||||
13
config/entity-manager.php
Normal file
13
config/entity-manager.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
return (static function (): EntityManagerInterface {
|
||||||
|
/** @var ContainerInterface $container */
|
||||||
|
$container = include __DIR__ . '/container.php';
|
||||||
|
return $container->get(EntityManager::class);
|
||||||
|
})();
|
||||||
42
config/roadrunner/.rr.dev.yml
Normal file
42
config/roadrunner/.rr.dev.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
rpc:
|
||||||
|
listen: tcp://127.0.0.1:6001
|
||||||
|
|
||||||
|
server:
|
||||||
|
command: 'php ../../bin/roadrunner-worker.php'
|
||||||
|
|
||||||
|
http:
|
||||||
|
address: '0.0.0.0:8080'
|
||||||
|
middleware: ['static']
|
||||||
|
static:
|
||||||
|
dir: '../../public'
|
||||||
|
forbid: ['.php', '.htaccess']
|
||||||
|
pool:
|
||||||
|
num_workers: 1
|
||||||
|
debug: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pool:
|
||||||
|
num_workers: 1
|
||||||
|
debug: true
|
||||||
|
timeout: 300
|
||||||
|
consume: ['shlink']
|
||||||
|
pipelines:
|
||||||
|
shlink:
|
||||||
|
driver: memory
|
||||||
|
config:
|
||||||
|
priority: 10
|
||||||
|
prefetch: 10
|
||||||
|
|
||||||
|
logs:
|
||||||
|
mode: development
|
||||||
|
channels:
|
||||||
|
http:
|
||||||
|
mode: 'off' # Disable logging as Shlink handles it internally
|
||||||
|
server:
|
||||||
|
level: info
|
||||||
|
metrics:
|
||||||
|
level: debug
|
||||||
|
jobs:
|
||||||
|
level: debug
|
||||||
38
config/roadrunner/.rr.yml
Normal file
38
config/roadrunner/.rr.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
rpc:
|
||||||
|
listen: tcp://127.0.0.1:6001
|
||||||
|
|
||||||
|
server:
|
||||||
|
command: 'php -dopcache.enable_cli=1 -dopcache.validate_timestamps=0 ../../bin/roadrunner-worker.php'
|
||||||
|
|
||||||
|
http:
|
||||||
|
address: '0.0.0.0:${PORT:-8080}'
|
||||||
|
middleware: ['static']
|
||||||
|
static:
|
||||||
|
dir: '../../public'
|
||||||
|
forbid: ['.php', '.htaccess']
|
||||||
|
pool:
|
||||||
|
num_workers: ${WEB_WORKER_NUM:-0}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
timeout: 300 # 5 minutes
|
||||||
|
pool:
|
||||||
|
num_workers: ${TASK_WORKER_NUM:-0}
|
||||||
|
consume: ['shlink']
|
||||||
|
pipelines:
|
||||||
|
shlink:
|
||||||
|
driver: memory
|
||||||
|
config:
|
||||||
|
priority: 10
|
||||||
|
prefetch: 10
|
||||||
|
|
||||||
|
logs:
|
||||||
|
mode: production
|
||||||
|
channels:
|
||||||
|
http:
|
||||||
|
mode: 'off' # Disable logging as Shlink handles it internally
|
||||||
|
server:
|
||||||
|
level: info
|
||||||
|
jobs:
|
||||||
|
level: debug
|
||||||
@@ -4,12 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
use Mezzio\Application;
|
use Mezzio\Application;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Symfony\Component\Console\Application as CliApp;
|
|
||||||
|
|
||||||
return function (bool $isCli = false): void {
|
return static function (): void {
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = include __DIR__ . '/container.php';
|
$container = include __DIR__ . '/container.php';
|
||||||
$app = $container->get($isCli ? CliApp::class : Application::class);
|
$app = $container->get(Application::class);
|
||||||
|
|
||||||
$app->run();
|
$app->run();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use Psr\Container\ContainerInterface;
|
|||||||
use function register_shutdown_function;
|
use function register_shutdown_function;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_HOST;
|
use const ShlinkioTest\Shlink\API_TESTS_HOST;
|
||||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT;
|
use const ShlinkioTest\Shlink\API_TESTS_PORT;
|
||||||
|
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = require __DIR__ . '/../container.php';
|
$container = require __DIR__ . '/../container.php';
|
||||||
@@ -20,15 +20,19 @@ $config = $container->get('config');
|
|||||||
$em = $container->get(EntityManager::class);
|
$em = $container->get(EntityManager::class);
|
||||||
$httpClient = $container->get('shlink_test_api_client');
|
$httpClient = $container->get('shlink_test_api_client');
|
||||||
|
|
||||||
// Start code coverage collecting on swoole process, and stop it when process shuts down
|
// Dump code coverage when process shuts down
|
||||||
$httpClient->request('GET', sprintf('http://%s:%s/api-tests/start-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT));
|
|
||||||
register_shutdown_function(function () use ($httpClient): void {
|
register_shutdown_function(function () use ($httpClient): void {
|
||||||
$httpClient->request(
|
$httpClient->request(
|
||||||
'GET',
|
'GET',
|
||||||
sprintf('http://%s:%s/api-tests/stop-coverage', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT),
|
sprintf('http://%s:%s/api-tests/stop-coverage', API_TESTS_HOST, API_TESTS_PORT),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
$testHelper->createTestDb();
|
$testHelper->createTestDb(
|
||||||
|
createDbCommand: ['bin/cli', 'db:create'],
|
||||||
|
migrateDbCommand: ['bin/cli', 'db:migrate'],
|
||||||
|
dropSchemaCommand: ['bin/doctrine', 'orm:schema-tool:drop'],
|
||||||
|
runSqlCommand: ['bin/doctrine', 'dbal:run-sql'],
|
||||||
|
);
|
||||||
ApiTest\ApiTestCase::setApiClient($httpClient);
|
ApiTest\ApiTestCase::setApiClient($httpClient);
|
||||||
ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []));
|
ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []));
|
||||||
|
|||||||
33
config/test/bootstrap_cli_tests.php
Normal file
33
config/test/bootstrap_cli_tests.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\TestUtils;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
use function file_exists;
|
||||||
|
use function unlink;
|
||||||
|
|
||||||
|
/** @var ContainerInterface $container */
|
||||||
|
$container = require __DIR__ . '/../container.php';
|
||||||
|
$testHelper = $container->get(Helper\TestHelper::class);
|
||||||
|
$config = $container->get('config');
|
||||||
|
$em = $container->get(EntityManager::class);
|
||||||
|
|
||||||
|
// Delete old coverage in PHP, to avoid merging older executions with current one
|
||||||
|
$covFile = __DIR__ . '/../../build/coverage-cli.cov';
|
||||||
|
if (file_exists($covFile)) {
|
||||||
|
unlink($covFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$testHelper->createTestDb(
|
||||||
|
createDbCommand: ['bin/cli', 'db:create'],
|
||||||
|
migrateDbCommand: ['bin/cli', 'db:migrate'],
|
||||||
|
dropSchemaCommand: ['bin/doctrine', 'orm:schema-tool:drop'],
|
||||||
|
runSqlCommand: ['bin/doctrine', 'dbal:run-sql'],
|
||||||
|
);
|
||||||
|
CliTest\CliTestCase::setSeedFixturesCallback(
|
||||||
|
static fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []),
|
||||||
|
);
|
||||||
@@ -8,5 +8,10 @@ use Psr\Container\ContainerInterface;
|
|||||||
|
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = require __DIR__ . '/../container.php';
|
$container = require __DIR__ . '/../container.php';
|
||||||
$container->get(Helper\TestHelper::class)->createTestDb();
|
$container->get(Helper\TestHelper::class)->createTestDb(
|
||||||
|
createDbCommand: ['bin/cli', 'db:create'],
|
||||||
|
migrateDbCommand: ['bin/cli', 'db:migrate'],
|
||||||
|
dropSchemaCommand: ['bin/doctrine', 'orm:schema-tool:drop'],
|
||||||
|
runSqlCommand: ['bin/doctrine', 'dbal:run-sql'],
|
||||||
|
);
|
||||||
DbTest\DatabaseTestCase::setEntityManager($container->get('em'));
|
DbTest\DatabaseTestCase::setEntityManager($container->get('em'));
|
||||||
|
|||||||
@@ -4,5 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ShlinkioTest\Shlink;
|
namespace ShlinkioTest\Shlink;
|
||||||
|
|
||||||
const SWOOLE_TESTING_HOST = '127.0.0.1';
|
const API_TESTS_HOST = '127.0.0.1';
|
||||||
const SWOOLE_TESTING_PORT = 9999;
|
const API_TESTS_PORT = 9999;
|
||||||
|
|
||||||
|
const ANDROID_USER_AGENT = 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||||
|
. 'Chrome/109.0.5414.86 Mobile Safari/537.36';
|
||||||
|
const IOS_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 '
|
||||||
|
. '(KHTML, like Gecko) FxiOS/109.0 Mobile/15E148 Safari/605.1.15';
|
||||||
|
const DESKTOP_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like '
|
||||||
|
. 'Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.61';
|
||||||
|
|||||||
@@ -8,55 +8,83 @@ use GuzzleHttp\Client;
|
|||||||
use Laminas\ConfigAggregator\ConfigAggregator;
|
use Laminas\ConfigAggregator\ConfigAggregator;
|
||||||
use Laminas\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||||
use Laminas\Stdlib\Glob;
|
use League\Event\EventDispatcher;
|
||||||
use PDO;
|
use Monolog\Level;
|
||||||
use PHPUnit\Runner\Version;
|
use PHPUnit\Runner\Version;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||||
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
||||||
use SebastianBergmann\CodeCoverage\Filter;
|
use SebastianBergmann\CodeCoverage\Filter;
|
||||||
|
use SebastianBergmann\CodeCoverage\Report\Html\Facade as Html;
|
||||||
use SebastianBergmann\CodeCoverage\Report\PHP;
|
use SebastianBergmann\CodeCoverage\Report\PHP;
|
||||||
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml;
|
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml;
|
||||||
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||||
|
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
|
||||||
|
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||||
|
|
||||||
|
use function file_exists;
|
||||||
use function Laminas\Stratigility\middleware;
|
use function Laminas\Stratigility\middleware;
|
||||||
use function Shlinkio\Shlink\Common\env;
|
use function Shlinkio\Shlink\Config\env;
|
||||||
|
use function Shlinkio\Shlink\Core\ArrayUtils\contains;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function sys_get_temp_dir;
|
use function sys_get_temp_dir;
|
||||||
|
|
||||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_HOST;
|
use const ShlinkioTest\Shlink\API_TESTS_HOST;
|
||||||
use const ShlinkioTest\Shlink\SWOOLE_TESTING_PORT;
|
use const ShlinkioTest\Shlink\API_TESTS_PORT;
|
||||||
|
|
||||||
$isApiTest = env('TEST_ENV') === 'api';
|
$isApiTest = env('TEST_ENV') === 'api';
|
||||||
if ($isApiTest) {
|
$isCliTest = env('TEST_ENV') === 'cli';
|
||||||
|
$isE2eTest = $isApiTest || $isCliTest;
|
||||||
|
$coverageType = env('GENERATE_COVERAGE');
|
||||||
|
$generateCoverage = contains($coverageType, ['yes', 'pretty']);
|
||||||
|
|
||||||
|
$coverage = null;
|
||||||
|
if ($isE2eTest && $generateCoverage) {
|
||||||
$filter = new Filter();
|
$filter = new Filter();
|
||||||
foreach (Glob::glob(__DIR__ . '/../../module/*/src') as $item) {
|
$filter->includeDirectory(__DIR__ . '/../../module/Core/src');
|
||||||
$filter->includeDirectory($item);
|
$filter->includeDirectory(__DIR__ . '/../../module/' . ($isApiTest ? 'Rest' : 'CLI') . '/src');
|
||||||
}
|
|
||||||
$coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter);
|
$coverage = new CodeCoverage((new Selector())->forLineCoverage($filter), $filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
$buildDbConnection = function (): array {
|
/**
|
||||||
|
* @param 'api'|'cli' $type
|
||||||
|
*/
|
||||||
|
$exportCoverage = static function (string $type = 'api') use (&$coverage, $coverageType): void {
|
||||||
|
if ($coverage === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$basePath = __DIR__ . '/../../build/coverage-' . $type;
|
||||||
|
$covPath = $basePath . '.cov';
|
||||||
|
|
||||||
|
// Every CLI test runs on its own process and dumps the coverage afterwards.
|
||||||
|
// Try to load it and merge it, so that we end up with the whole coverage at the end.
|
||||||
|
if ($type === 'cli' && file_exists($covPath)) {
|
||||||
|
$coverage->merge(require $covPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($coverageType === 'pretty') {
|
||||||
|
(new Html())->process($coverage, $basePath . '/coverage-html');
|
||||||
|
} else {
|
||||||
|
(new PHP())->process($coverage, $covPath);
|
||||||
|
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$buildDbConnection = static function (): array {
|
||||||
$driver = env('DB_DRIVER', 'sqlite');
|
$driver = env('DB_DRIVER', 'sqlite');
|
||||||
$isCi = env('CI', false);
|
$isCi = env('CI', false);
|
||||||
$getMysqlHost = fn (string $driver) => sprintf('shlink_db%s', $driver === 'mysql' ? '' : '_maria');
|
$getCiMysqlPort = static fn (string $driver) => $driver === 'mysql' ? '3307' : '3308';
|
||||||
$getCiMysqlPort = fn (string $driver) => $driver === 'mysql' ? '3307' : '3308';
|
|
||||||
|
|
||||||
$driverConfigMap = [
|
return match ($driver) {
|
||||||
'sqlite' => [
|
'sqlite' => [
|
||||||
'driver' => 'pdo_sqlite',
|
'driver' => 'pdo_sqlite',
|
||||||
'path' => sys_get_temp_dir() . '/shlink-tests.db',
|
'memory' => true,
|
||||||
],
|
|
||||||
'mysql' => [
|
|
||||||
'driver' => 'pdo_mysql',
|
|
||||||
'host' => $isCi ? '127.0.0.1' : $getMysqlHost($driver),
|
|
||||||
'port' => $isCi ? $getCiMysqlPort($driver) : '3306',
|
|
||||||
'user' => 'root',
|
|
||||||
'password' => 'root',
|
|
||||||
'dbname' => 'shlink_test',
|
|
||||||
'charset' => 'utf8',
|
|
||||||
'driverOptions' => [
|
|
||||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
|
||||||
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
'postgres' => [
|
'postgres' => [
|
||||||
'driver' => 'pdo_pgsql',
|
'driver' => 'pdo_pgsql',
|
||||||
@@ -73,13 +101,29 @@ $buildDbConnection = function (): array {
|
|||||||
'user' => 'sa',
|
'user' => 'sa',
|
||||||
'password' => 'Passw0rd!',
|
'password' => 'Passw0rd!',
|
||||||
'dbname' => 'shlink_test',
|
'dbname' => 'shlink_test',
|
||||||
|
'driverOptions' => [
|
||||||
|
'TrustServerCertificate' => 'true',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
];
|
default => [ // mysql and maria
|
||||||
$driverConfigMap['maria'] = $driverConfigMap['mysql'];
|
'driver' => 'pdo_mysql',
|
||||||
|
'host' => $isCi ? '127.0.0.1' : sprintf('shlink_db_%s', $driver),
|
||||||
return $driverConfigMap[$driver] ?? [];
|
'port' => $isCi ? $getCiMysqlPort($driver) : '3306',
|
||||||
|
'user' => 'root',
|
||||||
|
'password' => 'root',
|
||||||
|
'dbname' => 'shlink_test',
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
],
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$buildTestLoggerConfig = static fn (string $filename) => [
|
||||||
|
'level' => Level::Debug->value,
|
||||||
|
'type' => LoggerType::STREAM->value,
|
||||||
|
'destination' => sprintf('data/log/api-tests/%s', $filename),
|
||||||
|
'add_new_line' => true,
|
||||||
|
];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'debug' => true,
|
'debug' => true,
|
||||||
@@ -88,19 +132,19 @@ return [
|
|||||||
'url_shortener' => [
|
'url_shortener' => [
|
||||||
'domain' => [
|
'domain' => [
|
||||||
'schema' => 'http',
|
'schema' => 'http',
|
||||||
'hostname' => 'doma.in',
|
'hostname' => 's.test',
|
||||||
],
|
],
|
||||||
'validate_url' => true,
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
'mezzio-swoole' => [
|
||||||
'enable_coroutine' => false,
|
'enable_coroutine' => false,
|
||||||
'swoole-http-server' => [
|
'swoole-http-server' => [
|
||||||
'host' => SWOOLE_TESTING_HOST,
|
'host' => API_TESTS_HOST,
|
||||||
'port' => SWOOLE_TESTING_PORT,
|
'port' => API_TESTS_PORT,
|
||||||
'process-name' => 'shlink_test',
|
'process-name' => 'shlink_test',
|
||||||
'options' => [
|
'options' => [
|
||||||
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
|
'pid_file' => sys_get_temp_dir() . '/shlink-test-swoole.pid',
|
||||||
|
'log_file' => __DIR__ . '/../../data/log/api-tests/output.log',
|
||||||
'enable_coroutine' => false,
|
'enable_coroutine' => false,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -108,30 +152,33 @@ return [
|
|||||||
|
|
||||||
'routes' => !$isApiTest ? [] : [
|
'routes' => !$isApiTest ? [] : [
|
||||||
[
|
[
|
||||||
'name' => 'start_collecting_coverage',
|
'name' => 'dump_coverage',
|
||||||
'path' => '/api-tests/start-coverage',
|
'path' => '/api-tests/stop-coverage',
|
||||||
'middleware' => middleware(static function () use (&$coverage) {
|
'middleware' => middleware(static function () use ($exportCoverage) {
|
||||||
if ($coverage) {
|
// TODO I have tried moving this block to a listener so that it's invoked automatically,
|
||||||
$coverage->start('API tests');
|
// but then the coverage is generated empty ¯\_(ツ)_/¯
|
||||||
}
|
$exportCoverage();
|
||||||
return new EmptyResponse();
|
return new EmptyResponse();
|
||||||
}),
|
}),
|
||||||
'allowed_methods' => ['GET'],
|
'allowed_methods' => ['GET'],
|
||||||
],
|
],
|
||||||
[
|
],
|
||||||
'name' => 'dump_coverage',
|
|
||||||
'path' => '/api-tests/stop-coverage',
|
|
||||||
'middleware' => middleware(static function () use (&$coverage) {
|
|
||||||
if ($coverage) {
|
|
||||||
$basePath = __DIR__ . '/../../build/coverage-api';
|
|
||||||
$coverage->stop();
|
|
||||||
(new PHP())->process($coverage, $basePath . '.cov');
|
|
||||||
(new Xml(Version::getVersionString()))->process($coverage, $basePath . '/coverage-xml');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new EmptyResponse();
|
'middleware_pipeline' => !$isApiTest ? [] : [
|
||||||
|
'capture_code_coverage' => [
|
||||||
|
'middleware' => middleware(static function (
|
||||||
|
ServerRequestInterface $req,
|
||||||
|
RequestHandlerInterface $handler,
|
||||||
|
) use (&$coverage): ResponseInterface {
|
||||||
|
$coverage?->start($req->getHeaderLine('x-coverage-id'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $handler->handle($req);
|
||||||
|
} finally {
|
||||||
|
$coverage?->stop();
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
'allowed_methods' => ['GET'],
|
'priority' => 9999,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -144,13 +191,69 @@ return [
|
|||||||
'dependencies' => [
|
'dependencies' => [
|
||||||
'services' => [
|
'services' => [
|
||||||
'shlink_test_api_client' => new Client([
|
'shlink_test_api_client' => new Client([
|
||||||
'base_uri' => sprintf('http://%s:%s/', SWOOLE_TESTING_HOST, SWOOLE_TESTING_PORT),
|
'base_uri' => sprintf('http://%s:%s/', API_TESTS_HOST, API_TESTS_PORT),
|
||||||
'http_errors' => false,
|
'http_errors' => false,
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
'factories' => [
|
'factories' => [
|
||||||
TestUtils\Helper\TestHelper::class => InvokableFactory::class,
|
TestUtils\Helper\TestHelper::class => InvokableFactory::class,
|
||||||
],
|
],
|
||||||
|
'delegators' => $isCliTest ? [
|
||||||
|
Application::class => [
|
||||||
|
static function (
|
||||||
|
ContainerInterface $c,
|
||||||
|
string $serviceName,
|
||||||
|
callable $callback,
|
||||||
|
) use (
|
||||||
|
&$coverage,
|
||||||
|
$exportCoverage,
|
||||||
|
) {
|
||||||
|
/** @var Application $app */
|
||||||
|
$app = $callback();
|
||||||
|
$wrappedEventDispatcher = new EventDispatcher();
|
||||||
|
|
||||||
|
// When the command starts, start collecting coverage
|
||||||
|
$wrappedEventDispatcher->subscribeTo(
|
||||||
|
ConsoleCommandEvent::class,
|
||||||
|
static function () use (&$coverage): void {
|
||||||
|
$id = env('COVERAGE_ID');
|
||||||
|
if ($id === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage?->start($id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// When the command ends, stop collecting coverage
|
||||||
|
$wrappedEventDispatcher->subscribeTo(
|
||||||
|
ConsoleTerminateEvent::class,
|
||||||
|
static function () use (&$coverage, $exportCoverage): void {
|
||||||
|
$id = env('COVERAGE_ID');
|
||||||
|
if ($id === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage?->stop();
|
||||||
|
$exportCoverage('cli');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->setDispatcher(new class ($wrappedEventDispatcher) implements EventDispatcherInterface {
|
||||||
|
public function __construct(private EventDispatcher $wrappedDispatcher)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch(object $event, ?string $eventName = null): object
|
||||||
|
{
|
||||||
|
$this->wrappedDispatcher->dispatch($event);
|
||||||
|
return $event;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $app;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
] : [],
|
||||||
],
|
],
|
||||||
|
|
||||||
'entity_manager' => [
|
'entity_manager' => [
|
||||||
@@ -159,8 +262,14 @@ return [
|
|||||||
|
|
||||||
'data_fixtures' => [
|
'data_fixtures' => [
|
||||||
'paths' => [
|
'paths' => [
|
||||||
|
// TODO These are used for CLI tests too, so maybe should be somewhere else
|
||||||
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
__DIR__ . '/../../module/Rest/test-api/Fixtures',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'logger' => [
|
||||||
|
'Shlink' => $buildTestLoggerConfig('shlink.log'),
|
||||||
|
'Access' => $buildTestLoggerConfig('access.log'),
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
|
curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
|
||||||
curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
|
curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
|
||||||
apt-get update
|
apt-get update
|
||||||
ACCEPT_EULA=Y apt-get install msodbcsql17
|
ACCEPT_EULA=Y apt-get install msodbcsql18
|
||||||
apt-get install unixodbc-dev
|
# apt-get install unixodbc-dev
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
ServerName doma.in
|
ServerName s.test
|
||||||
DocumentRoot "/path/to/shlink/public"
|
DocumentRoot "/path/to/shlink/public"
|
||||||
|
|
||||||
<Directory "/path/to/shlink/public">
|
<Directory "/path/to/shlink/public">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
server {
|
server {
|
||||||
server_name doma.in;
|
server_name s.test;
|
||||||
listen 80;
|
listen 80;
|
||||||
root /path/to/shlink/public;
|
root /path/to/shlink/public;
|
||||||
index index.php;
|
index index.php;
|
||||||
@@ -11,7 +11,7 @@ server {
|
|||||||
|
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include fastcgi.conf;
|
include fastcgi.conf;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/var/log/shlink/shlink_swoole.log {
|
/var/log/shlink/shlink_openswoole.log {
|
||||||
su root root
|
su root root
|
||||||
daily
|
daily
|
||||||
missingok
|
missingok
|
||||||
@@ -8,6 +8,6 @@
|
|||||||
notifempty
|
notifempty
|
||||||
create 0640 root root
|
create 0640 root root
|
||||||
postrotate
|
postrotate
|
||||||
/etc/init.d/shlink_swoole restart
|
/etc/init.d/shlink_openswoole restart
|
||||||
endscript
|
endscript
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
### BEGIN INIT INFO
|
### BEGIN INIT INFO
|
||||||
# Provides: shlink_swoole
|
# Provides: shlink_openswoole
|
||||||
# Required-Start: $local_fs $network $named $time $syslog
|
# Required-Start: $local_fs $network $named $time $syslog
|
||||||
# Required-Stop: $local_fs $network $named $time $syslog
|
# Required-Stop: $local_fs $network $named $time $syslog
|
||||||
# Default-Start: 2 3 4 5
|
# Default-Start: 2 3 4 5
|
||||||
# Default-Stop: 0 1 6
|
# Default-Stop: 0 1 6
|
||||||
# Description: Shlink non-blocking server with swoole
|
# Description: Shlink non-blocking server with openswoole
|
||||||
### END INIT INFO
|
### END INIT INFO
|
||||||
|
|
||||||
SCRIPT=/path/to/shlink/vendor/bin/mezzio-swoole\ start
|
SCRIPT=/path/to/shlink/vendor/bin/laminas\ mezzio:swoole:start
|
||||||
RUNAS=root
|
RUNAS=root
|
||||||
|
|
||||||
PIDFILE=/var/run/shlink_swoole.pid
|
PIDFILE=/var/run/shlink_openswoole.pid
|
||||||
LOGDIR=/var/log/shlink
|
LOGDIR=/var/log/shlink
|
||||||
LOGFILE=${LOGDIR}/shlink_swoole.log
|
LOGFILE=${LOGDIR}/shlink_openswoole.log
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
if [[ -f "$PIDFILE" ]] && kill -0 $(cat "$PIDFILE"); then
|
||||||
echo 'Shlink with swoole already running' >&2
|
echo 'Shlink with openswoole already running' >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
echo 'Starting shlink with swoole' >&2
|
echo 'Starting shlink with openswoole' >&2
|
||||||
mkdir -p "$LOGDIR"
|
mkdir -p "$LOGDIR"
|
||||||
touch "$LOGFILE"
|
touch "$LOGFILE"
|
||||||
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
|
||||||
@@ -30,10 +30,10 @@ start() {
|
|||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
if [[ ! -f "$PIDFILE" ]] || ! kill -0 $(cat "$PIDFILE"); then
|
||||||
echo 'Shlink with swoole not running' >&2
|
echo 'Shlink with openswoole not running' >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
echo 'Stopping shlink with swoole' >&2
|
echo 'Stopping shlink with openswoole' >&2
|
||||||
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
|
||||||
echo 'Shlink stopped' >&2
|
echo 'Shlink stopped' >&2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
FROM php:7.4.11-fpm-alpine3.12
|
FROM php:8.2-fpm-alpine3.17
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.18
|
ENV APCU_VERSION 5.1.21
|
||||||
ENV APCU_BC_VERSION 1.0.5
|
ENV PDO_SQLSRV_VERSION 5.11.1
|
||||||
|
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
|
||||||
|
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
|
||||||
|
|
||||||
RUN apk update
|
RUN apk update
|
||||||
|
|
||||||
# Install common php extensions
|
# Install common php extensions
|
||||||
RUN docker-php-ext-install pdo_mysql
|
RUN docker-php-ext-install pdo_mysql
|
||||||
RUN docker-php-ext-install iconv
|
|
||||||
RUN docker-php-ext-install calendar
|
RUN docker-php-ext-install calendar
|
||||||
|
|
||||||
RUN apk add --no-cache oniguruma-dev
|
RUN apk add --no-cache oniguruma-dev
|
||||||
@@ -30,41 +31,29 @@ RUN docker-php-ext-install gd
|
|||||||
RUN apk add --no-cache postgresql-dev
|
RUN apk add --no-cache postgresql-dev
|
||||||
RUN docker-php-ext-install pdo_pgsql
|
RUN docker-php-ext-install pdo_pgsql
|
||||||
|
|
||||||
RUN apk add --no-cache gmp-dev
|
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
|
||||||
RUN docker-php-ext-install gmp
|
docker-php-ext-install sockets && \
|
||||||
|
apk del .phpize-deps
|
||||||
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||||
RUN mkdir -p /usr/src/php/ext/apcu\
|
RUN mkdir -p /usr/src/php/ext/apcu \
|
||||||
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1
|
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1 \
|
||||||
# configure and install
|
&& docker-php-ext-configure apcu \
|
||||||
RUN docker-php-ext-configure apcu\
|
&& docker-php-ext-install apcu \
|
||||||
&& docker-php-ext-install apcu
|
&& rm /tmp/apcu.tar.gz \
|
||||||
# cleanup
|
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||||
RUN rm /tmp/apcu.tar.gz
|
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||||
|
|
||||||
# Install APCu-BC extension
|
|
||||||
ADD https://pecl.php.net/get/apcu_bc-$APCU_BC_VERSION.tgz /tmp/apcu_bc.tar.gz
|
|
||||||
RUN mkdir -p /usr/src/php/ext/apcu-bc\
|
|
||||||
&& tar xf /tmp/apcu_bc.tar.gz -C /usr/src/php/ext/apcu-bc --strip-components=1
|
|
||||||
# configure and install
|
|
||||||
RUN docker-php-ext-configure apcu-bc\
|
|
||||||
&& docker-php-ext-install apcu-bc
|
|
||||||
# cleanup
|
|
||||||
RUN rm /tmp/apcu_bc.tar.gz
|
|
||||||
|
|
||||||
# Load APCU.ini before APC.ini
|
|
||||||
RUN rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini
|
|
||||||
RUN echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
|
||||||
|
|
||||||
# Install pcov and sqlsrv driver
|
# Install pcov and sqlsrv driver
|
||||||
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \
|
RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \
|
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||||
pecl install pdo_sqlsrv pcov && \
|
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||||
docker-php-ext-enable pdo_sqlsrv pcov && \
|
docker-php-ext-enable pdo_sqlsrv pcov && \
|
||||||
apk del .phpize-deps && \
|
apk del .phpize-deps && \
|
||||||
rm msodbcsql17_17.5.1.1-1_amd64.apk
|
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||||
|
|
||||||
# Install composer
|
# Install composer
|
||||||
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||||
|
|||||||
2
data/infra/redis/redis-acl.conf
Normal file
2
data/infra/redis/redis-acl.conf
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
user foo allcommands allkeys on >bar
|
||||||
|
requirepass barbar
|
||||||
76
data/infra/roadrunner.Dockerfile
Normal file
76
data/infra/roadrunner.Dockerfile
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
FROM php:8.2-alpine3.17
|
||||||
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
|
ENV APCU_VERSION 5.1.21
|
||||||
|
ENV PDO_SQLSRV_VERSION 5.11.1
|
||||||
|
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
|
||||||
|
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
|
||||||
|
|
||||||
|
RUN apk update
|
||||||
|
|
||||||
|
# Install common php extensions
|
||||||
|
RUN docker-php-ext-install pdo_mysql
|
||||||
|
RUN docker-php-ext-install calendar
|
||||||
|
|
||||||
|
RUN apk add --no-cache oniguruma-dev
|
||||||
|
RUN docker-php-ext-install mbstring
|
||||||
|
|
||||||
|
RUN apk add --no-cache sqlite-libs
|
||||||
|
RUN apk add --no-cache sqlite-dev
|
||||||
|
RUN docker-php-ext-install pdo_sqlite
|
||||||
|
|
||||||
|
RUN apk add --no-cache icu-dev
|
||||||
|
RUN docker-php-ext-install intl
|
||||||
|
|
||||||
|
RUN apk add --no-cache libzip-dev zlib-dev
|
||||||
|
RUN docker-php-ext-install zip
|
||||||
|
|
||||||
|
RUN apk add --no-cache libpng-dev
|
||||||
|
RUN docker-php-ext-install gd
|
||||||
|
|
||||||
|
RUN apk add --no-cache postgresql-dev
|
||||||
|
RUN docker-php-ext-install pdo_pgsql
|
||||||
|
|
||||||
|
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
|
||||||
|
docker-php-ext-install sockets && \
|
||||||
|
apk del .phpize-deps
|
||||||
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
|
# Install APCu extension
|
||||||
|
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||||
|
RUN mkdir -p /usr/src/php/ext/apcu \
|
||||||
|
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1 \
|
||||||
|
&& docker-php-ext-configure apcu \
|
||||||
|
&& docker-php-ext-install apcu \
|
||||||
|
&& rm /tmp/apcu.tar.gz \
|
||||||
|
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||||
|
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||||
|
|
||||||
|
# Install pcov and sqlsrv driver
|
||||||
|
RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
|
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
|
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||||
|
pecl install pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||||
|
docker-php-ext-enable pdo_sqlsrv pcov && \
|
||||||
|
apk del .phpize-deps && \
|
||||||
|
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||||
|
|
||||||
|
# Install composer
|
||||||
|
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||||
|
|
||||||
|
# Make home directory writable by anyone
|
||||||
|
RUN chmod 777 /home
|
||||||
|
|
||||||
|
VOLUME /home/shlink
|
||||||
|
WORKDIR /home/shlink
|
||||||
|
|
||||||
|
# Expose roadrunner port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD \
|
||||||
|
# Install dependencies if the vendor dir does not exist
|
||||||
|
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
||||||
|
# Download roadrunner binary
|
||||||
|
if [[ ! -f "./bin/rr" ]]; then ./vendor/bin/rr get --no-interaction --no-config --location bin/ && chmod +x bin/rr ; fi && \
|
||||||
|
# This forces the app to be started every second until the exit code is 0
|
||||||
|
until ./bin/rr serve -c config/roadrunner/.rr.dev.yml; do sleep 1 ; done
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
FROM php:7.4.11-alpine3.12
|
FROM php:8.2-alpine3.17
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.18
|
ENV APCU_VERSION 5.1.21
|
||||||
ENV APCU_BC_VERSION 1.0.5
|
ENV INOTIFY_VERSION 3.0.0
|
||||||
ENV INOTIFY_VERSION 2.0.0
|
ENV OPENSWOOLE_VERSION 22.1.0
|
||||||
ENV SWOOLE_VERSION 4.5.9
|
ENV PDO_SQLSRV_VERSION 5.11.1
|
||||||
|
ENV MS_ODBC_DOWNLOAD 'b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486'
|
||||||
|
ENV MS_ODBC_SQL_VERSION 18_18.1.1.1
|
||||||
|
|
||||||
RUN apk update
|
RUN apk update
|
||||||
|
|
||||||
# Install common php extensions
|
# Install common php extensions
|
||||||
RUN docker-php-ext-install pdo_mysql
|
RUN docker-php-ext-install pdo_mysql
|
||||||
RUN docker-php-ext-install iconv
|
|
||||||
RUN docker-php-ext-install calendar
|
RUN docker-php-ext-install calendar
|
||||||
|
|
||||||
RUN apk add --no-cache oniguruma-dev
|
RUN apk add --no-cache oniguruma-dev
|
||||||
@@ -32,51 +33,37 @@ RUN docker-php-ext-install gd
|
|||||||
RUN apk add --no-cache postgresql-dev
|
RUN apk add --no-cache postgresql-dev
|
||||||
RUN docker-php-ext-install pdo_pgsql
|
RUN docker-php-ext-install pdo_pgsql
|
||||||
|
|
||||||
RUN apk add --no-cache gmp-dev
|
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
|
||||||
RUN docker-php-ext-install gmp
|
docker-php-ext-install sockets && \
|
||||||
|
apk del .phpize-deps
|
||||||
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
ADD https://pecl.php.net/get/apcu-$APCU_VERSION.tgz /tmp/apcu.tar.gz
|
||||||
RUN mkdir -p /usr/src/php/ext/apcu\
|
RUN mkdir -p /usr/src/php/ext/apcu \
|
||||||
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1
|
&& tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1 \
|
||||||
# configure and install
|
&& docker-php-ext-configure apcu \
|
||||||
RUN docker-php-ext-configure apcu\
|
&& docker-php-ext-install apcu \
|
||||||
&& docker-php-ext-install apcu
|
&& rm /tmp/apcu.tar.gz \
|
||||||
# cleanup
|
&& rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \
|
||||||
RUN rm /tmp/apcu.tar.gz
|
&& echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
||||||
|
|
||||||
# Install APCu-BC extension
|
|
||||||
ADD https://pecl.php.net/get/apcu_bc-$APCU_BC_VERSION.tgz /tmp/apcu_bc.tar.gz
|
|
||||||
RUN mkdir -p /usr/src/php/ext/apcu-bc\
|
|
||||||
&& tar xf /tmp/apcu_bc.tar.gz -C /usr/src/php/ext/apcu-bc --strip-components=1
|
|
||||||
# configure and install
|
|
||||||
RUN docker-php-ext-configure apcu-bc\
|
|
||||||
&& docker-php-ext-install apcu-bc
|
|
||||||
# cleanup
|
|
||||||
RUN rm /tmp/apcu_bc.tar.gz
|
|
||||||
|
|
||||||
# Load APCU.ini before APC.ini
|
|
||||||
RUN rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini
|
|
||||||
RUN echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
|
|
||||||
|
|
||||||
# Install inotify extension
|
# Install inotify extension
|
||||||
ADD https://pecl.php.net/get/inotify-$INOTIFY_VERSION.tgz /tmp/inotify.tar.gz
|
ADD https://pecl.php.net/get/inotify-$INOTIFY_VERSION.tgz /tmp/inotify.tar.gz
|
||||||
RUN mkdir -p /usr/src/php/ext/inotify\
|
RUN mkdir -p /usr/src/php/ext/inotify \
|
||||||
&& tar xf /tmp/inotify.tar.gz -C /usr/src/php/ext/inotify --strip-components=1
|
&& tar xf /tmp/inotify.tar.gz -C /usr/src/php/ext/inotify --strip-components=1 \
|
||||||
# configure and install
|
&& docker-php-ext-configure inotify \
|
||||||
RUN docker-php-ext-configure inotify\
|
&& docker-php-ext-install inotify \
|
||||||
&& docker-php-ext-install inotify
|
&& rm /tmp/inotify.tar.gz
|
||||||
# cleanup
|
|
||||||
RUN rm /tmp/inotify.tar.gz
|
|
||||||
|
|
||||||
# Install swoole, pcov and mssql driver
|
# Install openswoole, pcov and mssql driver
|
||||||
RUN wget https://download.microsoft.com/download/e/4/e/e4e67866-dffd-428c-aac7-8d28ddafb39b/msodbcsql17_17.5.1.1-1_amd64.apk && \
|
RUN wget https://download.microsoft.com/download/${MS_ODBC_DOWNLOAD}/msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
apk add --allow-untrusted msodbcsql17_17.5.1.1-1_amd64.apk && \
|
apk add --allow-untrusted msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk && \
|
||||||
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS unixodbc-dev && \
|
||||||
pecl install swoole-${SWOOLE_VERSION} pdo_sqlsrv pcov && \
|
pecl install openswoole-${OPENSWOOLE_VERSION} pdo_sqlsrv-${PDO_SQLSRV_VERSION} pcov && \
|
||||||
docker-php-ext-enable swoole pdo_sqlsrv pcov && \
|
docker-php-ext-enable openswoole pdo_sqlsrv pcov && \
|
||||||
apk del .phpize-deps && \
|
apk del .phpize-deps && \
|
||||||
rm msodbcsql17_17.5.1.1-1_amd64.apk
|
rm msodbcsql${MS_ODBC_SQL_VERSION}-1_amd64.apk
|
||||||
|
|
||||||
# Install composer
|
# Install composer
|
||||||
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||||
@@ -87,12 +74,12 @@ RUN chmod 777 /home
|
|||||||
VOLUME /home/shlink
|
VOLUME /home/shlink
|
||||||
WORKDIR /home/shlink
|
WORKDIR /home/shlink
|
||||||
|
|
||||||
# Expose swoole port
|
# Expose openswoole port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
CMD \
|
CMD \
|
||||||
# Install dependencies if the vendor dir does not exist
|
# Install dependencies if the vendor dir does not exist
|
||||||
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \
|
||||||
# When restarting the container, swoole might think it is already in execution
|
# When restarting the container, openswoole might think it is already in execution
|
||||||
# This forces the app to be started every second until the exit code is 0
|
# This forces the app to be started every second until the exit code is 0
|
||||||
until php ./vendor/bin/mezzio-swoole start; do sleep 1 ; done
|
until php ./vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace ShlinkMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\DBALException;
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\DBAL\Schema\SchemaException;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
class Version20160819142757 extends AbstractMigration
|
|
||||||
{
|
|
||||||
private const MYSQL = 'mysql';
|
|
||||||
private const SQLITE = 'sqlite';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws DBALException
|
|
||||||
* @throws SchemaException
|
|
||||||
*/
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$db = $this->connection->getDatabasePlatform()->getName();
|
|
||||||
$table = $schema->getTable('short_urls');
|
|
||||||
$column = $table->getColumn('short_code');
|
|
||||||
|
|
||||||
if ($db === self::MYSQL) {
|
|
||||||
$column->setPlatformOption('collation', 'utf8_bin');
|
|
||||||
} elseif ($db === self::SQLITE) {
|
|
||||||
$column->setPlatformOption('collate', 'BINARY');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws DBALException
|
|
||||||
*/
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$db = $this->connection->getDatabasePlatform()->getName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace <namespace>;
|
namespace <namespace>;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
@@ -18,4 +19,9 @@ final class <className> extends AbstractMigration
|
|||||||
{
|
{
|
||||||
<down>
|
<down>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isTransactional(): bool
|
||||||
|
{
|
||||||
|
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
shlink_db:
|
shlink_db_mysql:
|
||||||
environment:
|
environment:
|
||||||
MYSQL_DATABASE: shlink_test
|
MYSQL_DATABASE: shlink_test
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ services:
|
|||||||
- /etc/passwd:/etc/passwd:ro
|
- /etc/passwd:/etc/passwd:ro
|
||||||
- /etc/group:/etc/group:ro
|
- /etc/group:/etc/group:ro
|
||||||
|
|
||||||
shlink_db:
|
shlink_roadrunner:
|
||||||
|
user: 1000:1000
|
||||||
|
volumes:
|
||||||
|
- /etc/passwd:/etc/passwd:ro
|
||||||
|
- /etc/group:/etc/group:ro
|
||||||
|
|
||||||
|
shlink_db_mysql:
|
||||||
user: 1000:1000
|
user: 1000:1000
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/passwd:/etc/passwd:ro
|
- /etc/passwd:/etc/passwd:ro
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
shlink_nginx:
|
shlink_nginx:
|
||||||
container_name: shlink_nginx
|
container_name: shlink_nginx
|
||||||
image: nginx:1.17.10-alpine
|
image: nginx:1.25-alpine
|
||||||
ports:
|
ports:
|
||||||
- "8000:80"
|
- "8000:80"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -18,23 +18,30 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./data/infra/php.Dockerfile
|
dockerfile: ./data/infra/php.Dockerfile
|
||||||
|
ports:
|
||||||
|
- '8888:8888'
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/home/shlink/www
|
- ./:/home/shlink/www
|
||||||
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
||||||
links:
|
links:
|
||||||
- shlink_db
|
- shlink_db_mysql
|
||||||
- shlink_db_postgres
|
- shlink_db_postgres
|
||||||
- shlink_db_maria
|
- shlink_db_maria
|
||||||
- shlink_db_ms
|
- shlink_db_ms
|
||||||
- shlink_redis
|
- shlink_redis
|
||||||
|
- shlink_redis_acl
|
||||||
- shlink_mercure
|
- shlink_mercure
|
||||||
- shlink_mercure_proxy
|
- shlink_mercure_proxy
|
||||||
|
- shlink_rabbitmq
|
||||||
|
- shlink_matomo
|
||||||
environment:
|
environment:
|
||||||
LC_ALL: C
|
LC_ALL: C
|
||||||
|
extra_hosts:
|
||||||
|
- 'host.docker.internal:host-gateway'
|
||||||
|
|
||||||
shlink_swoole_proxy:
|
shlink_swoole_proxy:
|
||||||
container_name: shlink_swoole_proxy
|
container_name: shlink_swoole_proxy
|
||||||
image: nginx:1.17.10-alpine
|
image: nginx:1.25-alpine
|
||||||
ports:
|
ports:
|
||||||
- "8002:80"
|
- "8002:80"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -55,19 +62,50 @@ services:
|
|||||||
- ./:/home/shlink
|
- ./:/home/shlink
|
||||||
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
||||||
links:
|
links:
|
||||||
- shlink_db
|
- shlink_db_mysql
|
||||||
- shlink_db_postgres
|
- shlink_db_postgres
|
||||||
- shlink_db_maria
|
- shlink_db_maria
|
||||||
- shlink_db_ms
|
- shlink_db_ms
|
||||||
- shlink_redis
|
- shlink_redis
|
||||||
|
- shlink_redis_acl
|
||||||
- shlink_mercure
|
- shlink_mercure
|
||||||
- shlink_mercure_proxy
|
- shlink_mercure_proxy
|
||||||
|
- shlink_rabbitmq
|
||||||
|
- shlink_matomo
|
||||||
environment:
|
environment:
|
||||||
LC_ALL: C
|
LC_ALL: C
|
||||||
|
extra_hosts:
|
||||||
|
- 'host.docker.internal:host-gateway'
|
||||||
|
|
||||||
shlink_db:
|
shlink_roadrunner:
|
||||||
container_name: shlink_db
|
container_name: shlink_roadrunner
|
||||||
image: mysql:5.7
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./data/infra/roadrunner.Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8800:8080"
|
||||||
|
volumes:
|
||||||
|
- ./:/home/shlink
|
||||||
|
- ./data/infra/php.ini:/usr/local/etc/php/php.ini
|
||||||
|
links:
|
||||||
|
- shlink_db_mysql
|
||||||
|
- shlink_db_postgres
|
||||||
|
- shlink_db_maria
|
||||||
|
- shlink_db_ms
|
||||||
|
- shlink_redis
|
||||||
|
- shlink_redis_acl
|
||||||
|
- shlink_mercure
|
||||||
|
- shlink_mercure_proxy
|
||||||
|
- shlink_rabbitmq
|
||||||
|
- shlink_matomo
|
||||||
|
environment:
|
||||||
|
LC_ALL: C
|
||||||
|
extra_hosts:
|
||||||
|
- 'host.docker.internal:host-gateway'
|
||||||
|
|
||||||
|
shlink_db_mysql:
|
||||||
|
container_name: shlink_db_mysql
|
||||||
|
image: mysql:8.0
|
||||||
ports:
|
ports:
|
||||||
- "3307:3306"
|
- "3307:3306"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -92,7 +130,7 @@ services:
|
|||||||
|
|
||||||
shlink_db_maria:
|
shlink_db_maria:
|
||||||
container_name: shlink_db_maria
|
container_name: shlink_db_maria
|
||||||
image: mariadb:10.5
|
image: mariadb:10.7
|
||||||
ports:
|
ports:
|
||||||
- "3308:3306"
|
- "3308:3306"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -114,13 +152,22 @@ services:
|
|||||||
|
|
||||||
shlink_redis:
|
shlink_redis:
|
||||||
container_name: shlink_redis
|
container_name: shlink_redis
|
||||||
image: redis:6.0-alpine
|
image: redis:6.2-alpine
|
||||||
ports:
|
ports:
|
||||||
- "6380:6379"
|
- "6380:6379"
|
||||||
|
|
||||||
|
shlink_redis_acl:
|
||||||
|
container_name: shlink_redis_acl
|
||||||
|
image: redis:6.2-alpine
|
||||||
|
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
|
||||||
|
ports:
|
||||||
|
- "6382:6379"
|
||||||
|
volumes:
|
||||||
|
- ./data/infra/redis/redis-acl.conf:/usr/local/etc/redis/redis.conf
|
||||||
|
|
||||||
shlink_mercure_proxy:
|
shlink_mercure_proxy:
|
||||||
container_name: shlink_mercure_proxy
|
container_name: shlink_mercure_proxy
|
||||||
image: nginx:1.17.10-alpine
|
image: nginx:1.25-alpine
|
||||||
ports:
|
ports:
|
||||||
- "8001:80"
|
- "8001:80"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -131,10 +178,47 @@ services:
|
|||||||
|
|
||||||
shlink_mercure:
|
shlink_mercure:
|
||||||
container_name: shlink_mercure
|
container_name: shlink_mercure
|
||||||
image: dunglas/mercure:v0.10
|
image: dunglas/mercure:v0.15
|
||||||
ports:
|
ports:
|
||||||
- "3080:80"
|
- "3080:80"
|
||||||
environment:
|
environment:
|
||||||
CORS_ALLOWED_ORIGINS: "*"
|
SERVER_NAME: ":80"
|
||||||
JWT_KEY: "mercure_jwt_key"
|
MERCURE_PUBLISHER_JWT_KEY: mercure_jwt_key_long_enough_to_avoid_error
|
||||||
USE_FORWARDED_HEADERS: "1"
|
MERCURE_SUBSCRIBER_JWT_KEY: mercure_jwt_key_long_enough_to_avoid_error
|
||||||
|
MERCURE_EXTRA_DIRECTIVES: "cors_origins https://app.shlink.io http://localhost:3000 http://127.0.0.1:3000"
|
||||||
|
|
||||||
|
shlink_rabbitmq:
|
||||||
|
container_name: shlink_rabbitmq
|
||||||
|
image: rabbitmq:3.11-management-alpine
|
||||||
|
ports:
|
||||||
|
- "15672:15672"
|
||||||
|
- "5672:5672"
|
||||||
|
environment:
|
||||||
|
RABBITMQ_DEFAULT_USER: "rabbit"
|
||||||
|
RABBITMQ_DEFAULT_PASS: "rabbit"
|
||||||
|
|
||||||
|
shlink_swagger_ui:
|
||||||
|
container_name: shlink_swagger_ui
|
||||||
|
image: swaggerapi/swagger-ui:v5.10.3
|
||||||
|
ports:
|
||||||
|
- "8005:8080"
|
||||||
|
volumes:
|
||||||
|
- ./docs/swagger:/app
|
||||||
|
|
||||||
|
shlink_matomo:
|
||||||
|
container_name: shlink_matomo
|
||||||
|
image: matomo:4.15-apache
|
||||||
|
ports:
|
||||||
|
- "8003:80"
|
||||||
|
volumes:
|
||||||
|
# Matomo does not persist port in trusted hosts. This volume is needed to edit config afterward
|
||||||
|
# https://github.com/matomo-org/matomo/issues/9549
|
||||||
|
- ./data/infra/matomo:/var/www/html
|
||||||
|
links:
|
||||||
|
- shlink_db_mysql
|
||||||
|
environment:
|
||||||
|
MATOMO_DATABASE_HOST: "shlink_db_mysql"
|
||||||
|
MATOMO_DATABASE_ADAPTER: "mysql"
|
||||||
|
MATOMO_DATABASE_DBNAME: "matomo"
|
||||||
|
MATOMO_DATABASE_USERNAME: "root"
|
||||||
|
MATOMO_DATABASE_PASSWORD: "root"
|
||||||
|
|||||||
296
docker/README.md
296
docker/README.md
@@ -1,304 +1,34 @@
|
|||||||
# Shlink Docker image
|
# Shlink Docker image
|
||||||
|
|
||||||
[](https://hub.docker.com/r/shlinkio/shlink/)
|
[](https://github.com/shlinkio/shlink/actions?query=workflow%3A%22Build+docker+image%22)
|
||||||
|
[](https://hub.docker.com/r/shlinkio/shlink/)
|
||||||
|
|
||||||
This image provides an easy way to set up [shlink](https://shlink.io) on a container-based runtime.
|
This image provides an easy way to set up [shlink](https://shlink.io) on a container-based runtime.
|
||||||
|
|
||||||
It exposes a shlink instance served with [swoole](https://www.swoole.co.uk/), which persists data in a local [sqlite](https://www.sqlite.org/index.html) database.
|
It exposes a shlink instance served with [RoadRunner](https://roadrunner.dev) or [openswoole](https://openswoole.com/), which can be linked to external databases to persist data.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Shlink docker image exposes port `8080` in order to interact with its HTTP interface.
|
The most basic way to run Shlink's docker image is by providing these mandatory env vars.
|
||||||
|
|
||||||
It also expects these two env vars to be provided, in order to properly generate short URLs at runtime.
|
* `DEFAULT_DOMAIN`: The default short domain used for this shlink instance. For example **s.test**.
|
||||||
|
* `IS_HTTPS_ENABLED`: Either **true** or **false**. Tells if Shlink is being served with HTTPs or not.
|
||||||
|
* `GEOLITE_LICENSE_KEY`: Your GeoLite2 license key. [Learn more](https://shlink.io/documentation/geolite-license-key/) about this.
|
||||||
|
|
||||||
* `SHORT_DOMAIN_HOST`: The custom short domain used for this shlink instance. For example **doma.in**.
|
To run shlink on top of a local docker service, and using an internal SQLite database, do the following:
|
||||||
* `SHORT_DOMAIN_SCHEMA`: Either **http** or **https**.
|
|
||||||
|
|
||||||
So based on this, to run shlink on a local docker service, you should run a command like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --name shlink -p 8080:8080 -e SHORT_DOMAIN_HOST=doma.in -e SHORT_DOMAIN_SCHEMA=https -e GEOLITE_LICENSE_KEY=kjh23ljkbndskj345 shlinkio/shlink:stable
|
|
||||||
```
|
|
||||||
|
|
||||||
### Interact with shlink's CLI on a running container.
|
|
||||||
|
|
||||||
Once the shlink container is running, you can interact with the CLI tool by running `shlink` with any of the supported commands.
|
|
||||||
|
|
||||||
For example, if the container is called `shlink_container`, you can generate a new API key with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker exec -it shlink_container shlink api-key:generate
|
|
||||||
```
|
|
||||||
|
|
||||||
Or you can list all tags with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker exec -it shlink_container shlink tag:list
|
|
||||||
```
|
|
||||||
|
|
||||||
Or locate remaining visits with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker exec -it shlink_container shlink visit:locate
|
|
||||||
```
|
|
||||||
|
|
||||||
All shlink commands will work the same way.
|
|
||||||
|
|
||||||
You can also list all available commands just by running this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker exec -it shlink_container shlink
|
|
||||||
```
|
|
||||||
|
|
||||||
## Use an external DB
|
|
||||||
|
|
||||||
The image comes with a working sqlite database, but in production, it's strongly recommended using a distributed database.
|
|
||||||
|
|
||||||
It is possible to use a set of env vars to make this shlink instance interact with an external MySQL, MariaDB, PostgreSQL or Microsoft SQL Server database.
|
|
||||||
|
|
||||||
* `DB_DRIVER`: **[Mandatory]**. Use the value **mysql**, **maria**, **postgres** or **mssql** to prevent the sqlite database to be used.
|
|
||||||
* `DB_NAME`: [Optional]. The database name to be used. Defaults to **shlink**.
|
|
||||||
* `DB_USER`: **[Mandatory]**. The username credential for the database server.
|
|
||||||
* `DB_PASSWORD`: **[Mandatory]**. The password credential for the database server.
|
|
||||||
* `DB_HOST`: **[Mandatory]**. The host name of the server running the database engine.
|
|
||||||
* `DB_PORT`: [Optional]. The port in which the database service is running.
|
|
||||||
* Default value is based on the value provided for `DB_DRIVER`:
|
|
||||||
* **mysql** or **maria** -> `3306`
|
|
||||||
* **postgres** -> `5432`
|
|
||||||
* **mssql** -> `1433`
|
|
||||||
|
|
||||||
> PostgreSQL is supported since v1.16.1 and Microsoft SQL server since v2.1.0. Do not try to use them with previous versions.
|
|
||||||
|
|
||||||
Taking this into account, you could run shlink on a local docker service like this:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run \
|
docker run \
|
||||||
--name shlink \
|
--name shlink \
|
||||||
-p 8080:8080 \
|
-p 8080:8080 \
|
||||||
-e SHORT_DOMAIN_HOST=doma.in \
|
-e DEFAULT_DOMAIN=s.test \
|
||||||
-e SHORT_DOMAIN_SCHEMA=https \
|
-e IS_HTTPS_ENABLED=true \
|
||||||
-e DB_DRIVER=mysql \
|
|
||||||
-e DB_USER=root \
|
|
||||||
-e DB_PASSWORD=123abc \
|
|
||||||
-e DB_HOST=something.rds.amazonaws.com \
|
|
||||||
shlinkio/shlink:stable
|
|
||||||
```
|
|
||||||
|
|
||||||
You could even link to a local database running on a different container:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run \
|
|
||||||
--name shlink \
|
|
||||||
-p 8080:8080 \
|
|
||||||
[...] \
|
|
||||||
-e DB_HOST=some_mysql_container \
|
|
||||||
--link some_mysql_container \
|
|
||||||
shlinkio/shlink:stable
|
|
||||||
```
|
|
||||||
|
|
||||||
> If you have considered using SQLite but sharing the database file with a volume, read [this issue](https://github.com/shlinkio/shlink-docker-image/issues/40) first.
|
|
||||||
|
|
||||||
## Other integrations
|
|
||||||
|
|
||||||
### Use an external redis server
|
|
||||||
|
|
||||||
If you plan to run more than one Shlink instance, there are some resources that should be shared ([Multi instance considerations](#multi-instance-considerations)).
|
|
||||||
|
|
||||||
One of those resources are the locks Shlink generates to prevent some operations to be run more than once in parallel (in the future, these redis servers could be used for other caching operations).
|
|
||||||
|
|
||||||
In order to share those locks, you should use an external redis server (or a cluster of redis servers), by providing the `REDIS_SERVERS` env var.
|
|
||||||
|
|
||||||
It can be either one server name or a comma-separated list of servers.
|
|
||||||
|
|
||||||
> If more than one redis server is provided, Shlink will expect them to be configured as a [redis cluster](https://redis.io/topics/cluster-tutorial).
|
|
||||||
|
|
||||||
### Integrate with a mercure hub server
|
|
||||||
|
|
||||||
One way to get real time updates when certain events happen in Shlink is by integrating it with a [mercure hub](https://mercure.rocks/) server.
|
|
||||||
|
|
||||||
If you do that, Shlink will publish updates and other clients can subscribe to those.
|
|
||||||
|
|
||||||
There are three env vars you need to provide if you want to enable this:
|
|
||||||
|
|
||||||
* `MERCURE_PUBLIC_HUB_URL`: **[Mandatory]**. The public URL of a mercure hub server to which Shlink will sent updates. This URL will also be served to consumers that want to subscribe to those updates.
|
|
||||||
* `MERCURE_INTERNAL_HUB_URL`: **[Optional]**. An internal URL for a mercure hub. Will be used only when publishing updates to mercure, and does not need to be public. If this is not provided, the `MERCURE_PUBLIC_HUB_URL` one will be used to publish updates.
|
|
||||||
* `MERCURE_JWT_SECRET`: **[Mandatory]**. The secret key that was provided to the mercure hub server, in order to be able to generate valid JWTs for publishing/subscribing to that server.
|
|
||||||
|
|
||||||
So in order to run shlink with mercure integration, you would do it like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run \
|
|
||||||
--name shlink \
|
|
||||||
-p 8080:8080 \
|
|
||||||
-e SHORT_DOMAIN_HOST=doma.in \
|
|
||||||
-e SHORT_DOMAIN_SCHEMA=https \
|
|
||||||
-e "MERCURE_PUBLIC_HUB_URL=https://example.com"
|
|
||||||
-e "MERCURE_INTERNAL_HUB_URL=http://my-mercure-hub.prod.svc.cluster.local"
|
|
||||||
-e MERCURE_JWT_SECRET=super_secret_key
|
|
||||||
shlinkio/shlink:stable
|
|
||||||
```
|
|
||||||
|
|
||||||
## All supported env vars
|
|
||||||
|
|
||||||
A few env vars have been already used in previous examples, but this image supports others that can be used to customize its behavior.
|
|
||||||
|
|
||||||
This is the complete list of supported env vars:
|
|
||||||
|
|
||||||
* `SHORT_DOMAIN_HOST`: The custom short domain used for this shlink instance. For example **doma.in**.
|
|
||||||
* `SHORT_DOMAIN_SCHEMA`: Either **http** or **https**.
|
|
||||||
* `DB_DRIVER`: **sqlite** (which is the default value), **mysql**, **maria**, **postgres** or **mssql**.
|
|
||||||
* `DB_NAME`: The database name to be used when using an external database driver. Defaults to **shlink**.
|
|
||||||
* `DB_USER`: The username credential to be used when using an external database driver.
|
|
||||||
* `DB_PASSWORD`: The password credential to be used when using an external database driver.
|
|
||||||
* `DB_HOST`: The host name of the database server when using an external database driver.
|
|
||||||
* `DB_PORT`: The port in which the database service is running when using an external database driver.
|
|
||||||
* Default value is based on the value provided for `DB_DRIVER`:
|
|
||||||
* **mysql** or **maria** -> `3306`
|
|
||||||
* **postgres** -> `5432`
|
|
||||||
* **mssql** -> `1433`
|
|
||||||
* `DB_UNIX_SOCKET`: Alternatively to the `DB_HOST`, you can provide this to connect through unix sockets when using `mysql`, `maria` or `postgres` drivers.
|
|
||||||
* `DISABLE_TRACK_PARAM`: The name of a query param that can be used to visit short URLs avoiding the visit to be tracked. This feature won't be available if not value is provided.
|
|
||||||
* `DELETE_SHORT_URL_THRESHOLD`: The amount of visits on short URLs which will not allow them to be deleted. Defaults to `15`.
|
|
||||||
* `VALIDATE_URLS`: Boolean which tells if shlink should validate a status 20x is returned (after following redirects) when trying to shorten a URL. Defaults to `false`.
|
|
||||||
* `INVALID_SHORT_URL_REDIRECT_TO`: If a URL is provided here, when a user tries to access an invalid short URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page.
|
|
||||||
* `REGULAR_404_REDIRECT_TO`: If a URL is provided here, when a user tries to access a URL not matching any one supported by the router, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page.
|
|
||||||
* `BASE_URL_REDIRECT_TO`: If a URL is provided here, when a user tries to access Shlink's base URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page.
|
|
||||||
* `BASE_PATH`: The base path from which you plan to serve shlink, in case you don't want to serve it from the root of the domain. Defaults to `''`.
|
|
||||||
* `WEB_WORKER_NUM`: The amount of concurrent http requests this shlink instance will be able to server. Defaults to 16.
|
|
||||||
* `TASK_WORKER_NUM`: The amount of concurrent background tasks this shlink instance will be able to execute. Defaults to 16.
|
|
||||||
* `VISITS_WEBHOOKS`: A comma-separated list of URLs that will receive a `POST` request when a short URL receives a visit.
|
|
||||||
* `DEFAULT_SHORT_CODES_LENGTH`: The length you want generated short codes to have. It defaults to 5 and has to be at least 4, so any value smaller than that will fall back to 4.
|
|
||||||
* `GEOLITE_LICENSE_KEY`: The license key used to download new GeoLite2 database files. This is not mandatory, as a default license key is provided, but it is **strongly recommended** that you provide your own. Go to [https://shlink.io/documentation/geolite-license-key](https://shlink.io/documentation/geolite-license-key) to know how to generate it.
|
|
||||||
* `REDIS_SERVERS`: A comma-separated list of redis servers where Shlink locks are stored (locks are used to prevent some operations to be run more than once in parallel).
|
|
||||||
* `MERCURE_PUBLIC_HUB_URL`: The public URL of a mercure hub server to which Shlink will sent updates. This URL will also be served to consumers that want to subscribe to those updates.
|
|
||||||
* `MERCURE_INTERNAL_HUB_URL`: An internal URL for a mercure hub. Will be used only when publishing updates to mercure, and does not need to be public. If this is not provided but `MERCURE_PUBLIC_HUB_URL` was, the former one will be used to publish updates.
|
|
||||||
* `MERCURE_JWT_SECRET`: The secret key that was provided to the mercure hub server, in order to be able to generate valid JWTs for publishing/subscribing to that server.
|
|
||||||
* `ANONYMIZE_REMOTE_ADDR`: Tells if IP addresses from visitors should be obfuscated before storing them in the database. Default value is `true`. **Careful!** Setting this to `false` will make your Shlink instance no longer be in compliance with the GDPR and other similar data protection regulations.
|
|
||||||
* `REDIRECT_STATUS_CODE`: Either **301** or **302**. Used to determine if redirects from short to long URLs should be done with a 301 or 302 status. Defaults to 302.
|
|
||||||
* `REDIRECT_CACHE_LIFETIME`: Allows to set the amount of seconds that redirects should be cached when redirect status is 301. Default values is 30.
|
|
||||||
* `PORT`: Can be used to set the port in which shlink listens. Defaults to 8080 (Some cloud providers, like Google cloud or Heroku, expect to be able to customize exposed port by providing this env var).
|
|
||||||
|
|
||||||
An example using all env vars could look like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run \
|
|
||||||
--name shlink \
|
|
||||||
-p 8080:8888 \
|
|
||||||
-e SHORT_DOMAIN_HOST=doma.in \
|
|
||||||
-e SHORT_DOMAIN_SCHEMA=https \
|
|
||||||
-e PORT=8888 \
|
|
||||||
-e DB_DRIVER=mysql \
|
|
||||||
-e DB_NAME=shlink \
|
|
||||||
-e DB_USER=root \
|
|
||||||
-e DB_PASSWORD=123abc \
|
|
||||||
-e DB_HOST=something.rds.amazonaws.com \
|
|
||||||
-e DB_PORT=3306 \
|
|
||||||
-e DISABLE_TRACK_PARAM="no-track" \
|
|
||||||
-e DELETE_SHORT_URL_THRESHOLD=30 \
|
|
||||||
-e VALIDATE_URLS=true \
|
|
||||||
-e "INVALID_SHORT_URL_REDIRECT_TO=https://my-landing-page.com" \
|
|
||||||
-e "REGULAR_404_REDIRECT_TO=https://my-landing-page.com" \
|
|
||||||
-e "BASE_URL_REDIRECT_TO=https://my-landing-page.com" \
|
|
||||||
-e "REDIS_SERVERS=tcp://172.20.0.1:6379,tcp://172.20.0.2:6379" \
|
|
||||||
-e "BASE_PATH=/my-campaign" \
|
|
||||||
-e WEB_WORKER_NUM=64 \
|
|
||||||
-e TASK_WORKER_NUM=32 \
|
|
||||||
-e "VISITS_WEBHOOKS=http://my-api.com/api/v2.3/notify,https://third-party.io/foo" \
|
|
||||||
-e DEFAULT_SHORT_CODES_LENGTH=6 \
|
|
||||||
-e GEOLITE_LICENSE_KEY=kjh23ljkbndskj345 \
|
-e GEOLITE_LICENSE_KEY=kjh23ljkbndskj345 \
|
||||||
-e "MERCURE_PUBLIC_HUB_URL=https://example.com" \
|
|
||||||
-e "MERCURE_INTERNAL_HUB_URL=http://my-mercure-hub.prod.svc.cluster.local" \
|
|
||||||
-e MERCURE_JWT_SECRET=super_secret_key \
|
|
||||||
-e ANONYMIZE_REMOTE_ADDR=false \
|
|
||||||
-e REDIRECT_STATUS_CODE=301 \
|
|
||||||
-e REDIRECT_CACHE_LIFETIME=90 \
|
|
||||||
shlinkio/shlink:stable
|
shlinkio/shlink:stable
|
||||||
```
|
```
|
||||||
|
|
||||||
## [DEPRECATED] Provide config via volumes
|
## Full documentation
|
||||||
|
|
||||||
> As of v2.5.0, providing config through volumes is deprecated, and no new options will be added anymore. Use env vars instead.
|
All the features supported by Shlink are also supported by the docker image.
|
||||||
>
|
|
||||||
> Support for config options through volumes will be removed in Shlink v3.0.0
|
|
||||||
|
|
||||||
Rather than providing custom configuration via env vars, it is also possible ot provide config files in json format.
|
If you want to learn more, visit the [full documentation](https://shlink.io/documentation/install-docker-image/).
|
||||||
|
|
||||||
Mounting a volume at `config/params` you will make shlink load all the files on it with the `.config.json` suffix.
|
|
||||||
|
|
||||||
The whole configuration should have this format, but it can be split into multiple files that will be merged:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"disable_track_param": "my_param",
|
|
||||||
"delete_short_url_threshold": 30,
|
|
||||||
"short_domain_schema": "https",
|
|
||||||
"short_domain_host": "doma.in",
|
|
||||||
"validate_url": true,
|
|
||||||
"invalid_short_url_redirect_to": "https://my-landing-page.com",
|
|
||||||
"regular_404_redirect_to": "https://my-landing-page.com",
|
|
||||||
"base_url_redirect_to": "https://my-landing-page.com",
|
|
||||||
"base_path": "/my-campaign",
|
|
||||||
"web_worker_num": 64,
|
|
||||||
"task_worker_num": 32,
|
|
||||||
"default_short_codes_length": 6,
|
|
||||||
"redis_servers": [
|
|
||||||
"tcp://172.20.0.1:6379",
|
|
||||||
"tcp://172.20.0.2:6379"
|
|
||||||
],
|
|
||||||
"visits_webhooks": [
|
|
||||||
"http://my-api.com/api/v2.3/notify",
|
|
||||||
"https://third-party.io/foo"
|
|
||||||
],
|
|
||||||
"db_config": {
|
|
||||||
"driver": "pdo_mysql",
|
|
||||||
"dbname": "shlink",
|
|
||||||
"user": "root",
|
|
||||||
"password": "123abc",
|
|
||||||
"host": "something.rds.amazonaws.com",
|
|
||||||
"port": "3306"
|
|
||||||
},
|
|
||||||
"geolite_license_key": "kjh23ljkbndskj345",
|
|
||||||
"mercure_public_hub_url": "https://example.com",
|
|
||||||
"mercure_internal_hub_url": "http://my-mercure-hub.prod.svc.cluster.local",
|
|
||||||
"mercure_jwt_secret": "super_secret_key",
|
|
||||||
"anonymize_remote_addr": false,
|
|
||||||
"redirect_status_code": 301,
|
|
||||||
"redirect_cache_lifetime": 90,
|
|
||||||
"port": 8888
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> This is internally parsed to how shlink expects the config. If you are using a version previous to 1.17.0, this parser is not present and you need to provide a config structure like the one [documented previously](https://github.com/shlinkio/shlink-docker-image/tree/v1.16.3#provide-config-via-volumes).
|
|
||||||
|
|
||||||
Once created just run shlink with the volume:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --name shlink -p 8080:8080 -v ${PWD}/my/config/dir:/etc/shlink/config/params shlinkio/shlink:stable
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multi-architecture
|
|
||||||
|
|
||||||
Starting on v2.3.0, Shlink's docker image is built for multiple architectures.
|
|
||||||
|
|
||||||
The only limitation is that images for architectures other than `amd64` will not have support for Microsoft SQL databases, since there are no official binaries.
|
|
||||||
|
|
||||||
## Multi-instance considerations
|
|
||||||
|
|
||||||
These are some considerations to take into account when running multiple instances of shlink.
|
|
||||||
|
|
||||||
* Some operations performed by Shlink should never be run more than once at the same time (like creating the database for the first time, or downloading the GeoLite2 database). For this reason, Shlink uses a locking system.
|
|
||||||
|
|
||||||
However, these locks are locally scoped to each Shlink instance by default.
|
|
||||||
|
|
||||||
You can (and should) make the locks to be shared by all Shlink instances by using a redis server/cluster. Just define the `REDIS_SERVERS` env var with the list of servers.
|
|
||||||
|
|
||||||
## Versions
|
|
||||||
|
|
||||||
Versioning on this docker image works as follows:
|
|
||||||
|
|
||||||
* `X.X.X`: when providing a specific version number, the image version will match the shlink version it contains. For example, installing `shlinkio/shlink:1.15.0`, you will get an image containing shlink v1.15.0.
|
|
||||||
* `stable`: always holds the latest stable tag. For example, if latest shlink version is 2.0.0, installing `shlinkio/shlink:stable`, you will get an image containing shlink v2.0.0
|
|
||||||
* `latest`: always holds the latest contents, and it's considered unstable and not suitable for production.
|
|
||||||
|
|
||||||
> **Important**: The docker image was introduced with shlink v1.15.0, so there are no official images previous to that versions.
|
|
||||||
|
|||||||
25
docker/build
25
docker/build
@@ -1,25 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
PLATFORMS="linux/arm/v7,linux/arm64/v8,linux/amd64"
|
|
||||||
DOCKER_IMAGE="shlinkio/shlink"
|
|
||||||
|
|
||||||
# If ref is not develop, then this is a tag. Build that docker tag and also "stable"
|
|
||||||
if [[ "$GITHUB_REF" != *"develop"* ]]; then
|
|
||||||
VERSION=${GITHUB_REF#refs/tags/v}
|
|
||||||
TAGS="-t ${DOCKER_IMAGE}:${VERSION}"
|
|
||||||
# Push stable tag only if this is not an alpha or beta tag
|
|
||||||
[[ $GITHUB_REF != *"alpha"* && $GITHUB_REF != *"beta"* ]] && TAGS="${TAGS} -t ${DOCKER_IMAGE}:stable"
|
|
||||||
|
|
||||||
docker buildx build --push \
|
|
||||||
--build-arg SHLINK_VERSION=${VERSION} \
|
|
||||||
--platform ${PLATFORMS} \
|
|
||||||
${TAGS} .
|
|
||||||
|
|
||||||
# If build branch is develop, build latest
|
|
||||||
elif [[ "$GITHUB_REF" == *"develop"* ]]; then
|
|
||||||
docker buildx build --push \
|
|
||||||
--platform ${PLATFORMS} \
|
|
||||||
-t ${DOCKER_IMAGE}:latest .
|
|
||||||
fi
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
log_errors_max_len=0
|
log_errors_max_len=0
|
||||||
zend.assertions=1
|
zend.assertions=1
|
||||||
assert.exception=1
|
assert.exception=1
|
||||||
|
memory_limit=512M
|
||||||
|
|||||||
@@ -4,173 +4,15 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Monolog\Handler\StreamHandler;
|
use Shlinkio\Shlink\Common\Logger\LoggerType;
|
||||||
use Monolog\Logger;
|
|
||||||
|
|
||||||
use function explode;
|
|
||||||
use function Functional\contains;
|
|
||||||
use function Shlinkio\Shlink\Common\env;
|
|
||||||
|
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_DELETE_SHORT_URL_THRESHOLD;
|
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_CACHE_LIFETIME;
|
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_REDIRECT_STATUS_CODE;
|
|
||||||
use const Shlinkio\Shlink\Core\DEFAULT_SHORT_CODES_LENGTH;
|
|
||||||
use const Shlinkio\Shlink\Core\MIN_SHORT_CODES_LENGTH;
|
|
||||||
|
|
||||||
$helper = new class {
|
|
||||||
private const DB_DRIVERS_MAP = [
|
|
||||||
'mysql' => 'pdo_mysql',
|
|
||||||
'maria' => 'pdo_mysql',
|
|
||||||
'postgres' => 'pdo_pgsql',
|
|
||||||
'mssql' => 'pdo_sqlsrv',
|
|
||||||
];
|
|
||||||
private const DB_PORTS_MAP = [
|
|
||||||
'mysql' => '3306',
|
|
||||||
'maria' => '3306',
|
|
||||||
'postgres' => '5432',
|
|
||||||
'mssql' => '1433',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function getDbConfig(): array
|
|
||||||
{
|
|
||||||
$driver = env('DB_DRIVER');
|
|
||||||
$isMysql = contains(['maria', 'mysql'], $driver);
|
|
||||||
if ($driver === null || $driver === 'sqlite') {
|
|
||||||
return [
|
|
||||||
'driver' => 'pdo_sqlite',
|
|
||||||
'path' => 'data/database.sqlite',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$driverOptions = ! $isMysql ? [] : [
|
|
||||||
// 1002 -> PDO::MYSQL_ATTR_INIT_COMMAND
|
|
||||||
1002 => 'SET NAMES utf8',
|
|
||||||
// 1000 -> PDO::MYSQL_ATTR_USE_BUFFERED_QUERY
|
|
||||||
1000 => true,
|
|
||||||
];
|
|
||||||
return [
|
|
||||||
'driver' => self::DB_DRIVERS_MAP[$driver],
|
|
||||||
'dbname' => env('DB_NAME', 'shlink'),
|
|
||||||
'user' => env('DB_USER'),
|
|
||||||
'password' => env('DB_PASSWORD'),
|
|
||||||
'host' => env('DB_HOST', $driver === 'postgres' ? env('DB_UNIX_SOCKET') : null),
|
|
||||||
'port' => env('DB_PORT', self::DB_PORTS_MAP[$driver]),
|
|
||||||
'driverOptions' => $driverOptions,
|
|
||||||
'unix_socket' => $isMysql ? env('DB_UNIX_SOCKET') : null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNotFoundRedirectsConfig(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'invalid_short_url' => env('INVALID_SHORT_URL_REDIRECT_TO'),
|
|
||||||
'regular_404' => env('REGULAR_404_REDIRECT_TO'),
|
|
||||||
'base_url' => env('BASE_URL_REDIRECT_TO'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitsWebhooks(): array
|
|
||||||
{
|
|
||||||
$webhooks = env('VISITS_WEBHOOKS');
|
|
||||||
return $webhooks === null ? [] : explode(',', $webhooks);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRedisConfig(): ?array
|
|
||||||
{
|
|
||||||
$redisServers = env('REDIS_SERVERS');
|
|
||||||
return $redisServers === null ? null : ['servers' => $redisServers];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDefaultShortCodesLength(): int
|
|
||||||
{
|
|
||||||
$value = (int) env('DEFAULT_SHORT_CODES_LENGTH', DEFAULT_SHORT_CODES_LENGTH);
|
|
||||||
return $value < MIN_SHORT_CODES_LENGTH ? MIN_SHORT_CODES_LENGTH : $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMercureConfig(): array
|
|
||||||
{
|
|
||||||
$publicUrl = env('MERCURE_PUBLIC_HUB_URL');
|
|
||||||
|
|
||||||
return [
|
|
||||||
'public_hub_url' => $publicUrl,
|
|
||||||
'internal_hub_url' => env('MERCURE_INTERNAL_HUB_URL', $publicUrl),
|
|
||||||
'jwt_secret' => env('MERCURE_JWT_SECRET'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'app_options' => [
|
|
||||||
'disable_track_param' => env('DISABLE_TRACK_PARAM'),
|
|
||||||
],
|
|
||||||
|
|
||||||
'delete_short_urls' => [
|
|
||||||
'check_visits_threshold' => true,
|
|
||||||
'visits_threshold' => (int) env('DELETE_SHORT_URL_THRESHOLD', DEFAULT_DELETE_SHORT_URL_THRESHOLD),
|
|
||||||
],
|
|
||||||
|
|
||||||
'entity_manager' => [
|
|
||||||
'connection' => $helper->getDbConfig(),
|
|
||||||
],
|
|
||||||
|
|
||||||
'url_shortener' => [
|
|
||||||
'domain' => [
|
|
||||||
'schema' => env('SHORT_DOMAIN_SCHEMA', 'http'),
|
|
||||||
'hostname' => env('SHORT_DOMAIN_HOST', ''),
|
|
||||||
],
|
|
||||||
'validate_url' => (bool) env('VALIDATE_URLS', false),
|
|
||||||
'anonymize_remote_addr' => (bool) env('ANONYMIZE_REMOTE_ADDR', true),
|
|
||||||
'visits_webhooks' => $helper->getVisitsWebhooks(),
|
|
||||||
'default_short_codes_length' => $helper->getDefaultShortCodesLength(),
|
|
||||||
'redirect_status_code' => (int) env('REDIRECT_STATUS_CODE', DEFAULT_REDIRECT_STATUS_CODE),
|
|
||||||
'redirect_cache_lifetime' => (int) env('REDIRECT_CACHE_LIFETIME', DEFAULT_REDIRECT_CACHE_LIFETIME),
|
|
||||||
],
|
|
||||||
|
|
||||||
'not_found_redirects' => $helper->getNotFoundRedirectsConfig(),
|
|
||||||
|
|
||||||
'logger' => [
|
'logger' => [
|
||||||
'Shlink' => [
|
'Shlink' => [
|
||||||
'handlers' => [
|
'type' => LoggerType::STREAM->value,
|
||||||
'shlink_handler' => [
|
'destination' => 'php://stderr',
|
||||||
'name' => StreamHandler::class,
|
|
||||||
'params' => [
|
|
||||||
'level' => Logger::INFO,
|
|
||||||
'stream' => 'php://stdout',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'dependencies' => [
|
|
||||||
'aliases' => env('REDIS_SERVERS') === null ? [] : [
|
|
||||||
'lock_store' => 'redis_lock_store',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'cache' => [
|
|
||||||
'redis' => $helper->getRedisConfig(),
|
|
||||||
],
|
|
||||||
|
|
||||||
'router' => [
|
|
||||||
'base_path' => env('BASE_PATH', ''),
|
|
||||||
],
|
|
||||||
|
|
||||||
'mezzio-swoole' => [
|
|
||||||
'swoole-http-server' => [
|
|
||||||
'port' => (int) env('PORT', 8080),
|
|
||||||
'options' => [
|
|
||||||
'worker_num' => (int) env('WEB_WORKER_NUM', 16),
|
|
||||||
'task_worker_num' => (int) env('TASK_WORKER_NUM', 16),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'geolite2' => [
|
|
||||||
'license_key' => env('GEOLITE_LICENSE_KEY', 'G4Lm0C60yJsnkdPi'),
|
|
||||||
],
|
|
||||||
|
|
||||||
'mercure' => $helper->getMercureConfig(),
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,18 +3,36 @@ set -e
|
|||||||
|
|
||||||
cd /etc/shlink
|
cd /etc/shlink
|
||||||
|
|
||||||
echo "Creating fresh database if needed..."
|
# Create data directories if they do not exist. This allows data dir to be mounted as an empty dir if needed
|
||||||
php bin/cli db:create -n -q
|
mkdir -p data/cache data/locks data/log data/proxies
|
||||||
|
|
||||||
echo "Updating database..."
|
flags="--no-interaction --clear-db-cache"
|
||||||
php bin/cli db:migrate -n -q
|
|
||||||
|
|
||||||
echo "Generating proxies..."
|
# Skip downloading GeoLite2 db file if the license key env var was not defined or skipping was explicitly set
|
||||||
php vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies -n -q
|
if [ -z "${GEOLITE_LICENSE_KEY}" ] || [ "${SKIP_INITIAL_GEOLITE_DOWNLOAD}" = "true" ]; then
|
||||||
|
flags="${flags} --skip-download-geolite"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Clearing entities cache..."
|
# If INITIAL_API_KEY was provided, create an initial API key
|
||||||
php vendor/doctrine/orm/bin/doctrine.php orm:clear-cache:metadata -n -q
|
if [ -n "${INITIAL_API_KEY}" ]; then
|
||||||
|
flags="${flags} --initial-api-key=${INITIAL_API_KEY}"
|
||||||
|
fi
|
||||||
|
|
||||||
# When restarting the container, swoole might think it is already in execution
|
php vendor/bin/shlink-installer init ${flags}
|
||||||
# This forces the app to be started every second until the exit code is 0
|
|
||||||
until php vendor/mezzio/mezzio-swoole/bin/mezzio-swoole start; do sleep 1 ; done
|
# Periodically run visit:locate every hour, if ENABLE_PERIODIC_VISIT_LOCATE=true was provided and running as root
|
||||||
|
# FIXME: ENABLE_PERIODIC_VISIT_LOCATE is deprecated. Remove cron support in Shlink 4.0.0
|
||||||
|
if [ "${ENABLE_PERIODIC_VISIT_LOCATE}" = "true" ] && [ "${SHLINK_USER_ID}" = "root" ]; then
|
||||||
|
echo "Configuring periodic visit location..."
|
||||||
|
echo "0 * * * * php /etc/shlink/bin/cli visit:locate -q" > /etc/crontabs/root
|
||||||
|
/usr/sbin/crond &
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$SHLINK_RUNTIME" = 'openswoole' ]; then
|
||||||
|
# Openswoole is deprecated. Remove in Shlink 4.0.0
|
||||||
|
# When restarting the container, openswoole might think it is already in execution
|
||||||
|
# This forces the app to be started every second until the exit code is 0
|
||||||
|
until php vendor/bin/laminas mezzio:swoole:start; do sleep 1 ; done
|
||||||
|
elif [ "$SHLINK_RUNTIME" = 'rr' ]; then
|
||||||
|
./bin/rr serve -c config/roadrunner/.rr.yml
|
||||||
|
fi
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user