Compare commits

...

217 Commits
v0.4.0 ... main

Author SHA1 Message Date
link
0d3b2f444e
Update README.md (#2323)
Some checks failed
Collect Code Coverage / build (push) Has been cancelled
Auto Publish Website / goreleaser (push) Has been cancelled
Signed-off-by: link <a624669980@163.com>
2025-08-06 16:54:02 +08:00
link
030bcd1095
Update README.md (#2322)
Signed-off-by: link <a624669980@163.com>
2025-08-06 16:09:10 +08:00
link
dad2f3f8c2
Update README.md (#2321)
Signed-off-by: link <a624669980@163.com>
2025-08-06 16:06:08 +08:00
link
b09160e76b
Update README.md (#2320)
Signed-off-by: link <a624669980@163.com>
2025-08-06 16:05:13 +08:00
link
12fc102753
Update README.md (#2319)
Signed-off-by: link <a624669980@163.com>
2025-08-06 15:51:40 +08:00
link
6ffe2cc89d
add twitter (#2318)
Signed-off-by: link <a624669980@163.com>
2025-08-06 15:48:06 +08:00
link
b9098101e2
Update system.go
Some checks failed
Collect Code Coverage / build (push) Has been cancelled
Auto Publish Website / goreleaser (push) Has been cancelled
Signed-off-by: link <a624669980@163.com>
2025-07-01 15:05:40 +08:00
U2FsdGVkX1
a6ff39e47d
Add initial RISC-V support (#2206)
Some checks failed
Collect Code Coverage / build (push) Has been cancelled
Auto Publish Website / goreleaser (push) Has been cancelled
It depends upon https://github.com/IceWhaleTech/github/pull/3

resolved https://github.com/IceWhaleTech/CasaOS/issues/1669
2025-04-16 14:16:50 +08:00
CorrectRoad
23eb739f01
Update README.md (#2185)
Some checks failed
Collect Code Coverage / build (push) Has been cancelled
Auto Publish Website / goreleaser (push) Has been cancelled
Signed-off-by: CorrectRoad <a778917369@gmail.com>
2025-03-08 12:38:58 +08:00
Ns2Kracy
ba285cb8bd
fix: update user info retrieval and clean up unused code (#2101)
Some checks failed
Collect Code Coverage / build (push) Has been cancelled
Auto Publish Website / goreleaser (push) Has been cancelled
2024-12-19 11:14:15 +08:00
Ns2Kracy
63f01489a8
update version (#2098)
Some checks failed
Collect Code Coverage / build (push) Has been cancelled
Auto Publish Website / goreleaser (push) Has been cancelled
2024-12-17 15:54:54 +08:00
Ns2Kracy
6bbb0802aa
fix: problem while getting storage type (#2097) 2024-12-17 15:53:04 +08:00
Ns2Kracy
c26cf4dbec
fix: cloud storage (#2096)
Some checks are pending
Collect Code Coverage / build (push) Waiting to run
Auto Publish Website / goreleaser (push) Waiting to run
2024-12-17 15:23:15 +08:00
Ns2Kracy
958a483385
Update constants.go (#2083)
Some checks failed
Collect Code Coverage / build (push) Has been cancelled
Auto Publish Website / goreleaser (push) Has been cancelled
2024-12-10 17:42:46 +08:00
Ns2Kracy
16e97ec66f
fix: too much log (#2078)
Some checks failed
Collect Code Coverage / build (push) Has been cancelled
Auto Publish Website / goreleaser (push) Has been cancelled
2024-12-06 15:43:54 +08:00
David Wood
5ea588b813
chore: fix problematic comments (#2030)
Some checks failed
Collect Code Coverage / build (push) Has been cancelled
Auto Publish Website / goreleaser (push) Has been cancelled
2024-12-04 11:04:17 +08:00
Lauren
7da0f0d49e
Update README.md
Signed-off-by: Lauren <Lauren-ED209@users.noreply.github.com>
2024-10-17 18:59:41 +08:00
Ns2Kracy
2adb795896
Update to 0.4.13 (#2004) 2024-09-18 15:53:41 +08:00
Ns2Kracy
8b251dc407
fix: get file content twice (#2000) 2024-09-14 23:21:03 +08:00
CorrectRoad
e4bf67dad5
chore: bump casaos version (#1999)
Signed-off-by: CorrectRoad <a778917369@gmail.com>
2024-09-14 11:37:28 +08:00
CorrectRoad
e8f9d3aaf5
fix: fix GitHub Action not work (#1996) 2024-09-11 16:04:30 +08:00
CorrectRoad
bb0d9ac25c
feat: add sync OpenAPI workflows (#1995) 2024-09-11 16:00:28 +08:00
link
3a835c00e3
Update constants.go (#1961)
Signed-off-by: link <a624669980@163.com>
2024-08-14 13:35:09 +08:00
link
e4447981cb
Update constants.go (#1950)
Signed-off-by: link <a624669980@163.com>
2024-08-12 14:19:40 +08:00
James Stroud
170b599e86
add instructions to update CasaOS (#1925)
This will help users to updates and determine the version of CasaOS from
a terminal session. I tested all commands multiple times. I hope you
like my suggestions. Take care
2024-07-31 14:26:24 +08:00
Ns2Kracy
18ce1d6342
fix: safe cmd (#1921) 2024-07-24 21:46:08 +08:00
Kian Kasad
1c483a5d9c
Fix typo in README.md: "tutoial" → "tutorial" (#1862)
Just fixing a typo I noticed while reading the README.

Signed-off-by: Kian Kasad <kian@kasad.com>
2024-07-23 10:34:45 +08:00
Ns2Kracy
8f7c99779f
feat: migrate gin to echo (#1854) 2024-06-04 14:14:55 +08:00
Link
0883f5f3aa update version 2024-05-15 09:27:01 +01:00
CorrectRoad
36dda53e9c
chore: bump version to 0.4.8 (#1750) 2024-03-26 11:28:20 +08:00
Link
53c3879907 update version 2024-03-05 06:43:33 +00:00
LinkLeong
ef57d3348d update version 2024-01-23 09:09:15 +00:00
link
7a76aca022
Update constants.go (#1615)
Signed-off-by: link <a624669980@163.com>
2024-01-18 15:45:35 +08:00
CorrectRoadH
fffdc7fd5e
fix: fix upload folder fail (#1614)
Signed-off-by: CorrectRoadH <a778917369@gmail.com>
2024-01-18 12:23:27 +08:00
CorrectRoadH
29d16d13ba
fix: fix upload specify file format is fail (#1613) 2024-01-18 11:39:49 +08:00
CorrectRoadH
54a115ae89
fix: fix upload folder is wrong (#1611) 2024-01-17 17:32:19 +08:00
John Guan
17fa7868a4
Update README.md (#1589)
add youtube link

Signed-off-by: John Guan <Guan.Ningchuan@gmail.com>
2024-01-05 15:05:16 +08:00
Eng Zer Jun
27d6dd86e2
Replace satori/go.uuid with google/uuid (#1573)
`github.com/satori/go.uuid` is no longer being actively maintained (last
commit was 5 years ago [^1]). So we should just use the newer
`github.com/google/uuid`.

[^1]: https://github.com/satori/go.uuid/commits/master/

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2023-12-28 16:40:17 +08:00
CorrectRoadH
ae50a9bb17
feat: add file upload implement to v2 api (#1566)
Signed-off-by: CorrectRoadH <a778917369@gmail.com>
2023-12-27 16:17:33 +08:00
Link
caf3347da9 update common 2023-12-26 09:51:43 +00:00
CorrectRoadH
718a08eab2
fix: fix codecov action run fail (#1567) 2023-12-21 15:14:36 +08:00
John Guan
6f722b3506
Update push_events_to_discord.yml (#1544)
remove alpha webhook

Signed-off-by: John Guan <Guan.Ningchuan@gmail.com>
2023-12-05 15:54:03 +08:00
Link
e722d841a9 update release 2023-11-28 08:08:59 +00:00
Link
3ccf58695b update go sum 2023-11-28 07:53:20 +00:00
Link
c52386cef4 update release 2023-11-28 07:48:38 +00:00
Link
acac2df40f update go.mod 2023-11-20 08:30:14 +00:00
Link
0b507cb7ad update action 2023-11-20 08:22:13 +00:00
Link
ac90509c66 update action 2023-11-20 08:20:03 +00:00
Link
abf7134710 update releaser 2023-11-20 08:13:41 +00:00
Link
f9a26af93f update goreleaser 2023-11-20 08:05:25 +00:00
John Guan
33eacbf4b3
Update demo.yml (#1513)
Update to CasaOS-v0.4.4-3-Demo-1700132299

Signed-off-by: John Guan <Guan.Ningchuan@gmail.com>
2023-11-16 19:05:48 +08:00
John Guan
262e0d49c8
Update demo.yml (#1512)
update CasaOS demo to v0.4.4.3-demo

Signed-off-by: John Guan <Guan.Ningchuan@gmail.com>
2023-11-16 18:14:22 +08:00
LinkLeong
689b0f769a update file 2023-11-10 03:24:24 +00:00
LinkLeong
8f8a082888 update zt 2023-11-09 06:04:45 +00:00
link
9b8c6f4299
Delete __debug_bin782867005
Signed-off-by: link <a624669980@163.com>
2023-10-24 18:57:41 +08:00
LinkLeong
657cbe5c41 delay delete temp file 2023-10-23 09:06:41 +01:00
Liviu
b17bff68dd
Added small description on th casosapi health and file sys (#1417)
🐙  Hi , 

hope the small description is oke ! :)
2023-09-27 18:35:17 +08:00
LinkLeong
7807cfdb01 updat version 2023-09-22 10:03:34 +01:00
LinkLeong
9e5381710f update file task 2023-09-12 09:16:18 +01:00
LinkLeong
fa62c65526 add amd cpu suport 2023-09-11 09:39:50 +01:00
LinkLeong
3aea945277 update readme 2023-09-11 04:22:08 +01:00
LinkLeong
4d8ca182cb Merge branch 'main' of github.com:IceWhaleTech/CasaOS 2023-09-04 04:30:59 +01:00
LinkLeong
b727606a0a update version 2023-09-04 04:30:53 +01:00
Ikko Eltociear Ashimine
428be5f2c2
Update casa.yml (#1375)
enviroment -> environment

Signed-off-by: Ikko Eltociear Ashimine <eltociear@gmail.com>
Co-authored-by: Tiger Wang <tigerwang@outlook.com>
2023-09-04 07:36:38 +08:00
aevum123
995d67543d
Update README.md (#1374)
Couple small grammar fixes

Signed-off-by: aevum123 <87996440+aevum123@users.noreply.github.com>
2023-09-04 07:35:20 +08:00
omahs
590beac43a
Fix minor typos (#1372)
Fix minor typos

Signed-off-by: omahs <73983677+omahs@users.noreply.github.com>
2023-09-03 20:56:48 +08:00
kerta1n
e7bf227232
Update README.md, Debian 12 stable (#1352)
Now that Debian 12 is stable, IWT's team should check to make sure that
CasaOS is stable and then update the wiki

Signed-off-by: kerta1n <36344851+kerta1n@users.noreply.github.com>
Co-authored-by: Tiger Wang <tigerwang@outlook.com>
2023-09-01 15:41:33 +08:00
LinkLeong
eedfdde311 add filter 2023-08-31 07:26:44 +01:00
LinkLeong
38c7b5a569 update name 2023-08-24 11:57:32 +01:00
LinkLeong
8c0b219621 update path 2023-08-24 11:21:44 +01:00
LinkLeong
23fc677f30 Merge branch 'main' of github.com:IceWhaleTech/CasaOS 2023-08-24 09:23:45 +01:00
LinkLeong
a06508783c update path 2023-08-24 09:23:40 +01:00
Tiger Wang
37fee157dd
add message-bus-codegen CLI tool for generating a markdown of all event types (#1346) 2023-08-23 18:21:12 +08:00
LinkLeong
3f1c7098bd add v3 file path 2023-08-23 08:29:41 +01:00
LinkLeong
b11d046c52 add route 2023-08-23 04:35:43 +01:00
LinkLeong
d6a9ba65ed add onedrive icon 2023-08-22 05:01:40 +01:00
link
5f1df76dbf
Community (#1341) 2023-08-18 15:46:29 +08:00
Tiger Wang
0cf353c56e
create default config if it does not already exist (#1304) 2023-07-31 16:53:03 +08:00
LinkLeong
5dc6297047 Merge branch 'main' of github.com:IceWhaleTech/CasaOS 2023-07-25 03:29:00 +01:00
LinkLeong
f26ae2793c Update zerotier 2023-07-25 03:28:52 +01:00
CorrectRoadH
d7641e1b93
chore: fix the readme error (#1270)
Signed-off-by: CorrectRoadH <a778917369@gmail.com>
2023-07-21 20:44:11 +08:00
CorrectRoadH
37c496fd6a
chore:add zhanghenxing to contributor (#1269)
same to the title
2023-07-21 20:43:07 +08:00
CorrectRoadH
cb15c06f7e
chore: add Contributor (#1268)
😆add me to the contributor list🤫
My Code in https://github.com/IceWhaleTech/CasaOS-AppManagement
My Document in https://github.com/IceWhaleTech/CasaOS-AppStore/pull/187
2023-07-21 20:38:04 +08:00
LinkLeong
89a0ea34b0 Add get version function 2023-07-20 04:12:46 +01:00
LinkLeong
d8dd815baa Update ip 2023-07-19 07:46:23 +01:00
LinkLeong
a77b5d1954 Update ip 2023-07-18 08:54:01 +01:00
LinkLeong
d0fb2f06f5 Supplementary log 2023-07-18 08:13:11 +01:00
raller1028
dd8032a8be
Update push_test_server.yml (#1243)
Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-07-12 15:06:55 +08:00
raller1028
760882e147
Update push_test_server.yml (#1233)
Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-07-03 17:10:45 +08:00
LinkLeong
dfeed76a66 Update permission issues 2023-07-03 10:08:05 +01:00
Tiger Wang
28d724731a
remove submit application template 2023-06-27 08:18:15 -04:00
Tiger Wang
4549d8778b
remove feedback template 2023-06-27 08:15:52 -04:00
Tiger Wang
05cadaabf3
update bug_report.md (#1216)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-06-26 17:36:34 -04:00
link
1a0c15208c
Update demo.yml (#1213)
Signed-off-by: link <a624669980@163.com>
2023-06-25 17:09:31 +08:00
link
4f5b4b0887
Update demo.yml (#1212)
Signed-off-by: link <a624669980@163.com>
2023-06-25 17:07:17 +08:00
link
cbfe44be61
Update demo.yml (#1211)
Signed-off-by: link <a624669980@163.com>
2023-06-25 17:05:27 +08:00
link
e1bbb998df
Update demo.yml (#1210)
Signed-off-by: link <a624669980@163.com>
2023-06-25 16:58:29 +08:00
link
f48cddf924
Up json (#1186) 2023-06-14 11:32:04 +08:00
raller1028
23ce3487e1
Update demo.yml (#1174)
Add demo reset error handle

Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-06-07 16:09:01 +08:00
link
6246421dae
Up json (#1173)
Signed-off-by: link <a624669980@163.com>
2023-06-07 15:23:29 +08:00
link
f867453573
Update entry json output (#1172) 2023-06-07 15:21:22 +08:00
link
78e7a8b411
Add zt dynamic ip (#1168) 2023-06-05 16:49:32 +08:00
link
1aa8fb600b
Remove 9527 (#1162) 2023-06-02 18:51:25 +08:00
link
7ecfea71ab
Update json (#1160) 2023-06-01 17:09:34 +08:00
link
d911d80254
Socket update (#1159) 2023-06-01 10:22:04 +08:00
link
9bfe37305c
Socket update (#1154) 2023-05-31 18:02:46 +08:00
link
94dd2e00c7
Socket update (#1153) 2023-05-31 16:10:14 +08:00
link
c5d2cebe92
Socket update (#1151) 2023-05-30 18:57:15 +08:00
link
955e8dea07
Update socket address (#1147) 2023-05-29 17:17:39 +08:00
link
4c7b3a749b
Zt update (#1146) 2023-05-29 15:38:39 +08:00
link
e86a61596b
Update zt (#1139) 2023-05-26 18:37:16 +08:00
link
c9617e583f
Add zertier (#1137)
Signed-off-by: link <a624669980@163.com>
2023-05-26 17:45:18 +08:00
link
dda15b0821
Add zertier (#1124)
Signed-off-by: link <a624669980@163.com>
2023-05-25 12:14:32 +08:00
link
50816c68b0
Update demo.yml (#1121)
Signed-off-by: link <a624669980@163.com>
2023-05-24 19:12:18 +08:00
李旭海
c5de0319fb
Update publish_npm.yaml (#1120)
Signed-off-by: 李旭海 <lxh_shine@hotmail.com>
Signed-off-by: zhanghengxin <812718649@qq.com>
Co-authored-by: zhanghengxin <812718649@qq.com>
2023-05-24 13:49:03 +08:00
link
78b7c6ce09
Add zertier (#1116) 2023-05-23 16:39:25 +08:00
link
b420a2d930
Add logs function (#1108) 2023-05-22 17:28:30 +08:00
zhanghengxin
c67ee1731c
Introducing new features.Incorporating automatic publishin… (#1107)
Introducing new features.Incorporating automatic publishing for the
openAPI npm package.
2023-05-22 16:45:06 +08:00
raller1028
eaca399ef9
Add zerotier route (#1098) 2023-05-22 16:44:39 +08:00
LinkLeong
e0e9f97764 Update ip address 2023-05-22 04:32:47 +01:00
link
2fefee87fa
Fixed compression error (#1097) 2023-05-19 16:50:48 +08:00
link
280ad4fcf9
Add merge json (#1079) 2023-05-15 11:01:21 +08:00
Tiger Wang
538639b623
fix (*service).Ports() missing ports from IPv6 (#1069) 2023-05-09 17:02:59 -04:00
老竭力
39535d6a38
Update README.md (#1067)
Signed-off-by: 老竭力 <jerrykuku@qq.com>
2023-05-09 18:13:29 +08:00
老竭力
4d977d7a62
Update demo.yml (#1049)
Signed-off-by: 老竭力 <jerrykuku@qq.com>
2023-05-06 11:35:44 +08:00
link
08c500c434
Add local jwt (#1047) 2023-05-05 10:37:23 +08:00
link
705bf1facb
Update jwt (#1025) 2023-04-26 16:00:43 +08:00
Tiger Wang
aae1802191 bump version from 0.4.3 to 0.4.4
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-04-24 16:11:16 -04:00
Tiger Wang
8e1b9b82c1
add /v2/casaos/health/ports to get ports in use (#1023)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-04-24 16:10:15 -04:00
link
af440eac55
Update samba (#1021) 2023-04-24 11:24:22 +08:00
link
34b4e154a1
fix reboot auto mount (#1007) 2023-04-12 13:53:27 +08:00
Lauren
79db93ec02
Update README.md
Update the project introduction

Signed-off-by: Lauren <Lauren-ED209@users.noreply.github.com>
2023-03-29 17:57:17 +08:00
Tiger Wang
f8ec3b20cd
upgrade dependencies (#978)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-03-25 23:37:00 -04:00
LinkLeong
8c7c8dc1ec Adding asynchronous methods 2023-03-24 08:07:38 +00:00
link
60a141fe25
Test init log (#974) 2023-03-23 12:09:46 +08:00
link
02e712f649
Test init log (#972) 2023-03-22 16:06:48 +08:00
link
7022cf5d29
Add test log init (#971) 2023-03-22 14:07:01 +08:00
Tiger Wang
6cf46ce50c
update github workflow with newer version of different actions (#970) 2023-03-21 22:59:31 -04:00
link
202c1de570
Update file count (#969) 2023-03-21 14:25:01 +08:00
link
858ab5b124
Update file count (#968) 2023-03-21 10:12:03 +08:00
link
e319975a60
Update file count (#966) 2023-03-20 19:47:13 +08:00
link
5c2c3b5e98
Update file count (#965) 2023-03-20 17:02:42 +08:00
link
a0dc58264a
Update interface result state (#964) 2023-03-20 15:24:45 +08:00
Tiger Wang
717b47ca2c
clean up some unnecessary dependencies, logics, and linter warnings (#963)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-03-18 20:55:51 -04:00
LinkLeong
f530f69ba5 Update migration.list 2023-03-17 08:10:01 +00:00
LinkLeong
87b190a84b Remove fs.go 2023-03-17 08:00:06 +00:00
link
10191a1be3
Test rclone (#961) 2023-03-17 15:37:28 +08:00
Tiger Wang
59f2ccbeb3
move app request to CasaOS-AppStore (#958)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-03-15 21:02:34 -04:00
link
076b7198b2 Special treatment for arm equipment 2023-03-15 06:20:58 +00:00
link
eb483a4c5d
Change log (#955) 2023-03-15 10:46:12 +08:00
link
60e81bc781
Change changelog (#953) 2023-03-14 14:02:44 +08:00
link
dc8ee89c85
Statistics on the number of files added (#951) 2023-03-13 18:30:22 +08:00
link
c995750312
Socket service (#949) 2023-03-13 09:42:18 +08:00
Tiger Wang
de6ed093a2
Create SECURITY.md
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-03-08 20:07:56 -05:00
link
449e1515d9
Rclone update (#935) 2023-03-06 16:10:19 +08:00
raller1028
e6ddb0d849
Update push_test_server.yml
Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-03-02 15:46:34 +08:00
link
c19e32c6e9
Modify user rights (#922) 2023-02-24 14:35:38 +08:00
link
94755e221a
Add judgment on mount points (#921) 2023-02-24 14:20:30 +08:00
link
86a3692dad
Add proxy search interface (#917) 2023-02-23 10:38:48 +08:00
raller1028
45a5567978
Update push_test_server.yml
Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-02-21 15:45:43 +08:00
link
3190421fd9
Start sequence (#904)
Signed-off-by: link <a624669980@163.com>
2023-02-17 19:31:22 +08:00
link
a7126cac63
Rotational training registration to message-bus (#903) 2023-02-17 18:40:19 +08:00
raller1028
151aa037bb
Update push_test_server.yml
Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-02-17 11:46:30 +08:00
raller1028
08ed9933ee
Update push_test_server.yml
Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-02-16 18:32:47 +08:00
raller1028
8425011e73
Update push_test_server.yml
Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-02-16 18:20:40 +08:00
link
96ff550d61
Fix permission change cannot be unmount (#902) 2023-02-16 16:55:51 +08:00
link
85a803d352
Update service (#900) 2023-02-16 14:23:49 +08:00
link
5def3e5856
Add error log (#899) 2023-02-16 09:08:35 +08:00
Tiger Wang
c768260b1b
add CODE_OF_CONDUCT.md (#898)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-02-15 16:14:47 -05:00
link
46e146e633
Fix the download file name error problem (#896) 2023-02-15 17:31:20 +08:00
link
1a2f917b30
Exception handling when adding a mount to the same device (#890) 2023-02-13 19:42:34 +08:00
link
86adfbaef8
Updated demo snapshot (#889) 2023-02-13 18:58:21 +08:00
link
143af78745
Add 0.4.2 changelog (#885) 2023-02-10 19:11:36 +08:00
link
7b07f22685
Fix sharing failure issue (#884) 2023-02-10 17:27:13 +08:00
Tiger Wang
c8b6a1c228
fix installation issue for Mint Linux (#881)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-02-09 19:51:00 -05:00
link
827fba2164
Add error logs (#879) 2023-02-09 17:29:33 +08:00
link
6217009caf
The same driver and the same account can only be hooked up once (#878)
Signed-off-by: link <a624669980@163.com>
2023-02-09 17:23:33 +08:00
link
fbfcc2c43a
Remount if the configuration file already exists (#877) 2023-02-09 16:10:16 +08:00
link
b331c484f5
Add uninstall error judgment (#875) 2023-02-09 10:31:15 +08:00
link
a1fbbf9584
Modify the configuration file (#872) 2023-02-08 18:11:46 +08:00
link
3611ec7d09
Return directly if name does not exist (#871) 2023-02-07 21:58:01 +08:00
link
7dedbafc8a Modify naming rules 2023-02-07 10:32:12 +00:00
link
b7634402bd Merge branch 'main' of https://github.com/IceWhaleTech/CasaOS 2023-02-07 06:39:31 +00:00
link
8e025a9836 Mounted folders are inoperable 2023-02-07 06:38:21 +00:00
Tiger Wang
8bfc8178cf
remove obsolete functions for notification of app installation/uninstallation (#867)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-02-06 21:53:09 -05:00
link
8343f52137
Return of error when modifying mount (#864) 2023-02-06 17:44:37 +08:00
link
7501833cf9
Modify file permissions (#860) 2023-02-06 17:43:40 +08:00
link
4c917a33a4
Remove client judgment (#862) 2023-02-06 17:10:05 +08:00
link
173997c44b
Send notification changes to message bus (#861) 2023-02-06 15:47:29 +08:00
link
76bc6e68ae Modify file list return data 2023-02-02 15:59:33 +00:00
link
d0fc2cc8cb Conflict resolution 2023-02-02 03:52:17 +00:00
link
32980f4033 Merge branch 'add_file_drive' 2023-02-02 03:51:41 +00:00
link
28d3ca0ca6 added google drive and dropbox driver 2023-02-02 03:36:59 +00:00
link
3d31ad6689
Create push_test_server.yml
Signed-off-by: link <a624669980@163.com>
2023-01-31 18:19:36 +08:00
Tiger Wang
0bb138e39e
add API for checking casaos-* service status (#852)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-01-30 17:01:47 -05:00
link
e8db1767e5
Update CHANGELOG.md
Signed-off-by: link <a624669980@163.com>
2023-01-19 21:42:10 +08:00
link
6b01263252
Update CHANGELOG.md
Signed-off-by: link <a624669980@163.com>
2023-01-19 10:16:55 +08:00
Tiger Wang
b2a4fafdb4 add race detection for tests 2023-01-17 12:49:49 -05:00
Tiger Wang
fcfb48d88e
enable code coverage (#825) 2023-01-16 17:58:25 -05:00
link
86380d912d
Allow specific special characters (#823) 2023-01-16 14:10:39 +08:00
link
87d8be8c61 added google drive 2023-01-16 05:54:44 +00:00
link
9123974811
Update CHANGELOG.md
Signed-off-by: link <a624669980@163.com>
2023-01-09 18:08:47 +08:00
link
be50579544
add update type (#812) 2023-01-09 15:16:04 +08:00
link
91bb0cba6f
Update CHANGELOG.md
Signed-off-by: link <a624669980@163.com>
2023-01-08 15:33:48 +08:00
link
b9946db854
repair the error of version number display after successful update (#805) 2023-01-05 15:16:54 +08:00
老竭力
9eb650b444
Add download triage for download migration-tool (#793)
Co-authored-by: Tiger Wang <tigerwang@outlook.com>
2023-01-05 10:47:44 +08:00
Tiger Wang
84f17b4c4b
add upx as part of building step to shrink the size of binary files (#797) 2022-12-29 18:24:36 -05:00
Tiger Wang
8cff99f726 enable unit tests as part of release process (with 3 always failing tests skipped)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2022-12-22 23:32:12 +00:00
Tiger Wang
ea166f890b remove deps under GPL and add license check to workflow
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2022-12-22 16:25:40 -05:00
LinkLeong
d350c3e96f remove socket-port function 2022-12-22 03:53:55 +00:00
LinkLeong
ec0d98627d Merge branch 'main' of https://github.com/IceWhaleTech/CasaOS 2022-12-22 03:50:55 +00:00
LinkLeong
fd3cb5b0f0 update common package 2022-12-22 03:50:52 +00:00
Tiger Wang
0155dc1877 bump version from 0.4.0 to 0.4.1
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2022-12-21 03:26:30 +00:00
link
cd79e51f8f
Socketio modification (#771) 2022-12-20 14:05:16 +08:00
Tiger Wang
c6d89f9cb2
update CasaOS-Common from v0.4.0 to v0.4.1-alpha1 for the new notify.Application struct (#768)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2022-12-19 19:08:12 -05:00
Tiger Wang
4b26631374
add logic to run scripts under /etc/casaos/start.d when starting (#756) 2022-12-15 18:31:06 -05:00
John Guan
ba742b9fb2 Update README.md 2022-12-14 11:32:19 +08:00
146 changed files with 8721 additions and 2714 deletions

View File

@ -1,53 +0,0 @@
name: "App Request"
description: "Request to add an app to the app store."
title: "[App Request] AppName"
labels: ["App Request"]
body:
- type: markdown
attributes:
value: |
### ❤ Thanks for taking the time to fill out this app request!
> Before proceeding, please make sure that this app is not in App Store and no one has [requested](https://github.com/IceWhaleTech/CasaOS/labels/App%20Request) the same app before.
> If you have already requested the app, please ask your friends to help add a 👍 to this issue. Then be patient and wait for the developers to work on it.
> If you have any questions, please ask them on [Discord](https://discord.gg/knqAbbBbeX) or [Github Discussions](https://github.com/IceWhaleTech/CasaOS/discussions).
- type: textarea
id: app-info
attributes:
label: "App Information"
description: "The formal information of this app, as detailed as possible."
value: |
- Name: <!-- eg. Nextcloud -->
- Short Description: <!-- eg. Personal cloud and file sharing solution -->
- Official Website: <!-- If available. eg. https://nextcloud.com -->
- GitHub Repository: <!-- If available. eg. https://github.com/nextcloud/server -->
- Docker Image: <!-- If available. eg. nextcloud/server:latest, ghcr.io/nextcloud/server:latest -->
validations:
required: true
- type: textarea
id: why
attributes:
label: "Why do you want this app?"
description: "Detailed notes can help developers and others understand the importance of this app."
placeholder: |
As a [what role], it helps me solve [what problem], and especially [what function] is great!
or
It solves [what problem] and especially [what feature] works well, which is hard to do with other app.
or
This is the app that [some device/service] must use and will not work without it.
or
others
- type: textarea
id: additional-info
attributes:
label: "Additional information?"
description: "Anything else you want to share with the developers and others?"
placeholder: |
Example:
- Noteworthy matters.
- Recommended Docker image.
- Validated Docker deployment instructions.
- Notable Docker setup details.
- Recommended config files, user data, accessible directory settings.

View File

@ -8,39 +8,57 @@ assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
> A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
> Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
> A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
> If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
```
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
```
**System Time**
> Run `timedatectl` and share the output
```
(timedatectl output here)
```
**Logs**
run following command to collect corresponding logs:
> Run following command to collect corresponding logs:
```bash
sudo journalctl -xef -u casaos-gateway
sudo journalctl -xef -u casaos-user-service
sudo journalctl -xef -u casaos-local-storage
sudo journalctl -xef -u casaos-app-management
sudo journalctl -xef -u casaos.service
```
**Additional context**
Add any other context about the problem here.
> Add any other context about the problem here.
>
> If you are a Zimaboard user, make it explicit with when you got your Zimaboard.

View File

@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: App Request
url: https://github.com/IceWhaleTech/CasaOS-AppStore/issues/new?assignees=&labels=App+Request&template=app_request.yml&title=%5BApp+Request%5D+AppName
about: Request to add an app to the app store.
- name: Feature/Enhancement Ideas
url: https://github.com/IceWhaleTech/CasaOS/discussions/164
about: Have an idea for a new feature/enhancement?
@ -8,4 +11,4 @@ contact_links:
about: Ask questions, propose ideas, or discuss anything related to CasaOS
- name: Discord
url: https://discord.gg/knqAbbBbeX
about: Get help or share great ideas on Discord!
about: Get help or share great ideas on Discord!

View File

@ -1,23 +0,0 @@
name: "Feedback"
description: Feedback, showcases, thoughts, needs and questions, etc.
title: "[Feedback] "
labels: ["feedback"]
body:
- type: markdown
attributes:
value: |
### ❤️ Thanks for your feedback!
> Come join our [Discord community](https://discord.gg/knqAbbBbeX) and paint the ideal home cloud with us.
- type: textarea
id: description
attributes:
label: Description
placeholder: What do you want to tell us?
validations:
required: true
- type: textarea
id: additional
attributes:
label: Additional Information
description: Please add logs/files/screenshots if you have them to help us better understanding.

View File

@ -1,15 +0,0 @@
---
name: Submit application
about: Add an app to this project
title: ''
labels: ''
assignees: LinkLeong
---
Tested platform
e.g. linux/amd64,linux/arm-v7,linux-arm64
Please export and upload the configuration file of this application

19
.github/sync_openapi.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Sync OpenAPI
on:
workflow_call:
inputs:
project-name:
required: true
type: string
push:
branches:
- main
jobs:
sync:
uses: IceWhaleTech/github/.github/workflows/sync_openapi.yml@main
with:
project-name: casaos
secrets:
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}

View File

@ -40,7 +40,7 @@ jobs:
# env:
# GITHUB_TOKEN: ${{ github.token }}
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: true
@ -65,7 +65,7 @@ jobs:
# ls
- name: Set enviroment for github-release
- name: Set environment for github-release
run: |
echo "VERSION=$(cat types/system.go | grep CURRENTVERSION | awk '$2 == "CURRENTVERSION"{print $4}' | sed 's/"//g')" >>$GITHUB_ENV
echo "BODY=$(cat types/system.go | grep BODY | awk -F= '{print $2}' | sed 's/"//g')" >>$GITHUB_ENV
@ -73,7 +73,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: '14'

28
.github/workflows/codecov.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Collect Code Coverage
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.20"
- name: generate OPENAPI
run: go generate
- name: Run coverage
run: go test -race -failfast -coverprofile=coverage.txt -covermode=atomic -v ./...
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3

View File

@ -22,7 +22,7 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Configure AWS credentials from Test account
uses: aws-actions/configure-aws-credentials@v1
@ -33,9 +33,10 @@ jobs:
- name: Get old instance and snapshot name, create new instance name
run: |
echo "OLD_INSTANCE_SNAPSHOT_NAME=$(aws lightsail get-instance-snapshots | grep '"name": "casaos-0.3.6-1666150291' | sed 's/ //g' | sed 's/"//g' | sed 's/,//g' | sed 's/name://g')" >> $GITHUB_ENV
echo "OLD_INSTANCE_NAME=$(aws lightsail get-instances | grep '"name": "CasaOS-Demo-[0-9]' | sed 's/ //g' | sed 's/"//g' | sed 's/,//g' | sed 's/name://g')" >> $GITHUB_ENV
echo "NEW_INSTANCE_NAME=CasaOS-Demo-$(date +%s)" >> $GITHUB_ENV
echo "OLD_INSTANCE_SNAPSHOT_NAME=CasaOS-v0.4.4-3-Demo-1700132299" >> $GITHUB_ENV
echo "OLD_INSTANCE_NAME=$(aws lightsail get-instances | grep '"name": "CasaOS-Demo-[0-9]' | tail -1 | sed 's/ //g' | sed 's/"//g' | sed 's/,//g' | sed 's/name://g')" >> $GITHUB_ENV
# echo "OLD_INSTANCE_NAME=CasaOS-Demo-1687680295" >> $GITHUB_ENV
echo "NEW_INSTANCE_NAME= CasaOS-Demo-$(date +%s)" >> $GITHUB_ENV
- name: Create instances from snapshot
run: |
@ -75,5 +76,9 @@ jobs:
run: |
aws lightsail delete-instance \
--instance-name ${{ env.OLD_INSTANCE_NAME }}
- name: Demo Reset Error Handling
if: ${{ failure() }}
run: |
curl -X POST -H "Content-Type: application/json" -d '{"msg_type":"text","content":{"text":"Demo Reset Error"}}' ${{ secrets.SSH_ROBOT_URL }}

46
.github/workflows/publish_npm.yaml vendored Normal file
View File

@ -0,0 +1,46 @@
name: publish npm
on:
push:
tags:
- v*.*.*
workflow_dispatch:
permissions:
contents: write
jobs:
publish-npm:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: git tag --sort=-creatordate | head -n 1
- name: Get version
id: get_version
run: echo "VERSION=$(git tag --sort=-creatordate | head -n 1)" >> $GITHUB_OUTPUT
- name: Get commit id
id: get_commit_id
run: echo "COMMIT_ID=$( git rev-parse --short "$GITHUB_SHA" )" >> $GITHUB_OUTPUT
- run: echo "${{ steps.get_version.outputs.VERSION }}-${{ steps.get_commit_id.outputs.COMMIT_ID }}"
- name: Set version
run: |
sudo apt-get install jq
jq '.version="${{ steps.get_version.outputs.VERSION }}-${{ steps.get_commit_id.outputs.COMMIT_ID }}"' package.json > package.json.new
mv package.json.new package.json
- name: Generate SDK
run: |
npm cache clean --force
npm install @openapitools/openapi-generator-cli -g
- run: npm i
- run: npm run start
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}

View File

@ -40,9 +40,3 @@ jobs:
uses: joseph-montanez/forward-event-action@v3.0.0
with:
webhook: ${{ secrets.Discord_CasaOS_Bug_Webhook }}
- name: Alpha Issues & Comments
if: ${{ ( github.event_name == 'issues' || github.event_name == 'issue_comment' ) && contains(github.event.issue.labels.*.name, 'alpha') }}
uses: joseph-montanez/forward-event-action@v3.0.0
with:
webhook: ${{ secrets.Discord_CasaOS_Alpha_Webhook }}

92
.github/workflows/push_test_server.yml vendored Normal file
View File

@ -0,0 +1,92 @@
name: Auto Publish Website
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-22.04
steps:
-
name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: isntall git
run: sudo apt install --yes git
- name: git global
run: sudo git config --global --add safe.directory '*'
- name: set version
run: sudo git tag v00.00.00-alpha
- name: Fetch all tags
run: sudo git fetch --force --tags
- name: Get version
id: get_version
# run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
run: echo "VERSION=$(git describe --abbrev=0 --tags | awk -F- '{print $1}')" >> $GITHUB_ENV
- name: show version
id: show_version
# run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
run: echo ${{env.VERSION}}
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 'stable'
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
version: 1.14.1
args: release --rm-dist --snapshot
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GoogleID: ${{ secrets.GoogleID }}
GoogleSecret: ${{ secrets.GoogleSecret }}
DropboxKey: ${{ secrets.DropboxKey }}
DropboxSecret: ${{ secrets.DropboxSecret }}
OneDriveID: ${{ secrets.OneDriveID }}
OneDriveSecret: ${{ secrets.OneDriveSecret }}
# Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
# GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- name: remove migration file
run: sudo find . -type f \( -name '*migration*' \) -delete
- name: install sshpass
run: sudo apt install sshpass --yes
- name: ZeroTier
uses: zerotier/github-action@v1.0.1
with:
network_id: ${{ secrets.ZEROTIER_NETWORK_ID }}
auth_token: ${{ secrets.ZEROTIER_CENTRAL_TOKEN }}
- name: ping host
shell: bash
run: |
count=10
while ! ping -c 1 10.147.18.11 ; do
echo "waiting..." ;
sleep 1 ;
let count=count-1
done
echo "ping success"
- name: copy tar to target host
shell: bash
run: |
sshpass -p "${{ secrets.ssh_password }}" scp -r -o StrictHostKeyChecking=no -P 22 ./dist/*.gz root@10.147.18.11:/var/www/download
echo "ping success"
- name: send message
run: |
curl -X POST -H "Content-Type: application/json" -d '{"msg_type":"text","content":{"text":"CasaOS updated"}}' ${{ secrets.SSH_ROBOT_URL }}

View File

@ -7,65 +7,19 @@ on:
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-22.04
steps:
-
name: Install dependencies for cross-compiling
run: |
sudo apt update
sudo apt-get --no-install-recommends --yes install \
libc6-dev-amd64-cross \
gcc-aarch64-linux-gnu libc6-dev-arm64-cross \
gcc-arm-linux-gnueabihf libc6-dev-armhf-cross
-
name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
-
name: Fetch all tags
run: git fetch --force --tags
- name: Get version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
-
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.19
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
# GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- name: Upload to oss
id: upload_to_oss
uses: tvrcgo/upload-to-oss@master
with:
key-id: ${{ secrets.OSS_KEY_ID }}
key-secret: ${{ secrets.OSS_KEY_SECRET }}
region: oss-cn-shanghai
bucket: casaos
assets: |
dist/checksums.txt:/IceWhaleTech/CasaOS/releases/download/${{ steps.get_version.outputs.VERSION }}/checksums.txt
dist/linux-arm-7-casaos-${{ steps.get_version.outputs.VERSION }}.tar.gz:/IceWhaleTech/CasaOS/releases/download/${{ steps.get_version.outputs.VERSION }}/linux-arm-7-casaos-${{ steps.get_version.outputs.VERSION }}.tar.gz
dist/linux-arm64-casaos-${{ steps.get_version.outputs.VERSION }}.tar.gz:/IceWhaleTech/CasaOS/releases/download/${{ steps.get_version.outputs.VERSION }}/linux-arm64-casaos-${{ steps.get_version.outputs.VERSION }}.tar.gz
dist/linux-amd64-casaos-${{ steps.get_version.outputs.VERSION }}.tar.gz:/IceWhaleTech/CasaOS/releases/download/${{ steps.get_version.outputs.VERSION }}/linux-amd64-casaos-${{ steps.get_version.outputs.VERSION }}.tar.gz
dist/linux-arm-7-casaos-migration-tool-${{ steps.get_version.outputs.VERSION }}.tar.gz:/IceWhaleTech/CasaOS/releases/download/${{ steps.get_version.outputs.VERSION }}/linux-arm-7-casaos-migration-tool-${{ steps.get_version.outputs.VERSION }}.tar.gz
dist/linux-arm64-casaos-migration-tool-${{ steps.get_version.outputs.VERSION }}.tar.gz:/IceWhaleTech/CasaOS/releases/download/${{ steps.get_version.outputs.VERSION }}/linux-arm64-casaos-migration-tool-${{ steps.get_version.outputs.VERSION }}.tar.gz
dist/linux-amd64-casaos-migration-tool-${{ steps.get_version.outputs.VERSION }}.tar.gz:/IceWhaleTech/CasaOS/releases/download/${{ steps.get_version.outputs.VERSION }}/linux-amd64-casaos-migration-tool-${{ steps.get_version.outputs.VERSION }}.tar.gz
call-workflow-passing-data:
uses: IceWhaleTech/github/.github/workflows/go_release.yml@main
with:
project-name: CasaOS
file-name: casaos
secrets:
GoogleID: ${{ secrets.GoogleID }}
GoogleSecret: ${{ secrets.GoogleSecret }}
DropboxKey: ${{ secrets.DropboxKey }}
DropboxSecret: ${{ secrets.DropboxSecret }}
OneDriveID: ${{ secrets.OneDriveID }}
OneDriveSecret: ${{ secrets.OneDriveSecret }}
OneDrivePublic: ${{ secrets.OneDrivePublic }}
OSS_KEY_ID: ${{ secrets.OSS_KEY_ID }}
OSS_KEY_SECRET: ${{ secrets.OSS_KEY_SECRET }}

19
.github/workflows/sync_openapi.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Sync OpenAPI
on:
workflow_call:
inputs:
project-name:
required: true
type: string
push:
branches:
- main
jobs:
sync:
uses: IceWhaleTech/github/.github/workflows/sync_openapi.yml@main
with:
project-name: casaos
secrets:
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}

1
.gitignore vendored
View File

@ -37,6 +37,7 @@ github.com
.all-contributorsrc
dist
CasaOS
/codegen
# System Files
.DS_Store

View File

@ -3,8 +3,11 @@
project_name: casaos
before:
hooks:
# You may remove this if you don't use go modules.
- go generate
- go run github.com/google/go-licenses@latest check . --disallowed_types=restricted
- go mod tidy
- go test -race -v ./...
builds:
- id: casaos-amd64
binary: build/sysroot/usr/bin/casaos
@ -22,9 +25,6 @@ builds:
- linux
goarch:
- amd64
hooks:
post:
- find build/sysroot -type f | xargs -L 1 realpath --relative-to=build/sysroot > build/sysroot.manifest
- id: casaos-arm64
binary: build/sysroot/usr/bin/casaos
env:
@ -41,9 +41,6 @@ builds:
- linux
goarch:
- arm64
hooks:
post:
- find build/sysroot -type f | xargs -L 1 realpath --relative-to=build/sysroot > build/sysroot.manifest
- id: casaos-arm-7
binary: build/sysroot/usr/bin/casaos
env:
@ -62,9 +59,22 @@ builds:
- arm
goarm:
- "7"
hooks:
post:
- find build/sysroot -type f | xargs -L 1 realpath --relative-to=build/sysroot > build/sysroot.manifest
- id: casaos-riscv64
binary: build/sysroot/usr/bin/casaos
env:
- CC=riscv64-linux-gnu-gcc
gcflags:
- all=-N -l
ldflags:
- -extldflags "-static"
tags:
- musl
- netgo
- osusergo
goos:
- linux
goarch:
- riscv64
- id: casaos-migration-tool-amd64
binary: build/sysroot/usr/bin/casaos-migration-tool
main: ./cmd/migration-tool
@ -118,25 +128,42 @@ builds:
- arm
goarm:
- "7"
- id: casaos-migration-tool-riscv64
binary: build/sysroot/usr/bin/casaos-migration-tool
main: ./cmd/migration-tool
env:
- CC=riscv64-linux-gnu-gcc
gcflags:
- all=-N -l
ldflags:
- -extldflags "-static"
tags:
- musl
- netgo
- osusergo
goos:
- linux
goarch:
- riscv64
archives:
- name_template: "{{ .Os }}-{{ .Arch }}-{{ .ProjectName }}-v{{ .Version }}"
- name_template: >-
{{ .Os }}-{{- if eq .Arch "arm" }}arm-7{{- else }}{{ .Arch }}{{- end }}-{{ .ProjectName }}-v{{ .Version }}
id: casaos
builds:
- casaos-amd64
- casaos-arm64
- casaos-arm-7
replacements:
arm: arm-7
- casaos-riscv64
files:
- build/**/*
- name_template: "{{ .Os }}-{{ .Arch }}-{{ .ProjectName }}-migration-tool-v{{ .Version }}"
- name_template: >-
{{ .Os }}-{{- if eq .Arch "arm" }}arm-7{{- else }}{{ .Arch }}{{- end }}-{{ .ProjectName }}-migration-tool-v{{ .Version }}
id: casaos-migration-tool
builds:
- casaos-migration-tool-amd64
- casaos-migration-tool-arm64
- casaos-migration-tool-arm-7
replacements:
arm: arm-7
- casaos-migration-tool-riscv64
files:
- build/sysroot/etc/**/*
checksum:

View File

@ -3,14 +3,28 @@
project_name: casaos
before:
hooks:
# You may remove this if you don't use go modules.
- go generate
- go run github.com/google/go-licenses@latest check . --disallowed_types=restricted
- go mod tidy
- go test -race -v ./...
builds:
- id: casaos-amd64
binary: build/sysroot/usr/bin/casaos
hooks:
post:
- upx --best --lzma -v --no-progress "{{ .Path }}"
env:
- CC=x86_64-linux-gnu-gcc
ldflags:
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
- -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_id={{.Env.GoogleID}}
- -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_secret={{.Env.GoogleSecret}}
- -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_id={{.Env.OneDriveID}}
- -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_secret={{.Env.OneDriveSecret}}
- -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_key={{.Env.DropboxKey}}
- -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_secret={{.Env.DropboxSecret}}
- -s
- -w
- -extldflags "-static"
@ -22,14 +36,22 @@ builds:
- linux
goarch:
- amd64
hooks:
post:
- find build/sysroot -type f | xargs -L 1 realpath --relative-to=build/sysroot > build/sysroot.manifest
- id: casaos-arm64
binary: build/sysroot/usr/bin/casaos
# hooks:
# post:
# - upx --best --lzma -v --no-progress "{{ .Path }}"
env:
- CC=aarch64-linux-gnu-gcc
ldflags:
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
- -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_id={{.Env.GoogleID}}
- -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_secret={{.Env.GoogleSecret}}
- -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_id={{.Env.OneDriveID}}
- -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_secret={{.Env.OneDriveSecret}}
- -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_key={{.Env.DropboxKey}}
- -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_secret={{.Env.DropboxSecret}}
- -s
- -w
- -extldflags "-static"
@ -41,14 +63,22 @@ builds:
- linux
goarch:
- arm64
hooks:
post:
- find build/sysroot -type f | xargs -L 1 realpath --relative-to=build/sysroot > build/sysroot.manifest
- id: casaos-arm-7
binary: build/sysroot/usr/bin/casaos
hooks:
post:
- upx --best --lzma -v --no-progress "{{ .Path }}"
env:
- CC=arm-linux-gnueabihf-gcc
ldflags:
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
- -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_id={{.Env.GoogleID}}
- -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_secret={{.Env.GoogleSecret}}
- -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_id={{.Env.OneDriveID}}
- -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_secret={{.Env.OneDriveSecret}}
- -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_key={{.Env.DropboxKey}}
- -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_secret={{.Env.DropboxSecret}}
- -s
- -w
- -extldflags "-static"
@ -62,15 +92,41 @@ builds:
- arm
goarm:
- "7"
hooks:
post:
- find build/sysroot -type f | xargs -L 1 realpath --relative-to=build/sysroot > build/sysroot.manifest
- id: casaos-riscv64
binary: build/sysroot/usr/bin/casaos
env:
- CC=riscv64-linux-gnu-gcc
ldflags:
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
- -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_id={{.Env.GoogleID}}
- -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_secret={{.Env.GoogleSecret}}
- -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_id={{.Env.OneDriveID}}
- -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_secret={{.Env.OneDriveSecret}}
- -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_key={{.Env.DropboxKey}}
- -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_secret={{.Env.DropboxSecret}}
- -s
- -w
- -extldflags "-static"
tags:
- musl
- netgo
- osusergo
goos:
- linux
goarch:
- riscv64
- id: casaos-migration-tool-amd64
binary: build/sysroot/usr/bin/casaos-migration-tool
hooks:
post:
- upx --best --lzma -v --no-progress "{{ .Path }}"
main: ./cmd/migration-tool
env:
- CC=x86_64-linux-gnu-gcc
ldflags:
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
- -s
- -w
- -extldflags "-static"
@ -84,10 +140,15 @@ builds:
- amd64
- id: casaos-migration-tool-arm64
binary: build/sysroot/usr/bin/casaos-migration-tool
# hooks:
# post:
# - upx --best --lzma -v --no-progress "{{ .Path }}"
main: ./cmd/migration-tool
env:
- CC=aarch64-linux-gnu-gcc
ldflags:
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
- -s
- -w
- -extldflags "-static"
@ -101,10 +162,15 @@ builds:
- arm64
- id: casaos-migration-tool-arm-7
binary: build/sysroot/usr/bin/casaos-migration-tool
hooks:
post:
- upx --best --lzma -v --no-progress "{{ .Path }}"
main: ./cmd/migration-tool
env:
- CC=arm-linux-gnueabihf-gcc
ldflags:
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
- -s
- -w
- -extldflags "-static"
@ -118,25 +184,44 @@ builds:
- arm
goarm:
- "7"
- id: casaos-migration-tool-riscv64
binary: build/sysroot/usr/bin/casaos-migration-tool
main: ./cmd/migration-tool
env:
- CC=riscv64-linux-gnu-gcc
ldflags:
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
- -s
- -w
- -extldflags "-static"
tags:
- musl
- netgo
- osusergo
goos:
- linux
goarch:
- riscv64
archives:
- name_template: "{{ .Os }}-{{ .Arch }}-{{ .ProjectName }}-v{{ .Version }}"
- name_template: >-
{{ .Os }}-{{- if eq .Arch "arm" }}arm-7{{- else }}{{ .Arch }}{{- end }}-{{ .ProjectName }}-v{{ .Version }}
id: casaos
builds:
- casaos-amd64
- casaos-arm64
- casaos-arm-7
replacements:
arm: arm-7
- casaos-riscv64
files:
- build/**/*
- name_template: "{{ .Os }}-{{ .Arch }}-{{ .ProjectName }}-migration-tool-v{{ .Version }}"
- name_template: >-
{{ .Os }}-{{- if eq .Arch "arm" }}arm-7{{- else }}{{ .Arch }}{{- end }}-{{ .ProjectName }}-migration-tool-v{{ .Version }}
id: casaos-migration-tool
builds:
- casaos-migration-tool-amd64
- casaos-migration-tool-arm64
- casaos-migration-tool-arm-7
replacements:
arm: arm-7
- casaos-migration-tool-riscv64
files:
- build/sysroot/etc/**/*
checksum:

View File

@ -16,6 +16,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security
## [0.4.3]
### Added
- [Disk] Now usb also supports merging to
### Changed
- [File] Solve the installation dependency problem, make the installation more smoothly
- [File] Change the default permissions of the sharing folder
### Fixed
- [System] Fixed not see wlan iface ([#909](https://github.com/IceWhaleTech/CasaOS/issues/909))
- [System] Terminal font issue fix ([#929](https://github.com/IceWhaleTech/CasaOS/issues/929))
- [File] Fixed the problem of not being able to launch after mounting
### Removed
## [0.4.2]
### Added
- [App] Increase the display of progress during the installation process
- [App] Label whether the current app supports x86 or Pi devices
- [App] Support single app version upgrade
- [File] Support mounting of Google Drive and Dropbox cloud drives
- [System] Support Mint Linux
### Changed
- [File] Optimize the download speed of a single file
### Fixed
- [Share] Fix the samba permission issue
- [Disk] Fix the problem of disk mount point plus 1 after upgrade ([#770](https://github.com/IceWhaleTech/CasaOS/issues/770))
- [File] Fix the problem of file permission change caused by modifying files in casaos ([#829](https://github.com/IceWhaleTech/CasaOS/issues/829))
- [Share] Fix the problem of files being deleted due to samba uninstallation failure ([#843](https://github.com/IceWhaleTech/CasaOS/issues/843))
## [0.4.1] - 2023-1-19
### Added
- [Disk] Added disk merging feature in storage management (beta) that allows for multiple disks to be merged into a single storage space
- [System] Added option for startpage.com search engine
- [APP] Added app cloning feature in the app's context menu.
### Changed
- [APP] Improved app installation process, including display of the installation process, checks for successful installation, and prompts
- [System] Binary sizes are 40%~60% smaller (thanks to upx)
- [App] Optimization of install and update for certain country.
- [All] Lots of bug fixes
## [0.4.0] - 2022-12-13
### Added

128
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
wiki@casaos.io.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@ -1,18 +1,16 @@
# CasaOS - Your Home Cloud OS
# CasaOS - Your Personal Cloud
<!-- Readme i18n links -->
<!-- > English | [中文](#) | [Français](#) -->
<p align="center">
<!-- CasaOS Banner -->
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_dark_night_800px.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_twilight_blue_800px.png">
<img alt="CasaOS" src="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_twilight_blue_800px.png">
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_dark_night_800x300.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_twilight_blue_800x300.png">
<img alt="CasaOS" src="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_twilight_blue_800x300.png">
</picture>
<br/>
<i>Connect with the community developing HOME CLOUD, creating self-sovereign, and defining the future of the distributed cloud.</i>
<i>Connect with the community, establish autonomy, reduce the cost of SaaS, and MAXIMIZE the potential for a personalized copilot.</i>
<br/>
<br/>
<!-- CasaOS Badges -->
@ -28,6 +26,9 @@
<a href="https://github.com/IceWhaleTech/CasaOS/issues" target="_blank">
<img alt="CasaOS Issues" src="https://img.shields.io/github/issues/IceWhaleTech/CasaOS?color=162453&style=flat-square&label=Issues" />
</a>
<a href="https://codecov.io/gh/IceWhaleTech/CasaOS" >
<img src="https://codecov.io/gh/IceWhaleTech/CasaOS/branch/main/graph/badge.svg?token=l9uMKGlkxM"/>
</a>
<a href="https://github.com/IceWhaleTech/CasaOS/stargazers" target="_blank">
<img alt="CasaOS Stargazers" src="https://img.shields.io/github/stars/IceWhaleTech/CasaOS?color=162453&style=flat-square&label=Stars" />
</a>
@ -43,8 +44,20 @@
<img alt="CasaOS GitHub Discussions" src="https://img.shields.io/github/discussions/IceWhaleTech/CasaOS?color=162453&style=flat-square&label=Discussions&logo=github" />
</a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<a href="#credits"><img alt="All Contributors" src="https://img.shields.io/static/v1?label=All%20Contributors&message=15&color=162453&style=flat-square&logo=Handshake&logoColor=fff" /></a>
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<a href="#credits">
<img alt="All Contributors" src="https://img.shields.io/static/v1?label=All%20Contributors&message=15&color=162453&style=flat-square&logo=Handshake&logoColor=fff" />
</a>
<!-- CasaOS YouTube -->
<a href="https://www.youtube.com/channel/UC2zMrUYT17AJhIl9XWZzT8g" target="_blank">
<img alt="YouTube Tutorial Views" src="https://img.shields.io/youtube/channel/views/UC2zMrUYT17AJhIl9XWZzT8g?style=flat-square&logo=youtube&logoColor=red&label=YouTube%20Tutorial%20Views" />
</a>
<br/>
<a href="http://bit.ly/45JQIiL" target="_blank">
<img alt="twitter ZimaSpace" src="https://img.shields.io/twitter/follow/ZimaSpace?style=flat-square&logo=X&label=Contact%20Us%20%40%20ZimaSpace&labelColor=555&color=555" />
</a>
<a href="http://bit.ly/4lgHj7V" target="_blank">
<img alt="facebook ZimaSpace" src="https://img.shields.io/badge/ZimaSpace-1877F2?style=flat-square&logo=Facebook&logoColor=fff&label=Contact%20Us&labelColor=555&color=162453" />
</a>
<br/>
<!-- CasaOS Links -->
<a href="https://www.casaos.io" target="_blank">Website</a> |
@ -62,18 +75,16 @@
</kbd>
</p>
## Why do we need Home Cloud?
## Why do you need Personal Cloud?
Think about it seriously. Is control of our data, smart devices and digital assets now only in the hands of some big company?
In 2020, the team noticed three important trends:
- The cost of computing power and storage was decreasing fast.
- A part of cloud computing was moving towards edge computing.
- The issue of consumer data asset ownership and attribution had been ignored.
- Is your photo album saved in their cloud service?
- Do your thermostats, monitors, lamps need to be used through their cloud services?
- Do your personal documents, memos, contacts, passwords, etc. reside in their cloud storage services?
- Are you just going to have to accept their decisions when they decide to change prices, review content or even discontinue services?
Based on these trends, the team proposed a thought experiment internally: what if personal clouds were available under $100 in next five years? This personal cloud would provide a low-cost data collaboration solution as a personal data center, storing and managing data for creators and small organizations. A distributed collaborative computing network can be formed by personal servers located around the world. It could also control and connect all smart devices, providing cross-ecosystem local intelligent services.
It sounds ridiculous, doesn't it? We are losing control of our own data!
Our ideal home cloud is one where you can manage all your data, devices and data assets very easily. In your own home, you have absolute control.
Furthermore, the personal cloud could combine personal data to train personalized AI assistants. The idea is that this technology would be an effective way to solve the issue of consumer data asset ownership and , as well as provide a more affordable and efficient computing solution for individuals and small organizations.
> If you think what we are doing is valuable. Please **give us a star ⭐** and **fork it 🤞**!
@ -84,7 +95,7 @@ Our ideal home cloud is one where you can manage all your data, devices and data
- Multiple hardware and base system support
- ZimaBoard, NUC, RPi, old computers, whatever is available.
- Selected apps in the app store, one-click installation
- Nextcloud, HomeAssiant, AdGuard, Jellyfin, *arr and more!
- Nextcloud, HomeAssistant, AdGuard, Jellyfin, *arr and more!
- Easily install numerous Docker apps
- Over 100,000 apps from the Docker ecosystem can be easily installed
- Elegant drive and file management
@ -105,7 +116,7 @@ CasaOS fully supports ZimaBoard, Intel NUC, and Raspberry Pi. Also, more compute
### System Compatibility
Official Support
- Debian 11 (✅ Tested, Recommended)
- Debian 12 (✅ Tested, Recommended)
- Ubuntu Server 20.04 (✅ Tested)
- Raspberry Pi OS (✅ Tested)
@ -130,6 +141,30 @@ or
curl -fsSL https://get.casaos.io | sudo bash
```
### Update CasaOS
CasaOS can be updated from the User Interface (UI), via `Settings ... Update`.
Alternatively it can be updated from a terminal session. To update from a terminal session, it must be done either from a secure shell (ssh) session to the device or from a directly attached terminal and keyboard to the device running CasaOS, this cannot be done from the terminal via the CasaOS User Interface (UI). To update to the latest release of CasaOS from a terminal session run this command:
```sh
wget -qO- https://get.casaos.io/update | sudo bash
```
or
```sh
curl -fsSL https://get.casaos.io/update | sudo bash
```
To determine version of CasaOS from a terminal session run this command:
```sh
casaos -v
```
### Uninstall CasaOS
@ -147,13 +182,13 @@ curl -fsSL https://get.icewhale.io/casaos-uninstall.sh | sudo bash
## Community
The word Casa comes from the Spanish word for "home". Project CasaOS originated as a pre-installed system for crowdfunded product [ZimaBoard](https://www.zimaboard.com) on Kickstarter.
The word Casa comes from the Spanish word for "home". Project CasaOS originated as a pre-installed system for the crowdfunded product [ZimaBoard](https://www.zimaboard.com) on Kickstarter.
After looking at many systems and software on the market, the team found no server system designed for home scenarios, sadly true.
So, we set out to build this open source project to develop CasaOS with our own hands, everyone in the community, and you.
So, we set out to build this open-source project to develop CasaOS with our own hands, everyone in the community, and you.
We believes that through community-driven collaborative innovation and open communication with global developers, we can reshape the digital home experience like never before.
We believe that through community-driven collaborative innovation and open communication with global developers, we can reshape the digital home experience like never before.
**A warm welcome for you to get help or share great ideas in the [Discord](https://discord.gg/knqAbbBbeX)!**
@ -163,8 +198,9 @@ We believes that through community-driven collaborative innovation and open comm
CasaOS is a community-driven open source project and the people involved are CasaOS users. That means CasaOS will always need contributions from community members just like you!
- See <https://wiki.casaos.io/en/contribute> for ways of contribution to CasaOS
- See <https://wiki.casaos.io/en/contribute/development> if you want to be involved in code contribution specificially
- See <https://wiki.casaos.io/en/contribute> for ways of contributing to CasaOS
- See <https://wiki.casaos.io/en/contribute/development> if you want to be involved in code contribution specifically
## Credits
@ -179,7 +215,7 @@ Everyone's contribution is greatly appreciated. ([Emoji Key](https://allcontribu
<tr>
<td align="center"><a href="https://github.com/jerrykuku"><img src="https://avatars.githubusercontent.com/u/9485680?v=4?s=100" width="100px;" alt=""/><br /><sub><b>老竭力</b></sub></a><br /><a href="https://github.com/IceWhaleTech/CasaOS/commits?author=jerrykuku" title="Code">💻</a> <a href="https://github.com/IceWhaleTech/CasaOS/commits?author=jerrykuku" title="Documentation">📖</a> <a href="#ideas-jerrykuku" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-jerrykuku" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-jerrykuku" title="Maintenance">🚧</a> <a href="#platform-jerrykuku" title="Packaging/porting to new platform">📦</a> <a href="#question-jerrykuku" title="Answering Questions">💬</a> <a href="https://github.com/IceWhaleTech/CasaOS/pulls?q=is%3Apr+reviewed-by%3Ajerrykuku" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/LinkLeong"><img src="https://avatars.githubusercontent.com/u/13556972?v=4?s=100" width="100px;" alt=""/><br /><sub><b>link</b></sub></a><br /><a href="https://github.com/IceWhaleTech/CasaOS/commits?author=LinkLeong" title="Code">💻</a> <a href="https://github.com/IceWhaleTech/CasaOS/commits?author=LinkLeong" title="Documentation">📖</a> <a href="#ideas-LinkLeong" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-LinkLeong" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-LinkLeong" title="Maintenance">🚧</a> <a href="#question-LinkLeong" title="Answering Questions">💬</a> <a href="https://github.com/IceWhaleTech/CasaOS/pulls?q=is%3Apr+reviewed-by%3ALinkLeong" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/tigerinus"><img src="https://avatars.githubusercontent.com/u/7172560?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tiger Wang (王豫)</b></sub></a><br /><a href="https://github.com/IceWhaleTech/CasaOS/commits?author=tigerinus" title="Code">💻</a> <a href="https://github.com/IceWhaleTech/CasaOS/commits?author=tigerinus" title="Documentation">📖</a> <a href="#ideas-tigerinus" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-tigerinus" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-tigerinus" title="Maintenance">🚧</a> <a href="#mentoring-tigerinus" title="Mentoring">🧑‍🏫</a> <a href="#security-tigerinus" title="Security">🛡️</a> <a href="#question-tigerinus" title="Answering Questions">💬</a> <a href="https://github.com/IceWhaleTech/CasaOS/pulls?q=is%3Apr+reviewed-by%3Atigerinus" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/tigerinus"><img src="https://avatars.githubusercontent.com/u/7172560?v=4?s=100" width="100px;" alt=""/><br /><sub><b>太戈</b></sub></a><br /><a href="https://github.com/IceWhaleTech/CasaOS/commits?author=tigerinus" title="Code">💻</a> <a href="https://github.com/IceWhaleTech/CasaOS/commits?author=tigerinus" title="Documentation">📖</a> <a href="#ideas-tigerinus" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-tigerinus" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-tigerinus" title="Maintenance">🚧</a> <a href="#mentoring-tigerinus" title="Mentoring">🧑‍🏫</a> <a href="#security-tigerinus" title="Security">🛡️</a> <a href="#question-tigerinus" title="Answering Questions">💬</a> <a href="https://github.com/IceWhaleTech/CasaOS/pulls?q=is%3Apr+reviewed-by%3Atigerinus" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/Lauren-ED209"><img src="https://avatars.githubusercontent.com/u/8243355?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lauren</b></sub></a><br /><a href="#ideas-Lauren-ED209" title="Ideas, Planning, & Feedback">🤔</a> <a href="#fundingFinding-Lauren-ED209" title="Funding Finding">🔍</a> <a href="#projectManagement-Lauren-ED209" title="Project Management">📆</a> <a href="#question-Lauren-ED209" title="Answering Questions">💬</a> <a href="https://github.com/IceWhaleTech/CasaOS/commits?author=Lauren-ED209" title="Tests">⚠️</a></td>
<td align="center"><a href="https://JohnGuan.Cn"><img src="https://avatars.githubusercontent.com/u/3358477?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Guan</b></sub></a><br /><a href="#blog-JohnGuan" title="Blogposts">📝</a> <a href="#content-JohnGuan" title="Content">🖋</a> <a href="https://github.com/IceWhaleTech/CasaOS/commits?author=JohnGuan" title="Documentation">📖</a> <a href="#ideas-JohnGuan" title="Ideas, Planning, & Feedback">🤔</a> <a href="#eventOrganizing-JohnGuan" title="Event Organizing">📋</a> <a href="#mentoring-JohnGuan" title="Mentoring">🧑‍🏫</a> <a href="#question-JohnGuan" title="Answering Questions">💬</a> <a href="https://github.com/IceWhaleTech/CasaOS/pulls?q=is%3Apr+reviewed-by%3AJohnGuan" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://blog.tippybits.com"><img src="https://avatars.githubusercontent.com/u/17506770?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Tippett</b></sub></a><br /><a href="https://github.com/IceWhaleTech/CasaOS/commits?author=dtaivpp" title="Documentation">📖</a> <a href="#ideas-dtaivpp" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-dtaivpp" title="Answering Questions">💬</a></td>
@ -196,6 +232,8 @@ Everyone's contribution is greatly appreciated. ([Emoji Key](https://allcontribu
</tr>
<tr>
<td align="center"><a href="https://github.com/llwaini"><img src="https://avatars.githubusercontent.com/u/59589857?v=4?s=100" width="100px;" alt=""/><br /><sub><b>llwaini</b></sub></a><br /><a href="#projectManagement-llwaini" title="Project Management">📆</a> <a href="https://github.com/IceWhaleTech/CasaOS/commits?author=llwaini" title="Tests">⚠️</a> <a href="#tutorial-llwaini" title="Tutorials"></a></td>
<td align="center"><a href="https://github.com/CorrectRoadH"><img src="https://avatars.githubusercontent.com/u/29306285?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CorrectRoadH</b></sub></a><br /><a href="https://github.com/IceWhaleTech/CasaOS/commits?author=correctroadh" title="Code">💻</a> <a href="https://github.com/IceWhaleTech/CasaOS/commits?author=correctroadh" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/zhanghengxin"><img src="https://avatars.githubusercontent.com/u/24197448?v=4?s=100" width="100px;" alt=""/><br /><sub><b>zhanghengxin</b></sub></a><br /><a href="https://github.com/IceWhaleTech/CasaOS/commits?author=zhanghengxin" title="Code">💻</a> <a href="https://github.com/IceWhaleTech/CasaOS/commits?author=zhanghengxin" title="Documentation">📖</a></td>
</tr>
</table>
@ -204,7 +242,7 @@ Everyone's contribution is greatly appreciated. ([Emoji Key](https://allcontribu
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome!
## Changelog

9
SECURITY.md Normal file
View File

@ -0,0 +1,9 @@
# Security Policy
## Supported Versions
CasaOS is currently under active development. Support is limited before CasaOS reaches v1.0.
## Reporting a Vulnerability
If you see any vulnerabiility, email us at wiki@casaos.io

359
api/casaos/openapi.yaml Normal file
View File

@ -0,0 +1,359 @@
openapi: 3.0.3
info:
title: CasaOS API
version: v2
description: |
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_dark_night_800px.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_twilight_blue_800px.png">
<img alt="CasaOS" src="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_twilight_blue_800px.png">
</picture>
CasaOS API provides miscellaneous methods for different scenarios.
For issues and discussions, please visit the [GitHub repository](https://github.com/IceWhaleTech/CasaOS) or join [our Discord](https://discord.gg/knqAbbBbeX).
servers:
- url: /v2/casaos
tags:
- name: Health methods
description: |-
These methods are used to check the health and status of the CasaOS API and associated services.
- name: File methods
description: |-
The File methods allow you to interact with files and directories on the CasaOS system.
x-tagGroups:
- name: Methods
tags:
- Health methods
security:
- access_token: []
paths:
/health/services:
get:
tags:
- Health methods
summary: Get service status
description: |-
Get running status of each `casaos-*` service.
operationId: getHealthServices
responses:
"200":
$ref: "#/components/responses/GetHealthServicesOK"
"500":
$ref: "#/components/responses/ResponseInternalServerError"
/health/ports:
get:
tags:
- Health methods
summary: Get port in use
operationId: getHealthPorts
responses:
"200":
$ref: "#/components/responses/GetHealthPortsOK"
"500":
$ref: "#/components/responses/ResponseInternalServerError"
/health/logs:
get:
tags:
- Health methods
summary: Get log
operationId: getHealthlogs
responses:
"200":
description: OK
content:
application/octet-stream:
schema:
type: string
format: binary
"500":
$ref: "#/components/responses/ResponseInternalServerError"
/file/test:
get:
tags:
- File methods
summary: Test file methods
description: |-
Test file methods.
operationId: getFileTest
responses:
"200":
$ref: "#/components/responses/ResponseOK"
"500":
$ref: "#/components/responses/ResponseInternalServerError"
/file/upload:
get:
tags:
- File
summary: Check upload chunk
parameters:
- name: path
in: query
description: File path
required: true
example: "/DATA/test.log"
schema:
type: string
- name: relativePath
in: query
description: File path
required: true
example: "/DATA/test.log"
schema:
type: string
- name: filename
in: query
description: File name
required: true
example: "test.log"
schema:
type: string
- name: chunkNumber
in: query
description: chunk number
required: true
example: 1
schema:
type: string
- name: totalChunks
in: query
description: total chunks
example: 2
required: true
schema:
type: integer
description: Check if the file block has been uploaded (needs to be modified later)
operationId: checkUploadChunk
responses:
"200":
$ref: "#/components/responses/ResponseStringOK"
"400":
$ref: "#/components/responses/ResponseClientError"
"500":
$ref: "#/components/responses/ResponseInternalServerError"
post:
tags:
- File
summary: Upload file
description: Upload file
operationId: postUploadFile
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
relativePath:
type: string
example: "/DATA/test.log"
filename:
type: string
example: "/DATA/test2.log"
totalChunks:
type: string
example: "2"
chunkNumber:
type: string
example: "20"
path:
type: string
example: "/DATA"
file:
type: string
format: binary
chunkSize:
type: string
example: "1024"
currentChunkSize:
type: string
example: "1024"
totalSize:
type: string
example: "1024"
identifier:
type: string
example: "test.log"
responses:
"200":
$ref: "#/components/responses/ResponseStringOK"
"400":
$ref: "#/components/responses/ResponseClientError"
"500":
$ref: "#/components/responses/ResponseInternalServerError"
/zt/info:
get:
tags:
- Zerotier methods
summary: Get Zerotier info
description: |-
Get Zerotier info.
operationId: getZerotierInfo
responses:
"200":
$ref: "#/components/responses/GetZTInfoOK"
"500":
$ref: "#/components/responses/ResponseInternalServerError"
/zt/{network_id}/status:
put:
tags:
- Zerotier methods
summary: Set Zerotier network status
description: |-
Set Zerotier network status.
operationId: setZerotierNetworkStatus
parameters:
- name: network_id
in: path
description: network id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
properties:
status:
enum:
- online
- offline
type: string
example: "online"
responses:
"200":
$ref: "#/components/responses/GetZTInfoOK"
"500":
$ref: "#/components/responses/ResponseInternalServerError"
components:
securitySchemes:
access_token:
type: apiKey
in: header
name: Authorization
responses:
ResponseOK:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/BaseResponse"
ResponseStringOK:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/SuccessResponseString"
ResponseClientError:
description: Client Error
content:
application/json:
schema:
$ref: "#/components/schemas/BaseResponse"
ResponseInternalServerError:
description: Internal Server Error
content:
application/json:
schema:
$ref: "#/components/schemas/BaseResponse"
GetHealthServicesOK:
description: OK
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/BaseResponse"
- properties:
data:
$ref: "#/components/schemas/HealthServices"
GetHealthPortsOK:
description: OK
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/BaseResponse"
- properties:
data:
$ref: "#/components/schemas/HealthPorts"
GetZTInfoOK:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/ZTInfo"
schemas:
BaseResponse:
properties:
message:
readOnly: true
description: message returned by server side if there is any
type: string
example: ""
SuccessResponseString:
allOf:
- $ref: "#/components/schemas/BaseResponse"
- properties:
data:
type: string
description: When the interface returns success, this field is the specific success information
HealthServices:
properties:
running:
type: array
items:
type: string
example: "casaos-gateway.service"
not_running:
type: array
items:
type: string
example: "casaos.service"
HealthPorts:
properties:
tcp:
type: array
items:
type: integer
example: 80
x-go-name: TCP
udp:
type: array
items:
type: integer
example: 53
x-go-name: UDP
ZTInfo:
properties:
id:
type: string
example: "1234567890"
name:
type: string
example: "CasaOS"
status:
type: string
example: "online"

24
api/index.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>CasaOS | Developers</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='casaos/openapi.yaml' expandResponses='all' jsonSampleExpandLevel='all'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>

View File

@ -56,6 +56,22 @@ __is_migration_needed() {
__is_version_gt "${version2}" "${version1}"
}
__get_download_domain(){
local region
# Use ipconfig.io/country and https://ifconfig.io/country_code to get the country code
region=$(curl --connect-timeout 2 -s ipconfig.io/country || echo "")
if [ "${region}" = "" ]; then
region=$(curl --connect-timeout 2 -s https://ifconfig.io/country_code || echo "")
fi
if [[ "${region}" = "China" ]] || [[ "${region}" = "CN" ]]; then
echo "https://casaos.oss-cn-shanghai.aliyuncs.com/"
else
echo "https://github.com/"
fi
}
DOWNLOAD_DOMAIN=$(__get_download_domain)
BUILD_PATH=$(dirname "${BASH_SOURCE[0]}")/../../..
SOURCE_ROOT=${BUILD_PATH}/sysroot
@ -71,7 +87,7 @@ CURRENT_BIN_FILE=${CURRENT_BIN_PATH}/${APP_NAME}
CURRENT_BIN_FILE_LEGACY=$(realpath -e ${CURRENT_BIN_PATH_LEGACY}/${APP_NAME} || which ${APP_NAME} || echo CURRENT_BIN_FILE_LEGACY_NOT_FOUND)
SOURCE_VERSION="$(${SOURCE_BIN_FILE} -v)"
CURRENT_VERSION="$(${CURRENT_BIN_FILE} -v || ${CURRENT_BIN_FILE_LEGACY} -v || (stat "${CURRENT_BIN_FILE_LEGACY}" > /dev/null && echo LEGACY_WITHOUT_VERSION) || echo CURRENT_VERSION_NOT_FOUND)"
CURRENT_VERSION="$(${CURRENT_BIN_FILE} -v || ${CURRENT_BIN_FILE_LEGACY} -v || (stat "${CURRENT_BIN_FILE_LEGACY}" >/dev/null && echo LEGACY_WITHOUT_VERSION) || echo CURRENT_VERSION_NOT_FOUND)"
__info_done "CURRENT_VERSION: ${CURRENT_VERSION}"
__info_done "SOURCE_VERSION: ${SOURCE_VERSION}"
@ -86,18 +102,21 @@ fi
ARCH="unknown"
case $(uname -m) in
x86_64)
ARCH="amd64"
;;
aarch64)
ARCH="arm64"
;;
armv7l)
ARCH="arm-7"
;;
*)
__error "Unsupported architecture"
;;
x86_64)
ARCH="amd64"
;;
aarch64)
ARCH="arm64"
;;
armv7l)
ARCH="arm-7"
;;
riscv64)
ARCH="riscv64"
;;
*)
__error "Unsupported architecture"
;;
esac
__info "ARCH: ${ARCH}"
@ -134,7 +153,7 @@ while read -r VERSION_PAIR; do
if [ "${CURRENT_VERSION_FOUND}" = "true" ]; then
MIGRATION_PATH+=("${URL// /}")
fi
done < "${MIGRATION_LIST_FILE}"
done <"${MIGRATION_LIST_FILE}"
if [ ${#MIGRATION_PATH[@]} -eq 0 ]; then
__warning "No migration path found from ${CURRENT_VERSION} to ${SOURCE_VERSION}"
@ -143,7 +162,8 @@ fi
pushd "${MIGRATION_SERVICE_DIR}"
{ for URL in "${MIGRATION_PATH[@]}"; do
{
for URL in "${MIGRATION_PATH[@]}"; do
MIGRATION_TOOL_FILE=$(basename "${URL}")
if [ -f "${MIGRATION_TOOL_FILE}" ]; then

View File

@ -1,3 +1,3 @@
LEGACY_WITHOUT_VERSION https://github.com/IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
v0.3.5 https://github.com/IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
v0.3.5.1 https://github.com/IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
LEGACY_WITHOUT_VERSION ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
v0.3.5 ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
v0.3.5.1 ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz

View File

@ -18,7 +18,9 @@ __get_setup_script_directory_by_os_release() {
} || {
pushd "${ID}" >/dev/null
} || {
pushd "${ID_LIKE}" >/dev/null
[[ -n ${ID_LIKE} ]] && for ID in ${ID_LIKE}; do
pushd "${ID}" >/dev/null && break
done
} || {
echo "Unsupported OS: ${ID} ${VERSION_CODENAME} (${ID_LIKE})"
exit 1

View File

@ -1,6 +1,6 @@
[Unit]
After=casaos-gateway.service
ConditionFileNotEmpty=/etc/casaos/casaos.conf
After=casaos-message-bus.service
After=rclone.service
Description=CasaOS Main Service
[Service]

View File

@ -0,0 +1,12 @@
[Unit]
Description=rclone
[Service]
ExecStartPre=/usr/bin/mkdir -p /var/run/rclone
ExecStartPre=/usr/bin/rm -f /var/run/rclone/rclone.sock
ExecStart=/usr/bin/rclone rcd --rc-addr unix:///var/run/rclone/rclone.sock --rc-no-auth --rc-allow-origin "*"
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View File

@ -26,7 +26,9 @@ __get_setup_script_directory_by_os_release() {
} || {
pushd "${ID}" &>/dev/null
} || {
pushd "${ID_LIKE}" &>/dev/null
[[ -n ${ID_LIKE} ]] && for ID in ${ID_LIKE}; do
pushd "${ID}" >/dev/null && break
done
} || {
echo "Unsupported OS: ${ID} ${VERSION_CODENAME} (${ID_LIKE})"
exit 1

View File

@ -25,7 +25,7 @@ GetNetCard() {
fi
else
if [ -d "/sys/devices/virtual/net" ] && [ -d "/sys/class/net" ]; then
ls /sys/class/net/ | grep -v "$(ls /sys/devices/virtual/net/)"
ls /sys/class/net/ | grep -v "$(ls /sys/devices/virtual/net/)" -w
fi
fi
}
@ -66,18 +66,6 @@ GetLocalJoinNetworks() {
zerotier-cli listnetworks -j
}
#移除挂载点,删除已挂在的文件夹
UMountPorintAndRemoveDir() {
DEVICE=$1
MOUNT_POINT=$(mount | grep ${DEVICE} | awk '{ print $3 }')
if [[ -z ${MOUNT_POINT} ]]; then
${log} "Warning: ${DEVICE} is not mounted"
else
umount -lf ${DEVICE}
/bin/rmdir "${MOUNT_POINT}"
fi
}
#格式化fat32磁盘
#param 需要格式化的目录 /dev/sda1
#param 格式
@ -133,11 +121,7 @@ GetPlugInDisk() {
fdisk -l | grep 'Disk' | grep 'sd' | awk -F , '{print substr($1,11,3)}'
}
#获取磁盘状态
#param 磁盘路径
GetDiskHealthState() {
smartctl -H $1 | grep "SMART Health Status" | awk -F ":" '{print$2}'
}
#获取磁盘字节数量和扇区数量
#param 磁盘路径 /dev/sda
@ -370,19 +354,6 @@ MountCIFS(){
$sudo_cmd mount -t cifs -o username=$1,password=$6,port=$4 //$2/$3 $5
}
# $1:service name
CheckServiceStatus(){
rs="`systemctl status $1 |grep -E 'Active|PID'`"
#echo "$rs"
run="`echo "$rs" |grep -B 2 'running'`"
fai="`echo "$rs" |grep -E -B 2 'failed|inactive|dead'`"
if [ "$run" == "" ]
then
echo "failed"
else
echo "running"
fi
}
UDEVILUmount(){
$sudo_cmd udevil umount -f $1
}

View File

@ -0,0 +1,28 @@
package main
import (
"github.com/IceWhaleTech/CasaOS-Common/external"
"github.com/IceWhaleTech/CasaOS/codegen/message_bus"
"github.com/IceWhaleTech/CasaOS/common"
"github.com/samber/lo"
)
func main() {
eventTypes := lo.Map(common.EventTypes, func(item message_bus.EventType, index int) external.EventType {
return external.EventType{
Name: item.Name,
SourceID: item.SourceID,
PropertyTypeList: lo.Map(
item.PropertyTypeList, func(item message_bus.PropertyType, index int) external.PropertyType {
return external.PropertyType{
Name: item.Name,
Description: item.Description,
Example: item.Example,
}
},
),
}
})
external.PrintEventTypesAsMarkdown(common.SERVICENAME, common.VERSION, eventTypes)
}

View File

@ -17,7 +17,7 @@ import (
interfaces "github.com/IceWhaleTech/CasaOS-Common"
"github.com/IceWhaleTech/CasaOS-Common/utils/systemctl"
"github.com/IceWhaleTech/CasaOS-Gateway/common"
"github.com/IceWhaleTech/CasaOS/common"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/sqlite"
"github.com/IceWhaleTech/CasaOS/service"
@ -29,40 +29,35 @@ const (
)
var (
commit = "private build"
date = "private build"
_logger *Logger
sqliteDB *gorm.DB
)
var (
configFlag = ""
dbFlag = ""
)
func init() {
config.InitSetup(configFlag)
if len(dbFlag) == 0 {
dbFlag = config.AppInfo.DBPath + "/db"
}
sqliteDB = sqlite.GetDb(dbFlag)
// gredis.GetRedisConn(config.RedisInfo),
service.MyService = service.NewService(sqliteDB, "")
}
func main() {
versionFlag := flag.Bool("v", false, "version")
debugFlag := flag.Bool("d", true, "debug")
forceFlag := flag.Bool("f", true, "force")
flag.Parse()
_logger = NewLogger()
if *versionFlag {
fmt.Println(common.Version)
fmt.Println("v" + common.VERSION)
os.Exit(0)
}
println("git commit:", commit)
println("build date:", date)
_logger = NewLogger()
if os.Getuid() != 0 {
_logger.Info("Root privileges are required to run this program.")
os.Exit(1)
}
@ -82,6 +77,19 @@ func main() {
}
}
config.InitSetup(configFlag, "")
if len(dbFlag) == 0 {
dbFlag = config.AppInfo.DBPath + "/db"
}
sqliteDB = sqlite.GetDb(dbFlag)
// gredis.GetRedisConn(config.RedisInfo),
service.MyService = service.NewService(sqliteDB, "")
}
func main() {
migrationTools := []interfaces.MigrationTool{
// nothing to migrate from last version
}

View File

@ -1,60 +0,0 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-08-24 17:36:00
* @LastEditors: LinkLeong
* @LastEditTime: 2022-09-05 11:24:27
* @FilePath: /CasaOS/cmd/migration-tool/migration-034-035.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package main
import (
interfaces "github.com/IceWhaleTech/CasaOS-Common"
"github.com/IceWhaleTech/CasaOS-Common/utils/version"
)
type migrationTool struct{}
func (u *migrationTool) IsMigrationNeeded() (bool, error) {
majorVersion, minorVersion, patchVersion, err := version.DetectLegacyVersion()
if err != nil {
if err == version.ErrLegacyVersionNotFound {
return false, nil
}
return false, err
}
if majorVersion > 0 {
return false, nil
}
if minorVersion > 3 {
return false, nil
}
if minorVersion == 3 && patchVersion > 5 {
return false, nil
}
_logger.Info("Migration is needed for a CasaOS version 0.3.5 and older...")
return true, nil
}
func (u *migrationTool) PreMigrate() error {
return nil
}
func (u *migrationTool) Migrate() error {
return nil
}
func (u *migrationTool) PostMigrate() error {
return nil
}
func NewMigrationToolFor_036() interfaces.MigrationTool {
return &migrationTool{}
}

View File

@ -0,0 +1,27 @@
package main
import (
interfaces "github.com/IceWhaleTech/CasaOS-Common"
)
type migrationTool struct{}
func (u *migrationTool) IsMigrationNeeded() (bool, error) {
return false, nil
}
func (u *migrationTool) PreMigrate() error {
return nil
}
func (u *migrationTool) Migrate() error {
return nil
}
func (u *migrationTool) PostMigrate() error {
return nil
}
func NewMigrationDummy() interfaces.MigrationTool {
return &migrationTool{}
}

8
common/constants.go Normal file
View File

@ -0,0 +1,8 @@
package common
const (
SERVICENAME = "casaos"
VERSION = "0.4.15"
BODY = " "
RANW_NAME = "IceWhale-RemoteAccess"
)

12
common/message.go Normal file
View File

@ -0,0 +1,12 @@
package common
import (
"github.com/IceWhaleTech/CasaOS/codegen/message_bus"
)
// devtype -> action -> event
var EventTypes = []message_bus.EventType{
{Name: "casaos:system:utilization", SourceID: SERVICENAME, PropertyTypeList: []message_bus.PropertyType{}},
{Name: "casaos:file:recover", SourceID: SERVICENAME, PropertyTypeList: []message_bus.PropertyType{}},
{Name: "casaos:file:operate", SourceID: SERVICENAME, PropertyTypeList: []message_bus.PropertyType{}},
}

13
drivers/all.go Normal file
View File

@ -0,0 +1,13 @@
package drivers
import (
_ "github.com/IceWhaleTech/CasaOS/drivers/dropbox"
_ "github.com/IceWhaleTech/CasaOS/drivers/google_drive"
_ "github.com/IceWhaleTech/CasaOS/drivers/onedrive"
)
// All do nothing,just for import
// same as _ import
func All() {
}

30
drivers/base/client.go Normal file
View File

@ -0,0 +1,30 @@
package base
import (
"net/http"
"time"
"github.com/go-resty/resty/v2"
)
var NoRedirectClient *resty.Client
var RestyClient = NewRestyClient()
var HttpClient = &http.Client{}
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
var DefaultTimeout = time.Second * 30
func init() {
NoRedirectClient = resty.New().SetRedirectPolicy(
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}),
)
NoRedirectClient.SetHeader("user-agent", UserAgent)
}
func NewRestyClient() *resty.Client {
return resty.New().
SetHeader("user-agent", UserAgent).
SetRetryCount(3).
SetTimeout(DefaultTimeout)
}

12
drivers/base/types.go Normal file
View File

@ -0,0 +1,12 @@
package base
import "github.com/go-resty/resty/v2"
type Json map[string]interface{}
type TokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
type ReqCallback func(req *resty.Request)

103
drivers/dropbox/drive.go Normal file
View File

@ -0,0 +1,103 @@
package dropbox
import (
"context"
"errors"
"net/http"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
type Dropbox struct {
model.StorageA
Addition
AccessToken string
}
func (d *Dropbox) Config() driver.Config {
return config
}
func (d *Dropbox) GetAddition() driver.Additional {
return &d.Addition
}
func (d *Dropbox) Init(ctx context.Context) error {
if len(d.RefreshToken) == 0 {
d.getRefreshToken()
}
return d.refreshToken()
}
func (d *Dropbox) Drop(ctx context.Context) error {
return nil
}
func (d *Dropbox) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
files, err := d.getFiles(dir.GetID())
if err != nil {
return nil, err
}
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
return fileToObj(src), nil
})
}
func (d *Dropbox) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
url := "https://content.dropboxapi.com/2/files/download"
link := model.Link{
URL: url,
Method: http.MethodPost,
Header: http.Header{
"Authorization": []string{"Bearer " + d.AccessToken},
"Dropbox-API-Arg": []string{`{"path": "` + file.GetPath() + `"}`},
},
}
return &link, nil
}
func (d *Dropbox) GetUserInfo(ctx context.Context) (string, error) {
url := "https://api.dropboxapi.com/2/users/get_current_account"
user := UserInfo{}
resp, err := d.request(url, http.MethodPost, func(req *resty.Request) {
req.SetHeader("Content-Type", "")
}, &user)
if err != nil {
return "", err
}
logger.Info("resp", zap.Any("resp", string(resp)))
return user.Email, nil
}
func (d *Dropbox) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
return nil
}
func (d *Dropbox) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
return nil
}
func (d *Dropbox) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
return nil
}
func (d *Dropbox) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
return errors.New("not support")
}
func (d *Dropbox) Remove(ctx context.Context, obj model.Obj) error {
return nil
}
func (d *Dropbox) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
return nil
}
func (d *Dropbox) GetInfo(ctx context.Context) (string, string, string, error) {
return "", "", "", nil
}
var _ driver.Driver = (*Dropbox)(nil)

24
drivers/dropbox/meta.go Normal file
View File

@ -0,0 +1,24 @@
package dropbox
import (
"github.com/IceWhaleTech/CasaOS/internal/driver"
)
const ICONURL = "./img/driver/Dropbox.svg"
type Addition struct {
driver.RootID
RefreshToken string `json:"refresh_token" required:"true" omit:"true"`
AppKey string `json:"app_key" type:"string" default:"tciqajyazzdygt9" omit:"true"`
AppSecret string `json:"app_secret" type:"string" default:"e7gtmv441cwdf0n" omit:"true"`
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
AuthUrl string `json:"auth_url" type:"string" default:""`
Icon string `json:"icon" type:"string" default:"./img/driver/Dropbox.svg"`
Code string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
}
var config = driver.Config{
Name: "Dropbox",
OnlyProxy: true,
DefaultRoot: "root",
}

88
drivers/dropbox/types.go Normal file
View File

@ -0,0 +1,88 @@
package dropbox
import (
"time"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/model"
"go.uber.org/zap"
)
type UserInfo struct {
AccountID string `json:"account_id"`
Name struct {
GivenName string `json:"given_name"`
Surname string `json:"surname"`
FamiliarName string `json:"familiar_name"`
DisplayName string `json:"display_name"`
AbbreviatedName string `json:"abbreviated_name"`
} `json:"name"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
Disabled bool `json:"disabled"`
Country string `json:"country"`
Locale string `json:"locale"`
ReferralLink string `json:"referral_link"`
IsPaired bool `json:"is_paired"`
AccountType struct {
Tag string `json:".tag"`
} `json:"account_type"`
RootInfo struct {
Tag string `json:".tag"`
RootNamespaceID string `json:"root_namespace_id"`
HomeNamespaceID string `json:"home_namespace_id"`
} `json:"root_info"`
}
type TokenError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
type File struct {
Tag string `json:".tag"`
Name string `json:"name"`
PathLower string `json:"path_lower"`
PathDisplay string `json:"path_display"`
ID string `json:"id"`
ClientModified time.Time `json:"client_modified,omitempty"`
ServerModified time.Time `json:"server_modified,omitempty"`
Rev string `json:"rev,omitempty"`
Size int `json:"size,omitempty"`
IsDownloadable bool `json:"is_downloadable,omitempty"`
ContentHash string `json:"content_hash,omitempty"`
}
type Files struct {
Files []File `json:"entries"`
Cursor string `json:"cursor"`
HasMore bool `json:"has_more"`
}
type Error struct {
Error struct {
Errors []struct {
Domain string `json:"domain"`
Reason string `json:"reason"`
Message string `json:"message"`
LocationType string `json:"location_type"`
Location string `json:"location"`
}
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func fileToObj(f File) *model.ObjThumb {
logger.Info("dropbox file", zap.Any("file", f))
obj := &model.ObjThumb{
Object: model.Object{
ID: f.ID,
Name: f.Name,
Size: int64(f.Size),
Modified: f.ClientModified,
IsFolder: f.Tag == "folder",
Path: f.PathDisplay,
},
Thumbnail: model.Thumbnail{},
}
return obj
}

116
drivers/dropbox/util.go Normal file
View File

@ -0,0 +1,116 @@
package dropbox
import (
"fmt"
"net/http"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/drivers/base"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
var (
app_key = "private build"
app_secret = "private build"
)
func (d *Dropbox) getRefreshToken() error {
url := "https://api.dropbox.com/oauth2/token"
var resp base.TokenResp
var e TokenError
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
"code": d.Code,
"grant_type": "authorization_code",
"redirect_uri": "https://cloudoauth.files.casaos.app",
}).SetBasicAuth(d.Addition.AppKey, d.Addition.AppSecret).SetHeader("Content-Type", "application/x-www-form-urlencoded").Post(url)
if err != nil {
return err
}
logger.Info("get refresh token", zap.String("res", res.String()))
if e.Error != "" {
return fmt.Errorf(e.Error)
}
d.RefreshToken = resp.RefreshToken
return nil
}
func (d *Dropbox) refreshToken() error {
url := "https://api.dropbox.com/oauth2/token"
var resp base.TokenResp
var e TokenError
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
"refresh_token": d.RefreshToken,
"grant_type": "refresh_token",
}).SetBasicAuth(d.Addition.AppKey, d.Addition.AppSecret).SetHeader("Content-Type", "application/x-www-form-urlencoded").Post(url)
if err != nil {
return err
}
logger.Info("get refresh token", zap.String("res", res.String()))
if e.Error != "" {
return fmt.Errorf(e.Error)
}
d.AccessToken = resp.AccessToken
return nil
}
func (d *Dropbox) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
req.SetHeader("Content-Type", "application/json")
if callback != nil {
callback(req)
}
if resp != nil {
req.SetResult(resp)
}
var e Error
req.SetError(&e)
res, err := req.Execute(method, url)
if err != nil {
return nil, err
}
if e.Error.Code != 0 {
if e.Error.Code == 401 {
err = d.refreshToken()
if err != nil {
return nil, err
}
return d.request(url, method, callback, resp)
}
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
}
return res.Body(), nil
}
func (d *Dropbox) getFiles(path string) ([]File, error) {
res := make([]File, 0)
var resp Files
body := base.Json{
"limit": 2000,
"path": path,
}
_, err := d.request("https://api.dropboxapi.com/2/files/list_folder", http.MethodPost, func(req *resty.Request) {
req.SetBody(body)
}, &resp)
if err != nil {
return nil, err
}
res = append(res, resp.Files...)
return res, nil
}
func GetConfig() Dropbox {
dp := Dropbox{}
dp.RootFolderID = ""
dp.AuthUrl = "https://www.dropbox.com/oauth2/authorize?client_id=" + app_key + "&redirect_uri=https://cloudoauth.files.casaos.app&response_type=code&token_access_type=offline&state=${HOST}%2Fv1%2Frecover%2FDropbox&&force_reapprove=true&force_reauthentication=true"
dp.AppKey = app_key
dp.AppSecret = app_secret
dp.Icon = "./img/driver/Dropbox.svg"
return dp
}

View File

@ -0,0 +1,186 @@
package google_drive
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/drivers/base"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
type GoogleDrive struct {
model.StorageA
Addition
AccessToken string
}
func (d *GoogleDrive) Config() driver.Config {
return config
}
func (d *GoogleDrive) GetAddition() driver.Additional {
return &d.Addition
}
func (d *GoogleDrive) Init(ctx context.Context) error {
if d.ChunkSize == 0 {
d.ChunkSize = 5
}
if len(d.RefreshToken) == 0 {
d.getRefreshToken()
}
return d.refreshToken()
}
func (d *GoogleDrive) Drop(ctx context.Context) error {
return nil
}
func (d *GoogleDrive) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
files, err := d.getFiles(dir.GetID())
if err != nil {
return nil, err
}
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
return fileToObj(src), nil
})
}
func (d *GoogleDrive) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
url := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?includeItemsFromAllDrives=true&supportsAllDrives=true", file.GetID())
_, err := d.request(url, http.MethodGet, nil, nil)
if err != nil {
return nil, err
}
link := model.Link{
Method: http.MethodGet,
URL: url + "&alt=media",
Header: http.Header{
"Authorization": []string{"Bearer " + d.AccessToken},
},
}
return &link, nil
}
func (d *GoogleDrive) GetUserInfo(ctx context.Context) (string, error) {
url := "https://content.googleapis.com/drive/v3/about?fields=user"
user := UserInfo{}
resp, err := d.request(url, http.MethodGet, nil, &user)
if err != nil {
return "", err
}
logger.Info("resp", zap.Any("resp", resp))
return user.User.EmailAddress, nil
}
func (d *GoogleDrive) GetInfo(ctx context.Context) (string, string, string, error) {
return "", "", "", nil
}
func (d *GoogleDrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
data := base.Json{
"name": dirName,
"parents": []string{parentDir.GetID()},
"mimeType": "application/vnd.google-apps.folder",
}
_, err := d.request("https://www.googleapis.com/drive/v3/files", http.MethodPost, func(req *resty.Request) {
req.SetBody(data)
}, nil)
return err
}
func (d *GoogleDrive) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
query := map[string]string{
"addParents": dstDir.GetID(),
"removeParents": "root",
}
url := "https://www.googleapis.com/drive/v3/files/" + srcObj.GetID()
_, err := d.request(url, http.MethodPatch, func(req *resty.Request) {
req.SetQueryParams(query)
}, nil)
return err
}
func (d *GoogleDrive) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
data := base.Json{
"name": newName,
}
url := "https://www.googleapis.com/drive/v3/files/" + srcObj.GetID()
_, err := d.request(url, http.MethodPatch, func(req *resty.Request) {
req.SetBody(data)
}, nil)
return err
}
func (d *GoogleDrive) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
return errors.New("not support")
}
func (d *GoogleDrive) Remove(ctx context.Context, obj model.Obj) error {
url := "https://www.googleapis.com/drive/v3/files/" + obj.GetID()
_, err := d.request(url, http.MethodDelete, nil, nil)
return err
}
func (d *GoogleDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
obj := stream.GetOld()
var (
e Error
url string
data base.Json
res *resty.Response
err error
)
if obj != nil {
url = fmt.Sprintf("https://www.googleapis.com/upload/drive/v3/files/%s?uploadType=resumable&supportsAllDrives=true", obj.GetID())
data = base.Json{}
} else {
data = base.Json{
"name": stream.GetName(),
"parents": []string{dstDir.GetID()},
}
url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&supportsAllDrives=true"
}
req := base.NoRedirectClient.R().
SetHeaders(map[string]string{
"Authorization": "Bearer " + d.AccessToken,
"X-Upload-Content-Type": stream.GetMimetype(),
"X-Upload-Content-Length": strconv.FormatInt(stream.GetSize(), 10),
}).
SetError(&e).SetBody(data).SetContext(ctx)
if obj != nil {
res, err = req.Patch(url)
} else {
res, err = req.Post(url)
}
if err != nil {
return err
}
if e.Error.Code != 0 {
if e.Error.Code == 401 {
err = d.refreshToken()
if err != nil {
return err
}
return d.Put(ctx, dstDir, stream, up)
}
return fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
}
putUrl := res.Header().Get("location")
if stream.GetSize() < d.ChunkSize*1024*1024 {
_, err = d.request(putUrl, http.MethodPut, func(req *resty.Request) {
req.SetHeader("Content-Length", strconv.FormatInt(stream.GetSize(), 10)).SetBody(stream.GetReadCloser())
}, nil)
} else {
err = d.chunkUpload(ctx, stream, putUrl)
}
return err
}
var _ driver.Driver = (*GoogleDrive)(nil)

View File

@ -0,0 +1,26 @@
package google_drive
import (
"github.com/IceWhaleTech/CasaOS/internal/driver"
)
const ICONURL = "./img/driver/GoogleDrive.svg"
type Addition struct {
driver.RootID
RefreshToken string `json:"refresh_token" required:"true" omit:"true"`
OrderBy string `json:"order_by" type:"string" help:"such as: folder,name,modifiedTime" omit:"true"`
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
ClientID string `json:"client_id" required:"true" default:"" omit:"true"`
ClientSecret string `json:"client_secret" required:"true" default:"" omit:"true"`
ChunkSize int64 `json:"chunk_size" type:"number" help:"chunk size while uploading (unit: MB)" omit:"true"`
AuthUrl string `json:"auth_url" type:"string" default:""`
Icon string `json:"icon" type:"string" default:"./img/driver/GoogleDrive.svg"`
Code string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
}
var config = driver.Config{
Name: "GoogleDrive",
OnlyProxy: true,
DefaultRoot: "root",
}

View File

@ -0,0 +1,77 @@
package google_drive
import (
"strconv"
"time"
"github.com/IceWhaleTech/CasaOS/model"
log "github.com/sirupsen/logrus"
)
type UserInfo struct {
User struct {
Kind string `json:"kind"`
DisplayName string `json:"displayName"`
PhotoLink string `json:"photoLink"`
Me bool `json:"me"`
PermissionID string `json:"permissionId"`
EmailAddress string `json:"emailAddress"`
} `json:"user"`
}
type TokenError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
type Files struct {
NextPageToken string `json:"nextPageToken"`
Files []File `json:"files"`
}
type File struct {
Id string `json:"id"`
Name string `json:"name"`
MimeType string `json:"mimeType"`
ModifiedTime time.Time `json:"modifiedTime"`
Size string `json:"size"`
ThumbnailLink string `json:"thumbnailLink"`
ShortcutDetails struct {
TargetId string `json:"targetId"`
TargetMimeType string `json:"targetMimeType"`
} `json:"shortcutDetails"`
}
func fileToObj(f File) *model.ObjThumb {
log.Debugf("google file: %+v", f)
size, _ := strconv.ParseInt(f.Size, 10, 64)
obj := &model.ObjThumb{
Object: model.Object{
ID: f.Id,
Name: f.Name,
Size: size,
Modified: f.ModifiedTime,
IsFolder: f.MimeType == "application/vnd.google-apps.folder",
},
Thumbnail: model.Thumbnail{},
}
if f.MimeType == "application/vnd.google-apps.shortcut" {
obj.ID = f.ShortcutDetails.TargetId
obj.IsFolder = f.ShortcutDetails.TargetMimeType == "application/vnd.google-apps.folder"
}
return obj
}
type Error struct {
Error struct {
Errors []struct {
Domain string `json:"domain"`
Reason string `json:"reason"`
Message string `json:"message"`
LocationType string `json:"location_type"`
Location string `json:"location"`
}
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
}

View File

@ -0,0 +1,167 @@
package google_drive
import (
"context"
"fmt"
"io"
"net/http"
"strconv"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/drivers/base"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"go.uber.org/zap"
)
var (
client_id = "private build"
client_secret = "private build"
)
// do others that not defined in Driver interface
func (d *GoogleDrive) getRefreshToken() error {
url := "https://www.googleapis.com/oauth2/v4/token"
var resp base.TokenResp
var e TokenError
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
"client_id": d.ClientID,
"client_secret": d.ClientSecret,
"code": d.Code,
"grant_type": "authorization_code",
"redirect_uri": "https://cloudoauth.files.casaos.app",
}).Post(url)
if err != nil {
return err
}
logger.Info("get refresh token", zap.String("res", res.String()))
if e.Error != "" {
return fmt.Errorf(e.Error)
}
d.RefreshToken = resp.RefreshToken
return nil
}
func (d *GoogleDrive) refreshToken() error {
url := "https://www.googleapis.com/oauth2/v4/token"
var resp base.TokenResp
var e TokenError
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
"client_id": d.ClientID,
"client_secret": d.ClientSecret,
"refresh_token": d.RefreshToken,
"grant_type": "refresh_token",
}).Post(url)
if err != nil {
return err
}
log.Debug(res.String())
if e.Error != "" {
return fmt.Errorf(e.Error)
}
d.AccessToken = resp.AccessToken
return nil
}
func (d *GoogleDrive) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
req.SetQueryParam("includeItemsFromAllDrives", "true")
req.SetQueryParam("supportsAllDrives", "true")
if callback != nil {
callback(req)
}
if resp != nil {
req.SetResult(resp)
}
var e Error
req.SetError(&e)
res, err := req.Execute(method, url)
if err != nil {
return nil, err
}
if e.Error.Code != 0 {
if e.Error.Code == 401 {
err = d.refreshToken()
if err != nil {
return nil, err
}
return d.request(url, method, callback, resp)
}
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
}
return res.Body(), nil
}
func (d *GoogleDrive) getFiles(id string) ([]File, error) {
pageToken := "first"
res := make([]File, 0)
for pageToken != "" {
if pageToken == "first" {
pageToken = ""
}
var resp Files
orderBy := "folder,name,modifiedTime desc"
if d.OrderBy != "" {
orderBy = d.OrderBy + " " + d.OrderDirection
}
query := map[string]string{
"orderBy": orderBy,
"fields": "files(id,name,mimeType,size,modifiedTime,thumbnailLink,shortcutDetails),nextPageToken",
"pageSize": "1000",
"q": fmt.Sprintf("'%s' in parents and trashed = false", id),
//"includeItemsFromAllDrives": "true",
//"supportsAllDrives": "true",
"pageToken": pageToken,
}
_, err := d.request("https://www.googleapis.com/drive/v3/files", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
}, &resp)
if err != nil {
return nil, err
}
pageToken = resp.NextPageToken
res = append(res, resp.Files...)
}
return res, nil
}
func (d *GoogleDrive) chunkUpload(ctx context.Context, stream model.FileStreamer, url string) error {
var defaultChunkSize = d.ChunkSize * 1024 * 1024
var finish int64 = 0
for finish < stream.GetSize() {
if utils.IsCanceled(ctx) {
return ctx.Err()
}
chunkSize := stream.GetSize() - finish
if chunkSize > defaultChunkSize {
chunkSize = defaultChunkSize
}
_, err := d.request(url, http.MethodPut, func(req *resty.Request) {
req.SetHeaders(map[string]string{
"Content-Length": strconv.FormatInt(chunkSize, 10),
"Content-Range": fmt.Sprintf("bytes %d-%d/%d", finish, finish+chunkSize-1, stream.GetSize()),
}).SetBody(io.LimitReader(stream.GetReadCloser(), chunkSize)).SetContext(ctx)
}, nil)
if err != nil {
return err
}
finish += chunkSize
}
return nil
}
func GetConfig() GoogleDrive {
config := GoogleDrive{}
config.ClientID = client_id
config.ClientSecret = client_secret
config.RootFolderID = "root"
config.AuthUrl = "https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?response_type=code&client_id=" + client_id + "&redirect_uri=https%3A%2F%2Fcloudoauth.files.casaos.app&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&approval_prompt=force&state=${HOST}%2Fv1%2Frecover%2FGoogleDrive&service=lso&o2v=1&flowName=GeneralOAuthFlow"
config.Icon = "./img/driver/GoogleDrive.svg"
return config
}

73
drivers/onedrive/drive.go Normal file
View File

@ -0,0 +1,73 @@
package onedrive
import (
"context"
"fmt"
"net/http"
"strconv"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/model"
"go.uber.org/zap"
)
type Onedrive struct {
model.StorageA
Addition
AccessToken string
}
func (d *Onedrive) Config() driver.Config {
return config
}
func (d *Onedrive) GetAddition() driver.Additional {
return &d.Addition
}
func (d *Onedrive) Init(ctx context.Context) error {
if d.ChunkSize < 1 {
d.ChunkSize = 5
}
if len(d.RefreshToken) == 0 {
return d.getRefreshToken()
}
return d.refreshToken()
}
func (d *Onedrive) GetUserInfo(ctx context.Context) (string, error) {
return "", nil
}
func (d *Onedrive) GetInfo(ctx context.Context) (string, string, string, error) {
url := d.GetMetaUrl(false, "/")
user := Info{}
_, err := d.Request(url, http.MethodGet, nil, &user)
if err != nil {
return "", "", "", err
}
return user.CreatedBy.User.Email, user.ParentReference.DriveID, user.ParentReference.DriveType, nil
}
func (d *Onedrive) GetSpaceSize(ctx context.Context) (used string, total string, err error) {
host := onedriveHostMap[d.Region]
url := fmt.Sprintf("%s/v1.0/me/drive/quota", host.Api)
size := About{}
resp, err := d.Request(url, http.MethodGet, nil, &size)
if err != nil {
return used, total, err
}
logger.Info("resp", zap.Any("resp", resp))
used = strconv.Itoa(size.Used)
total = strconv.Itoa(size.Total)
return
}
func (d *Onedrive) Drop(ctx context.Context) error {
return nil
}
var _ driver.Driver = (*Onedrive)(nil)

62
drivers/onedrive/meta.go Normal file
View File

@ -0,0 +1,62 @@
package onedrive
import (
"github.com/IceWhaleTech/CasaOS/internal/driver"
)
const ICONURL = "./img/driver/OneDrive.svg"
type Host struct {
Oauth string
Api string
}
type TokenErr struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
type RespErr struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
type Addition struct {
Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de" default:"global"`
IsSharepoint bool `json:"is_sharepoint"`
ClientID string `json:"client_id" required:"true"`
ClientSecret string `json:"client_secret" required:"true"`
RedirectUri string `json:"redirect_uri" required:"true" default:""`
RefreshToken string `json:"refresh_token" required:"true"`
SiteId string `json:"site_id"`
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`
RootFolderID string `json:"root_folder_id"`
AuthUrl string `json:"auth_url" type:"string" default:""`
Icon string `json:"icon" type:"string" default:""`
Code string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
}
type About struct {
Total int `json:"total"`
Used int `json:"used"`
State string `json:"state"`
}
type Info struct {
CreatedBy struct {
User struct {
Email string `json:"email"`
DisplayName string `json:"displayName"`
} `json:"user"`
} `json:"createdBy"`
ParentReference struct {
DriveID string `json:"driveId"`
DriveType string `json:"driveType"`
} `json:"parentReference"`
}
var config = driver.Config{
Name: "Onedrive",
LocalSort: true,
DefaultRoot: "/",
}

160
drivers/onedrive/util.go Normal file
View File

@ -0,0 +1,160 @@
package onedrive
import (
"errors"
"fmt"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/drivers/base"
"github.com/IceWhaleTech/CasaOS/pkg/utils"
"go.uber.org/zap"
)
var (
client_id = "private build"
client_secret = "private build"
)
var onedriveHostMap = map[string]Host{
"global": {
Oauth: "https://login.microsoftonline.com",
Api: "https://graph.microsoft.com",
},
"cn": {
Oauth: "https://login.chinacloudapi.cn",
Api: "https://microsoftgraph.chinacloudapi.cn",
},
"us": {
Oauth: "https://login.microsoftonline.us",
Api: "https://graph.microsoft.us",
},
"de": {
Oauth: "https://login.microsoftonline.de",
Api: "https://graph.microsoft.de",
},
}
func (d *Onedrive) GetMetaUrl(auth bool, path string) string {
host := onedriveHostMap[d.Region]
path = utils.EncodePath(path, true)
if auth {
return host.Oauth
}
if d.IsSharepoint {
if path == "/" || path == "\\" {
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root", host.Api, d.SiteId)
} else {
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root:%s:", host.Api, d.SiteId, path)
}
} else {
if path == "/" || path == "\\" {
return fmt.Sprintf("%s/v1.0/me/drive/root", host.Api)
} else {
return fmt.Sprintf("%s/v1.0/me/drive/root:%s:", host.Api, path)
}
}
}
func (d *Onedrive) refreshToken() error {
var err error
for i := 0; i < 3; i++ {
err = d._refreshToken()
if err == nil {
break
}
}
return err
}
func (d *Onedrive) getRefreshToken() error {
url := d.GetMetaUrl(true, "") + "/common/oauth2/v2.0/token"
var resp base.TokenResp
var e TokenErr
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "authorization_code",
"client_id": d.ClientID,
"client_secret": d.ClientSecret,
"code": d.Code,
"redirect_uri": d.RedirectUri,
}).Post(url)
if err != nil {
return err
}
logger.Info("get refresh token", zap.String("res", res.String()))
if e.Error != "" {
return fmt.Errorf("%s", e.ErrorDescription)
}
if resp.RefreshToken == "" {
return errors.New("refresh token is empty")
}
d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken
return nil
}
func (d *Onedrive) _refreshToken() error {
url := d.GetMetaUrl(true, "") + "/common/oauth2/v2.0/token"
var resp base.TokenResp
var e TokenErr
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token",
"client_id": d.ClientID,
"client_secret": d.ClientSecret,
"redirect_uri": d.RedirectUri,
"refresh_token": d.RefreshToken,
}).Post(url)
if err != nil {
return err
}
logger.Info("get refresh token", zap.String("res", res.String()))
if e.Error != "" {
return fmt.Errorf("%s", e.ErrorDescription)
}
if resp.RefreshToken == "" {
return errors.New("refresh token is empty")
}
d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken
return nil
}
func (d *Onedrive) Request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
if callback != nil {
callback(req)
}
if resp != nil {
req.SetResult(resp)
}
var e RespErr
req.SetError(&e)
res, err := req.Execute(method, url)
if err != nil {
return nil, err
}
if e.Error.Code != "" {
if e.Error.Code == "InvalidAuthenticationToken" {
err = d.refreshToken()
if err != nil {
return nil, err
}
return d.Request(url, method, callback, resp)
}
return nil, errors.New(e.Error.Message)
}
return res.Body(), nil
}
func GetConfig() Onedrive {
config := Onedrive{}
config.ClientID = client_id
config.ClientSecret = client_secret
config.RootFolderID = "/"
config.AuthUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id + "&response_type=code&redirect_uri=https%3A%2F%2Fcloudoauth.files.casaos.app&scope=offline_access+files.readwrite.all&state=${HOST}%2Fv1%2Frecover%2FOnedrive"
config.Icon = "./img/driver/OneDrive.svg"
config.Region = "global"
config.RedirectUri = "https://cloudoauth.files.casaos.app"
return config
}

142
go.mod
View File

@ -1,42 +1,126 @@
module github.com/IceWhaleTech/CasaOS
go 1.16
go 1.21
require (
github.com/Curtis-Milo/nat-type-identifier-go v0.0.0-20220215191915-18d42168c63d
github.com/IceWhaleTech/CasaOS-Common v0.4.0-alpha1
github.com/IceWhaleTech/CasaOS-Gateway v0.3.6
github.com/ambelovsky/go-structs v1.1.0 // indirect
github.com/ambelovsky/gosf v0.0.0-20201109201340-237aea4d6109
github.com/ambelovsky/gosf-socketio v0.0.0-20201109193639-add9d32f8b19 // indirect
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
github.com/IceWhaleTech/CasaOS-Common v0.4.11-alpha4
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/deckarep/golang-set/v2 v2.3.0
github.com/deepmap/oapi-codegen v1.12.4
github.com/disintegration/imaging v1.6.2
github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
github.com/gin-contrib/gzip v0.0.6
github.com/gin-gonic/gin v1.8.1
github.com/glebarez/sqlite v1.5.0
github.com/go-ini/ini v1.62.0
github.com/dsoprea/go-exif/v3 v3.0.1
github.com/getkin/kin-openapi v0.117.0
github.com/glebarez/sqlite v1.8.0
github.com/go-ini/ini v1.67.0
github.com/go-resty/resty/v2 v2.7.0
github.com/golang/mock v1.6.0
github.com/gomodule/redigo v1.8.5
github.com/gomodule/redigo v1.8.9
github.com/google/go-github/v36 v36.0.0
github.com/googollee/go-socket.io v1.6.2
github.com/google/uuid v1.5.0
github.com/googollee/go-socket.io v1.7.0
github.com/gorilla/websocket v1.5.0
github.com/h2non/filetype v1.1.3
github.com/hirochachacha/go-smb2 v1.1.0
github.com/lucas-clemente/quic-go v0.25.0
github.com/json-iterator/go v1.1.12
github.com/labstack/echo/v4 v4.12.0
github.com/maruel/natural v1.1.0
github.com/mholt/archiver/v3 v3.5.1
github.com/mileusna/useragent v1.2.1
github.com/moby/sys/mount v0.3.3
github.com/moby/sys/mountinfo v0.6.2
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/robfig/cron v1.2.0
github.com/satori/go.uuid v1.2.0
github.com/shirou/gopsutil/v3 v3.22.7
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.8.0
github.com/tidwall/gjson v1.10.2
go.uber.org/zap v1.21.0
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/mod v0.5.0 // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
golang.org/x/tools v0.1.7 // indirect
gorm.io/gorm v1.24.0
github.com/pkg/errors v0.9.1
github.com/robfig/cron/v3 v3.0.1
github.com/samber/lo v1.38.1
github.com/shirou/gopsutil/v3 v3.23.2
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0
github.com/tidwall/gjson v1.17.0
go.uber.org/goleak v1.2.1
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.23.0
golang.org/x/oauth2 v0.7.0
golang.org/x/sync v0.3.0
golang.org/x/sys v0.20.0
gorm.io/gorm v1.25.0
gotest.tools v2.2.0+incompatible
)
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/benbjohnson/clock v1.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/geoffgarside/ber v1.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.1 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/safetext v0.0.0-20240104143208-7a7d9b3d812f // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/labstack/echo-jwt/v4 v4.2.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/perimeterx/marshmallow v1.1.4 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
golang.org/x/image v0.6.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.4 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.21.2 // indirect
mvdan.cc/sh/v3 v3.7.0 // indirect
)

1319
go.sum

File diff suppressed because it is too large Load Diff

43
internal/conf/config.go Normal file
View File

@ -0,0 +1,43 @@
package conf
type Database struct {
Type string `json:"type" env:"DB_TYPE"`
Host string `json:"host" env:"DB_HOST"`
Port int `json:"port" env:"DB_PORT"`
User string `json:"user" env:"DB_USER"`
Password string `json:"password" env:"DB_PASS"`
Name string `json:"name" env:"DB_NAME"`
DBFile string `json:"db_file" env:"DB_FILE"`
TablePrefix string `json:"table_prefix" env:"DB_TABLE_PREFIX"`
SSLMode string `json:"ssl_mode" env:"DB_SSL_MODE"`
}
type Scheme struct {
Https bool `json:"https" env:"HTTPS"`
CertFile string `json:"cert_file" env:"CERT_FILE"`
KeyFile string `json:"key_file" env:"KEY_FILE"`
}
type LogConfig struct {
Enable bool `json:"enable" env:"LOG_ENABLE"`
Name string `json:"name" env:"LOG_NAME"`
MaxSize int `json:"max_size" env:"MAX_SIZE"`
MaxBackups int `json:"max_backups" env:"MAX_BACKUPS"`
MaxAge int `json:"max_age" env:"MAX_AGE"`
Compress bool `json:"compress" env:"COMPRESS"`
}
type Config struct {
Force bool `json:"force" env:"FORCE"`
Address string `json:"address" env:"ADDR"`
Port int `json:"port" env:"PORT"`
SiteURL string `json:"site_url" env:"SITE_URL"`
Cdn string `json:"cdn" env:"CDN"`
JwtSecret string `json:"jwt_secret" env:"JWT_SECRET"`
TokenExpiresIn int `json:"token_expires_in" env:"TOKEN_EXPIRES_IN"`
Database Database `json:"database"`
Scheme Scheme `json:"scheme"`
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
BleveDir string `json:"bleve_dir" env:"BLEVE_DIR"`
Log LogConfig `json:"log"`
}

72
internal/conf/const.go Normal file
View File

@ -0,0 +1,72 @@
package conf
const (
TypeString = "string"
TypeSelect = "select"
TypeBool = "bool"
TypeText = "text"
TypeNumber = "number"
)
const (
// site
VERSION = "version"
ApiUrl = "api_url"
BasePath = "base_path"
SiteTitle = "site_title"
Announcement = "announcement"
AllowIndexed = "allow_indexed"
Logo = "logo"
Favicon = "favicon"
MainColor = "main_color"
// preview
TextTypes = "text_types"
AudioTypes = "audio_types"
VideoTypes = "video_types"
ImageTypes = "image_types"
ProxyTypes = "proxy_types"
ProxyIgnoreHeaders = "proxy_ignore_headers"
AudioAutoplay = "audio_autoplay"
VideoAutoplay = "video_autoplay"
// global
HideFiles = "hide_files"
CustomizeHead = "customize_head"
CustomizeBody = "customize_body"
LinkExpiration = "link_expiration"
SignAll = "sign_all"
PrivacyRegs = "privacy_regs"
OcrApi = "ocr_api"
FilenameCharMapping = "filename_char_mapping"
// index
SearchIndex = "search_index"
AutoUpdateIndex = "auto_update_index"
IndexPaths = "index_paths"
IgnorePaths = "ignore_paths"
// aria2
Aria2Uri = "aria2_uri"
Aria2Secret = "aria2_secret"
// single
Token = "token"
IndexProgress = "index_progress"
//Github
GithubClientId = "github_client_id"
GithubClientSecrets = "github_client_secrets"
GithubLoginEnabled = "github_login_enabled"
)
const (
UNKNOWN = iota
FOLDER
//OFFICE
VIDEO
AUDIO
TEXT
IMAGE
)

30
internal/conf/var.go Normal file
View File

@ -0,0 +1,30 @@
package conf
import "regexp"
var (
BuiltAt string
GoVersion string
GitAuthor string
GitCommit string
Version string = "dev"
WebVersion string
)
var (
Conf *Config
)
var SlicesMap = make(map[string][]string)
var FilenameCharMap = make(map[string]string)
var PrivacyReg []*regexp.Regexp
var (
// StoragesLoaded loaded success if empty
StoragesLoaded = false
)
var (
RawIndexHtml string
ManageHtml string
IndexHtml string
)

25
internal/driver/config.go Normal file
View File

@ -0,0 +1,25 @@
/*
* @Author: a624669980@163.com a624669980@163.com
* @Date: 2022-12-13 11:05:05
* @LastEditors: a624669980@163.com a624669980@163.com
* @LastEditTime: 2022-12-13 11:05:13
* @FilePath: /drive/internal/driver/config.go
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
package driver
type Config struct {
Name string `json:"name"`
LocalSort bool `json:"local_sort"`
OnlyLocal bool `json:"only_local"`
OnlyProxy bool `json:"only_proxy"`
NoCache bool `json:"no_cache"`
NoUpload bool `json:"no_upload"`
NeedMs bool `json:"need_ms"` // if need get message from user, such as validate code
DefaultRoot string `json:"default_root"`
CheckStatus bool
}
func (c Config) MustProxy() bool {
return c.OnlyProxy || c.OnlyLocal
}

133
internal/driver/driver.go Normal file
View File

@ -0,0 +1,133 @@
package driver
import (
"context"
"github.com/IceWhaleTech/CasaOS/model"
)
type Driver interface {
Meta
Reader
User
//Writer
//Other
}
type Meta interface {
Config() Config
// GetStorage just get raw storage, no need to implement, because model.Storage have implemented
GetStorage() *model.StorageA
SetStorage(model.StorageA)
// GetAddition Additional is used for unmarshal of JSON, so need return pointer
GetAddition() Additional
// Init If already initialized, drop first
Init(ctx context.Context) error
Drop(ctx context.Context) error
}
type Other interface {
Other(ctx context.Context, args model.OtherArgs) (interface{}, error)
}
type Reader interface {
// List files in the path
// if identify files by path, need to set ID with path,like path.Join(dir.GetID(), obj.GetName())
// if identify files by id, need to set ID with corresponding id
// List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error)
// Link get url/filepath/reader of file
// Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error)
}
type User interface {
// GetRoot get root directory of user
GetUserInfo(ctx context.Context) (string, error)
GetInfo(ctx context.Context) (string, string, string, error)
}
type Getter interface {
GetRoot(ctx context.Context) (model.Obj, error)
}
//type Writer interface {
// Mkdir
// Move
// Rename
// Copy
// Remove
// Put
//}
type Mkdir interface {
MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error
}
type Move interface {
Move(ctx context.Context, srcObj, dstDir model.Obj) error
}
type Rename interface {
Rename(ctx context.Context, srcObj model.Obj, newName string) error
}
type Copy interface {
Copy(ctx context.Context, srcObj, dstDir model.Obj) error
}
type Remove interface {
Remove(ctx context.Context, obj model.Obj) error
}
type Put interface {
Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up UpdateProgress) error
}
//type WriteResult interface {
// MkdirResult
// MoveResult
// RenameResult
// CopyResult
// PutResult
// Remove
//}
type MkdirResult interface {
MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error)
}
type MoveResult interface {
Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error)
}
type RenameResult interface {
Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error)
}
type CopyResult interface {
Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error)
}
type PutResult interface {
Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up UpdateProgress) (model.Obj, error)
}
type UpdateProgress func(percentage int)
type Progress struct {
Total int64
Done int64
up UpdateProgress
}
func (p *Progress) Write(b []byte) (n int, err error) {
n = len(b)
p.Done += int64(n)
p.up(int(float64(p.Done) / float64(p.Total) * 100))
return
}
func NewProgress(total int64, up UpdateProgress) *Progress {
return &Progress{
Total: total,
up: up,
}
}

56
internal/driver/item.go Normal file
View File

@ -0,0 +1,56 @@
/*
* @Author: a624669980@163.com a624669980@163.com
* @Date: 2022-12-13 11:05:47
* @LastEditors: a624669980@163.com a624669980@163.com
* @LastEditTime: 2022-12-13 11:05:54
* @FilePath: /drive/internal/driver/item.go
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
package driver
type Additional interface{}
type Select string
type Item struct {
Name string `json:"name"`
Type string `json:"type"`
Default string `json:"default"`
Options string `json:"options"`
Required bool `json:"required"`
Help string `json:"help"`
}
type Info struct {
Common []Item `json:"common"`
Additional []Item `json:"additional"`
Config Config `json:"config"`
}
type IRootPath interface {
GetRootPath() string
}
type IRootId interface {
GetRootId() string
}
type RootPath struct {
RootFolderPath string `json:"root_folder_path"`
}
type RootID struct {
RootFolderID string `json:"root_folder_id" omit:"true"`
}
func (r RootPath) GetRootPath() string {
return r.RootFolderPath
}
func (r *RootPath) SetRootPath(path string) {
r.RootFolderPath = path
}
func (r RootID) GetRootId() string {
return r.RootFolderID
}

6
internal/op/const.go Normal file
View File

@ -0,0 +1,6 @@
package op
const (
WORK = "work"
RootName = "root"
)

173
internal/op/driver.go Normal file
View File

@ -0,0 +1,173 @@
package op
import (
"reflect"
"strings"
"github.com/IceWhaleTech/CasaOS/internal/conf"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/pkg/errors"
)
type New func() driver.Driver
var driverNewMap = map[string]New{}
var driverInfoMap = map[string][]driver.Item{} //driver.Info{}
func RegisterDriver(driver New) {
// log.Infof("register driver: [%s]", config.Name)
tempDriver := driver()
tempConfig := tempDriver.Config()
registerDriverItems(tempConfig, tempDriver.GetAddition())
driverNewMap[tempConfig.Name] = driver
}
func GetDriverNew(name string) (New, error) {
n, ok := driverNewMap[name]
if !ok {
return nil, errors.Errorf("no driver named: %s", name)
}
return n, nil
}
func GetDriverNames() []string {
var driverNames []string
for k := range driverInfoMap {
driverNames = append(driverNames, k)
}
return driverNames
}
// func GetDriverInfoMap() map[string]driver.Info {
// return driverInfoMap
// }
func GetDriverInfoMap() map[string][]driver.Item {
return driverInfoMap
}
func registerDriverItems(config driver.Config, addition driver.Additional) {
// log.Debugf("addition of %s: %+v", config.Name, addition)
tAddition := reflect.TypeOf(addition)
for tAddition.Kind() == reflect.Pointer {
tAddition = tAddition.Elem()
}
//mainItems := getMainItems(config)
additionalItems := getAdditionalItems(tAddition, config.DefaultRoot)
driverInfoMap[config.Name] = additionalItems
// driver.Info{
// Common: mainItems,
// Additional: additionalItems,
// Config: config,
// }
}
func getMainItems(config driver.Config) []driver.Item {
items := []driver.Item{{
Name: "mount_path",
Type: conf.TypeString,
Required: true,
Help: "",
}, {
Name: "order",
Type: conf.TypeNumber,
Help: "use to sort",
}, {
Name: "remark",
Type: conf.TypeText,
}}
if !config.NoCache {
items = append(items, driver.Item{
Name: "cache_expiration",
Type: conf.TypeNumber,
Default: "30",
Required: true,
Help: "The cache expiration time for this storage",
})
}
if !config.OnlyProxy && !config.OnlyLocal {
items = append(items, []driver.Item{{
Name: "web_proxy",
Type: conf.TypeBool,
}, {
Name: "webdav_policy",
Type: conf.TypeSelect,
Options: "302_redirect,use_proxy_url,native_proxy",
Default: "302_redirect",
Required: true,
},
}...)
} else {
items = append(items, driver.Item{
Name: "webdav_policy",
Type: conf.TypeSelect,
Default: "native_proxy",
Options: "use_proxy_url,native_proxy",
Required: true,
})
}
items = append(items, driver.Item{
Name: "down_proxy_url",
Type: conf.TypeText,
})
if config.LocalSort {
items = append(items, []driver.Item{{
Name: "order_by",
Type: conf.TypeSelect,
Options: "name,size,modified",
}, {
Name: "order_direction",
Type: conf.TypeSelect,
Options: "asc,desc",
}}...)
}
items = append(items, driver.Item{
Name: "extract_folder",
Type: conf.TypeSelect,
Options: "front,back",
})
return items
}
func getAdditionalItems(t reflect.Type, defaultRoot string) []driver.Item {
var items []driver.Item
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Type.Kind() == reflect.Struct {
items = append(items, getAdditionalItems(field.Type, defaultRoot)...)
continue
}
tag := field.Tag
ignore, ok1 := tag.Lookup("ignore")
name, ok2 := tag.Lookup("json")
if (ok1 && ignore == "true") || !ok2 {
continue
}
if tag.Get("omit") == "true" {
continue
}
item := driver.Item{
Name: name,
Type: strings.ToLower(field.Type.Name()),
Default: tag.Get("default"),
Options: tag.Get("options"),
Required: tag.Get("required") == "true",
Help: tag.Get("help"),
}
if tag.Get("type") != "" {
item.Type = tag.Get("type")
}
if item.Name == "root_folder_id" || item.Name == "root_folder_path" {
if item.Default == "" {
item.Default = defaultRoot
}
item.Required = item.Default != ""
}
// set default type to string
if item.Type == "" {
item.Type = "string"
}
items = append(items, item)
}
return items
}

109
internal/op/hook.go Normal file
View File

@ -0,0 +1,109 @@
package op
import (
"regexp"
"strings"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/internal/conf"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/model"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"go.uber.org/zap"
)
// Obj
type ObjsUpdateHook = func(parent string, objs []model.Obj)
var (
ObjsUpdateHooks = make([]ObjsUpdateHook, 0)
)
func RegisterObjsUpdateHook(hook ObjsUpdateHook) {
ObjsUpdateHooks = append(ObjsUpdateHooks, hook)
}
func HandleObjsUpdateHook(parent string, objs []model.Obj) {
for _, hook := range ObjsUpdateHooks {
hook(parent, objs)
}
}
// Setting
type SettingItemHook func(item *model.SettingItem) error
var settingItemHooks = map[string]SettingItemHook{
conf.VideoTypes: func(item *model.SettingItem) error {
conf.SlicesMap[conf.VideoTypes] = strings.Split(item.Value, ",")
return nil
},
conf.AudioTypes: func(item *model.SettingItem) error {
conf.SlicesMap[conf.AudioTypes] = strings.Split(item.Value, ",")
return nil
},
conf.ImageTypes: func(item *model.SettingItem) error {
conf.SlicesMap[conf.ImageTypes] = strings.Split(item.Value, ",")
return nil
},
conf.TextTypes: func(item *model.SettingItem) error {
conf.SlicesMap[conf.TextTypes] = strings.Split(item.Value, ",")
return nil
},
conf.ProxyTypes: func(item *model.SettingItem) error {
conf.SlicesMap[conf.ProxyTypes] = strings.Split(item.Value, ",")
return nil
},
conf.ProxyIgnoreHeaders: func(item *model.SettingItem) error {
conf.SlicesMap[conf.ProxyIgnoreHeaders] = strings.Split(item.Value, ",")
return nil
},
conf.PrivacyRegs: func(item *model.SettingItem) error {
regStrs := strings.Split(item.Value, "\n")
regs := make([]*regexp.Regexp, 0, len(regStrs))
for _, regStr := range regStrs {
reg, err := regexp.Compile(regStr)
if err != nil {
return errors.WithStack(err)
}
regs = append(regs, reg)
}
conf.PrivacyReg = regs
return nil
},
conf.FilenameCharMapping: func(item *model.SettingItem) error {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
err := json.UnmarshalFromString(item.Value, &conf.FilenameCharMap)
if err != nil {
return err
}
logger.Info("filename char mapping", zap.Any("FilenameCharMap", conf.FilenameCharMap))
return nil
},
}
func RegisterSettingItemHook(key string, hook SettingItemHook) {
settingItemHooks[key] = hook
}
func HandleSettingItemHook(item *model.SettingItem) (hasHook bool, err error) {
if hook, ok := settingItemHooks[item.Key]; ok {
return true, hook(item)
}
return false, nil
}
// Storage
type StorageHook func(typ string, storage driver.Driver)
var storageHooks = make([]StorageHook, 0)
func CallStorageHooks(typ string, storage driver.Driver) {
for _, hook := range storageHooks {
hook(typ, storage)
}
}
func RegisterStorageHook(hook StorageHook) {
storageHooks = append(storageHooks, hook)
}

36
internal/sign/sign.go Normal file
View File

@ -0,0 +1,36 @@
package sign
import (
"sync"
"time"
"github.com/IceWhaleTech/CasaOS/pkg/sign"
)
var once sync.Once
var instance sign.Sign
func Sign(data string) string {
return NotExpired(data)
}
func WithDuration(data string, d time.Duration) string {
once.Do(Instance)
return instance.Sign(data, time.Now().Add(d).Unix())
}
func NotExpired(data string) string {
once.Do(Instance)
return instance.Sign(data, 0)
}
func Verify(data string, sign string) error {
once.Do(Instance)
return instance.Verify(data, sign)
}
func Instance() {
instance = sign.NewHMACSign([]byte("token"))
}

135
main.go
View File

@ -1,6 +1,10 @@
//go:generate bash -c "mkdir -p codegen && go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4 -generate types,server,spec -package codegen api/casaos/openapi.yaml > codegen/casaos_api.go"
//go:generate bash -c "mkdir -p codegen/message_bus && go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4 -generate types,client -package message_bus https://raw.githubusercontent.com/IceWhaleTech/CasaOS-MessageBus/main/api/message_bus/openapi.yaml > codegen/message_bus/api.go"
package main
import (
"context"
_ "embed"
"flag"
"fmt"
"net"
@ -9,19 +13,23 @@ import (
"time"
"github.com/IceWhaleTech/CasaOS-Common/model"
"github.com/IceWhaleTech/CasaOS-Common/utils/command"
"github.com/IceWhaleTech/CasaOS-Common/utils/constants"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/model/notify"
util_http "github.com/IceWhaleTech/CasaOS-Common/utils/http"
"github.com/IceWhaleTech/CasaOS/common"
"github.com/IceWhaleTech/CasaOS/pkg/cache"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/sqlite"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/route"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/IceWhaleTech/CasaOS/types"
"github.com/coreos/go-systemd/daemon"
"go.uber.org/zap"
"github.com/robfig/cron"
"github.com/robfig/cron/v3"
"gorm.io/gorm"
)
@ -30,6 +38,18 @@ const LOCALHOST = "127.0.0.1"
var sqliteDB *gorm.DB
var (
commit = "private build"
date = "private build"
//go:embed api/index.html
_docHTML string
//go:embed api/casaos/openapi.yaml
_docYAML string
//go:embed build/sysroot/etc/casaos/casaos.conf.sample
_confSample string
configFlag = flag.String("c", "", "config address")
dbFlag = flag.String("db", "", "db path")
versionFlag = flag.Bool("v", false, "version")
@ -38,10 +58,14 @@ var (
func init() {
flag.Parse()
if *versionFlag {
fmt.Println("v" + types.CURRENTVERSION)
fmt.Println("v" + common.VERSION)
return
}
config.InitSetup(*configFlag)
println("git commit:", commit)
println("build date:", date)
config.InitSetup(*configFlag, _confSample)
logger.LogInit(config.AppInfo.LogPath, config.AppInfo.LogSaveName, config.AppInfo.LogFileExt)
if len(*dbFlag) == 0 {
@ -58,6 +82,11 @@ func init() {
service.GetCPUThermalZone()
route.InitFunction()
//service.MyService.System().GenreateSystemEntry()
///
//service.MountLists = make(map[string]*mountlib.MountPoint)
//configfile.Install()
}
// @title casaOS API
@ -72,52 +101,80 @@ func init() {
// @name Authorization
// @BasePath /v1
func main() {
service.NotifyMsg = make(chan notify.Message, 10)
if *versionFlag {
return
}
go route.SocketInit(service.NotifyMsg)
// model.Setup()
// gredis.Setup()
v1Router := route.InitV1Router()
r := route.InitRouter()
// service.SyncTask(sqliteDB)
cron2 := cron.New()
// every day execution
err := cron2.AddFunc("0/5 * * * * *", func() {
if service.ClientCount > 0 {
// route.SendNetINfoBySocket()
// route.SendCPUBySocket()
// route.SendMemBySocket()
// route.SendDiskBySocket()
// route.SendUSBBySocket()
route.SendAllHardwareStatusBySocket()
}
})
if err != nil {
fmt.Println(err)
v2Router := route.InitV2Router()
v2DocRouter := route.InitV2DocRouter(_docHTML, _docYAML)
v3File := route.InitFile()
mux := &util_http.HandlerMultiplexer{
HandlerMap: map[string]http.Handler{
"v1": v1Router,
"v2": v2Router,
"v3": v3File,
"doc": v2DocRouter,
},
}
cron2.Start()
defer cron2.Stop()
crontab := cron.New(cron.WithSeconds())
if _, err := crontab.AddFunc("@every 5s", route.SendAllHardwareStatusBySocket); err != nil {
logger.Error("add crontab error", zap.Error(err))
}
crontab.Start()
defer crontab.Stop()
listener, err := net.Listen("tcp", net.JoinHostPort(LOCALHOST, "0"))
if err != nil {
panic(err)
}
routers := []string{"sys", "port", "file", "folder", "batch", "image", "samba", "notify"}
for _, v := range routers {
routers := []string{
"/v1/sys",
"/v1/port",
"/v1/file",
"/v1/folder",
"/v1/batch",
"/v1/image",
"/v1/samba",
"/v1/notify",
"/v1/driver",
"/v1/cloud",
"/v1/recover",
"/v1/other",
"/v1/zt",
"/v1/test",
route.V2APIPath,
route.V2DocPath,
route.V3FilePath,
}
for _, apiPath := range routers {
err = service.MyService.Gateway().CreateRoute(&model.Route{
Path: "/v1/" + v,
Path: apiPath,
Target: "http://" + listener.Addr().String(),
})
if err != nil {
fmt.Println("err", err)
panic(err)
}
}
// register at message bus
for i := 0; i < 10; i++ {
response, err := service.MyService.MessageBus().RegisterEventTypesWithResponse(context.Background(), common.EventTypes)
if err != nil {
logger.Error("error when trying to register one or more event types - some event type will not be discoverable", zap.Error(err))
}
if response != nil && response.StatusCode() != http.StatusOK {
logger.Error("error when trying to register one or more event types - some event type will not be discoverable", zap.String("status", response.Status()), zap.String("body", string(response.Body)))
}
if response.StatusCode() == http.StatusOK {
break
}
time.Sleep(time.Second)
}
go func() {
time.Sleep(time.Second * 2)
// v0.3.6
@ -140,6 +197,10 @@ func main() {
)
}
// run any script that needs to be executed
scriptDirectory := filepath.Join(constants.DefaultConfigPath, "start.d")
command.ExecuteScripts(scriptDirectory)
if supported, err := daemon.SdNotify(false, daemon.SdNotifyReady); err != nil {
logger.Error("Failed to notify systemd that casaos main service is ready", zap.Any("error", err))
} else if supported {
@ -147,14 +208,20 @@ func main() {
} else {
logger.Info("This process is not running as a systemd service.")
}
// http.HandleFunc("/v1/file/test", func(w http.ResponseWriter, r *http.Request) {
// //http.ServeFile(w, r, r.URL.Path[1:])
// http.ServeFile(w, r, "/DATA/test.img")
// })
// go http.ListenAndServe(":8081", nil)
s := &http.Server{
Handler: r,
Handler: mux,
ReadHeaderTimeout: 5 * time.Second, // fix G112: Potential slowloris attack (see https://github.com/securego/gosec)
}
logger.Info("CasaOS main service is listening...", zap.Any("address", listener.Addr().String()))
// defer service.MyService.Storage().UnmountAllStorage()
err = s.Serve(listener) // not using http.serve() to fix G114: Use of net/http serve function that has no support for setting timeouts (see https://github.com/securego/gosec)
if err != nil {
panic(err)

39
model/args.go Normal file
View File

@ -0,0 +1,39 @@
package model
import (
"io"
"net/http"
"time"
)
type ListArgs struct {
ReqPath string
}
type LinkArgs struct {
IP string
Header http.Header
Type string
}
type Link struct {
URL string `json:"url"`
Header http.Header `json:"header"` // needed header
Data io.ReadCloser // return file reader directly
Status int // status maybe 200 or 206, etc
FilePath *string // local file, return the filepath
Expiration *time.Duration // url expiration time
Method string `json:"method"` // http method
}
type OtherArgs struct {
Obj Obj
Method string
Data interface{}
}
type FsOtherArgs struct {
Path string `json:"path" form:"path"`
Method string `json:"method" form:"method"`
Data interface{} `json:"data" form:"data"`
}

6
model/common.go Normal file
View File

@ -0,0 +1,6 @@
package model
type PageResp struct {
Content interface{} `json:"content"`
Total int64 `json:"total"`
}

7
model/drive.go Normal file
View File

@ -0,0 +1,7 @@
package model
type Drive struct {
Name string `json:"name"`
Icon string `json:"icon"`
AuthUrl string `json:"auth_url"`
}

View File

@ -1,20 +0,0 @@
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-05-26 14:39:22
* @LastEditors: LinkLeong
* @LastEditTime: 2022-05-26 19:08:52
* @FilePath: /CasaOS/model/notify/message.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package notify
import (
f "github.com/ambelovsky/gosf"
)
type Message struct {
Path string `json:"path"`
Msg f.Message `json:"msg"`
}

186
model/obj.go Normal file
View File

@ -0,0 +1,186 @@
package model
import (
"io"
"regexp"
"sort"
"strings"
"time"
mapset "github.com/deckarep/golang-set/v2"
"github.com/maruel/natural"
)
type UnwrapObj interface {
Unwrap() Obj
}
type Obj interface {
GetSize() int64
GetName() string
ModTime() time.Time
IsDir() bool
// The internal information of the driver.
// If you want to use it, please understand what it means
GetID() string
GetPath() string
}
type FileStreamer interface {
io.ReadCloser
Obj
GetMimetype() string
SetReadCloser(io.ReadCloser)
NeedStore() bool
GetReadCloser() io.ReadCloser
GetOld() Obj
}
type URL interface {
URL() string
}
type Thumb interface {
Thumb() string
}
type SetPath interface {
SetPath(path string)
}
func SortFiles(objs []Obj, orderBy, orderDirection string) {
if orderBy == "" {
return
}
sort.Slice(objs, func(i, j int) bool {
switch orderBy {
case "name":
{
c := natural.Less(objs[i].GetName(), objs[j].GetName())
if orderDirection == "desc" {
return !c
}
return c
}
case "size":
{
if orderDirection == "desc" {
return objs[i].GetSize() >= objs[j].GetSize()
}
return objs[i].GetSize() <= objs[j].GetSize()
}
case "modified":
if orderDirection == "desc" {
return objs[i].ModTime().After(objs[j].ModTime())
}
return objs[i].ModTime().Before(objs[j].ModTime())
}
return false
})
}
func ExtractFolder(objs []Obj, extractFolder string) {
if extractFolder == "" {
return
}
front := extractFolder == "front"
sort.SliceStable(objs, func(i, j int) bool {
if objs[i].IsDir() || objs[j].IsDir() {
if !objs[i].IsDir() {
return !front
}
if !objs[j].IsDir() {
return front
}
}
return false
})
}
// Wrap
func WrapObjName(objs Obj) Obj {
return &ObjWrapName{Obj: objs}
}
func WrapObjsName(objs []Obj) {
for i := 0; i < len(objs); i++ {
objs[i] = &ObjWrapName{Obj: objs[i]}
}
}
func UnwrapObjs(obj Obj) Obj {
if unwrap, ok := obj.(UnwrapObj); ok {
obj = unwrap.Unwrap()
}
return obj
}
func GetThumb(obj Obj) (thumb string, ok bool) {
if obj, ok := obj.(Thumb); ok {
return obj.Thumb(), true
}
if unwrap, ok := obj.(UnwrapObj); ok {
return GetThumb(unwrap.Unwrap())
}
return thumb, false
}
func GetUrl(obj Obj) (url string, ok bool) {
if obj, ok := obj.(URL); ok {
return obj.URL(), true
}
if unwrap, ok := obj.(UnwrapObj); ok {
return GetUrl(unwrap.Unwrap())
}
return url, false
}
// Merge
func NewObjMerge() *ObjMerge {
return &ObjMerge{
set: mapset.NewSet[string](),
}
}
type ObjMerge struct {
regs []*regexp.Regexp
set mapset.Set[string]
}
func (om *ObjMerge) Merge(objs []Obj, objs_ ...Obj) []Obj {
newObjs := make([]Obj, 0, len(objs)+len(objs_))
newObjs = om.insertObjs(om.insertObjs(newObjs, objs...), objs_...)
return newObjs
}
func (om *ObjMerge) insertObjs(objs []Obj, objs_ ...Obj) []Obj {
for _, obj := range objs_ {
if om.clickObj(obj) {
objs = append(objs, obj)
}
}
return objs
}
func (om *ObjMerge) clickObj(obj Obj) bool {
for _, reg := range om.regs {
if reg.MatchString(obj.GetName()) {
return false
}
}
return om.set.Add(obj.GetName())
}
func (om *ObjMerge) InitHideReg(hides string) {
rs := strings.Split(hides, "\n")
om.regs = make([]*regexp.Regexp, 0, len(rs))
for _, r := range rs {
om.regs = append(om.regs, regexp.MustCompile(r))
}
}
func (om *ObjMerge) Reset() {
om.set.Clear()
}

90
model/object.go Normal file
View File

@ -0,0 +1,90 @@
package model
import (
"time"
)
type ObjWrapName struct {
Name string
Obj
}
func (o *ObjWrapName) Unwrap() Obj {
return o.Obj
}
func (o *ObjWrapName) GetName() string {
if o.Name == "" {
o.Name = o.Obj.GetName()
}
return o.Name
}
type Object struct {
ID string
Path string
Name string
Size int64
Modified time.Time
IsFolder bool
}
func (o *Object) GetName() string {
return o.Name
}
func (o *Object) GetSize() int64 {
return o.Size
}
func (o *Object) ModTime() time.Time {
return o.Modified
}
func (o *Object) IsDir() bool {
return o.IsFolder
}
func (o *Object) GetID() string {
return o.ID
}
func (o *Object) GetPath() string {
return o.Path
}
func (o *Object) SetPath(id string) {
o.Path = id
}
type Thumbnail struct {
Thumbnail string
}
type Url struct {
Url string
}
func (w Url) URL() string {
return w.Url
}
func (t Thumbnail) Thumb() string {
return t.Thumbnail
}
type ObjThumb struct {
Object
Thumbnail
}
type ObjectURL struct {
Object
Url
}
type ObjThumbURL struct {
Object
Thumbnail
Url
}

23
model/req.go Normal file
View File

@ -0,0 +1,23 @@
package model
type PageReq struct {
Index int `json:"page" form:"index"`
Size int `json:"size" form:"size"`
}
const MaxUint = ^uint(0)
const MinUint = 0
const MaxInt = int(MaxUint >> 1)
const MinInt = -MaxInt - 1
func (p *PageReq) Validate() {
if p.Index < 1 {
p.Index = 1
}
if p.Size < 1 {
p.Size = 100000
}
// if p.PerPage < 1 {
// p.PerPage = MaxInt
// }
}

View File

@ -1,7 +1,9 @@
package model
type SearchFileInfo struct {
Path string `json:"path"`
Name string `json:"name"`
Type int `json:"type"`
type SearchEngine struct {
Name string `json:"name"`
Icon string `json:"icon"`
SearchUrl string `json:"search_url"`
RecoUrl string `json:"reco_url"`
Data []string `json:"data"`
}

33
model/setting.go Normal file
View File

@ -0,0 +1,33 @@
package model
const (
SINGLE = iota
SITE
STYLE
PREVIEW
GLOBAL
ARIA2
INDEX
GITHUB
)
const (
PUBLIC = iota
PRIVATE
READONLY
DEPRECATED
)
type SettingItem struct {
Key string `json:"key" gorm:"primaryKey" binding:"required"` // unique key
Value string `json:"value"` // value
Help string `json:"help"` // help message
Type string `json:"type"` // string, number, bool, select
Options string `json:"options"` // values for select
Group int `json:"group"` // use to group setting in frontend
Flag int `json:"flag"` // 0 = public, 1 = private, 2 = readonly, 3 = deprecated, etc.
}
func (s SettingItem) IsDeprecated() bool {
return s.Flag == DEPRECATED
}

View File

@ -1,69 +0,0 @@
package model
//
type SmartctlA struct {
Smartctl struct {
Version []int `json:"version"`
SvnRevision string `json:"svn_revision"`
PlatformInfo string `json:"platform_info"`
BuildInfo string `json:"build_info"`
Argv []string `json:"argv"`
ExitStatus int `json:"exit_status"`
} `json:"smartctl"`
Device struct {
Name string `json:"name"`
InfoName string `json:"info_name"`
Type string `json:"type"`
Protocol string `json:"protocol"`
} `json:"device"`
ModelName string `json:"model_name"`
SerialNumber string `json:"serial_number"`
FirmwareVersion string `json:"firmware_version"`
UserCapacity struct {
Blocks int `json:"blocks"`
Bytes int64 `json:"bytes"`
} `json:"user_capacity"`
SmartStatus struct {
Passed bool `json:"passed"`
} `json:"smart_status"`
AtaSmartData struct {
OfflineDataCollection struct {
Status struct {
Value int `json:"value"`
String string `json:"string"`
} `json:"status"`
CompletionSeconds int `json:"completion_seconds"`
} `json:"offline_data_collection"`
SelfTest struct {
Status struct {
Value int `json:"value"`
String string `json:"string"`
Passed bool `json:"passed"`
} `json:"status"`
PollingMinutes struct {
Short int `json:"short"`
Extended int `json:"extended"`
Conveyance int `json:"conveyance"`
} `json:"polling_minutes"`
} `json:"self_test"`
Capabilities struct {
Values []int `json:"values"`
ExecOfflineImmediateSupported bool `json:"exec_offline_immediate_supported"`
OfflineIsAbortedUponNewCmd bool `json:"offline_is_aborted_upon_new_cmd"`
OfflineSurfaceScanSupported bool `json:"offline_surface_scan_supported"`
SelfTestsSupported bool `json:"self_tests_supported"`
ConveyanceSelfTestSupported bool `json:"conveyance_self_test_supported"`
SelectiveSelfTestSupported bool `json:"selective_self_test_supported"`
AttributeAutosaveEnabled bool `json:"attribute_autosave_enabled"`
ErrorLoggingSupported bool `json:"error_logging_supported"`
GpLoggingSupported bool `json:"gp_logging_supported"`
} `json:"capabilities"`
} `json:"ata_smart_data"`
PowerOnTime struct {
Hours int `json:"hours"`
} `json:"power_on_time"`
PowerCycleCount int `json:"power_cycle_count"`
Temperature struct {
Current int `json:"current"`
} `json:"temperature"`
}

54
model/storage.go Normal file
View File

@ -0,0 +1,54 @@
package model
import "time"
type StorageA struct {
ID uint `json:"id" gorm:"primaryKey"` // unique key
MountPath string `json:"mount_path" gorm:"unique" binding:"required"` // must be standardized
Order int `json:"order"` // use to sort
Driver string `json:"driver"` // driver used
CacheExpiration int `json:"cache_expiration"` // cache expire time
Status string `json:"status"`
Addition string `json:"addition" gorm:"type:text"` // Additional information, defined in the corresponding driver
Remark string `json:"remark"`
Modified time.Time `json:"modified"`
Disabled bool `json:"disabled"` // if disabled
Sort
Proxy
}
type Sort struct {
OrderBy string `json:"order_by"`
OrderDirection string `json:"order_direction"`
ExtractFolder string `json:"extract_folder"`
}
type Proxy struct {
WebProxy bool `json:"web_proxy"`
WebdavPolicy string `json:"webdav_policy"`
DownProxyUrl string `json:"down_proxy_url"`
}
func (s *StorageA) GetStorage() *StorageA {
return s
}
func (s *StorageA) SetStorage(storage StorageA) {
*s = storage
}
func (s *StorageA) SetStatus(status string) {
s.Status = status
}
func (p Proxy) Webdav302() bool {
return p.WebdavPolicy == "302_redirect"
}
func (p Proxy) WebdavProxy() bool {
return p.WebdavPolicy == "use_proxy_url"
}
func (p Proxy) WebdavNative() bool {
return !p.Webdav302() && !p.WebdavProxy()
}

33
model/stream.go Normal file
View File

@ -0,0 +1,33 @@
package model
import (
"io"
)
type FileStream struct {
Obj
io.ReadCloser
Mimetype string
WebPutAsTask bool
Old Obj
}
func (f *FileStream) GetMimetype() string {
return f.Mimetype
}
func (f *FileStream) NeedStore() bool {
return f.WebPutAsTask
}
func (f *FileStream) GetReadCloser() io.ReadCloser {
return f.ReadCloser
}
func (f *FileStream) SetReadCloser(rc io.ReadCloser) {
f.ReadCloser = rc
}
func (f *FileStream) GetOld() Obj {
return f.Old
}

View File

@ -25,7 +25,6 @@ type ServerModel struct {
LockAccount bool
Token string
USBAutoMount string
SocketPort string
UpdateUrl string
}
@ -71,7 +70,8 @@ type FileSetting struct {
DownloadDir string `json:"download_dir"`
}
type BaseInfo struct {
Hash string `json:"i"`
Version string `json:"v"`
Channel string `json:"c,omitempty"`
Hash string `json:"i"`
Version string `json:"v"`
Channel string `json:"c,omitempty"`
DriveModel string `json:"m,omitempty"`
}

View File

@ -23,3 +23,14 @@ type Path struct {
Write bool `json:"write"`
Extensions map[string]interface{} `json:"extensions"`
}
type DeviceInfo struct {
LanIpv4 []string `json:"lan_ipv4"`
Port int `json:"port"`
DeviceName string `json:"device_name"`
DeviceModel string `json:"device_model"`
DeviceSN string `json:"device_sn"`
Initialized bool `json:"initialized"`
OS_Version string `json:"os_version"`
Hash string `json:"hash"`
}

39
package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "@icewhale/casaos-openapi",
"version": "0.0.1",
"scripts": {
"clean": "rm -rf generate",
"build": "rm -rf dist && tsc && yarn clean",
"generate:local": "openapi-generator-cli generate -g typescript-axios -i ./api/casaos/openapi.yaml -o ./generate",
"generate:npx": "npx @openapitools/openapi-generator-cli generate -g typescript-axios -i ./api/casaos/openapi.yaml -o ./generate",
"generate:ts": "npx openapi-typescript-codegen --input ./api/casaos/openapi.yaml --output ./generate",
"start": "yarn generate:local && yarn build"
},
"homepage": "https://github.com/IceWhaleTech/CasaOS#readme",
"description": "Casaos Typescript+Axios SDK",
"keywords": [
"CasaOS",
"SDK",
"CasaOS Axios"
],
"main": "dist/index.js",
"files": [
"LICENSE",
"README.md",
"dist",
"generate"
],
"dependencies": {
"axios": "^1.1.0"
},
"devDependencies": {
"all-contributors-cli": "^6.24.0",
"@openapitools/openapi-generator-cli": "2.5.2",
"@types/node": "^18.8.3",
"openapi-typescript-codegen": "^0.23.0",
"typescript": "^4.9.5"
},
"author": "casaos",
"license": "Apache-2.0"
}

View File

@ -10,6 +10,10 @@
*/
package config
const (
USERCONFIGURL = "/etc/casaos/casaos.conf"
import (
"path/filepath"
"github.com/IceWhaleTech/CasaOS-Common/utils/constants"
)
var CasaOSConfigFilePath = filepath.Join(constants.DefaultConfigPath, "casaos.conf")

View File

@ -14,80 +14,72 @@ import (
"fmt"
"log"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"github.com/IceWhaleTech/CasaOS-Common/utils/constants"
"github.com/IceWhaleTech/CasaOS/common"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/go-ini/ini"
)
// 系统配置
var SysInfo = &model.SysInfoModel{}
var (
SysInfo = &model.SysInfoModel{}
AppInfo = &model.APPModel{
DBPath: constants.DefaultDataPath,
LogPath: constants.DefaultLogPath,
LogSaveName: common.SERVICENAME,
LogFileExt: "log",
ShellPath: "/usr/share/casaos/shell",
UserDataPath: filepath.Join(constants.DefaultDataPath, "conf"),
}
CommonInfo = &model.CommonModel{
RuntimePath: constants.DefaultRuntimePath,
}
ServerInfo = &model.ServerModel{}
SystemConfigInfo = &model.SystemConfig{}
FileSettingInfo = &model.FileSetting{}
// 用户相关
var AppInfo = &model.APPModel{}
var CommonInfo = &model.CommonModel{}
// var RedisInfo = &model.RedisModel{}
// server相关
var ServerInfo = &model.ServerModel{}
var SystemConfigInfo = &model.SystemConfig{}
var FileSettingInfo = &model.FileSetting{}
var Cfg *ini.File
Cfg *ini.File
ConfigFilePath string
)
// 初始化设置,获取系统的部分信息。
func InitSetup(config string) {
configDir := USERCONFIGURL
func InitSetup(config string, sample string) {
ConfigFilePath = CasaOSConfigFilePath
if len(config) > 0 {
configDir = config
ConfigFilePath = config
}
if runtime.GOOS == "darwin" {
configDir = "./conf/conf.conf"
}
var err error
// 读取文件
Cfg, err = ini.Load(configDir)
if err != nil {
Cfg, err = ini.Load("/etc/casaos.conf")
// create default config file if not exist
if _, err := os.Stat(ConfigFilePath); os.IsNotExist(err) {
fmt.Println("config file not exist, create it")
// create config file
file, err := os.Create(ConfigFilePath)
if err != nil {
Cfg, err = ini.Load("/casaOS/server/conf/conf.ini")
if err != nil {
fmt.Printf("Fail to read file: %v", err)
os.Exit(1)
}
panic(err)
}
defer file.Close()
// write default config
_, err = file.WriteString(sample)
if err != nil {
panic(err)
}
}
var err error
// 读取文件
Cfg, err = ini.Load(ConfigFilePath)
if err != nil {
panic(err)
}
mapTo("app", AppInfo)
// mapTo("redis", RedisInfo)
mapTo("server", ServerInfo)
mapTo("system", SystemConfigInfo)
mapTo("file", FileSettingInfo)
mapTo("common", CommonInfo)
SystemConfigInfo.ConfigPath = configDir
if len(AppInfo.DBPath) == 0 {
AppInfo.DBPath = "/var/lib/casaos"
}
if len(AppInfo.LogPath) == 0 {
AppInfo.LogPath = "/var/log/casaos/"
}
if len(AppInfo.ShellPath) == 0 {
AppInfo.ShellPath = "/usr/share/casaos/shell"
}
if len(AppInfo.UserDataPath) == 0 {
AppInfo.UserDataPath = "/var/lib/casaos/conf"
}
if len(CommonInfo.RuntimePath) == 0 {
CommonInfo.RuntimePath = "/var/run/casaos"
}
Cfg.SaveTo(configDir)
// AppInfo.ProjectPath = getCurrentDirectory() //os.Getwd()
}
// 映射
@ -97,21 +89,3 @@ func mapTo(section string, v interface{}) {
log.Fatalf("Cfg.MapTo %s err: %v", section, err)
}
}
// 获取当前执行文件绝对路径go run
func getCurrentAbPathByCaller() string {
var abPath string
_, filename, _, ok := runtime.Caller(0)
if ok {
abPath = path.Dir(filename)
}
return abPath
}
func getCurrentDirectory() string {
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Fatal(err)
}
return strings.Replace(dir, "\\", "/", -1)
}

12
pkg/fs/fs.go Normal file
View File

@ -0,0 +1,12 @@
package fs
import "io"
// CheckClose is a utility function used to check the return from
// Close in a defer statement.
func CheckClose(c io.Closer, err *error) {
cerr := c.Close()
if *err == nil {
*err = cerr
}
}

View File

@ -0,0 +1,412 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generic_sync
import (
"sync"
"sync/atomic"
"unsafe"
)
// MapOf is like a Go map[interface{}]interface{} but is safe for concurrent use
// by multiple goroutines without additional locking or coordination.
// Loads, stores, and deletes run in amortized constant time.
//
// The MapOf type is specialized. Most code should use a plain Go map instead,
// with separate locking or coordination, for better type safety and to make it
// easier to maintain other invariants along with the map content.
//
// The MapOf type is optimized for two common use cases: (1) when the entry for a given
// key is only ever written once but read many times, as in caches that only grow,
// or (2) when multiple goroutines read, write, and overwrite entries for disjoint
// sets of keys. In these two cases, use of a MapOf may significantly reduce lock
// contention compared to a Go map paired with a separate Mutex or RWMutex.
//
// The zero MapOf is empty and ready for use. A MapOf must not be copied after first use.
type MapOf[K comparable, V any] struct {
mu sync.Mutex
// read contains the portion of the map's contents that are safe for
// concurrent access (with or without mu held).
//
// The read field itself is always safe to load, but must only be stored with
// mu held.
//
// Entries stored in read may be updated concurrently without mu, but updating
// a previously-expunged entry requires that the entry be copied to the dirty
// map and unexpunged with mu held.
read atomic.Value // readOnly
// dirty contains the portion of the map's contents that require mu to be
// held. To ensure that the dirty map can be promoted to the read map quickly,
// it also includes all of the non-expunged entries in the read map.
//
// Expunged entries are not stored in the dirty map. An expunged entry in the
// clean map must be unexpunged and added to the dirty map before a new value
// can be stored to it.
//
// If the dirty map is nil, the next write to the map will initialize it by
// making a shallow copy of the clean map, omitting stale entries.
dirty map[K]*entry[V]
// misses counts the number of loads since the read map was last updated that
// needed to lock mu to determine whether the key was present.
//
// Once enough misses have occurred to cover the cost of copying the dirty
// map, the dirty map will be promoted to the read map (in the unamended
// state) and the next store to the map will make a new dirty copy.
misses int
}
// readOnly is an immutable struct stored atomically in the MapOf.read field.
type readOnly[K comparable, V any] struct {
m map[K]*entry[V]
amended bool // true if the dirty map contains some key not in m.
}
// expunged is an arbitrary pointer that marks entries which have been deleted
// from the dirty map.
var expunged = unsafe.Pointer(new(interface{}))
// An entry is a slot in the map corresponding to a particular key.
type entry[V any] struct {
// p points to the interface{} value stored for the entry.
//
// If p == nil, the entry has been deleted and m.dirty == nil.
//
// If p == expunged, the entry has been deleted, m.dirty != nil, and the entry
// is missing from m.dirty.
//
// Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty
// != nil, in m.dirty[key].
//
// An entry can be deleted by atomic replacement with nil: when m.dirty is
// next created, it will atomically replace nil with expunged and leave
// m.dirty[key] unset.
//
// An entry's associated value can be updated by atomic replacement, provided
// p != expunged. If p == expunged, an entry's associated value can be updated
// only after first setting m.dirty[key] = e so that lookups using the dirty
// map find the entry.
p unsafe.Pointer // *interface{}
}
func newEntry[V any](i V) *entry[V] {
return &entry[V]{p: unsafe.Pointer(&i)}
}
// Load returns the value stored in the map for a key, or nil if no
// value is present.
// The ok result indicates whether value was found in the map.
func (m *MapOf[K, V]) Load(key K) (value V, ok bool) {
read, _ := m.read.Load().(readOnly[K, V])
e, ok := read.m[key]
if !ok && read.amended {
m.mu.Lock()
// Avoid reporting a spurious miss if m.dirty got promoted while we were
// blocked on m.mu. (If further loads of the same key will not miss, it's
// not worth copying the dirty map for this key.)
read, _ = m.read.Load().(readOnly[K, V])
e, ok = read.m[key]
if !ok && read.amended {
e, ok = m.dirty[key]
// Regardless of whether the entry was present, record a miss: this key
// will take the slow path until the dirty map is promoted to the read
// map.
m.missLocked()
}
m.mu.Unlock()
}
if !ok {
return value, false
}
return e.load()
}
func (m *MapOf[K, V]) Has(key K) bool {
_, ok := m.Load(key)
return ok
}
func (e *entry[V]) load() (value V, ok bool) {
p := atomic.LoadPointer(&e.p)
if p == nil || p == expunged {
return value, false
}
return *(*V)(p), true
}
// Store sets the value for a key.
func (m *MapOf[K, V]) Store(key K, value V) {
read, _ := m.read.Load().(readOnly[K, V])
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
m.mu.Lock()
read, _ = m.read.Load().(readOnly[K, V])
if e, ok := read.m[key]; ok {
if e.unexpungeLocked() {
// The entry was previously expunged, which implies that there is a
// non-nil dirty map and this entry is not in it.
m.dirty[key] = e
}
e.storeLocked(&value)
} else if e, ok := m.dirty[key]; ok {
e.storeLocked(&value)
} else {
if !read.amended {
// We're adding the first new key to the dirty map.
// Make sure it is allocated and mark the read-only map as incomplete.
m.dirtyLocked()
m.read.Store(readOnly[K, V]{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
}
// tryStore stores a value if the entry has not been expunged.
//
// If the entry is expunged, tryStore returns false and leaves the entry
// unchanged.
func (e *entry[V]) tryStore(i *V) bool {
for {
p := atomic.LoadPointer(&e.p)
if p == expunged {
return false
}
if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
return true
}
}
}
// unexpungeLocked ensures that the entry is not marked as expunged.
//
// If the entry was previously expunged, it must be added to the dirty map
// before m.mu is unlocked.
func (e *entry[V]) unexpungeLocked() (wasExpunged bool) {
return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}
// storeLocked unconditionally stores a value to the entry.
//
// The entry must be known not to be expunged.
func (e *entry[V]) storeLocked(i *V) {
atomic.StorePointer(&e.p, unsafe.Pointer(i))
}
// LoadOrStore returns the existing value for the key if present.
// Otherwise, it stores and returns the given value.
// The loaded result is true if the value was loaded, false if stored.
func (m *MapOf[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
// Avoid locking if it's a clean hit.
read, _ := m.read.Load().(readOnly[K, V])
if e, ok := read.m[key]; ok {
actual, loaded, ok := e.tryLoadOrStore(value)
if ok {
return actual, loaded
}
}
m.mu.Lock()
read, _ = m.read.Load().(readOnly[K, V])
if e, ok := read.m[key]; ok {
if e.unexpungeLocked() {
m.dirty[key] = e
}
actual, loaded, _ = e.tryLoadOrStore(value)
} else if e, ok := m.dirty[key]; ok {
actual, loaded, _ = e.tryLoadOrStore(value)
m.missLocked()
} else {
if !read.amended {
// We're adding the first new key to the dirty map.
// Make sure it is allocated and mark the read-only map as incomplete.
m.dirtyLocked()
m.read.Store(readOnly[K, V]{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
actual, loaded = value, false
}
m.mu.Unlock()
return actual, loaded
}
// tryLoadOrStore atomically loads or stores a value if the entry is not
// expunged.
//
// If the entry is expunged, tryLoadOrStore leaves the entry unchanged and
// returns with ok==false.
func (e *entry[V]) tryLoadOrStore(i V) (actual V, loaded, ok bool) {
p := atomic.LoadPointer(&e.p)
if p == expunged {
return actual, false, false
}
if p != nil {
return *(*V)(p), true, true
}
// Copy the interface after the first load to make this method more amenable
// to escape analysis: if we hit the "load" path or the entry is expunged, we
// shouldn'V bother heap-allocating.
ic := i
for {
if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) {
return i, false, true
}
p = atomic.LoadPointer(&e.p)
if p == expunged {
return actual, false, false
}
if p != nil {
return *(*V)(p), true, true
}
}
}
// Delete deletes the value for a key.
func (m *MapOf[K, V]) Delete(key K) {
read, _ := m.read.Load().(readOnly[K, V])
e, ok := read.m[key]
if !ok && read.amended {
m.mu.Lock()
read, _ = m.read.Load().(readOnly[K, V])
e, ok = read.m[key]
if !ok && read.amended {
delete(m.dirty, key)
}
m.mu.Unlock()
}
if ok {
e.delete()
}
}
func (e *entry[V]) delete() (hadValue bool) {
for {
p := atomic.LoadPointer(&e.p)
if p == nil || p == expunged {
return false
}
if atomic.CompareAndSwapPointer(&e.p, p, nil) {
return true
}
}
}
// Range calls f sequentially for each key and value present in the map.
// If f returns false, range stops the iteration.
//
// Range does not necessarily correspond to any consistent snapshot of the MapOf's
// contents: no key will be visited more than once, but if the value for any key
// is stored or deleted concurrently, Range may reflect any mapping for that key
// from any point during the Range call.
//
// Range may be O(N) with the number of elements in the map even if f returns
// false after a constant number of calls.
func (m *MapOf[K, V]) Range(f func(key K, value V) bool) {
// We need to be able to iterate over all of the keys that were already
// present at the start of the call to Range.
// If read.amended is false, then read.m satisfies that property without
// requiring us to hold m.mu for a long time.
read, _ := m.read.Load().(readOnly[K, V])
if read.amended {
// m.dirty contains keys not in read.m. Fortunately, Range is already O(N)
// (assuming the caller does not break out early), so a call to Range
// amortizes an entire copy of the map: we can promote the dirty copy
// immediately!
m.mu.Lock()
read, _ = m.read.Load().(readOnly[K, V])
if read.amended {
read = readOnly[K, V]{m: m.dirty}
m.read.Store(read)
m.dirty = nil
m.misses = 0
}
m.mu.Unlock()
}
for k, e := range read.m {
v, ok := e.load()
if !ok {
continue
}
if !f(k, v) {
break
}
}
}
// Values returns a slice of the values in the map.
func (m *MapOf[K, V]) Values() []V {
var values []V
m.Range(func(key K, value V) bool {
values = append(values, value)
return true
})
return values
}
func (m *MapOf[K, V]) Count() int {
return len(m.dirty)
}
func (m *MapOf[K, V]) Empty() bool {
return m.Count() == 0
}
func (m *MapOf[K, V]) ToMap() map[K]V {
ans := make(map[K]V)
m.Range(func(key K, value V) bool {
ans[key] = value
return true
})
return ans
}
func (m *MapOf[K, V]) Clear() {
m.Range(func(key K, value V) bool {
m.Delete(key)
return true
})
}
func (m *MapOf[K, V]) missLocked() {
m.misses++
if m.misses < len(m.dirty) {
return
}
m.read.Store(readOnly[K, V]{m: m.dirty})
m.dirty = nil
m.misses = 0
}
func (m *MapOf[K, V]) dirtyLocked() {
if m.dirty != nil {
return
}
read, _ := m.read.Load().(readOnly[K, V])
m.dirty = make(map[K]*entry[V], len(read.m))
for k, e := range read.m {
if !e.tryExpungeLocked() {
m.dirty[k] = e
}
}
}
func (e *entry[V]) tryExpungeLocked() (isExpunged bool) {
p := atomic.LoadPointer(&e.p)
for p == nil {
if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
return true
}
p = atomic.LoadPointer(&e.p)
}
return p == expunged
}

View File

@ -1,51 +0,0 @@
package quic_helper
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"math/big"
"github.com/lucas-clemente/quic-go"
)
// Setup a bare-bones TLS config for the server
func GetGenerateTLSConfig(token string) *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
NextProtos: []string{token},
SessionTicketsDisabled: true,
}
}
func GetClientTlsConfig(otherToken string) *tls.Config {
return &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{otherToken},
SessionTicketsDisabled: true,
}
}
func GetQUICConfig() *quic.Config {
return &quic.Config{
ConnectionIDLength: 4,
KeepAlive: true,
}
}

52
pkg/sign/hmac.go Normal file
View File

@ -0,0 +1,52 @@
package sign
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"io"
"strconv"
"strings"
"time"
)
type HMACSign struct {
SecretKey []byte
}
func (s HMACSign) Sign(data string, expire int64) string {
h := hmac.New(sha256.New, s.SecretKey)
expireTimeStamp := strconv.FormatInt(expire, 10)
_, err := io.WriteString(h, data+":"+expireTimeStamp)
if err != nil {
return ""
}
return base64.URLEncoding.EncodeToString(h.Sum(nil)) + ":" + expireTimeStamp
}
func (s HMACSign) Verify(data, sign string) error {
signSlice := strings.Split(sign, ":")
// check whether contains expire time
if signSlice[len(signSlice)-1] == "" {
return ErrExpireMissing
}
// check whether expire time is expired
expires, err := strconv.ParseInt(signSlice[len(signSlice)-1], 10, 64)
if err != nil {
return ErrExpireInvalid
}
// if expire time is expired, return error
if expires < time.Now().Unix() && expires != 0 {
return ErrSignExpired
}
// verify sign
if s.Sign(data, expires) != sign {
return ErrSignInvalid
}
return nil
}
func NewHMACSign(secret []byte) Sign {
return HMACSign{SecretKey: secret}
}

15
pkg/sign/sign.go Normal file
View File

@ -0,0 +1,15 @@
package sign
import "errors"
type Sign interface {
Sign(data string, expire int64) string
Verify(data, sign string) error
}
var (
ErrSignExpired = errors.New("sign expired")
ErrSignInvalid = errors.New("sign invalid")
ErrExpireInvalid = errors.New("expire invalid")
ErrExpireMissing = errors.New("expire missing")
)

View File

@ -0,0 +1,212 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package singleflight provides a duplicate function call suppression
// mechanism.
package singleflight
import (
"bytes"
"errors"
"fmt"
"runtime"
"runtime/debug"
"sync"
)
// errGoexit indicates the runtime.Goexit was called in
// the user given function.
var errGoexit = errors.New("runtime.Goexit was called")
// A panicError is an arbitrary value recovered from a panic
// with the stack trace during the execution of given function.
type panicError struct {
value any
stack []byte
}
// Error implements error interface.
func (p *panicError) Error() string {
return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
}
func newPanicError(v any) error {
stack := debug.Stack()
// The first line of the stack trace is of the form "goroutine N [status]:"
// but by the time the panic reaches Do the goroutine may no longer exist
// and its status will have changed. Trim out the misleading line.
if line := bytes.IndexByte(stack[:], '\n'); line >= 0 {
stack = stack[line+1:]
}
return &panicError{value: v, stack: stack}
}
// call is an in-flight or completed singleflight.Do call
type call[T any] struct {
wg sync.WaitGroup
// These fields are written once before the WaitGroup is done
// and are only read after the WaitGroup is done.
val T
err error
// forgotten indicates whether Forget was called with this call's key
// while the call was still in flight.
forgotten bool
// These fields are read and written with the singleflight
// mutex held before the WaitGroup is done, and are read but
// not written after the WaitGroup is done.
dups int
chans []chan<- Result[T]
}
// Group represents a class of work and forms a namespace in
// which units of work can be executed with duplicate suppression.
type Group[T any] struct {
mu sync.Mutex // protects m
m map[string]*call[T] // lazily initialized
}
// Result holds the results of Do, so they can be passed
// on a channel.
type Result[T any] struct {
Val T
Err error
Shared bool
}
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
// The return value shared indicates whether v was given to multiple callers.
func (g *Group[T]) Do(key string, fn func() (T, error)) (v T, err error, shared bool) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call[T])
}
if c, ok := g.m[key]; ok {
c.dups++
g.mu.Unlock()
c.wg.Wait()
if e, ok := c.err.(*panicError); ok {
panic(e)
} else if c.err == errGoexit {
runtime.Goexit()
}
return c.val, c.err, true
}
c := new(call[T])
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
g.doCall(c, key, fn)
return c.val, c.err, c.dups > 0
}
// DoChan is like Do but returns a channel that will receive the
// results when they are ready.
//
// The returned channel will not be closed.
func (g *Group[T]) DoChan(key string, fn func() (T, error)) <-chan Result[T] {
ch := make(chan Result[T], 1)
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call[T])
}
if c, ok := g.m[key]; ok {
c.dups++
c.chans = append(c.chans, ch)
g.mu.Unlock()
return ch
}
c := &call[T]{chans: []chan<- Result[T]{ch}}
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
go g.doCall(c, key, fn)
return ch
}
// doCall handles the single call for a key.
func (g *Group[T]) doCall(c *call[T], key string, fn func() (T, error)) {
normalReturn := false
recovered := false
// use double-defer to distinguish panic from runtime.Goexit,
// more details see https://golang.org/cl/134395
defer func() {
// the given function invoked runtime.Goexit
if !normalReturn && !recovered {
c.err = errGoexit
}
c.wg.Done()
g.mu.Lock()
defer g.mu.Unlock()
if !c.forgotten {
delete(g.m, key)
}
if e, ok := c.err.(*panicError); ok {
// In order to prevent the waiting channels from being blocked forever,
// needs to ensure that this panic cannot be recovered.
if len(c.chans) > 0 {
go panic(e)
select {} // Keep this goroutine around so that it will appear in the crash dump.
} else {
panic(e)
}
} else if c.err == errGoexit {
// Already in the process of goexit, no need to call again
} else {
// Normal return
for _, ch := range c.chans {
ch <- Result[T]{c.val, c.err, c.dups > 0}
}
}
}()
func() {
defer func() {
if !normalReturn {
// Ideally, we would wait to take a stack trace until we've determined
// whether this is a panic or a runtime.Goexit.
//
// Unfortunately, the only way we can distinguish the two is to see
// whether the recover stopped the goroutine from terminating, and by
// the time we know that, the part of the stack trace relevant to the
// panic has been discarded.
if r := recover(); r != nil {
c.err = newPanicError(r)
}
}
}()
c.val, c.err = fn()
normalReturn = true
}()
if !normalReturn {
recovered = true
}
}
// Forget tells the singleflight to forget about a key. Future calls
// to Do for this key will call the function rather than waiting for
// an earlier call to complete.
func (g *Group[T]) Forget(key string) {
g.mu.Lock()
if c, ok := g.m[key]; ok {
c.forgotten = true
}
delete(g.m, key)
g.mu.Unlock()
}

View File

@ -11,13 +11,12 @@
package sqlite
import (
"fmt"
"time"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
model2 "github.com/IceWhaleTech/CasaOS/service/model"
"github.com/glebarez/sqlite"
"go.uber.org/zap"
"gorm.io/gorm"
)
@ -32,23 +31,24 @@ func GetDb(dbPath string) *gorm.DB {
// db, err := gorm.Open(mysql2.Open(dsn), &gorm.Config{})
file.IsNotExistMkDir(dbPath)
db, err := gorm.Open(sqlite.Open(dbPath+"/casaOS.db"), &gorm.Config{})
if err != nil {
panic("sqlite connect error")
}
c, _ := db.DB()
c.SetMaxIdleConns(10)
c.SetMaxOpenConns(1)
c.SetConnMaxIdleTime(time.Second * 1000)
if err != nil {
logger.Error("sqlite connect error", zap.Any("db connect error", err))
panic("sqlite connect error")
}
gdb = db
err = db.AutoMigrate(&model2.AppNotify{}, model2.SharesDBModel{}, model2.ConnectionsDBModel{})
err = db.AutoMigrate(&model2.AppNotify{}, model2.SharesDBModel{}, model2.ConnectionsDBModel{}, model2.PeerDriveDBModel{})
if err != nil {
fmt.Println(err)
}
db.Exec("DROP TABLE IF EXISTS o_application")
db.Exec("DROP TABLE IF EXISTS o_friend")
db.Exec("DROP TABLE IF EXISTS o_person_download")
db.Exec("DROP TABLE IF EXISTS o_person_down_record")
if err != nil {
logger.Error("check or create db error", zap.Any("error", err))
}
return db
}

18
pkg/utils/balance.go Normal file
View File

@ -0,0 +1,18 @@
package utils
import "strings"
var balance = ".balance"
func IsBalance(str string) bool {
return strings.Contains(str, balance)
}
// GetActualMountPath remove balance suffix
func GetActualMountPath(virtualPath string) string {
bIndex := strings.LastIndex(virtualPath, ".balance")
if bIndex != -1 {
virtualPath = virtualPath[:bIndex]
}
return virtualPath
}

5
pkg/utils/bool.go Normal file
View File

@ -0,0 +1,5 @@
package utils
func IsBool(bs ...bool) bool {
return len(bs) > 0 && bs[0]
}

View File

@ -1,113 +0,0 @@
package command
import (
"bufio"
"context"
"fmt"
"io/ioutil"
"os/exec"
"time"
)
func OnlyExec(cmdStr string) {
cmd := exec.Command("/bin/bash", "-c", cmdStr)
stdout, err := cmd.StdoutPipe()
if err != nil {
return
}
defer stdout.Close()
if err := cmd.Start(); err != nil {
return
}
cmd.Wait()
return
}
func ExecResultStrArray(cmdStr string) []string {
cmd := exec.Command("/bin/bash", "-c", cmdStr)
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Println(err)
return nil
}
defer stdout.Close()
if err = cmd.Start(); err != nil {
fmt.Println(err)
return nil
}
// str, err := ioutil.ReadAll(stdout)
networklist := []string{}
outputBuf := bufio.NewReader(stdout)
for {
output, _, err := outputBuf.ReadLine()
if err != nil {
if err.Error() != "EOF" {
fmt.Printf("Error :%s\n", err)
}
break
}
networklist = append(networklist, string(output))
}
cmd.Wait()
return networklist
}
func ExecResultStr(cmdStr string) string {
cmd := exec.Command("/bin/bash", "-c", cmdStr)
println(cmd.String())
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Println(err)
return ""
}
defer stdout.Close()
if err := cmd.Start(); err != nil {
fmt.Println(err)
return ""
}
str, err := ioutil.ReadAll(stdout)
cmd.Wait()
if err != nil {
fmt.Println(err)
return ""
}
return string(str)
}
// 执行 lsblk 命令
func ExecLSBLK() []byte {
output, err := exec.Command("lsblk", "-O", "-J", "-b").Output()
if err != nil {
fmt.Println("lsblk", err)
return nil
}
return output
}
// 执行 lsblk 命令
func ExecLSBLKByPath(path string) []byte {
output, err := exec.Command("lsblk", path, "-O", "-J", "-b").Output()
if err != nil {
fmt.Println("lsblk", err)
return nil
}
return output
}
// exec smart
func ExecSmartCTLByPath(path string) []byte {
timeout := 3
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
output, err := exec.CommandContext(ctx, "smartctl", "-a", path, "-j").Output()
if err != nil {
fmt.Println("smartctl", err)
return nil
}
return output
}
func ExecEnabledSMART(path string) {
exec.Command("smartctl", "-s on", path).Output()
}

View File

@ -46,11 +46,12 @@ const (
ERROR_APP_NAME_EXIST = 50004
//file
FILE_DOES_NOT_EXIST = 60001
FILE_READ_ERROR = 60002
FILE_DELETE_ERROR = 60003
DIR_NOT_EXISTS = 60004
SOURCE_DES_SAME = 60005
FILE_DOES_NOT_EXIST = 60001
FILE_READ_ERROR = 60002
FILE_DELETE_ERROR = 60003
DIR_NOT_EXISTS = 60004
SOURCE_DES_SAME = 60005
MOUNTED_DIRECTIORIES = 60006
//share
SHARE_ALREADY_EXISTS = 70001
@ -109,8 +110,9 @@ var MsgFlags = map[int]string{
DIR_NOT_EXISTS: "Directory does not exist",
FILE_READ_ERROR: "File read error",
FILE_DELETE_ERROR: "Delete error",
FILE_READ_ERROR: "File read error",
FILE_DELETE_ERROR: "Delete error",
MOUNTED_DIRECTIORIES: "The directory is mounted, please unmount it first.",
COMMAND_ERROR_INVALID_OPERATION: "invalid operation",
}

14
pkg/utils/ctx.go Normal file
View File

@ -0,0 +1,14 @@
package utils
import (
"context"
)
func IsCanceled(ctx context.Context) bool {
select {
case <-ctx.Done():
return true
default:
return false
}
}

View File

@ -0,0 +1,11 @@
package utils
import "github.com/labstack/echo/v4"
func DefaultPostForm(ctx echo.Context, key, defaultValue string) string {
value := ctx.Request().Form.Get(key)
if value == "" {
return defaultValue
}
return value
}

View File

@ -0,0 +1,11 @@
package utils
import "github.com/labstack/echo/v4"
func DefaultQuery(ctx echo.Context, key string, defaultValue string) string {
if value := ctx.QueryParam(key); value != "" {
return value
}
return defaultValue
}

View File

@ -2,6 +2,7 @@ package file
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
@ -76,6 +77,22 @@ func RMDir(src string) error {
return nil
}
func RemoveAll(dir string) error {
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
return os.Remove(path)
}
return nil
})
if err != nil {
return err
}
return os.Remove(dir)
}
// Open a file according to a specific mode
func Open(name string, flag int, perm os.FileMode) (*os.File, error) {
f, err := os.OpenFile(name, flag, perm)
@ -162,7 +179,7 @@ func CreateFileAndWriteContent(path string, content string) error {
return nil
}
// IsNotExistMkDir create a directory if it does not exist
// IsNotExistCreateFile create a file if it does not exist
func IsNotExistCreateFile(src string) error {
if notExist := CheckNotExist(src); notExist {
if err := CreateFile(src); err != nil {
@ -430,7 +447,9 @@ func AddFile(ar archiver.Writer, path, commonPath string) error {
defer file.Close()
if path != commonPath {
filename := info.Name()
//filename := info.Name()
filename := strings.TrimPrefix(path, commonPath)
filename = strings.TrimPrefix(filename, string(filepath.Separator))
err = ar.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
@ -577,3 +596,165 @@ func ReadLine(lineNumber int, path string) string {
defer file.Close()
return ""
}
func NameAccumulation(name string, dir string) string {
path := filepath.Join(dir, name)
if _, err := os.Stat(path); os.IsNotExist(err) {
return name
}
base := name
strings.Split(base, "_")
index := strings.LastIndex(base, "_")
if index < 0 {
index = len(base)
}
for i := 1; ; i++ {
newPath := filepath.Join(dir, fmt.Sprintf("%s_%d", base[:index], i))
if _, err := os.Stat(newPath); os.IsNotExist(err) {
return fmt.Sprintf("%s_%d", base[:index], i)
}
}
}
func ParseFileHeader(h []byte, boundary []byte) (map[string]string, bool) {
arr := bytes.Split(h, boundary)
//var out_header FileHeader
//out_header.ContentLength = -1
const (
CONTENT_DISPOSITION = "Content-Disposition: "
NAME = "name=\""
FILENAME = "filename=\""
CONTENT_TYPE = "Content-Type: "
CONTENT_LENGTH = "Content-Length: "
)
result := make(map[string]string)
for _, item := range arr {
tarr := bytes.Split(item, []byte(";"))
if len(tarr) != 2 {
continue
}
tbyte := tarr[1]
fmt.Println(string(tbyte))
tbyte = bytes.ReplaceAll(tbyte, []byte("\r\n--"), []byte(""))
tbyte = bytes.ReplaceAll(tbyte, []byte("name=\""), []byte(""))
tempArr := bytes.Split(tbyte, []byte("\"\r\n\r\n"))
if len(tempArr) != 2 {
continue
}
bytes.HasPrefix(item, []byte("name="))
result[strings.TrimSpace(string(tempArr[0]))] = strings.TrimSpace(string(tempArr[1]))
}
// for _, item := range arr {
// if bytes.HasPrefix(item, []byte(CONTENT_DISPOSITION)) {
// l := len(CONTENT_DISPOSITION)
// arr1 := bytes.Split(item[l:], []byte("; "))
// out_header.ContentDisposition = string(arr1[0])
// if bytes.HasPrefix(arr1[1], []byte(NAME)) {
// out_header.Name = string(arr1[1][len(NAME) : len(arr1[1])-1])
// }
// l = len(arr1[2])
// if bytes.HasPrefix(arr1[2], []byte(FILENAME)) && arr1[2][l-1] == 0x22 {
// out_header.FileName = string(arr1[2][len(FILENAME) : l-1])
// }
// } else if bytes.HasPrefix(item, []byte(CONTENT_TYPE)) {
// l := len(CONTENT_TYPE)
// out_header.ContentType = string(item[l:])
// } else if bytes.HasPrefix(item, []byte(CONTENT_LENGTH)) {
// l := len(CONTENT_LENGTH)
// s := string(item[l:])
// content_length, err := strconv.ParseInt(s, 10, 64)
// if err != nil {
// log.Printf("content length error:%s", string(item))
// return out_header, false
// } else {
// out_header.ContentLength = content_length
// }
// } else {
// log.Printf("unknown:%s\n", string(item))
// }
// }
//fmt.Println(result)
// if len(out_header.FileName) == 0 {
// return out_header, false
// }
return result, true
}
func ReadToBoundary(boundary []byte, stream io.ReadCloser, target io.WriteCloser) ([]byte, bool, error) {
read_data := make([]byte, 1024*8)
read_data_len := 0
buf := make([]byte, 1024*4)
b_len := len(boundary)
reach_end := false
for !reach_end {
read_len, err := stream.Read(buf)
if err != nil {
if err != io.EOF && read_len <= 0 {
return nil, true, err
}
reach_end = true
}
copy(read_data[read_data_len:], buf[:read_len])
read_data_len += read_len
if read_data_len < b_len+4 {
continue
}
loc := bytes.Index(read_data[:read_data_len], boundary)
if loc >= 0 {
target.Write(read_data[:loc-4])
return read_data[loc:read_data_len], reach_end, nil
}
target.Write(read_data[:read_data_len-b_len-4])
copy(read_data[0:], read_data[read_data_len-b_len-4:])
read_data_len = b_len + 4
}
target.Write(read_data[:read_data_len])
return nil, reach_end, nil
}
func ParseFromHead(read_data []byte, read_total int, boundary []byte, stream io.ReadCloser) (map[string]string, []byte, error) {
buf := make([]byte, 1024*8)
found_boundary := false
boundary_loc := -1
for {
read_len, err := stream.Read(buf)
if err != nil {
if err != io.EOF {
return nil, nil, err
}
break
}
if read_total+read_len > cap(read_data) {
return nil, nil, fmt.Errorf("not found boundary")
}
copy(read_data[read_total:], buf[:read_len])
read_total += read_len
if !found_boundary {
boundary_loc = bytes.LastIndex(read_data[:read_total], boundary)
if boundary_loc == -1 {
continue
}
found_boundary = true
}
start_loc := boundary_loc + len(boundary)
fmt.Println(string(read_data))
file_head_loc := bytes.Index(read_data[start_loc:read_total], []byte("\r\n\r\n"))
if file_head_loc == -1 {
continue
}
file_head_loc += start_loc
ret := false
headMap, ret := ParseFileHeader(read_data, boundary)
if !ret {
return headMap, nil, fmt.Errorf("ParseFileHeader fail:%s", string(read_data[start_loc:file_head_loc]))
}
return headMap, read_data[file_head_loc+4 : read_total], nil
}
return nil, nil, fmt.Errorf("reach to sream EOF")
}

View File

@ -0,0 +1,16 @@
package file
import (
"fmt"
"testing"
"go.uber.org/goleak"
)
func TestNameAccumulation(t *testing.T) {
goleak.VerifyNone(t)
fmt.Println("aaa")
a := NameAccumulation("/mnt/test_1_1", "/")
fmt.Println(a)
}

171
pkg/utils/httper/drive.go Normal file
View File

@ -0,0 +1,171 @@
package httper
import (
"encoding/json"
"fmt"
"net"
"net/http"
"time"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
type MountList struct {
MountPoints []MountPoints `json:"mountPoints"`
}
type MountPoints struct {
MountPoint string `json:"MountPoint"`
Fs string `json:"Fs"`
Icon string `json:"Icon"`
Name string `json:"Name"`
}
type MountPoint struct {
MountPoint string `json:"mount_point"`
Fs string `json:"fs"`
Icon string `json:"icon"`
Name string `json:"name"`
}
type MountResult struct {
Error string `json:"error"`
Input struct {
Fs string `json:"fs"`
MountPoint string `json:"mountPoint"`
} `json:"input"`
Path string `json:"path"`
Status int `json:"status"`
}
type RemotesResult struct {
Remotes []string `json:"remotes"`
}
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
var DefaultTimeout = time.Second * 30
func NewRestyClient() *resty.Client {
unixSocket := "/var/run/rclone/rclone.sock"
transport := http.Transport{
Dial: func(_, _ string) (net.Conn, error) {
return net.Dial("unix", unixSocket)
},
}
client := resty.New()
client.SetTransport(&transport).SetBaseURL("http://localhost")
client.SetRetryCount(3).SetRetryWaitTime(5*time.Second).SetTimeout(DefaultTimeout).SetHeader("User-Agent", UserAgent)
return client
}
func GetMountList() (MountList, error) {
var result MountList
res, err := NewRestyClient().R().Post("/mount/listmounts")
if err != nil {
return result, err
}
if res.StatusCode() != 200 {
return result, fmt.Errorf("get mount list failed")
}
json.Unmarshal(res.Body(), &result)
for i := 0; i < len(result.MountPoints); i++ {
result.MountPoints[i].Fs = result.MountPoints[i].Fs[:len(result.MountPoints[i].Fs)-1]
}
return result, err
}
func Mount(mountPoint string, fs string) error {
res, err := NewRestyClient().R().SetFormData(map[string]string{
"mountPoint": mountPoint,
"fs": fs,
"mountOpt": `{"AllowOther": true}`,
"vfsOpt": `{"CacheMode": 3}`,
}).Post("/mount/mount")
if err != nil {
return err
}
if res.StatusCode() != 200 {
return fmt.Errorf("mount failed")
}
logger.Info("mount then", zap.Any("res", res.Body()))
return nil
}
func Unmount(mountPoint string) error {
res, err := NewRestyClient().R().SetFormData(map[string]string{
"mountPoint": mountPoint,
}).Post("/mount/unmount")
if err != nil {
logger.Error("when unmount", zap.Error(err))
return err
}
if res.StatusCode() != 200 {
logger.Error("then unmount failed", zap.Any("res", res.Body()))
return fmt.Errorf("unmount failed")
}
logger.Info("unmount then", zap.Any("res", res.Body()))
return nil
}
func CreateConfig(data map[string]string, name, t string) error {
data["config_is_local"] = "false"
dataStr, _ := json.Marshal(data)
res, err := NewRestyClient().R().SetFormData(map[string]string{
"name": name,
"parameters": string(dataStr),
"type": t,
}).Post("/config/create")
logger.Info("when create config then", zap.Any("res", res.Body()))
if err != nil {
return err
}
if res.StatusCode() != 200 {
return fmt.Errorf("create config failed")
}
return nil
}
func GetConfigByName(name string) (map[string]string, error) {
res, err := NewRestyClient().R().SetFormData(map[string]string{
"name": name,
}).Post("/config/get")
if err != nil {
return nil, err
}
if res.StatusCode() != 200 {
return nil, fmt.Errorf("create config failed")
}
var result map[string]string
json.Unmarshal(res.Body(), &result)
return result, nil
}
func GetAllConfigName() (RemotesResult, error) {
var result RemotesResult
res, err := NewRestyClient().R().SetFormData(map[string]string{}).Post("/config/listremotes")
if err != nil {
return result, err
}
if res.StatusCode() != 200 {
return result, fmt.Errorf("get config failed")
}
json.Unmarshal(res.Body(), &result)
return result, nil
}
func DeleteConfigByName(name string) error {
res, err := NewRestyClient().R().SetFormData(map[string]string{
"name": name,
}).Post("/config/delete")
if err != nil {
return err
}
if res.StatusCode() != 200 {
return fmt.Errorf("delete config failed")
}
return nil
}

View File

@ -0,0 +1,78 @@
package httper
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
)
func ZTGet(url string) ([]byte, error) {
port, err := ioutil.ReadFile("/var/lib/zerotier-one/zerotier-one.port")
if err != nil {
return nil, err
}
// Build the target URL
targetURL := fmt.Sprintf("http://localhost:%s%s", strings.TrimSpace(string(port)), url)
// Create a new request
req, err := http.NewRequest("GET", targetURL, nil)
if err != nil {
return nil, err
}
// Add the X-ZT1-AUTH header
authToken, err := ioutil.ReadFile("/var/lib/zerotier-one/authtoken.secret")
if err != nil {
return nil, err
}
req.Header.Set("X-ZT1-AUTH", strings.TrimSpace(string(authToken)))
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return respBody, nil
}
func ZTPost(url string, body string) ([]byte, error) {
port, err := ioutil.ReadFile("/var/lib/zerotier-one/zerotier-one.port")
if err != nil {
return nil, err
}
// Build the target URL
targetURL := fmt.Sprintf("http://localhost:%s%s", strings.TrimSpace(string(port)), url)
// Create a new request
req, err := http.NewRequest("POST", targetURL, strings.NewReader(body))
if err != nil {
return nil, err
}
// Add the X-ZT1-AUTH header
authToken, err := ioutil.ReadFile("/var/lib/zerotier-one/authtoken.secret")
if err != nil {
return nil, err
}
req.Header.Set("X-ZT1-AUTH", strings.TrimSpace(string(authToken)))
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return respBody, nil
}

View File

@ -1,6 +1,7 @@
package ip_helper
import (
"fmt"
"net"
"strings"
@ -10,21 +11,22 @@ import (
func IsIPv4(address string) bool {
return strings.Count(address, ":") < 2
}
func IsIPv6(address string) bool {
return strings.Count(address, ":") >= 2
}
//获取外网ip
// 获取外网ip
func GetExternalIPV4() string {
return httper2.Get("https://api.ipify.org", nil)
}
//获取外网ip
// 获取外网ip
func GetExternalIPV6() string {
return httper2.Get("https://api6.ipify.org", nil)
}
//获取本地ip
// 获取本地ip
func GetLoclIp() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
@ -35,11 +37,11 @@ func GetLoclIp() string {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
return "127.0.0.1"
}
func GetDeviceAllIP(port string) []string {
var address []string
addrs, err := net.InterfaceAddrs()
@ -55,12 +57,34 @@ func GetDeviceAllIP(port string) []string {
}
return address
}
func GetDeviceAllIPv4() map[string]string {
address := make(map[string]string)
addrs, err := net.Interfaces()
if err != nil {
return address
}
for _, a := range addrs {
if a.Flags&net.FlagLoopback != 0 || a.Flags&net.FlagUp == 0 {
continue
}
addrs, err := a.Addrs()
if err != nil {
fmt.Println("Error:", err)
continue
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil {
address[a.Name] = ipnet.IP.String()
}
}
}
return address
}
func HasLocalIP(ip net.IP) bool {
if ip.IsLoopback() {
return true
}
ip.String()
ip4 := ip.To4()
if ip4 == nil {

View File

@ -4,23 +4,28 @@ import (
"fmt"
"net"
"testing"
"go.uber.org/goleak"
)
func TestGetExternalIPV4(t *testing.T) {
goleak.VerifyNone(t)
ipv4 := make(chan string)
go func() { ipv4 <- GetExternalIPV4() }()
fmt.Println(<-ipv4)
}
func TestGetExternalIPV6(t *testing.T) {
ipv6 := make(chan string)
go func() { ipv6 <- GetExternalIPV6() }()
fmt.Println(<-ipv6)
}
func TestGetLoclIp(t *testing.T) {
fmt.Println(GetLoclIp())
}
func TestHasLocalIP(t *testing.T) {
fmt.Println("dddd")
fmt.Println(HasLocalIP(net.ParseIP("192.168.2.10")))

Some files were not shown because too many files have changed in this diff Show More