mirror of
https://github.com/IceWhaleTech/CasaOS.git
synced 2025-11-07 07:09:46 +00:00
Compare commits
221 Commits
v0.4.0-alp
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d3b2f444e | ||
|
|
030bcd1095 | ||
|
|
dad2f3f8c2 | ||
|
|
b09160e76b | ||
|
|
12fc102753 | ||
|
|
6ffe2cc89d | ||
|
|
b9098101e2 | ||
|
|
a6ff39e47d | ||
|
|
23eb739f01 | ||
|
|
ba285cb8bd | ||
|
|
63f01489a8 | ||
|
|
6bbb0802aa | ||
|
|
c26cf4dbec | ||
|
|
958a483385 | ||
|
|
16e97ec66f | ||
|
|
5ea588b813 | ||
|
|
7da0f0d49e | ||
|
|
2adb795896 | ||
|
|
8b251dc407 | ||
|
|
e4bf67dad5 | ||
|
|
e8f9d3aaf5 | ||
|
|
bb0d9ac25c | ||
|
|
3a835c00e3 | ||
|
|
e4447981cb | ||
|
|
170b599e86 | ||
|
|
18ce1d6342 | ||
|
|
1c483a5d9c | ||
|
|
8f7c99779f | ||
|
|
0883f5f3aa | ||
|
|
36dda53e9c | ||
|
|
53c3879907 | ||
|
|
ef57d3348d | ||
|
|
7a76aca022 | ||
|
|
fffdc7fd5e | ||
|
|
29d16d13ba | ||
|
|
54a115ae89 | ||
|
|
17fa7868a4 | ||
|
|
27d6dd86e2 | ||
|
|
ae50a9bb17 | ||
|
|
caf3347da9 | ||
|
|
718a08eab2 | ||
|
|
6f722b3506 | ||
|
|
e722d841a9 | ||
|
|
3ccf58695b | ||
|
|
c52386cef4 | ||
|
|
acac2df40f | ||
|
|
0b507cb7ad | ||
|
|
ac90509c66 | ||
|
|
abf7134710 | ||
|
|
f9a26af93f | ||
|
|
33eacbf4b3 | ||
|
|
262e0d49c8 | ||
|
|
689b0f769a | ||
|
|
8f8a082888 | ||
|
|
9b8c6f4299 | ||
|
|
657cbe5c41 | ||
|
|
b17bff68dd | ||
|
|
7807cfdb01 | ||
|
|
9e5381710f | ||
|
|
fa62c65526 | ||
|
|
3aea945277 | ||
|
|
4d8ca182cb | ||
|
|
b727606a0a | ||
|
|
428be5f2c2 | ||
|
|
995d67543d | ||
|
|
590beac43a | ||
|
|
e7bf227232 | ||
|
|
eedfdde311 | ||
|
|
38c7b5a569 | ||
|
|
8c0b219621 | ||
|
|
23fc677f30 | ||
|
|
a06508783c | ||
|
|
37fee157dd | ||
|
|
3f1c7098bd | ||
|
|
b11d046c52 | ||
|
|
d6a9ba65ed | ||
|
|
5f1df76dbf | ||
|
|
0cf353c56e | ||
|
|
5dc6297047 | ||
|
|
f26ae2793c | ||
|
|
d7641e1b93 | ||
|
|
37c496fd6a | ||
|
|
cb15c06f7e | ||
|
|
89a0ea34b0 | ||
|
|
d8dd815baa | ||
|
|
a77b5d1954 | ||
|
|
d0fb2f06f5 | ||
|
|
dd8032a8be | ||
|
|
760882e147 | ||
|
|
dfeed76a66 | ||
|
|
28d724731a | ||
|
|
4549d8778b | ||
|
|
05cadaabf3 | ||
|
|
1a0c15208c | ||
|
|
4f5b4b0887 | ||
|
|
cbfe44be61 | ||
|
|
e1bbb998df | ||
|
|
f48cddf924 | ||
|
|
23ce3487e1 | ||
|
|
6246421dae | ||
|
|
f867453573 | ||
|
|
78e7a8b411 | ||
|
|
1aa8fb600b | ||
|
|
7ecfea71ab | ||
|
|
d911d80254 | ||
|
|
9bfe37305c | ||
|
|
94dd2e00c7 | ||
|
|
c5d2cebe92 | ||
|
|
955e8dea07 | ||
|
|
4c7b3a749b | ||
|
|
e86a61596b | ||
|
|
c9617e583f | ||
|
|
dda15b0821 | ||
|
|
50816c68b0 | ||
|
|
c5de0319fb | ||
|
|
78b7c6ce09 | ||
|
|
b420a2d930 | ||
|
|
c67ee1731c | ||
|
|
eaca399ef9 | ||
|
|
e0e9f97764 | ||
|
|
2fefee87fa | ||
|
|
280ad4fcf9 | ||
|
|
538639b623 | ||
|
|
39535d6a38 | ||
|
|
4d977d7a62 | ||
|
|
08c500c434 | ||
|
|
705bf1facb | ||
|
|
aae1802191 | ||
|
|
8e1b9b82c1 | ||
|
|
af440eac55 | ||
|
|
34b4e154a1 | ||
|
|
79db93ec02 | ||
|
|
f8ec3b20cd | ||
|
|
8c7c8dc1ec | ||
|
|
60a141fe25 | ||
|
|
02e712f649 | ||
|
|
7022cf5d29 | ||
|
|
6cf46ce50c | ||
|
|
202c1de570 | ||
|
|
858ab5b124 | ||
|
|
e319975a60 | ||
|
|
5c2c3b5e98 | ||
|
|
a0dc58264a | ||
|
|
717b47ca2c | ||
|
|
f530f69ba5 | ||
|
|
87b190a84b | ||
|
|
10191a1be3 | ||
|
|
59f2ccbeb3 | ||
|
|
076b7198b2 | ||
|
|
eb483a4c5d | ||
|
|
60e81bc781 | ||
|
|
dc8ee89c85 | ||
|
|
c995750312 | ||
|
|
de6ed093a2 | ||
|
|
449e1515d9 | ||
|
|
e6ddb0d849 | ||
|
|
c19e32c6e9 | ||
|
|
94755e221a | ||
|
|
86a3692dad | ||
|
|
45a5567978 | ||
|
|
3190421fd9 | ||
|
|
a7126cac63 | ||
|
|
151aa037bb | ||
|
|
08ed9933ee | ||
|
|
8425011e73 | ||
|
|
96ff550d61 | ||
|
|
85a803d352 | ||
|
|
5def3e5856 | ||
|
|
c768260b1b | ||
|
|
46e146e633 | ||
|
|
1a2f917b30 | ||
|
|
86adfbaef8 | ||
|
|
143af78745 | ||
|
|
7b07f22685 | ||
|
|
c8b6a1c228 | ||
|
|
827fba2164 | ||
|
|
6217009caf | ||
|
|
fbfcc2c43a | ||
|
|
b331c484f5 | ||
|
|
a1fbbf9584 | ||
|
|
3611ec7d09 | ||
|
|
7dedbafc8a | ||
|
|
b7634402bd | ||
|
|
8e025a9836 | ||
|
|
8bfc8178cf | ||
|
|
8343f52137 | ||
|
|
7501833cf9 | ||
|
|
4c917a33a4 | ||
|
|
173997c44b | ||
|
|
76bc6e68ae | ||
|
|
d0fc2cc8cb | ||
|
|
32980f4033 | ||
|
|
28d3ca0ca6 | ||
|
|
3d31ad6689 | ||
|
|
0bb138e39e | ||
|
|
e8db1767e5 | ||
|
|
6b01263252 | ||
|
|
b2a4fafdb4 | ||
|
|
fcfb48d88e | ||
|
|
86380d912d | ||
|
|
87d8be8c61 | ||
|
|
9123974811 | ||
|
|
be50579544 | ||
|
|
91bb0cba6f | ||
|
|
b9946db854 | ||
|
|
9eb650b444 | ||
|
|
84f17b4c4b | ||
|
|
8cff99f726 | ||
|
|
ea166f890b | ||
|
|
d350c3e96f | ||
|
|
ec0d98627d | ||
|
|
fd3cb5b0f0 | ||
|
|
0155dc1877 | ||
|
|
cd79e51f8f | ||
|
|
c6d89f9cb2 | ||
|
|
4b26631374 | ||
|
|
ba742b9fb2 | ||
|
|
57e5a710e0 | ||
|
|
062d95c1eb | ||
|
|
75643287a5 | ||
|
|
6bda9406fb |
53
.github/ISSUE_TEMPLATE/app_request.yml
vendored
53
.github/ISSUE_TEMPLATE/app_request.yml
vendored
@ -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.
|
||||
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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.
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -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?
|
||||
|
||||
23
.github/ISSUE_TEMPLATE/feedback.yml
vendored
23
.github/ISSUE_TEMPLATE/feedback.yml
vendored
@ -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.
|
||||
|
||||
15
.github/ISSUE_TEMPLATE/submit-application.md
vendored
15
.github/ISSUE_TEMPLATE/submit-application.md
vendored
@ -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
19
.github/sync_openapi.yml
vendored
Normal 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 }}
|
||||
6
.github/workflows/casa.yml
vendored
6
.github/workflows/casa.yml
vendored
@ -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
28
.github/workflows/codecov.yml
vendored
Normal 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
|
||||
11
.github/workflows/demo.yml
vendored
11
.github/workflows/demo.yml
vendored
@ -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,8 +33,9 @@ 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 "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
|
||||
@ -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
46
.github/workflows/publish_npm.yaml
vendored
Normal 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}}
|
||||
6
.github/workflows/push_events_to_discord.yml
vendored
6
.github/workflows/push_events_to_discord.yml
vendored
@ -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
92
.github/workflows/push_test_server.yml
vendored
Normal 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 }}
|
||||
74
.github/workflows/release.yml
vendored
74
.github/workflows/release.yml
vendored
@ -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
|
||||
call-workflow-passing-data:
|
||||
uses: IceWhaleTech/github/.github/workflows/go_release.yml@main
|
||||
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
|
||||
|
||||
|
||||
|
||||
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
19
.github/workflows/sync_openapi.yml
vendored
Normal 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
1
.gitignore
vendored
@ -37,6 +37,7 @@ github.com
|
||||
.all-contributorsrc
|
||||
dist
|
||||
CasaOS
|
||||
/codegen
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
|
||||
@ -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:
|
||||
|
||||
117
.goreleaser.yaml
117
.goreleaser.yaml
@ -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:
|
||||
|
||||
77
CHANGELOG.md
77
CHANGELOG.md
@ -16,8 +16,85 @@ 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
|
||||
|
||||
- [Developer] Included `casaos-cli` command tool for debugging
|
||||
- [Developer] Added message bus for events and actions - Use `casaos-cli message-bus` to manage.
|
||||
- [Disk] Disk notification in Dashboard
|
||||
- [System] Restart/shutdown directly from CasaOS Dashboard
|
||||
### Changed
|
||||
|
||||
- [General] CasaOS new logo!
|
||||
- [App] Redesign of Featured App
|
||||
- [App] Now you can choose to delete userdata along with app uninstallation
|
||||
|
||||
### Security
|
||||
|
||||
- [System] Fixed a shell injection issue for better security
|
||||
|
||||
### Fixed
|
||||
|
||||
- [System] Re-instate default zone0 for CPU Temp ([#694](https://github.com/IceWhaleTech/CasaOS/issues/694))
|
||||
- [Disk] Fixed storage name with extra `-1` after rebooting ([#698](https://github.com/IceWhaleTech/CasaOS/issues/698))
|
||||
- [Disk] Fixed disk check so it does not impact disk going into idle ([#704](https://github.com/IceWhaleTech/CasaOS/issues/704))
|
||||
|
||||
## [0.3.8] 2022-11-21
|
||||
|
||||
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal 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.
|
||||
92
README.md
92
README.md
@ -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
9
SECURITY.md
Normal 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
359
api/casaos/openapi.yaml
Normal 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
24
api/index.html
Normal 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>
|
||||
@ -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
|
||||
|
||||
@ -95,6 +111,9 @@ case $(uname -m) in
|
||||
armv7l)
|
||||
ARCH="arm-7"
|
||||
;;
|
||||
riscv64)
|
||||
ARCH="riscv64"
|
||||
;;
|
||||
*)
|
||||
__error "Unsupported architecture"
|
||||
;;
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
12
build/sysroot/usr/lib/systemd/system/rclone.service
Normal file
12
build/sysroot/usr/lib/systemd/system/rclone.service
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
28
cmd/message-bus-docgen/main.go
Normal file
28
cmd/message-bus-docgen/main.go
Normal 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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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{}
|
||||
}
|
||||
27
cmd/migration-tool/migration_dummy.go
Normal file
27
cmd/migration-tool/migration_dummy.go
Normal 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
8
common/constants.go
Normal 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
12
common/message.go
Normal 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
13
drivers/all.go
Normal 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
30
drivers/base/client.go
Normal 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
12
drivers/base/types.go
Normal 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
103
drivers/dropbox/drive.go
Normal 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
24
drivers/dropbox/meta.go
Normal 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
88
drivers/dropbox/types.go
Normal 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
116
drivers/dropbox/util.go
Normal 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
|
||||
}
|
||||
186
drivers/google_drive/drive.go
Normal file
186
drivers/google_drive/drive.go
Normal 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)
|
||||
26
drivers/google_drive/meta.go
Normal file
26
drivers/google_drive/meta.go
Normal 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",
|
||||
}
|
||||
77
drivers/google_drive/types.go
Normal file
77
drivers/google_drive/types.go
Normal 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"`
|
||||
}
|
||||
167
drivers/google_drive/util.go
Normal file
167
drivers/google_drive/util.go
Normal 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
73
drivers/onedrive/drive.go
Normal 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
62
drivers/onedrive/meta.go
Normal 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
160
drivers/onedrive/util.go
Normal 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
142
go.mod
@ -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
|
||||
)
|
||||
|
||||
43
internal/conf/config.go
Normal file
43
internal/conf/config.go
Normal 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
72
internal/conf/const.go
Normal 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
30
internal/conf/var.go
Normal 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
25
internal/driver/config.go
Normal 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
133
internal/driver/driver.go
Normal 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
56
internal/driver/item.go
Normal 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
6
internal/op/const.go
Normal file
@ -0,0 +1,6 @@
|
||||
package op
|
||||
|
||||
const (
|
||||
WORK = "work"
|
||||
RootName = "root"
|
||||
)
|
||||
173
internal/op/driver.go
Normal file
173
internal/op/driver.go
Normal 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
109
internal/op/hook.go
Normal 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
36
internal/sign/sign.go
Normal 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
135
main.go
@ -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()
|
||||
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,
|
||||
},
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
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
39
model/args.go
Normal 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
6
model/common.go
Normal file
@ -0,0 +1,6 @@
|
||||
package model
|
||||
|
||||
type PageResp struct {
|
||||
Content interface{} `json:"content"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
7
model/drive.go
Normal file
7
model/drive.go
Normal file
@ -0,0 +1,7 @@
|
||||
package model
|
||||
|
||||
type Drive struct {
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
AuthUrl string `json:"auth_url"`
|
||||
}
|
||||
@ -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
186
model/obj.go
Normal 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
90
model/object.go
Normal 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
23
model/req.go
Normal 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
|
||||
// }
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
package model
|
||||
|
||||
type SearchFileInfo struct {
|
||||
Path string `json:"path"`
|
||||
type SearchEngine struct {
|
||||
Name string `json:"name"`
|
||||
Type int `json:"type"`
|
||||
Icon string `json:"icon"`
|
||||
SearchUrl string `json:"search_url"`
|
||||
RecoUrl string `json:"reco_url"`
|
||||
Data []string `json:"data"`
|
||||
}
|
||||
|
||||
33
model/setting.go
Normal file
33
model/setting.go
Normal 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
|
||||
}
|
||||
@ -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
54
model/storage.go
Normal 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
33
model/stream.go
Normal 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
|
||||
}
|
||||
@ -25,7 +25,6 @@ type ServerModel struct {
|
||||
LockAccount bool
|
||||
Token string
|
||||
USBAutoMount string
|
||||
SocketPort string
|
||||
UpdateUrl string
|
||||
}
|
||||
|
||||
@ -74,4 +73,5 @@ type BaseInfo struct {
|
||||
Hash string `json:"i"`
|
||||
Version string `json:"v"`
|
||||
Channel string `json:"c,omitempty"`
|
||||
DriveModel string `json:"m,omitempty"`
|
||||
}
|
||||
|
||||
@ -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
39
package.json
Normal 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"
|
||||
}
|
||||
@ -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")
|
||||
|
||||
@ -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"
|
||||
|
||||
// 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 {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// write default config
|
||||
_, err = file.WriteString(sample)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// 读取文件
|
||||
Cfg, err = ini.Load(configDir)
|
||||
Cfg, err = ini.Load(ConfigFilePath)
|
||||
if err != nil {
|
||||
Cfg, err = ini.Load("/etc/casaos.conf")
|
||||
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)
|
||||
}
|
||||
|
||||
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
12
pkg/fs/fs.go
Normal 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
|
||||
}
|
||||
}
|
||||
412
pkg/generic_sync/generic_sync.go
Normal file
412
pkg/generic_sync/generic_sync.go
Normal 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
|
||||
}
|
||||
@ -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
52
pkg/sign/hmac.go
Normal 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
15
pkg/sign/sign.go
Normal 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")
|
||||
)
|
||||
212
pkg/singleflight/singleflight.go
Normal file
212
pkg/singleflight/singleflight.go
Normal 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()
|
||||
}
|
||||
@ -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
18
pkg/utils/balance.go
Normal 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
5
pkg/utils/bool.go
Normal file
@ -0,0 +1,5 @@
|
||||
package utils
|
||||
|
||||
func IsBool(bs ...bool) bool {
|
||||
return len(bs) > 0 && bs[0]
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -51,6 +51,7 @@ const (
|
||||
FILE_DELETE_ERROR = 60003
|
||||
DIR_NOT_EXISTS = 60004
|
||||
SOURCE_DES_SAME = 60005
|
||||
MOUNTED_DIRECTIORIES = 60006
|
||||
|
||||
//share
|
||||
SHARE_ALREADY_EXISTS = 70001
|
||||
@ -111,6 +112,7 @@ var MsgFlags = map[int]string{
|
||||
|
||||
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
14
pkg/utils/ctx.go
Normal file
@ -0,0 +1,14 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
func IsCanceled(ctx context.Context) bool {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
11
pkg/utils/default_post_form.go
Normal file
11
pkg/utils/default_post_form.go
Normal 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
|
||||
}
|
||||
11
pkg/utils/default_query.go
Normal file
11
pkg/utils/default_query.go
Normal 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
|
||||
}
|
||||
@ -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")
|
||||
}
|
||||
|
||||
16
pkg/utils/file/file_test.go
Normal file
16
pkg/utils/file/file_test.go
Normal 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
171
pkg/utils/httper/drive.go
Normal 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
|
||||
}
|
||||
78
pkg/utils/httper/zerotier.go
Normal file
78
pkg/utils/httper/zerotier.go
Normal 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
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package ip_helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
@ -10,6 +11,7 @@ import (
|
||||
func IsIPv4(address string) bool {
|
||||
return strings.Count(address, ":") < 2
|
||||
}
|
||||
|
||||
func IsIPv6(address string) bool {
|
||||
return strings.Count(address, ":") >= 2
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user