Compare commits

...

54 Commits

Author SHA1 Message Date
Tiger Wang
ec7f6573ad wip 2022-09-28 17:56:28 -04:00
Tiger Wang
50d68f3f76 update goreleaser configuration 2022-09-28 12:21:44 -04:00
Tiger Wang
d9c6a5c875 remove /DATA directory initialization - moved to local-storage (#578) 2022-09-28 11:14:19 -04:00
LinkLeong
4bace9b16a remove temp path 2022-09-28 16:13:42 +01:00
LinkLeong
84dfa7f5bf add share function to common 2022-09-19 10:09:04 +01:00
LinkLeong
582f85c3ba update http status 2022-09-16 04:47:42 +01:00
LinkLeong
7f4562629a remove disk and test common package 2022-09-15 08:13:07 +01:00
LinkLeong
4776b76b16 add system notiry 2022-09-13 07:22:24 +01:00
a624669980@163.com
9f938f65b1 Merge branch 'main' into v0.3.7 2022-09-09 12:02:19 +08:00
a624669980@163.com
314dbdab57 add shell script 2022-09-09 11:28:30 +08:00
LinkLeong
4ccae3f67f add shell script 2022-09-09 04:12:34 +01:00
LinkLeong
9f2e8dae6f add send notify function 2022-09-08 10:55:20 +01:00
John Guan
727aca564b Update CHANGELOG.md
Signed-off-by: John Guan <Guan.Ningchuan@gmail.com>
2022-09-06 20:40:14 +08:00
link
70ba4d5e3d 0.3.6 (#519)
* Add registration route

* add route registration

* remove ui

* 0.3.6 build scaffold (#473)

* wip

* wip

* wip

* Merge Branches

* Update helper.sh

* add goreleaser

* fix migration not working for LEGACY_WITHOUT_VERSION (#479)

* wip

* wip

* add error handling to migration script

* add migration tool

* Adjusting shell script location

* update disk user authentication method

* delayed port modification

* Update system.go

Co-authored-by: Tiger Wang (王豫) <tigerwang@outlook.com>
2022-09-06 14:28:49 +08:00
a624669980@163.com
8d088afcc5 update system version 2022-08-25 10:50:11 +08:00
link
34f35bbf20 0.3.5 (#470)
* initial completion of the sharing function

* Adjusting multi-partition disk mounts

* Add file sharing function

* update usb auto mount shell

* update samba config

* add umount disk function

* update change log

* update usb auto mount \

* update usb auto mount

* Update periodical.go

* Update periodical.go

* resolve alpha.1 issues

* Update UI

* update usb show name

* update web

* update image version
2022-08-22 18:22:12 +08:00
link
b6f413b9e9 0.3.5 (#464)
* initial completion of the sharing function

* Adjusting multi-partition disk mounts

* Add file sharing function

* update usb auto mount shell

* update samba config

* add umount disk function

* update change log

* update usb auto mount \

* update usb auto mount

* Update periodical.go

* Update periodical.go

* resolve alpha.1 issues

* Update UI
2022-08-18 11:18:27 +08:00
John Guan
091be6d12e Update push_events_to_discord.yml 2022-08-17 15:49:44 +08:00
John Guan
aefa68d8f8 Merge branch 'main' of https://github.com/IceWhaleTech/CasaOS 2022-08-16 14:23:28 +08:00
John Guan
9aed323d8d Update push_events_to_discord.yml 2022-08-16 14:23:25 +08:00
John Guan
eba607fc53 Revert "Update push_events_to_discord.yml"
This reverts commit d064f20a04.
2022-08-15 15:52:20 +08:00
John Guan
d064f20a04 Update push_events_to_discord.yml 2022-08-15 15:46:56 +08:00
link
079e431d33 0.3.5 (#452)
* initial completion of the sharing function

* Adjusting multi-partition disk mounts

* Add file sharing function

* update usb auto mount shell

* update samba config

* add umount disk function

* update change log

* update usb auto mount \

* update usb auto mount

* Update periodical.go

* Update periodical.go
2022-08-15 11:51:29 +08:00
link
cee34ec1c2 0.3.5 (#451)
* initial completion of the sharing function

* Adjusting multi-partition disk mounts

* Add file sharing function

* update usb auto mount shell

* update samba config

* add umount disk function

* update change log

* update usb auto mount \

* update usb auto mount

* Update periodical.go
2022-08-15 11:37:21 +08:00
John Guan
848ee63386 Update app_request.yaml 2022-08-12 11:40:37 +08:00
John Guan
c12c6cc940 Update app_request.yaml 2022-08-12 11:27:20 +08:00
John Guan
f31fb22039 Update alpha_bug_report.yaml 2022-08-12 11:27:17 +08:00
John Guan
a5133bdbfa Merge branch 'main' of https://github.com/IceWhaleTech/CasaOS 2022-08-10 14:44:32 +08:00
John Guan
1902b2d4f2 Update bug_report.md 2022-08-10 14:44:29 +08:00
John Guan
fc5e351f0b Update push_events_to_discord.yml 2022-08-10 11:18:14 +08:00
John Guan
7f642a7a4c Update push_events_to_discord.yml 2022-08-10 10:31:46 +08:00
John Guan
c36a046a15 Update and rename move_issues_to_projects.yml to add_issues_to_projects.yml 2022-08-08 17:31:38 +08:00
John Guan
8b2c9486da Update push_events_to_discord.yml 2022-08-08 17:30:30 +08:00
John Guan
ce5ecfcf84 Rename move_issue_to_project.yml to move_issues_to_projects.yml 2022-08-08 15:41:00 +08:00
John Guan
bd1a2f5751 Update move_issue_to_project.yml 2022-08-08 15:40:43 +08:00
John Guan
d2e0695d73 Create push_events_to_discord.yml 2022-08-08 15:40:05 +08:00
John Guan
8cd18c3218 Create move_issue_to_project.yml 2022-08-05 16:37:46 +08:00
John Guan
cc0ba82c92 Update config.yml 2022-08-05 15:58:43 +08:00
John Guan
ddca242d23 Update app_request.yaml 2022-08-05 15:49:42 +08:00
John Guan
eaa07ba95a Update app_request.yaml 2022-08-05 12:21:54 +08:00
John Guan
39a4c29680 Update app_request.yaml 2022-08-04 20:55:03 +08:00
John Guan
af6900b1c7 Create app_request.yaml 2022-08-04 20:53:30 +08:00
John Guan
9ec43a7691 Update config.yml 2022-08-04 17:37:19 +08:00
a624669980@163.com
bd85cda9fa update check version 2022-07-29 16:23:53 +08:00
a624669980@163.com
94eaba6ecb Update jwt_helper.go 2022-07-29 16:14:50 +08:00
a624669980@163.com
e207c9c75f Update CHANGELOG.md 2022-07-29 15:50:31 +08:00
a624669980@163.com
81d3293991 Update route.go 2022-07-29 15:47:43 +08:00
link
364fbf01f5 0.3.4 (#412)
* delete connect

* update user

* change branch

* API feedback (#341)

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* change branch

* update route

* change branch

* Update route.go

* 0.3.4 Function development completed

* Update system.go

* update ui

* Completed v0.3.4 and released alpha

* Update gin.go

* fix the problem of docker environment variables being displayed more than once

* update update shell

Co-authored-by: Tiger Wang (王豫) <tigerwang@outlook.com>
2022-07-27 18:37:22 +08:00
link
87e66aae8a 0.3.4 (#408)
* delete connect

* update user

* change branch

* API feedback (#341)

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* change branch

* update route

* change branch

* Update route.go

* 0.3.4 Function development completed

* Update system.go

* update ui

* Completed v0.3.4 and released alpha

* Update gin.go

* fix the problem of docker environment variables being displayed more than once

Co-authored-by: Tiger Wang (王豫) <tigerwang@outlook.com>
2022-07-26 11:03:45 +08:00
a624669980@163.com
3a60db3729 solve the first time app store opening failure problem 2022-07-22 15:24:13 +08:00
a624669980@163.com
d3f78b52fa Delete alpha.md 2022-07-22 11:27:05 +08:00
a624669980@163.com
7d67a66d6b update change log 2022-07-22 11:19:25 +08:00
a624669980@163.com
cb9e1b9dd9 Merge branch 'main' of ssh://github.com/IceWhaleTech/CasaOS 2022-07-22 11:18:05 +08:00
a624669980@163.com
dc8282acbb update chage log 2022-07-22 11:17:33 +08:00
131 changed files with 3318 additions and 9775 deletions

View File

@@ -1,6 +1,6 @@
name: "[Alpha Only] Bug Report"
description: CasaOS Alpha Testing specific bug report form.
title: "[Alpha][Bug]: "
title: "[Alpha][Bug] "
labels: ["alpha", "bug"]
body:
- type: markdown

53
.github/ISSUE_TEMPLATE/app_request.yaml vendored Normal file
View File

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

View File

@@ -1,8 +1,8 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
title: '[Bug] '
labels: 'bug'
assignees: ''
---

View File

@@ -1,5 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Questions, Ideas, Discussions
- name: Feature/Enhancement Ideas
url: https://github.com/IceWhaleTech/CasaOS/discussions/164
about: Have an idea for a new feature/enhancement?
- name: Questions, Discussions
url: https://github.com/IceWhaleTech/CasaOS/discussions
about: Ask questions, propose ideas, or discuss anything related to CasaOS
- name: Discord
url: https://discord.gg/knqAbbBbeX
about: Get help or share great ideas on Discord!

View File

@@ -0,0 +1,33 @@
name: Add Issues To Projects
on:
issues:
types:
- opened
jobs:
add-issues:
runs-on: ubuntu-latest
steps:
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0
with:
app_id: ${{ secrets.ALPHA_BOT_ID }}
private_key: ${{ secrets.ALPHA_BOT_PEM }}
- name: Add Alpha Bug Issue To project
uses: actions/add-to-project@v0.3.0
with:
github-token: ${{ steps.generate_token.outputs.token }}
project-url: https://github.com/orgs/IceWhaleTech/projects/5
labeled: alpha, bug
label-operator: AND
- name: Add App Request Issue To project
uses: actions/add-to-project@v0.3.0
with:
github-token: ${{ steps.generate_token.outputs.token }}
project-url: https://github.com/orgs/IceWhaleTech/projects/8
labeled: "App Request"
label-operator: AND

View File

@@ -0,0 +1,48 @@
name: Push Events to Discord
on:
issues:
types:
- opened
issue_comment:
types:
- created
discussion:
types:
- created
- transferred
- answered
discussion_comment:
types:
- created
jobs:
push-events:
runs-on: ubuntu-latest
steps:
- name: General Discussions & Comments
if: ${{ ( github.event_name == 'discussion' || github.event_name == 'discussion_comment' ) && github.event.discussion.category.name == 'General' }}
uses: joseph-montanez/forward-event-action@v3.0.0
with:
webhook: ${{ secrets.Discord_CasaOS_General_Webhook }}
- name: App Request Issues & Comments
if: ${{ ( github.event_name == 'issues' || github.event_name == 'issue_comment' ) && contains(github.event.issue.labels.*.name, 'App Request') }}
uses: joseph-montanez/forward-event-action@v3.0.0
with:
webhook: ${{ secrets.Discord_CasaOS_App_Request_Webhook }}
- name: Bug Issues & Comments
if: ${{ ( github.event_name == 'issues' || github.event_name == 'issue_comment' ) && contains(github.event.issue.labels.*.name, 'bug') && !contains(github.event.issue.labels.*.name, 'alpha') }}
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 }}

47
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: goreleaser
on:
push:
tags:
- v*.*.*
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-22.04
steps:
-
name: Install dependencies for cross-compiling
run: |
sudo apt update
sudo apt-get --no-install-recommends --yes install \
libc6-dev-amd64-cross \
gcc-aarch64-linux-gnu libc6-dev-arm64-cross \
gcc-arm-linux-gnueabihf libc6-dev-armhf-cross
-
name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
-
name: Fetch all tags
run: git fetch --force --tags
-
name: 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 }}

6
.gitignore vendored
View File

@@ -28,12 +28,12 @@ gen
/sql/
/out/
/db/
/docs/
/web/
/conf/conf.ini
/conf/conf.conf
/conf/conf.json
__debug_bin
main
CasaOS
github.com
.all-contributorsrc
dist
CasaOS

167
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,167 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
project_name: casaos
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
builds:
- id: casaos-amd64
binary: build/sysroot/usr/bin/casaos
env:
- CGO_ENABLED=1
- CC=x86_64-linux-gnu-gcc
ldflags:
- -s
- -w
- -extldflags "-static"
tags:
- musl
- netgo
goos:
- 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:
- CGO_ENABLED=1
- CC=aarch64-linux-gnu-gcc
ldflags:
- -s
- -w
- -extldflags "-static"
tags:
- musl
- netgo
goos:
- 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:
- CGO_ENABLED=1
- CC=arm-linux-gnueabihf-gcc
ldflags:
- -s
- -w
- -extldflags "-static"
tags:
- musl
- netgo
goos:
- linux
goarch:
- arm
goarm:
- "7"
hooks:
post:
- find build/sysroot -type f | xargs -L 1 realpath --relative-to=build/sysroot > build/sysroot.manifest
- id: casaos-migration-tool-amd64
binary: build/sysroot/usr/bin/casaos-migration-tool
main: ./cmd/migration-tool
env:
- CGO_ENABLED=1
- CC=x86_64-linux-gnu-gcc
ldflags:
- -s
- -w
- -extldflags "-static"
tags:
- musl
- netgo
goos:
- linux
goarch:
- amd64
- id: casaos-migration-tool-arm64
binary: build/sysroot/usr/bin/casaos-migration-tool
main: ./cmd/migration-tool
env:
- CGO_ENABLED=1
- CC=aarch64-linux-gnu-gcc
ldflags:
- -s
- -w
- -extldflags "-static"
tags:
- musl
- netgo
goos:
- linux
goarch:
- arm64
- id: casaos-migration-tool-arm-7
binary: build/sysroot/usr/bin/casaos-migration-tool
main: ./cmd/migration-tool
env:
- CGO_ENABLED=1
- CC=arm-linux-gnueabihf-gcc
ldflags:
- -s
- -w
- -extldflags "-static"
tags:
- musl
- netgo
goos:
- linux
goarch:
- arm
goarm:
- "7"
archives:
- name_template: "{{ .Os }}-{{ .Arch }}-{{ .ProjectName }}-v{{ .Version }}"
id: casaos
builds:
- casaos-amd64
- casaos-arm64
- casaos-arm-7
replacements:
arm: arm-7
files:
- build/**/*
- name_template: "{{ .Os }}-{{ .Arch }}-{{ .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
files:
- build/sysroot/etc/**/*
checksum:
name_template: "checksums.txt"
snapshot:
name_template: "{{ incpatch .Version }}"
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
# release:
# github:
# owner: IceWhaleTech
# name: CasaOS
# draft: true
# prerelease: auto
# mode: replace
# name_template: "v{{ .Version }}"
release:
github:
owner: IceWhaleTech
name: CasaOS
draft: true
prerelease: auto
mode: replace
name_template: "v{{ .Version }}"

View File

@@ -18,6 +18,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
## [0.3.6-alpha.1] - 2022-09-06
### Added
- [System] Added power and temperature info to performance widget (Intel)
- [Apps] Custom links can be added to Apps section
### Fixed
- [Apps] Fixed the problem of not being able to modify some App settings ([#510](https://github.com/IceWhaleTech/CasaOS/issues/510))
### Changed
- [System] Architecture optimization. Improved performance.
## [0.3.5] - 2022-08-23
### Added
- [File] Mount the shared samba
- [File] File sharing via Samba
- [System] You can share casaos on Twitter, facebook, reddit
### Changed
- [Disk] Support for mounting existing data disks
### Fixed
- [App] fixed uninstalling imported docker container apps results in wiping ALL your config data from them ([#360](https://github.com/IceWhaleTech/CasaOS/issues/360))
## [0.3.4] - 2022-07-29(UTC)
### Added
- SSH adds port-side options and prompts for connection status. ([#286](https://github.com/IceWhaleTech/CasaOS/issues/286))
### Changed
- Normalize all routes
- Application names now support spaces ([#211](https://github.com/IceWhaleTech/CasaOS/issues/211))
### Removed
- Removed casaos connect
### Security
- Adjustment of authentication method
### Fixed
- Fixed storage format and remove password error issues ([#344](https://github.com/IceWhaleTech/CasaOS/issues/344) [#357](https://github.com/IceWhaleTech/CasaOS/issues/357))
## [0.3.3] - 2022-07-08(UTC)
### Added

1
UI

Submodule UI deleted from bca27426e1

View File

@@ -1,41 +0,0 @@
<!--
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-06-27 11:37:26
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-21 18:12:31
* @FilePath: /CasaOS/alpha.md
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
-->
# alpha Test Description
## :warning: Warning
There is a risk of data loss in non-release versions, so please be careful to back up your data.
## Install/Update
``` curl -fsSL https://get.casaos.io | bash -s -- -v v0.3.4-alpha ```
## Check change log
[CHANGELOG](https://github.com/IceWhaleTech/CasaOS/blob/main/CHANGELOG.md)
## Check Design drawings
[Design drawings](https://www.figma.com/file/pvlGobvuWEvbCb3GLqXfim/CasaOS-V0.3.3)
## Feedback questions
Go [here]() to give feedback on your question, note that try to match the picture or video
## Retest after update
Wait for the fix to appear and update and retest, and follow up on the issue
## Thanks
Thank you for your great support, we will return your support from time to time.

View File

@@ -0,0 +1,181 @@
#!/bin/bash
set -e
# functions
__info() {
echo -e "🟩 ${1}"
}
__info_done() {
echo -e "${1}"
}
__warning() {
echo -e "🟨 ${1}"
}
__error() {
echo "🟥 ${1}"
exit 1
}
__is_version_gt() {
test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"
}
__normalize_version() {
local version
if [ "${1::1}" = "v" ]; then
version="${1:1}"
else
version="${1}"
fi
echo "$version"
}
__is_migration_needed() {
local version1
local version2
version1=$(__normalize_version "${1}")
version2=$(__normalize_version "${2}")
if [ "${version1}" = "${version2}" ]; then
return 1
fi
if [ "CURRENT_VERSION_NOT_FOUND" = "${version1}" ]; then
return 1
fi
if [ "LEGACY_WITHOUT_VERSION" = "${version1}" ]; then
return 0
fi
__is_version_gt "${version2}" "${version1}"
}
BUILD_PATH=$(dirname "${BASH_SOURCE[0]}")/../../..
SOURCE_ROOT=${BUILD_PATH}/sysroot
APP_NAME="casaos"
APP_NAME_FORMAL="CasaOS"
#APP_NAME_FORMAL="casaos-alpha"
# check if migration is needed
SOURCE_BIN_PATH=${SOURCE_ROOT}/usr/bin
SOURCE_BIN_FILE=${SOURCE_BIN_PATH}/${APP_NAME}
CURRENT_BIN_PATH=/usr/bin
CURRENT_BIN_PATH_LEGACY=/usr/local/bin
CURRENT_BIN_FILE=${CURRENT_BIN_PATH}/${APP_NAME}
CURRENT_BIN_FILE_LEGACY=$(realpath -e ${CURRENT_BIN_PATH_LEGACY}/${APP_NAME} || which ${APP_NAME} || echo CURRENT_BIN_FILE_LEGACY_NOT_FOUND)
SOURCE_VERSION="$(${SOURCE_BIN_FILE} -v)"
CURRENT_VERSION="$(${CURRENT_BIN_FILE} -v || ${CURRENT_BIN_FILE_LEGACY} -v || (stat "${CURRENT_BIN_FILE_LEGACY}" > /dev/null && echo LEGACY_WITHOUT_VERSION) || echo CURRENT_VERSION_NOT_FOUND)"
__info_done "CURRENT_VERSION: ${CURRENT_VERSION}"
__info_done "SOURCE_VERSION: ${SOURCE_VERSION}"
NEED_MIGRATION=$(__is_migration_needed "${CURRENT_VERSION}" "${SOURCE_VERSION}" && echo "true" || echo "false")
if [ "${NEED_MIGRATION}" = "false" ]; then
__info_done "Migration is not needed."
exit 0
fi
MIGRATION_SERVICE_DIR=${1}
if [ -z "${MIGRATION_SERVICE_DIR}" ]; then
MIGRATION_SERVICE_DIR=${BUILD_PATH}/scripts/migration/service.d/${APP_NAME}
fi
MIGRATION_LIST_FILE=${MIGRATION_SERVICE_DIR}/migration.list
MIGRATION_PATH=()
CURRENT_VERSION_FOUND="false"
# a VERSION_PAIR looks like "v0.3.5 v0.3.6-alpha2"
#
# - "v0.3.5" is the current version installed on this host
# - "v0.3.6-alpha2" is the version of the migration tool from GitHub
while read -r VERSION_PAIR; do
if [ -z "${VERSION_PAIR}" ]; then
continue
fi
# obtain "v0.3.5" from "v0.3.5 v0.3.6-alpha2"
VER1=$(echo "${VERSION_PAIR}" | cut -d' ' -f1)
# obtain "v0.3.6-alpha2" from "v0.3.5 v0.3.6-alpha2"
VER2=$(echo "${VERSION_PAIR}" | cut -d' ' -f2)
if [ "${CURRENT_VERSION}" = "${VER1// /}" ] || [ "${CURRENT_VERSION}" = "LEGACY_WITHOUT_VERSION" ]; then
CURRENT_VERSION_FOUND="true"
fi
if [ "${CURRENT_VERSION_FOUND}" = "true" ]; then
MIGRATION_PATH+=("${VER2// /}")
fi
done < "${MIGRATION_LIST_FILE}"
if [ ${#MIGRATION_PATH[@]} -eq 0 ]; then
__warning "No migration path found from ${CURRENT_VERSION} to ${SOURCE_VERSION}"
exit 0
fi
ARCH="unknown"
case $(uname -m) in
x86_64)
ARCH="amd64"
;;
aarch64)
ARCH="arm64"
;;
armv7l)
ARCH="arm-7"
;;
*)
__error "Unsupported architecture"
;;
esac
pushd "${MIGRATION_SERVICE_DIR}"
{ for VER2 in "${MIGRATION_PATH[@]}"; do
MIGRATION_TOOL_FILE=linux-"${ARCH}"-"${APP_NAME}"-migration-tool-"${VER2}".tar.gz
if [ -f "${MIGRATION_TOOL_FILE}" ]; then
__info "Migration tool ${MIGRATION_TOOL_FILE} exists. Skip downloading."
continue
fi
# MIGRATION_TOOL_URL=http://192.168.2.197:8000/v1/package/migration?type=release&name="${APP_NAME_FORMAL}"&version=${VER2}&arch=${ARCH}
MIGRATION_TOOL_URL=https://github.com/IceWhaleTech/"${APP_NAME_FORMAL}"/releases/download/"${VER2}"/linux-"${ARCH}"-"${APP_NAME}"-migration-tool-"${VER2}".tar.gz
echo "Dowloading ${MIGRATION_TOOL_URL}..."
curl -sL -O "${MIGRATION_TOOL_URL}"
done
} || {
popd
__error "Failed to download migration tools"
}
{
for VER2 in "${MIGRATION_PATH[@]}"; do
MIGRATION_TOOL_FILE=linux-"${ARCH}"-"${APP_NAME}"-migration-tool-"${VER2}".tar.gz
__info "Extracting ${MIGRATION_TOOL_FILE}..."
tar zxvf "${MIGRATION_TOOL_FILE}" || __error "Failed to extract ${MIGRATION_TOOL_FILE}"
MIGRATION_TOOL_PATH=build/sysroot/usr/bin/${APP_NAME}-migration-tool
__info "Running ${MIGRATION_TOOL_PATH}..."
${MIGRATION_TOOL_PATH}
done
} || {
popd
__error "Failed to extract and run migration tools"
}
popd

View File

@@ -0,0 +1,4 @@
LEGACY_WITHOUT_VERSION v0.3.6
v0.3.5 v0.3.6
v0.3.5.1 v0.3.6
v0.3.6 v0.3.7

View File

@@ -0,0 +1,54 @@
#!/bin/bash
set -e
BUILD_PATH=$(dirname "${BASH_SOURCE[0]}")/../../..
APP_NAME_SHORT=casaos
__get_setup_script_directory_by_os_release() {
pushd "$(dirname "${BASH_SOURCE[0]}")/../service.d/${APP_NAME_SHORT}" >/dev/null
{
# shellcheck source=/dev/null
{
source /etc/os-release
{
pushd "${ID}"/"${VERSION_CODENAME}" >/dev/null
} || {
pushd "${ID}" >/dev/null
} || {
pushd "${ID_LIKE}" >/dev/null
} || {
echo "Unsupported OS: ${ID} ${VERSION_CODENAME} (${ID_LIKE})"
exit 1
}
pwd
popd >/dev/null
} || {
echo "Unsupported OS: unknown"
exit 1
}
}
popd >/dev/null
}
SETUP_SCRIPT_DIRECTORY=$(__get_setup_script_directory_by_os_release)
SETUP_SCRIPT_FILENAME="setup-${APP_NAME_SHORT}.sh"
SETUP_SCRIPT_FILEPATH="${SETUP_SCRIPT_DIRECTORY}/${SETUP_SCRIPT_FILENAME}"
{
echo "🟩 Running ${SETUP_SCRIPT_FILENAME}..."
$SHELL "${SETUP_SCRIPT_FILEPATH}" "${BUILD_PATH}"
} || {
echo "🟥 ${SETUP_SCRIPT_FILENAME} failed."
exit 1
}
echo "${SETUP_SCRIPT_FILENAME} finished."

View File

@@ -0,0 +1 @@
../setup-casaos.sh

View File

@@ -0,0 +1,45 @@
#!/bin/bash
###
# @Author: LinkLeong link@icewhale.org
# @Date: 2022-08-25 11:41:22
# @LastEditors: LinkLeong
# @LastEditTime: 2022-08-31 17:54:17
# @FilePath: /CasaOS/build/scripts/setup/service.d/casaos/debian/setup-casaos.sh
# @Description:
# @Website: https://www.casaos.io
# Copyright (c) 2022 by icewhale, All Rights Reserved.
###
set -e
APP_NAME="casaos"
# copy config files
CONF_PATH=/etc/casaos
OLD_CONF_PATH=/etc/casaos.conf
CONF_FILE=${CONF_PATH}/${APP_NAME}.conf
CONF_FILE_SAMPLE=${CONF_PATH}/${APP_NAME}.conf.sample
if [ -f "${OLD_CONF_PATH}" ]; then
echo "copy old conf"
cp "${OLD_CONF_PATH}" "${CONF_FILE}"
fi
if [ ! -f "${CONF_FILE}" ]; then
echo "Initializing config file..."
cp -v "${CONF_FILE_SAMPLE}" "${CONF_FILE}"
fi
if systemctl is-active "${APP_NAME}.service" &>/dev/null ;then
echo "server started"
else
# enable and start service
systemctl daemon-reload
echo "Enabling service..."
systemctl enable --force --no-ask-password "${APP_NAME}.service"
echo "Starting service..."
systemctl start --force --no-ask-password "${APP_NAME}.service"
fi

View File

@@ -0,0 +1 @@
../setup-casaos.sh

View File

@@ -0,0 +1 @@
../debian/setup-casaos.sh

View File

@@ -0,0 +1,22 @@
[app]
PAGE_SIZE = 10
RuntimeRootPath = runtime/
LogPath = /var/log/casaos/
LogSaveName = log
LogFileExt = log
DateStrFormat = 20060102
DateTimeFormat = 2006-01-02 15:04:05
TimeFormat = 15:04:05
DateFormat = 2006-01-02
DBPath = /var/lib/casaos
ShellPath = /usr/share/casaos/shell
UserDataPath = /var/lib/casaos/conf
[server]
RunMode = release
ServerApi = https://api.casaos.io/casaos-api
Handshake = socket.casaos.io
Token =
USBAutoMount =
[system]

View File

@@ -0,0 +1,13 @@
[Unit]
After=casaos-gateway.service
ConditionFileNotEmpty=/etc/casaos/casaos.conf
Description=CasaOS Main Service
[Service]
ExecStart=/usr/bin/casaos -c /etc/casaos/casaos.conf
PIDFile=/var/run/casaos/casaos.pid
Restart=always
Type=notify
[Install]
WantedBy=multi-user.target

View File

@@ -330,19 +330,59 @@ TarFolder() {
du -sh /DATA
}
USB_Move_File() {
USB_Start_Auto() {
((EUID)) && sudo_cmd="sudo"
$sudo_cmd cp -rf /casaOS/server/shell/11-usb-mount.rules /etc/udev/rules.d/
$sudo_cmd chmod +x /casaOS/server/shell/usb-mount.sh
$sudo_cmd cp -rf /casaOS/server/shell/usb-mount@.service /etc/systemd/system/
$sudo_cmd systemctl enable devmon@devmon
$sudo_cmd systemctl start devmon@devmon
}
USB_Remove_File() {
USB_Stop_Auto() {
((EUID)) && sudo_cmd="sudo"
$sudo_cmd rm -fr /etc/udev/rules.d/11-usb-mount.rules
$sudo_cmd rm -fr /etc/systemd/system/usb-mount@.service
$sudo_cmd systemctl stop devmon@devmon
$sudo_cmd systemctl disable devmon@devmon
$sudo_cmd udevil clean
}
GetDeviceTree(){
cat /proc/device-tree/model
}
# restart samba service
RestartSMBD(){
$sudo_cmd systemctl restart smbd
}
# edit user password $1:username
EditSmabaUserPassword(){
$sudo_cmd smbpasswd $1
}
AddSmabaUser(){
$sudo_cmd useradd $1
$sudo_cmd smbpasswd -a $1 <<EOF
$2
$2
EOF
}
# $1:username $2:host $3:share $4:port $5:mountpoint $6:password
MountCIFS(){
$sudo_cmd mount -t cifs -o username=$1,password=$6,port=$4 //$2/$3 $5
}
# $1:service name
CheckServiceStatus(){
rs="`systemctl status $1 |grep -E 'Active|PID'`"
#echo "$rs"
run="`echo "$rs" |grep -B 2 'running'`"
fai="`echo "$rs" |grep -E -B 2 'failed|inactive|dead'`"
if [ "$run" == "" ]
then
echo "failed"
else
echo "running"
fi
}
UDEVILUmount(){
$sudo_cmd udevil umount -f $1
}

View File

@@ -0,0 +1,12 @@
#!/bin/bash
###
# @Author: LinkLeong link@icewhale.com
# @Date: 2022-06-30 10:08:33
# @LastEditors: LinkLeong
# @LastEditTime: 2022-09-01 22:33:06
# @FilePath: /CasaOS/build/sysroot/usr/share/casaos/shell/update.sh
# @Description:
###
curl -fsSL https://raw.githubusercontent.com/LinkLeong/casaos-alpha/main/new.update.sh | bash

37
cmd/migration-tool/log.go Normal file
View File

@@ -0,0 +1,37 @@
package main
import (
"log"
"os"
)
type Logger struct {
DebugMode bool
_debug *log.Logger
_info *log.Logger
_error *log.Logger
}
func NewLogger() *Logger {
return &Logger{
DebugMode: false,
_debug: log.New(os.Stdout, "DEBUG: ", 0),
_info: log.New(os.Stdout, "", 0),
_error: log.New(os.Stderr, "ERROR: ", 0),
}
}
func (l *Logger) Debug(format string, v ...interface{}) {
if l.DebugMode {
l._debug.Printf(format, v...)
}
}
func (l *Logger) Info(format string, v ...interface{}) {
l._info.Printf(format, v...)
}
func (l *Logger) Error(format string, v ...interface{}) {
l._error.Printf(format, v...)
}

122
cmd/migration-tool/main.go Normal file
View File

@@ -0,0 +1,122 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-08-23 18:09:11
* @LastEditors: LinkLeong
* @LastEditTime: 2022-08-31 14:17:51
* @FilePath: /CasaOS/cmd/migration-tool/main.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package main
import (
"flag"
"fmt"
"os"
interfaces "github.com/IceWhaleTech/CasaOS-Common"
"github.com/IceWhaleTech/CasaOS-Common/utils/systemctl"
"github.com/IceWhaleTech/CasaOS-Gateway/common"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/sqlite"
"github.com/IceWhaleTech/CasaOS/service"
"gorm.io/gorm"
)
const (
casaosServiceName = "casaos.service"
)
var (
_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)
os.Exit(0)
}
if os.Getuid() != 0 {
os.Exit(1)
}
if *debugFlag {
_logger.DebugMode = true
}
if !*forceFlag {
serviceEnabled, err := systemctl.IsServiceEnabled(casaosServiceName)
if err != nil {
panic(err)
}
if serviceEnabled {
_logger.Info("%s is already enabled. If migration is still needed, try with -f.", casaosServiceName)
os.Exit(1)
}
}
migrationTools := []interfaces.MigrationTool{
NewMigrationToolFor_035(),
NewMigrationToolFor_036(),
}
var selectedMigrationTool interfaces.MigrationTool
// look for the right migration tool matching current version
for _, tool := range migrationTools {
migrationNeeded, err := tool.IsMigrationNeeded()
if err != nil {
panic(err)
}
if migrationNeeded {
selectedMigrationTool = tool
break
}
}
if selectedMigrationTool == nil {
_logger.Error("selectedMigrationTool is null")
return
}
if err := selectedMigrationTool.PreMigrate(); err != nil {
panic(err)
}
if err := selectedMigrationTool.Migrate(); err != nil {
panic(err)
}
selectedMigrationTool.PostMigrate()
_logger.Info("casaos migration ok")
// panic(err)
}

View File

@@ -0,0 +1,182 @@
/*
* @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 (
"io"
"io/ioutil"
"os"
"path"
"strings"
interfaces "github.com/IceWhaleTech/CasaOS-Common"
"github.com/IceWhaleTech/CasaOS-Common/utils/version"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/utils/command"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/service"
)
type migrationTool036 struct{}
func (u *migrationTool036) 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 *migrationTool036) PreMigrate() error {
return nil
}
func (u *migrationTool036) Migrate() error {
if service.MyService.System().GetSysInfo().KernelArch == "aarch64" && config.ServerInfo.USBAutoMount != "True" && strings.Contains(service.MyService.System().GetDeviceTree(), "Raspberry Pi") {
service.MyService.System().UpdateUSBAutoMount("False")
service.MyService.System().ExecUSBAutoMountShell("False")
}
newAPIUrl := "https://api.casaos.io/casaos-api"
if config.ServerInfo.ServerApi == "https://api.casaos.zimaboard.com" {
config.ServerInfo.ServerApi = newAPIUrl
config.Cfg.Section("server").Key("ServerApi").SetValue(newAPIUrl)
config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
}
command.OnlyExec("curl -fsSL https://raw.githubusercontent.com/IceWhaleTech/get/main/assist.sh | bash")
if !file.CheckNotExist("/casaOS") {
command.OnlyExec("source /casaOS/server/shell/update.sh ;")
command.OnlyExec("source " + config.AppInfo.ShellPath + "/delete-old-service.sh ;")
}
service.MyService.App().ImportApplications(true)
src := "/casaOS/server/conf/conf.ini"
if file.Exists(src) {
dst := "/etc/casaos/casaos.conf"
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()
_, err = io.Copy(destination, source)
if err != nil {
return err
}
}
if file.Exists("/casaOS/server/db") {
var fds []os.FileInfo
var err error
to := "/var/lib/casaos/db"
file.IsNotExistMkDir(to)
from := "/casaOS/server/db"
if fds, err = ioutil.ReadDir(from); err != nil {
return err
}
for _, fd := range fds {
srcfp := path.Join(from, fd.Name())
dstfp := path.Join(to, fd.Name())
source, err := os.Open(srcfp)
if err != nil {
return err
}
defer source.Close()
destination, err := os.Create(dstfp)
if err != nil {
return err
}
defer destination.Close()
_, err = io.Copy(destination, source)
if err != nil {
return err
}
}
}
if file.Exists("/casaOS/server/conf") {
var fds []os.FileInfo
var err error
to := "/var/lib/casaos/conf"
file.IsNotExistMkDir(to)
from := "/casaOS/server/conf"
if fds, err = ioutil.ReadDir(from); err != nil {
return err
}
for _, fd := range fds {
fExt := path.Ext(fd.Name())
if fExt != ".json" {
continue
}
srcfp := path.Join(from, fd.Name())
dstfp := path.Join(to, fd.Name())
source, err := os.Open(srcfp)
if err != nil {
return err
}
defer source.Close()
destination, err := os.Create(dstfp)
if err != nil {
return err
}
defer destination.Close()
_, err = io.Copy(destination, source)
if err != nil {
return err
}
}
}
_logger.Info("update done")
return nil
}
func (u *migrationTool036) PostMigrate() error {
return nil
}
func NewMigrationToolFor_035() interfaces.MigrationTool {
return &migrationTool{}
}

View File

@@ -0,0 +1,74 @@
/*
* @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 (
"strings"
interfaces "github.com/IceWhaleTech/CasaOS-Common"
"github.com/IceWhaleTech/CasaOS-Common/utils/version"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/service"
)
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 {
if service.MyService.System().GetSysInfo().KernelArch == "aarch64" && config.ServerInfo.USBAutoMount != "True" && strings.Contains(service.MyService.System().GetDeviceTree(), "Raspberry Pi") {
service.MyService.System().UpdateUSBAutoMount("False")
service.MyService.System().ExecUSBAutoMountShell("False")
}
_logger.Info("update done")
return nil
}
func (u *migrationTool) PostMigrate() error {
return nil
}
func NewMigrationToolFor_036() interfaces.MigrationTool {
return &migrationTool{}
}

89
common/notify.go Normal file
View File

@@ -0,0 +1,89 @@
package common
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
)
const (
CasaOSURLFilename = "casaos.url"
APICasaOSNotify = "/v1/notify"
)
type NotifyService interface {
SendNotify(path string, message map[string]interface{}) error
SendSystemStatusNotify(message map[string]interface{}) error
}
type notifyService struct {
address string
}
func (n *notifyService) SendNotify(path string, message map[string]interface{}) error {
url := strings.TrimSuffix(n.address, "/") + APICasaOSNotify + "/" + path
body, err := json.Marshal(message)
if err != nil {
return err
}
response, err := http.Post(url, "application/json", bytes.NewBuffer(body))
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return errors.New("failed to send notify (status code: " + fmt.Sprint(response.StatusCode) + ")")
}
return nil
}
// disk: "sys_disk":{"size":56866869248,"avail":5855485952,"health":true,"used":48099700736}
// usb: "sys_usb":[{"name": "sdc","size": 7747397632,"model": "DataTraveler_2.0","avail": 7714418688,"children": null}]
func (n *notifyService) SendSystemStatusNotify(message map[string]interface{}) error {
url := strings.TrimSuffix(n.address, "/") + APICasaOSNotify + "/system_status"
fmt.Println(url)
body, err := json.Marshal(message)
if err != nil {
return err
}
response, err := http.Post(url, "application/json", bytes.NewBuffer(body))
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return errors.New("failed to send notify (status code: " + fmt.Sprint(response.StatusCode) + ")")
}
return nil
}
func NewNotifyService(runtimePath string) (NotifyService, error) {
casaosAddressFile := filepath.Join(runtimePath, CasaOSURLFilename)
buf, err := os.ReadFile(casaosAddressFile)
if err != nil {
return nil, err
}
address := string(buf)
response, err := http.Get(address + "/ping")
if err != nil {
return nil, err
}
if response.StatusCode != 200 {
return nil, errors.New("failed to ping casaos service")
}
return &notifyService{
address: address,
}, nil
}

29
common/notify_test.go Normal file
View File

@@ -0,0 +1,29 @@
package common
import "testing"
func TestSendNotify(t *testing.T) {
notify, err := NewNotifyService("/var/run/casaos")
if err != nil {
t.Fatal(err)
}
err = notify.SendNotify("test", map[string]interface{}{
"test": "test",
})
if err != nil {
t.Fatal(err)
}
}
func TestSendSystemStatusNotify(t *testing.T) {
notify, err := NewNotifyService("/var/run/casaos")
if err != nil {
t.Fatal(err)
}
err = notify.SendSystemStatusNotify(map[string]interface{}{
"sys_usb": `[{"name": "sdc","size": 7747397632,"model": "DataTraveler_2.0","avail": 7714418688,"children": null}]`,
})
if err != nil {
t.Fatal(err)
}
}

78
common/share.go Normal file
View File

@@ -0,0 +1,78 @@
package common
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
)
const (
APICasaOSShare = "/v1/samba/shares"
)
type ShareService interface {
DeleteShare(id string) error
}
type shareService struct {
address string
}
func (n *shareService) DeleteShare(id string) error {
url := strings.TrimSuffix(n.address, "/") + APICasaOSShare + "/" + id
fmt.Println(url)
message := "{}"
body, err := json.Marshal(message)
if err != nil {
return err
}
client := &http.Client{}
// Create request
req, err := http.NewRequest("DELETE", url, bytes.NewBuffer(body))
if err != nil {
return err
}
// Fetch Request
response, err := client.Do(req)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return errors.New("failed to send share (status code: " + fmt.Sprint(response.StatusCode) + ")")
}
return nil
}
func NewShareService(runtimePath string) (ShareService, error) {
casaosAddressFile := filepath.Join(runtimePath, CasaOSURLFilename)
buf, err := os.ReadFile(casaosAddressFile)
if err != nil {
return nil, err
}
address := string(buf)
response, err := http.Get(address + "/ping")
if err != nil {
return nil, err
}
if response.StatusCode != 200 {
return nil, errors.New("failed to ping casaos service")
}
return &shareService{
address: address,
}, nil
}

14
common/share_test.go Normal file
View File

@@ -0,0 +1,14 @@
package common
import "testing"
func TestDeleteShare(t *testing.T) {
share, err := NewShareService("/var/run/casaos")
if err != nil {
t.Fatal(err)
}
err = share.DeleteShare("1")
if err != nil {
t.Fatal(err)
}
}

View File

@@ -11,14 +11,15 @@ DateFormat = 2006-01-02
DBPath = /var/lib/casaos
ShellPath = /usr/share/casaos/shell
UserDataPath = /var/lib/casaos/conf
TempPath = /var/lib/casaos/temp
[server]
HttpPort = 80
RunMode = release
ServerApi = https://api.casaos.io/casaos-api
Handshake = socket.casaos.io
Token =
USBAutoMount =
[system]
[system]
[common]
RuntimePath=/var/run/casaos

51
go.mod
View File

@@ -4,78 +4,53 @@ go 1.16
require (
github.com/Curtis-Milo/nat-type-identifier-go v0.0.0-20220215191915-18d42168c63d
github.com/IceWhaleTech/CasaOS-Common v0.0.0-20220901034123-ca130f6b5ce9
github.com/IceWhaleTech/CasaOS-Gateway v0.3.6
github.com/Microsoft/go-winio v0.5.0 // indirect
github.com/Microsoft/hcsshim v0.8.22 // indirect
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
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/bits-and-blooms/bitset v1.2.1 // indirect
github.com/containerd/containerd v1.5.7
github.com/containerd/continuity v0.2.0 // indirect
github.com/containerd/containerd v1.5.7 // indirect
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
github.com/disintegration/imaging v1.6.2
github.com/docker/distribution v2.8.0+incompatible // indirect
github.com/docker/docker v20.10.7+incompatible
github.com/docker/go-connections v0.4.0
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.2
github.com/gin-gonic/gin v1.7.2
github.com/gin-contrib/gzip v0.0.6
github.com/gin-gonic/gin v1.8.1
github.com/go-ini/ini v1.62.0
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/go-playground/validator/v10 v10.6.1 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/golang-jwt/jwt/v4 v4.4.1
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0
github.com/gomodule/redigo v1.8.5
github.com/google/go-github/v36 v36.0.0
github.com/google/uuid v1.3.0 // indirect
github.com/googollee/go-socket.io v1.6.2
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.4.2
github.com/hirochachacha/go-smb2 v1.1.0
github.com/jinzhu/copier v0.3.2
github.com/json-iterator/go v1.1.11 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lucas-clemente/quic-go v0.25.0
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.11 // indirect
github.com/mholt/archiver/v3 v3.5.1
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/selinux v1.8.5 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/prometheus/procfs v0.7.3 // indirect
github.com/robfig/cron v1.2.0
github.com/satori/go.uuid v1.2.0
github.com/shirou/gopsutil/v3 v3.21.5
github.com/shirou/gopsutil/v3 v3.22.7
github.com/sirupsen/logrus v1.8.1
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
github.com/tklauser/go-sysconf v0.3.6 // indirect
github.com/ugorji/go v1.2.6 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/zap v1.10.0
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
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/net v0.0.0-20211020060615-d418f374d309 // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
golang.org/x/sys v0.0.0-20211020174200-9d6173849985 // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/tools v0.1.7 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0 // indirect
google.golang.org/grpc v1.41.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gorm.io/driver/sqlite v1.2.6
gorm.io/gorm v1.22.5
)

525
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-08-24 17:37:36
* @LastEditors: LinkLeong
* @LastEditTime: 2022-08-24 17:38:48
* @FilePath: /CasaOS/interfaces/migrationTool.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package interfaces
type MigrationTool interface {
IsMigrationNeeded() (bool, error)
PostMigrate() error
Migrate() error
PreMigrate() error
}

133
main.go
View File

@@ -3,50 +3,65 @@ package main
import (
"flag"
"fmt"
"net"
"net/http"
"path/filepath"
"time"
"github.com/IceWhaleTech/CasaOS-Gateway/common"
"github.com/IceWhaleTech/CasaOS/model/notify"
"github.com/IceWhaleTech/CasaOS/pkg/cache"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/sqlite"
"github.com/IceWhaleTech/CasaOS/pkg/utils/encryption"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/pkg/utils/loger"
"github.com/IceWhaleTech/CasaOS/pkg/utils/random"
"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"
"gorm.io/gorm"
)
const LOCALHOST = "127.0.0.1"
var sqliteDB *gorm.DB
var configFlag = flag.String("c", "", "config address")
var dbFlag = flag.String("db", "", "db path")
var resetUser = flag.Bool("ru", false, "reset user")
var user = flag.String("user", "", "user name")
var (
configFlag = flag.String("c", "", "config address")
dbFlag = flag.String("db", "", "db path")
versionFlag = flag.Bool("v", false, "version")
)
func init() {
flag.Parse()
if *versionFlag {
fmt.Println("v" + types.CURRENTVERSION)
return
}
config.InitSetup(*configFlag)
config.UpdateSetup()
loger.LogInit()
if len(*dbFlag) == 0 {
*dbFlag = config.AppInfo.DBPath + "/db"
}
sqliteDB = sqlite.GetDb(*dbFlag)
//gredis.GetRedisConn(config.RedisInfo),
service.MyService = service.NewService(sqliteDB)
// gredis.GetRedisConn(config.RedisInfo),
service.MyService = service.NewService(sqliteDB, config.CommonInfo.RuntimePath)
service.Cache = cache.Init()
service.GetToken()
service.NewVersionApp = make(map[string]string)
route.InitFunction()
// go service.LoopFriend()
// go service.MyService.App().CheckNewImage()
}
// @title casaOS API
@@ -62,40 +77,25 @@ func init() {
// @BasePath /v1
func main() {
service.NotifyMsg = make(chan notify.Message, 10)
if *resetUser {
if user == nil || len(*user) == 0 {
fmt.Println("user is empty")
return
}
userData := service.MyService.User().GetUserAllInfoByName(*user)
if userData.Id == 0 {
fmt.Println("user not exist")
return
}
password := random.RandomString(6, false)
userData.Password = encryption.GetMD5ByStr(password)
service.MyService.User().UpdateUserPassword(userData)
fmt.Println("User reset successful")
fmt.Println("UserName:" + userData.Username)
fmt.Println("Password:" + password)
if *versionFlag {
return
}
go route.SocketInit(service.NotifyMsg)
// model.Setup()
// gredis.Setup()
//model.Setup()
//gredis.Setup()
r := route.InitRouter()
//service.SyncTask(sqliteDB)
// service.SyncTask(sqliteDB)
cron2 := cron.New()
//every day execution
// every day execution
err := cron2.AddFunc("0/5 * * * * *", func() {
if service.ClientCount > 0 {
//route.SendNetINfoBySocket()
//route.SendCPUBySocket()
//route.SendMemBySocket()
// route.SendNetINfoBySocket()
// route.SendCPUBySocket()
// route.SendMemBySocket()
// route.SendDiskBySocket()
//route.SendUSBBySocket()
// route.SendUSBBySocket()
route.SendAllHardwareStatusBySocket()
}
})
@@ -103,18 +103,63 @@ func main() {
fmt.Println(err)
}
cron2.Start()
defer cron2.Stop()
s := &http.Server{
Addr: fmt.Sprintf(":%v", config.ServerInfo.HttpPort),
Handler: r,
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
MaxHeaderBytes: 1 << 20,
listener, err := net.Listen("tcp", net.JoinHostPort(LOCALHOST, "0"))
if err != nil {
panic(err)
}
routers := []string{"sys", "apps", "container", "app-categories", "port", "file", "folder", "batch", "image", "samba", "notify"}
for _, v := range routers {
err = service.MyService.Gateway().CreateRoute(&common.Route{
Path: "/v1/" + v,
Target: "http://" + listener.Addr().String(),
})
if err != nil {
fmt.Println("err", err)
panic(err)
}
}
go func() {
time.Sleep(time.Second * 2)
// v0.3.6
if config.ServerInfo.HttpPort != "" {
changePort := common.ChangePortRequest{}
changePort.Port = config.ServerInfo.HttpPort
err := service.MyService.Gateway().ChangePort(&changePort)
if err == nil {
config.Cfg.Section("server").Key("HttpPort").SetValue("")
config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
}
}
}()
urlFilePath := filepath.Join(config.CommonInfo.RuntimePath, "casaos.url")
err = file.CreateFileAndWriteContent(urlFilePath, "http://"+listener.Addr().String())
if err != nil {
loger.Error("Management service is listening...",
zap.Any("address", listener.Addr().String()),
zap.Any("filepath", urlFilePath),
)
}
s.ListenAndServe()
if supported, err := daemon.SdNotify(false, daemon.SdNotifyReady); err != nil {
loger.Error("Failed to notify systemd that casaos main service is ready", zap.Any("error", err))
} else if supported {
loger.Info("Notified systemd that casaos main service is ready")
} else {
loger.Info("This process is not running as a systemd service.")
}
// if err := r.Run(fmt.Sprintf(":%v", config.ServerInfo.HttpPort)); err != nil {
// fmt.Println("failed run app: ", err)
// }
s := &http.Server{
Handler: r,
ReadHeaderTimeout: 5 * time.Second, // fix G112: Potential slowloris attack (see https://github.com/securego/gosec)
}
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)
}
}

View File

@@ -45,7 +45,7 @@ type ServerAppList struct {
Index string `json:"index"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
State string `json:"state"`
State int `json:"state"`
Author string `json:"author"`
MinMemory int `json:"min_memory"`
MinDisk int `json:"min_disk"`

20
model/connections.go Normal file
View File

@@ -0,0 +1,20 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-07-27 10:30:43
* @LastEditors: LinkLeong
* @LastEditTime: 2022-08-04 20:06:04
* @FilePath: /CasaOS/model/connections.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package model
type Connections struct {
ID uint `json:"id"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
Host string `json:"host"`
Port string `json:"port"`
MountPoint string `json:"mount_point"`
}

View File

@@ -1,86 +0,0 @@
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-07-13 10:43:45
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-13 11:00:04
* @FilePath: /CasaOS/model/disk.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package model
type LSBLKModel struct {
Name string `json:"name"`
FsType string `json:"fstype"`
Size uint64 `json:"size"`
FSSize string `json:"fssize"`
Path string `json:"path"`
Model string `json:"model"` //设备标识符
RM bool `json:"rm"` //是否为可移动设备
RO bool `json:"ro"` //是否为只读设备
State string `json:"state"`
PhySec int `json:"phy-sec"` //物理扇区大小
Type string `json:"type"`
Vendor string `json:"vendor"` //供应商
Rev string `json:"rev"` //修订版本
FSAvail string `json:"fsavail"` //可用空间
FSUse string `json:"fsuse%"` //已用百分比
MountPoint string `json:"mountpoint"`
Format string `json:"format"`
Health string `json:"health"`
HotPlug bool `json:"hotplug"`
UUID string `json:"uuid"`
FSUsed string `json:"fsused"`
Temperature int `json:"temperature"`
Tran string `json:"tran"`
MinIO uint64 `json:"min-io"`
UsedPercent float64 `json:"used_percent"`
Serial string `json:"serial"`
Children []LSBLKModel `json:"children"`
SubSystems string `json:"subsystems"`
//详情特有
StartSector uint64 `json:"start_sector,omitempty"`
Rota bool `json:"rota"` //true(hhd) false(ssd)
DiskType string `json:"disk_type"`
EndSector uint64 `json:"end_sector,omitempty"`
}
type Drive struct {
Name string `json:"name"`
Size uint64 `json:"size"`
Model string `json:"model"`
Health string `json:"health"`
Temperature int `json:"temperature"`
DiskType string `json:"disk_type"`
NeedFormat bool `json:"need_format"`
Serial string `json:"serial"`
Path string `json:"path"`
}
type DriveUSB struct {
Name string `json:"name"`
Size uint64 `json:"size"`
Used uint64 `json:"use"`
Model string `json:"model"`
Mount bool `json:"mount"`
Avail uint64 `json:"avail"`
}
type Storage struct {
Name string `json:"name"`
MountPoint string `json:"mountpoint"`
Size string `json:"size"`
Avail string `json:"avail"` //可用空间
Type string `json:"type"`
CreatedAt int64 `json:"create_at"`
Path string `json:"path"`
DriveName string `json:"drive_name"`
}
type Summary struct {
Size uint64 `json:"size"`
Avail uint64 `json:"avail"` //可用空间
Health bool `json:"health"`
Used uint64 `json:"used"`
}

View File

@@ -16,3 +16,7 @@ type DockerStatsModel struct {
Data interface{} `json:"data"`
Previous interface{} `json:"previous"`
}
type DeckerDaemonModel struct {
Graph string `json:"graph"`
}

View File

@@ -1 +0,0 @@
package receive

17
model/share.go Normal file
View File

@@ -0,0 +1,17 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-07-26 11:12:12
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-27 14:58:55
* @FilePath: /CasaOS/model/share.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package model
type Shares struct {
ID uint `json:"id"`
Anonymous bool `json:"anonymous"`
Path string `json:"path"`
}

View File

@@ -2,7 +2,7 @@
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-05-13 18:15:46
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-14 11:02:06
* @LastEditTime: 2022-09-02 22:12:34
* @FilePath: /CasaOS/model/sys_common.go
* @Description:
* @Website: https://www.casaos.io
@@ -12,26 +12,12 @@ package model
import "time"
//系统配置
// 系统配置
type SysInfoModel struct {
Name string //系统名称
}
//用户相关
type UserModel struct {
UserName string
PWD string
Token string
Head string
Email string
Description string
Initialized bool
Avatar string
NickName string
Public string
}
//服务配置
// 服务配置
type ServerModel struct {
HttpPort string
RunMode string
@@ -42,7 +28,7 @@ type ServerModel struct {
SocketPort string
}
//服务配置
// 服务配置
type APPModel struct {
LogPath string
LogSaveName string
@@ -54,17 +40,19 @@ type APPModel struct {
DateFormat string
DBPath string
ShellPath string
TempPath string
}
type CommonModel struct {
RuntimePath string
}
//公共返回模型
// 公共返回模型
type Result struct {
Success int `json:"success" example:"200"`
Message string `json:"message" example:"ok"`
Data interface{} `json:"data" example:"返回结果"`
}
//redis配置文件
// redis配置文件
type RedisModel struct {
Host string
Password string

View File

@@ -1,14 +1,25 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-05-13 18:15:46
* @LastEditors: LinkLeong
* @LastEditTime: 2022-08-01 18:32:57
* @FilePath: /CasaOS/model/zima.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package model
import "time"
type Path struct {
Name string `json:"name"` //File name or document name
Path string `json:"path"` //Full path to file or folder
IsDir bool `json:"is_dir"` //Is it a folder
Date time.Time `json:"date"`
Size int64 `json:"size"` //File Size
Type string `json:"type,omitempty"`
Label string `json:"label,omitempty"`
Write bool `json:"write"`
Name string `json:"name"` //File name or document name
Path string `json:"path"` //Full path to file or folder
IsDir bool `json:"is_dir"` //Is it a folder
Date time.Time `json:"date"`
Size int64 `json:"size"` //File Size
Type string `json:"type,omitempty"`
Label string `json:"label,omitempty"`
Write bool `json:"write"`
Extensions map[string]interface{} `json:"extensions"`
}

View File

@@ -2,7 +2,7 @@
* @Author: LinkLeong link@icewhale.com
* @Date: 2021-09-30 18:18:14
* @LastEditors: LinkLeong
* @LastEditTime: 2022-06-21 11:09:30
* @LastEditTime: 2022-08-31 17:04:02
* @FilePath: /CasaOS/pkg/config/config.go
* @Description:
* @Website: https://www.casaos.io
@@ -11,5 +11,5 @@
package config
const (
USERCONFIGURL = "/etc/casaos.conf"
USERCONFIGURL = "/etc/casaos/casaos.conf"
)

View File

@@ -2,7 +2,7 @@
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-05-13 18:15:46
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-14 10:58:45
* @LastEditTime: 2022-09-05 11:58:02
* @FilePath: /CasaOS/pkg/config/init.go
* @Description:
* @Website: https://www.casaos.io
@@ -23,18 +23,17 @@ import (
"github.com/go-ini/ini"
)
//系统配置
// 系统配置
var SysInfo = &model.SysInfoModel{}
//用户相关
var UserInfo = &model.UserModel{}
//用户相关
// 用户相关
var AppInfo = &model.APPModel{}
var CommonInfo = &model.CommonModel{}
//var RedisInfo = &model.RedisModel{}
//server相关
// server相关
var ServerInfo = &model.ServerModel{}
var SystemConfigInfo = &model.SystemConfig{}
@@ -45,7 +44,7 @@ var FileSettingInfo = &model.FileSetting{}
var Cfg *ini.File
//初始化设置,获取系统的部分信息。
// 初始化设置,获取系统的部分信息。
func InitSetup(config string) {
var configDir = USERCONFIGURL
@@ -59,42 +58,43 @@ func InitSetup(config string) {
//读取文件
Cfg, err = ini.Load(configDir)
if err != nil {
fmt.Printf("Fail to read file: %v", err)
os.Exit(1)
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)
}
}
}
mapTo("user", UserInfo)
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"
Cfg.SaveTo(configDir)
}
if len(AppInfo.LogPath) == 0 {
AppInfo.LogPath = "/var/log/casaos/"
Cfg.SaveTo(configDir)
}
if len(AppInfo.ShellPath) == 0 {
AppInfo.ShellPath = "/usr/share/casaos/shell"
Cfg.SaveTo(configDir)
}
if len(AppInfo.UserDataPath) == 0 {
AppInfo.UserDataPath = "/var/lib/casaos/conf"
Cfg.SaveTo(configDir)
}
if len(AppInfo.TempPath) == 0 {
AppInfo.TempPath = "/var/lib/casaos/temp"
Cfg.SaveTo(configDir)
if len(CommonInfo.RuntimePath) == 0 {
CommonInfo.RuntimePath = "/var/run/casaos"
}
Cfg.SaveTo(configDir)
// AppInfo.ProjectPath = getCurrentDirectory() //os.Getwd()
}
//映射
// 映射
func mapTo(section string, v interface{}) {
err := Cfg.Section(section).MapTo(v)
if err != nil {

View File

@@ -1,32 +0,0 @@
package config
import (
"runtime"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
)
//检查目录是否存在
func mkdirDATAAll() {
sysType := runtime.GOOS
var dirArray []string
if sysType == "linux" {
dirArray = []string{"/DATA/AppData", "/DATA/Documents", "/DATA/Downloads", "/DATA/Gallery", "/DATA/Media/Movies", "/DATA/Media/TV Shows", "/DATA/Media/Music"}
}
if sysType == "windows" {
dirArray = []string{"C:\\CasaOS\\DATA\\AppData", "C:\\CasaOS\\DATA\\Documents", "C:\\CasaOS\\DATA\\Downloads", "C:\\CasaOS\\DATA\\Gallery", "C:\\CasaOS\\DATA\\Media/Movies", "C:\\CasaOS\\DATA\\Media\\TV Shows", "C:\\CasaOS\\DATA\\Media\\Music"}
}
if sysType == "darwin" {
dirArray = []string{"./CasaOS/DATA/AppData", "./CasaOS/DATA/Documents", "./CasaOS/DATA/Downloads", "./CasaOS/DATA/Gallery", "./CasaOS/DATA/Media/Movies", "./CasaOS/DATA/Media/TV Shows", "./CasaOS/DATA/Media/Music"}
}
for _, v := range dirArray {
file.IsNotExistMkDir(v)
}
}
func UpdateSetup() {
mkdirDATAAll()
}

75
pkg/samba/smaba.go Normal file
View File

@@ -0,0 +1,75 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-07-27 10:35:29
* @LastEditors: LinkLeong
* @LastEditTime: 2022-08-01 13:56:44
* @FilePath: /CasaOS/pkg/samba/smaba.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package samba
import (
"errors"
"net"
"github.com/hirochachacha/go-smb2"
)
func ConnectSambaService(host, port, username, password, directory string) error {
conn, err := net.Dial("tcp", host+":"+port)
if err != nil {
return err
}
defer conn.Close()
d := &smb2.Dialer{
Initiator: &smb2.NTLMInitiator{
User: username,
Password: password,
},
}
s, err := d.Dial(conn)
if err != nil {
return err
}
defer s.Logoff()
names, err := s.ListSharenames()
if err != nil {
return err
}
for _, name := range names {
if name == directory {
return nil
}
}
return errors.New("directory not found")
}
//get share name list
func GetSambaSharesList(host, port, username, password string) ([]string, error) {
conn, err := net.Dial("tcp", host+":"+port)
if err != nil {
return nil, err
}
defer conn.Close()
d := &smb2.Dialer{
Initiator: &smb2.NTLMInitiator{
User: username,
Password: password,
},
}
s, err := d.Dial(conn)
if err != nil {
return nil, err
}
defer s.Logoff()
names, err := s.ListSharenames()
if err != nil {
return nil, err
}
return names, err
}

View File

@@ -2,7 +2,7 @@
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-05-13 18:15:46
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-11 18:10:53
* @LastEditTime: 2022-08-31 13:39:24
* @FilePath: /CasaOS/pkg/sqlite/db.go
* @Description:
* @Website: https://www.casaos.io
@@ -43,17 +43,7 @@ func GetDb(dbPath string) *gorm.DB {
}
gdb = db
db.Exec(`alter table o_user rename to old_user;
create table o_users ( id integer primary key,username text,password text,role text,email text,nickname text,avatar text,description text,created_at datetime,updated_at datetime);
insert into o_users select id,user_name,password,role,email,nick_name,avatar,description,created_at,updated_at from old_user;
drop table old_user;
drop table o_user;
`)
err = db.AutoMigrate(&model2.AppNotify{}, &model2.AppListDBModel{}, &model2.SerialDisk{}, model2.UserDBModel{})
err = db.AutoMigrate(&model2.AppNotify{}, &model2.AppListDBModel{}, model2.SharesDBModel{}, model2.ConnectionsDBModel{})
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")

View File

@@ -27,6 +27,9 @@ const (
PORT_IS_OCCUPIED = 20004
COMMAND_ERROR_INVALID_OPERATION = 20005
VERIFICATION_FAILURE = 20006
Record_NOT_EXIST = 20007
Record_ALREADY_EXIST = 20008
SERVICE_NOT_RUNNING = 20009
//disk
NAME_NOT_AVAILABLE = 40001
@@ -48,8 +51,9 @@ const (
DIR_NOT_EXISTS = 60004
SOURCE_DES_SAME = 60005
//shortcuts
SHORTCUTS_URL_ERROR = 70001
//share
SHARE_ALREADY_EXISTS = 70001
SHARE_NAME_ALREADY_EXISTS = 70002
)
var MsgFlags = map[int]string{
@@ -78,6 +82,9 @@ var MsgFlags = map[int]string{
FILE_OR_DIR_EXISTS: "File or folder already exists",
PORT_IS_OCCUPIED: "Port is occupied",
VERIFICATION_FAILURE: "Verification failure",
Record_ALREADY_EXIST: "Record already exists",
Record_NOT_EXIST: "Record does not exist",
SERVICE_NOT_RUNNING: "Service is not running",
//app
UNINSTALL_APP_ERROR: "Error uninstalling app",
@@ -91,16 +98,17 @@ var MsgFlags = map[int]string{
REMOVE_MOUNT_POINT_ERROR: "Failed to remove mount point",
DISK_BUSYING: "Drive is busy",
FORMAT_ERROR: "Formatting failed, please check if the directory is occupied",
//share
SHARE_ALREADY_EXISTS: "Share already exists",
SHARE_NAME_ALREADY_EXISTS: "Share name already exists",
//
SOURCE_DES_SAME: "Source and destination cannot be the same.",
FILE_DOES_NOT_EXIST: "File does not exist",
DIR_NOT_EXISTS: "Directory does not exist",
FILE_READ_ERROR: "File read error",
FILE_DELETE_ERROR: "Delete error",
SHORTCUTS_URL_ERROR: "URL error",
FILE_READ_ERROR: "File read error",
FILE_DELETE_ERROR: "Delete error",
COMMAND_ERROR_INVALID_OPERATION: "invalid operation",
}

View File

@@ -547,3 +547,20 @@ func MoveFile(sourcePath, destPath string) error {
}
return nil
}
func ReadLine(lineNumber int, path string) string {
file, err := os.Open(path)
if err != nil {
return ""
}
fileScanner := bufio.NewScanner(file)
lineCount := 1
for fileScanner.Scan() {
if lineCount == lineNumber {
return fileScanner.Text()
}
lineCount++
}
defer file.Close()
return ""
}

View File

@@ -1,63 +0,0 @@
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2021-09-30 18:18:14
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-18 17:30:38
* @FilePath: /CasaOS/pkg/utils/jwt/jwt.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package jwt
import (
"time"
jwt "github.com/golang-jwt/jwt/v4"
)
type Claims struct {
Username string `json:"username"`
PassWord string `json:"password"`
Id int `json:"id"`
jwt.RegisteredClaims
}
var jwtSecret []byte
//创建token
func GenerateToken(username, password string, id int, issuer string, t time.Duration) (string, error) {
clims := Claims{
username,
password,
id,
jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(t)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: issuer,
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, clims)
token, err := tokenClaims.SignedString(jwtSecret)
return token, err
}
//解析token
func ParseToken(token string, valid bool) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if tokenClaims != nil {
if clims, ok := tokenClaims.Claims.(*Claims); ok {
if valid && tokenClaims.Valid {
return clims, nil
} else if !valid {
return clims, nil
}
}
}
return nil, err
}

View File

@@ -1,75 +0,0 @@
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-06-17 14:01:25
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-18 17:31:15
* @FilePath: /CasaOS/pkg/utils/jwt/jwt_helper.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package jwt
import (
"fmt"
"strconv"
"time"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
loger2 "github.com/IceWhaleTech/CasaOS/pkg/utils/loger"
"github.com/gin-gonic/gin"
)
func JWT() gin.HandlerFunc {
return func(c *gin.Context) {
var code int
code = common_err.SUCCESS
token := c.GetHeader("Authorization")
if len(token) == 0 {
token = c.Query("token")
}
if token == "" {
code = common_err.INVALID_PARAMS
}
claims, err := ParseToken(token, false)
//_, err := ParseToken(token)
if err != nil {
code = common_err.ERROR_AUTH_TOKEN
} else if (c.Request.URL.Path == "/v1/file" || c.Request.URL.Path == "/v1/image" || c.Request.URL.Path == "/v1/file/upload" || c.Request.URL.Path == "/v1/batch") && claims.VerifyIssuer("casaos", true) {
//Special treatment
} else if !claims.VerifyExpiresAt(time.Now(), true) || !claims.VerifyIssuer("casaos", true) {
code = common_err.ERROR_AUTH_TOKEN
}
if code != common_err.SUCCESS {
c.JSON(code, model.Result{Success: code, Message: common_err.GetMsg(code)})
c.Abort()
return
}
c.Request.Header.Add("user_id", strconv.Itoa(claims.Id))
c.Next()
}
}
//get AccessToken
func GetAccessToken(username, pwd string, id int) string {
token, err := GenerateToken(username, pwd, id, "casaos", 3*time.Hour*time.Duration(1))
if err == nil {
return token
} else {
loger2.Error(fmt.Sprintf("Get Token Fail: %V", err))
return ""
}
}
func GetRefreshToken(username, pwd string, id int) string {
token, err := GenerateToken(username, pwd, id, "refresh", 7*24*time.Hour*time.Duration(1))
if err == nil {
return token
} else {
loger2.Error(fmt.Sprintf("Get Token Fail: %V", err))
return ""
}
}

33
pkg/utils/udev_helper.go Normal file
View File

@@ -0,0 +1,33 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-08-10 16:06:12
* @LastEditors: LinkLeong
* @LastEditTime: 2022-08-10 16:11:37
* @FilePath: /CasaOS/pkg/utils/udev_helper.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package utils
// func getOptionnalMatcher() (matcher netlink.Matcher, err error) {
// if filePath == nil || *filePath == "" {
// return nil, nil
// }
// stream, err := ioutil.ReadFile(*filePath)
// if err != nil {
// return nil, err
// }
// if stream == nil {
// return nil, fmt.Errorf("Empty, no rules provided in \"%s\", err: %w", *filePath, err)
// }
// var rules netlink.RuleDefinitions
// if err := json.Unmarshal(stream, &rules); err != nil {
// return nil, fmt.Errorf("Wrong rule syntax, err: %w", err)
// }
// return &rules, nil
// }

25
route/darwin.go Normal file
View File

@@ -0,0 +1,25 @@
//go:build darwin
// +build darwin
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-08-12 14:22:28
* @LastEditors: LinkLeong
* @LastEditTime: 2022-09-05 16:27:55
* @FilePath: /CasaOS/route/darwin.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package route
func MonitoryUSB() {
}
func SendAllHardwareStatusBySocket() {
}
func SendUSBBySocket() {
}

View File

@@ -1,140 +1,48 @@
package route
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/utils/command"
"github.com/IceWhaleTech/CasaOS/pkg/utils/encryption"
"github.com/IceWhaleTech/CasaOS/pkg/samba"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/pkg/utils/loger"
"github.com/IceWhaleTech/CasaOS/service"
model2 "github.com/IceWhaleTech/CasaOS/service/model"
uuid "github.com/satori/go.uuid"
"go.uber.org/zap"
)
func InitFunction() {
ShellInit()
CheckSerialDiskMount()
CheckToken2_11()
ImportApplications()
ChangeAPIUrl()
MoveUserToDB()
go InitNetworkMount()
}
func CheckSerialDiskMount() {
// check mount point
dbList := service.MyService.Disk().GetSerialAll()
list := service.MyService.Disk().LSBLK(true)
mountPoint := make(map[string]string, len(dbList))
//remount
for _, v := range dbList {
mountPoint[v.UUID] = v.MountPoint
}
for _, v := range list {
command.ExecEnabledSMART(v.Path)
if v.Children != nil {
for _, h := range v.Children {
if len(h.MountPoint) == 0 && len(v.Children) == 1 && h.FsType == "ext4" {
if m, ok := mountPoint[h.UUID]; ok {
//mount point check
volume := m
if !file.CheckNotExist(m) {
for i := 0; file.CheckNotExist(volume); i++ {
volume = m + strconv.Itoa(i+1)
}
}
service.MyService.Disk().MountDisk(h.Path, volume)
if volume != m {
ms := model2.SerialDisk{}
ms.UUID = v.UUID
ms.MountPoint = volume
service.MyService.Disk().UpdateMountPoint(ms)
}
}
}
}
func InitNetworkMount() {
time.Sleep(time.Second * 10)
connections := service.MyService.Connections().GetConnectionsList()
for _, v := range connections {
connection := service.MyService.Connections().GetConnectionByID(fmt.Sprint(v.ID))
directories, err := samba.GetSambaSharesList(connection.Host, connection.Port, connection.Username, connection.Password)
if err != nil {
service.MyService.Connections().DeleteConnection(fmt.Sprint(connection.ID))
loger.Error("mount samba err", zap.Any("err", err), zap.Any("info", connection))
continue
}
}
service.MyService.Disk().RemoveLSBLKCache()
command.OnlyExec("source " + config.AppInfo.ShellPath + "/helper.sh ;AutoRemoveUnuseDir")
}
func ShellInit() {
command.OnlyExec("curl -fsSL https://raw.githubusercontent.com/IceWhaleTech/get/main/assist.sh | bash")
if !file.CheckNotExist("/casaOS") {
command.OnlyExec("source /casaOS/server/shell/update.sh ;")
command.OnlyExec("source " + config.AppInfo.ShellPath + "/delete-old-service.sh ;")
}
baseHostPath := "/mnt/" + connection.Host
}
func CheckToken2_11() {
if len(config.ServerInfo.Token) == 0 {
token := uuid.NewV4().String
config.ServerInfo.Token = token()
config.Cfg.Section("server").Key("Token").SetValue(token())
config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
}
if len(config.UserInfo.Description) == 0 {
config.Cfg.Section("user").Key("Description").SetValue("nothing")
config.UserInfo.Description = "nothing"
config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
}
if service.MyService.System().GetSysInfo().KernelArch == "aarch64" && config.ServerInfo.USBAutoMount != "True" && strings.Contains(service.MyService.System().GetDeviceTree(), "Raspberry Pi") {
service.MyService.System().UpdateUSBAutoMount("False")
service.MyService.System().ExecUSBAutoMountShell("False")
}
// str := []string{}
// str = append(str, "ddd")
// str = append(str, "aaa")
// ddd := strings.Join(str, "|")
// config.Cfg.Section("file").Key("ShareDir").SetValue(ddd)
// config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
}
func ImportApplications() {
service.MyService.App().ImportApplications(true)
}
// 0.3.1
func ChangeAPIUrl() {
newAPIUrl := "https://api.casaos.io/casaos-api"
if config.ServerInfo.ServerApi == "https://api.casaos.zimaboard.com" {
config.ServerInfo.ServerApi = newAPIUrl
config.Cfg.Section("server").Key("ServerApi").SetValue(newAPIUrl)
config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
}
}
//0.3.3
//Transferring user data to the database
func MoveUserToDB() {
if len(config.UserInfo.UserName) > 0 && service.MyService.User().GetUserInfoByUserName(config.UserInfo.UserName).Id == 0 {
user := model2.UserDBModel{}
user.Username = config.UserInfo.UserName
user.Email = config.UserInfo.Email
user.Nickname = config.UserInfo.NickName
user.Password = encryption.GetMD5ByStr(config.UserInfo.PWD)
user.Role = "admin"
user = service.MyService.User().CreateUser(user)
if user.Id > 0 {
userPath := config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id)
file.MkDir(userPath)
os.Rename("/casaOS/server/conf/app_order.json", userPath+"/app_order.json")
mountPointList := service.MyService.System().GetDirPath(baseHostPath)
for _, v := range mountPointList {
service.MyService.Connections().UnmountSmaba(v.Path)
}
os.RemoveAll(baseHostPath)
file.IsNotExistMkDir(baseHostPath)
for _, v := range directories {
mountPoint := baseHostPath + "/" + v
file.IsNotExistMkDir(mountPoint)
service.MyService.Connections().MountSmaba(connection.Username, connection.Host, v, connection.Port, mountPoint, connection.Password)
}
connection.Directories = strings.Join(directories, ",")
service.MyService.Connections().UpdateConnection(&connection)
}
}

View File

@@ -1,8 +1,11 @@
//go:build !darwin
// +build !darwin
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-07-01 15:11:36
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-21 15:25:07
* @LastEditTime: 2022-09-05 16:28:46
* @FilePath: /CasaOS/route/periodical.go
* @Description:
* @Website: https://www.casaos.io
@@ -11,8 +14,6 @@
package route
import (
"reflect"
"strconv"
"strings"
"time"
"unsafe"
@@ -52,108 +53,6 @@ func SendMemBySocket() {
service.MyService.Notify().SendMemInfoBySocket(service.MyService.System().GetMemInfo())
}
func SendDiskBySocket() {
list := service.MyService.Disk().LSBLK(true)
summary := model.Summary{}
healthy := true
findSystem := 0
for i := 0; i < len(list); i++ {
if len(list[i].Children) > 0 && findSystem == 0 {
for j := 0; j < len(list[i].Children); j++ {
if len(list[i].Children[j].Children) > 0 {
for _, v := range list[i].Children[j].Children {
if v.MountPoint == "/" {
s, _ := strconv.ParseUint(v.FSSize, 10, 64)
a, _ := strconv.ParseUint(v.FSAvail, 10, 64)
u, _ := strconv.ParseUint(v.FSUsed, 10, 64)
summary.Size += s
summary.Avail += a
summary.Used += u
findSystem = 1
break
}
}
} else {
if list[i].Children[j].MountPoint == "/" {
s, _ := strconv.ParseUint(list[i].Children[j].FSSize, 10, 64)
a, _ := strconv.ParseUint(list[i].Children[j].FSAvail, 10, 64)
u, _ := strconv.ParseUint(list[i].Children[j].FSUsed, 10, 64)
summary.Size += s
summary.Avail += a
summary.Used += u
findSystem = 1
break
}
}
}
}
if findSystem == 1 {
findSystem += 1
continue
}
if list[i].Tran == "sata" || list[i].Tran == "nvme" || list[i].Tran == "spi" || list[i].Tran == "sas" || strings.Contains(list[i].SubSystems, "virtio") || (list[i].Tran == "ata" && list[i].Type == "disk") {
temp := service.MyService.Disk().SmartCTL(list[i].Path)
if reflect.DeepEqual(temp, model.SmartctlA{}) {
healthy = true
} else {
healthy = temp.SmartStatus.Passed
}
//list[i].Temperature = temp.Temperature.Current
if len(list[i].Children) > 0 {
for _, v := range list[i].Children {
s, _ := strconv.ParseUint(v.FSSize, 10, 64)
a, _ := strconv.ParseUint(v.FSAvail, 10, 64)
u, _ := strconv.ParseUint(v.FSUsed, 10, 64)
summary.Size += s
summary.Avail += a
summary.Used += u
}
}
}
}
summary.Health = healthy
service.MyService.Notify().SendDiskInfoBySocket(summary)
}
func SendUSBBySocket() {
usbList := service.MyService.Disk().LSBLK(false)
usb := []model.DriveUSB{}
for _, v := range usbList {
if v.Tran == "usb" {
temp := model.DriveUSB{}
temp.Model = v.Model
temp.Name = v.Name
temp.Size = v.Size
mountTemp := true
if len(v.Children) == 0 {
mountTemp = false
}
for _, child := range v.Children {
if len(child.MountPoint) > 0 {
avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
temp.Avail += avail
used, _ := strconv.ParseUint(child.FSUsed, 10, 64)
temp.Used += used
} else {
mountTemp = false
}
}
temp.Mount = mountTemp
usb = append(usb, temp)
}
}
service.MyService.Notify().SendUSBInfoBySocket(usb)
}
func SendAllHardwareStatusBySocket() {
netList := service.MyService.System().GetNetInfo()
@@ -175,101 +74,47 @@ func SendAllHardwareStatusBySocket() {
cpuData := make(map[string]interface{})
cpuData["percent"] = cpu
cpuData["num"] = num
cpuData["temperature"] = service.MyService.System().GetCPUTemperature()
cpuData["power"] = service.MyService.System().GetCPUPower()
list := service.MyService.Disk().LSBLK(true)
summary := model.Summary{}
healthy := true
findSystem := 0
for i := 0; i < len(list); i++ {
if len(list[i].Children) > 0 && findSystem == 0 {
for j := 0; j < len(list[i].Children); j++ {
if len(list[i].Children[j].Children) > 0 {
for _, v := range list[i].Children[j].Children {
if v.MountPoint == "/" {
s, _ := strconv.ParseUint(v.FSSize, 10, 64)
a, _ := strconv.ParseUint(v.FSAvail, 10, 64)
u, _ := strconv.ParseUint(v.FSUsed, 10, 64)
summary.Size += s
summary.Avail += a
summary.Used += u
findSystem = 1
break
}
}
} else {
if list[i].Children[j].MountPoint == "/" {
s, _ := strconv.ParseUint(list[i].Children[j].FSSize, 10, 64)
a, _ := strconv.ParseUint(list[i].Children[j].FSAvail, 10, 64)
u, _ := strconv.ParseUint(list[i].Children[j].FSUsed, 10, 64)
summary.Size += s
summary.Avail += a
summary.Used += u
findSystem = 1
break
}
}
}
}
if findSystem == 1 {
findSystem += 1
continue
}
if list[i].Tran == "sata" || list[i].Tran == "nvme" || list[i].Tran == "spi" || list[i].Tran == "sas" || strings.Contains(list[i].SubSystems, "virtio") || (list[i].Tran == "ata" && list[i].Type == "disk") {
temp := service.MyService.Disk().SmartCTL(list[i].Path)
if reflect.DeepEqual(temp, model.SmartctlA{}) {
healthy = true
} else {
healthy = temp.SmartStatus.Passed
}
if len(list[i].Children) > 0 {
for _, v := range list[i].Children {
s, _ := strconv.ParseUint(v.FSSize, 10, 64)
a, _ := strconv.ParseUint(v.FSAvail, 10, 64)
u, _ := strconv.ParseUint(v.FSUsed, 10, 64)
summary.Size += s
summary.Avail += a
summary.Used += u
}
}
}
}
summary.Health = healthy
usbList := service.MyService.Disk().LSBLK(false)
usb := []model.DriveUSB{}
for _, v := range usbList {
if v.Tran == "usb" {
temp := model.DriveUSB{}
temp.Model = v.Model
temp.Name = v.Name
temp.Size = v.Size
mountTemp := true
if len(v.Children) == 0 {
mountTemp = false
}
for _, child := range v.Children {
if len(child.MountPoint) > 0 {
avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
temp.Avail += avail
used, _ := strconv.ParseUint(child.FSUsed, 10, 64)
temp.Used += used
} else {
mountTemp = false
}
}
temp.Mount = mountTemp
usb = append(usb, temp)
}
}
memInfo := service.MyService.System().GetMemInfo()
service.MyService.Notify().SendAllHardwareStatusBySocket(summary, usb, memInfo, cpuData, newNet)
service.MyService.Notify().SendAllHardwareStatusBySocket(memInfo, cpuData, newNet)
}
// func MonitoryUSB() {
// var matcher netlink.Matcher
// conn := new(netlink.UEventConn)
// if err := conn.Connect(netlink.UdevEvent); err != nil {
// loger.Error("udev err", zap.Any("Unable to connect to Netlink Kobject UEvent socket", err))
// }
// defer conn.Close()
// queue := make(chan netlink.UEvent)
// errors := make(chan error)
// quit := conn.Monitor(queue, errors, matcher)
// signals := make(chan os.Signal, 1)
// signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
// go func() {
// <-signals
// close(quit)
// os.Exit(0)
// }()
// for {
// select {
// case uevent := <-queue:
// if uevent.Env["DEVTYPE"] == "disk" {
// time.Sleep(time.Microsecond * 500)
// SendUSBBySocket()
// continue
// }
// case err := <-errors:
// loger.Error("udev err", zap.Any("err", err))
// }
// }
// }

View File

@@ -1,20 +1,15 @@
package route
import (
"net/http"
"github.com/IceWhaleTech/CasaOS-Common/utils/jwt"
"github.com/IceWhaleTech/CasaOS/middleware"
"github.com/IceWhaleTech/CasaOS/pkg/config"
jwt2 "github.com/IceWhaleTech/CasaOS/pkg/utils/jwt"
v1 "github.com/IceWhaleTech/CasaOS/route/v1"
"github.com/IceWhaleTech/CasaOS/web"
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
)
var OnlineDemo bool = false
func InitRouter() *gin.Engine {
r := gin.Default()
@@ -24,53 +19,56 @@ func InitRouter() *gin.Engine {
r.Use(gzip.Gzip(gzip.DefaultCompression))
gin.SetMode(config.ServerInfo.RunMode)
r.StaticFS("/ui", http.FS(web.Static))
r.GET("/", WebUIHome)
// r.StaticFS("/ui", http.FS(web.Static))
// r.GET("/", WebUIHome)
// r.StaticFS("/assets", http.Dir("./static/assets"))
// r.StaticFile("/favicon.ico", "./static/favicon.ico")
//r.GET("/", func(c *gin.Context) {
// c.Redirect(http.StatusMovedPermanently, "ui/")
//})
r.POST("/v1/users/register", v1.PostUserRegister)
r.POST("/v1/users/login", v1.PostUserLogin)
r.GET("/v1/users/name", v1.GetUserAllUsername) //all/name
r.POST("/v1/user/refresh", v1.PostUserRefreshToken)
// No short-term modifications
r.GET("/v1/users/image", v1.GetUserImage)
// r.POST("/v1/users/register", v1.PostUserRegister)
// r.POST("/v1/users/login", v1.PostUserLogin)
// r.GET("/v1/users/name", v1.GetUserAllUsername) //all/name
// r.POST("/v1/users/refresh", v1.PostUserRefreshToken)
// // No short-term modifications
// r.GET("/v1/users/image", v1.GetUserImage)
r.GET("/v1/users/status", v1.GetUserStatus) //init/check
// r.GET("/v1/users/status", v1.GetUserStatus) //init/check
//r.GET("/v1/guide/check", v1.GetGuideCheck) // /v1/sys/guide_check
r.GET("/v1/sys/debug", v1.GetSystemConfigDebug) // //debug
r.GET("/v1/sys/socket-port", v1.GetSystemSocketPort) //sys/socket_port
r.GET("/v1/sys/version/check", v1.GetSystemCheckVersion)
r.GET("/ping", func(ctx *gin.Context) {
ctx.String(200, "pong")
})
v1Group := r.Group("/v1")
v1Group.Use(jwt2.JWT())
v1Group.Use(jwt.ExceptLocalhost())
{
v1UsersGroup := v1Group.Group("/users")
v1UsersGroup.Use()
{
v1UsersGroup.GET("/current", v1.GetUserInfo)
v1UsersGroup.PUT("/current", v1.PutUserInfo)
v1UsersGroup.PUT("/current/password", v1.PutUserPassword)
// v1UsersGroup := v1Group.Group("/users")
// v1UsersGroup.Use()
// {
// v1UsersGroup.GET("/current", v1.GetUserInfo)
// v1UsersGroup.PUT("/current", v1.PutUserInfo)
// v1UsersGroup.PUT("/current/password", v1.PutUserPassword)
v1UsersGroup.GET("/current/custom/:key", v1.GetUserCustomConf)
v1UsersGroup.POST("/current/custom/:key", v1.PostUserCustomConf)
v1UsersGroup.DELETE("/current/custom/:key", v1.DeleteUserCustomConf)
// v1UsersGroup.GET("/current/custom/:key", v1.GetUserCustomConf)
// v1UsersGroup.POST("/current/custom/:key", v1.PostUserCustomConf)
// v1UsersGroup.DELETE("/current/custom/:key", v1.DeleteUserCustomConf)
v1UsersGroup.POST("/current/image/:key", v1.PostUserUploadImage)
v1UsersGroup.PUT("/current/image/:key", v1.PutUserImage)
//v1UserGroup.POST("/file/image/:key", v1.PostUserFileImage)
v1UsersGroup.DELETE("/current/image", v1.DeleteUserImage)
// v1UsersGroup.POST("/current/image/:key", v1.PostUserUploadImage)
// v1UsersGroup.PUT("/current/image/:key", v1.PutUserImage)
// //v1UserGroup.POST("/file/image/:key", v1.PostUserFileImage)
// v1UsersGroup.DELETE("/current/image", v1.DeleteUserImage)
//v1UserGroup.PUT("/avatar", v1.PutUserAvatar)
//v1UserGroup.GET("/avatar", v1.GetUserAvatar)
v1UsersGroup.DELETE("/:id", v1.DeleteUser)
v1UsersGroup.GET("/:username", v1.GetUserInfoByUsername)
v1UsersGroup.DELETE("", v1.DeleteUserAll)
}
// //v1UserGroup.PUT("/avatar", v1.PutUserAvatar)
// //v1UserGroup.GET("/avatar", v1.GetUserAvatar)
// v1UsersGroup.DELETE("/:id", v1.DeleteUser)
// v1UsersGroup.GET("/:username", v1.GetUserInfoByUsername)
// v1UsersGroup.DELETE("", v1.DeleteUserAll)
// }
v1AppsGroup := v1Group.Group("/apps")
v1AppsGroup.Use()
@@ -81,6 +79,7 @@ func InitRouter() *gin.Engine {
v1ContainerGroup := v1Group.Group("/container")
v1ContainerGroup.Use()
{
v1ContainerGroup.GET("", v1.MyAppList) ///my/list
v1ContainerGroup.GET("/usage", v1.AppUsageList)
v1ContainerGroup.GET("/:id", v1.ContainerUpdateInfo) ///update/:id/info
@@ -101,6 +100,8 @@ func InitRouter() *gin.Engine {
v1ContainerGroup.PUT("/:id/latest", v1.PutAppUpdate)
//Not used
v1ContainerGroup.POST("/share", v1.ShareAppFile)
v1ContainerGroup.GET("/info", v1.GetcontainerInfo)
v1ContainerGroup.PUT("/info", v1.PutcontainerInfo)
}
v1AppCategoriesGroup := v1Group.Group("/app-categories")
@@ -113,6 +114,7 @@ func InitRouter() *gin.Engine {
v1SysGroup.Use()
{
v1SysGroup.GET("/version", v1.GetSystemCheckVersion) //version/check
v1SysGroup.POST("/update", v1.SystemUpdate)
v1SysGroup.GET("/hardware", v1.GetSystemHardwareInfo) //hardware/info
@@ -139,8 +141,9 @@ func InitRouter() *gin.Engine {
v1SysGroup.GET("/server-info", nil)
v1SysGroup.PUT("/server-info", nil)
v1SysGroup.GET("/apps-state", v1.GetSystemAppsStatus)
v1SysGroup.GET("/port", v1.GetCasaOSPort)
v1SysGroup.PUT("/port", v1.PutCasaOSPort)
//v1SysGroup.GET("/port", v1.GetCasaOSPort)
//v1SysGroup.PUT("/port", v1.PutCasaOSPort)
v1SysGroup.GET("/proxy", v1.GetSystemProxy)
}
v1PortGroup := v1Group.Group("/port")
v1PortGroup.Use()
@@ -185,37 +188,31 @@ func InitRouter() *gin.Engine {
{
v1ImageGroup.GET("", v1.GetFileImage)
}
v1DisksGroup := v1Group.Group("/disks")
v1DisksGroup.Use()
v1SambaGroup := v1Group.Group("/samba")
v1SambaGroup.Use()
{
//v1DiskGroup.GET("/check", v1.GetDiskCheck) //delete
//v1DisksGroup.GET("", v1.GetDiskInfo)
//v1DisksGroup.POST("", v1.PostMountDisk)
v1DisksGroup.GET("", v1.GetDiskList)
// //format storage
// v1DiskGroup.POST("/format", v1.PostDiskFormat)
// //mount SATA disk
// v1DiskGroup.POST("/mount", v1.PostMountDisk)
// //umount sata disk
// v1DiskGroup.POST("/umount", v1.PostDiskUmount)
//v1DiskGroup.GET("/type", v1.FormatDiskType)//delete
v1DisksGroup.DELETE("/part", v1.RemovePartition) //disk/delpart
v1ConnectionsGroup := v1SambaGroup.Group("/connections")
v1ConnectionsGroup.Use()
{
v1ConnectionsGroup.GET("", v1.GetSambaConnectionsList)
v1ConnectionsGroup.POST("", v1.PostSambaConnectionsCreate)
v1ConnectionsGroup.DELETE("/:id", v1.DeleteSambaConnections)
}
v1SharesGroup := v1SambaGroup.Group("/shares")
v1SharesGroup.Use()
{
v1SharesGroup.GET("", v1.GetSambaSharesList)
v1SharesGroup.POST("", v1.PostSambaSharesCreate)
v1SharesGroup.DELETE("/:id", v1.DeleteSambaShares)
v1SharesGroup.GET("/status", v1.GetSambaStatus)
}
}
v1StorageGroup := v1Group.Group("/storage")
v1StorageGroup.Use()
v1NotifyGroup := v1Group.Group("/notify")
v1NotifyGroup.Use()
{
v1StorageGroup.POST("", v1.PostDiskAddPartition)
v1StorageGroup.PUT("", v1.PostDiskFormat)
v1StorageGroup.DELETE("", v1.PostDiskUmount)
v1NotifyGroup.POST("/:path", v1.PostNotifyMessage)
//merge to system
v1NotifyGroup.POST("/system_status", v1.PostSystemStatusNotify)
}
}
return r

View File

@@ -1,25 +0,0 @@
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-06-23 17:27:43
* @LastEditors: LinkLeong
* @LastEditTime: 2022-06-23 17:27:48
* @FilePath: /CasaOS/route/ui.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package route
import (
"html/template"
"github.com/IceWhaleTech/CasaOS/web"
"github.com/gin-gonic/gin"
)
func WebUIHome(c *gin.Context) {
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
index, _ := template.ParseFS(web.Static, "index.html")
index.Execute(c.Writer, nil)
return
}

View File

@@ -2,10 +2,12 @@ package v1
import (
"encoding/json"
"fmt"
"io/ioutil"
"strconv"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils/command"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
@@ -137,6 +139,7 @@ func MyAppList(c *gin.Context) {
func AppUsageList(c *gin.Context) {
list := service.MyService.App().GetHardwareUsage()
c.JSON(common_err.SUCCESS, &model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: list})
//c.JSON(common_err.SUCCESS, &model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: nil})
}
// @Summary 应用详情
@@ -219,7 +222,7 @@ func AppInfo(c *gin.Context) {
// sort.EnvSort(envOrder).Sort(info.Envs)
// sort.VolSort(volOrder).Sort(info.Volumes.([]model.PathMap))
// sort.DevSort(devOrder).Sort(info.Devices)
info.Image += ":" + info.ImageVersion
info.MaxMemory = (service.MyService.System().GetMemInfo()["total"]).(uint64) >> 20
c.JSON(common_err.SUCCESS, &model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: info})
@@ -261,3 +264,54 @@ func ShareAppFile(c *gin.Context) {
content := service.MyService.Casa().ShareAppFile(str)
c.JSON(common_err.SUCCESS, json.RawMessage(content))
}
func GetcontainerInfo(c *gin.Context) {
// info, err := service.MyService.Docker().GetDockerInfo()
// if err != nil {
// c.JSON(common_err.SERVICE_ERROR, &model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
// return
// }
daemon := model.DeckerDaemonModel{}
data := make(map[string]interface{}, 1)
data["docker_root_dir"] = ""
if file.Exists("/etc/docker/daemon.json") {
byteResult := file.ReadFullFile("/etc/docker/daemon.json")
err := json.Unmarshal(byteResult, &daemon)
if err != nil {
c.JSON(common_err.CLIENT_ERROR, &model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.INVALID_PARAMS), Data: err.Error()})
return
}
data["docker_root_dir"] = daemon.Graph
}
c.JSON(common_err.SUCCESS, &model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
}
func PutcontainerInfo(c *gin.Context) {
js := make(map[string]interface{})
err := c.BindJSON(&js)
if err != nil {
c.JSON(common_err.CLIENT_ERROR, &model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.INVALID_PARAMS), Data: err.Error()})
return
}
dockerRootDir := js["docker_root_dir"].(string)
daemon := model.DeckerDaemonModel{}
if file.Exists("/etc/docker/daemon.json") {
byteResult := file.ReadFullFile("/etc/docker/daemon.json")
err := json.Unmarshal(byteResult, &daemon)
if err != nil {
c.JSON(common_err.CLIENT_ERROR, &model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.INVALID_PARAMS), Data: err.Error()})
return
}
}
if !file.Exists(dockerRootDir) {
c.JSON(common_err.CLIENT_ERROR, &model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.DIR_NOT_EXISTS), Data: common_err.GetMsg(common_err.DIR_NOT_EXISTS)})
return
}
daemon.Graph = dockerRootDir
byteMode, _ := json.Marshal(daemon)
file.WriteToPath(byteMode, "/etc/docker", "daemon.json")
fmt.Println(command.ExecResultStr("systemctl daemon-reload"))
fmt.Println(command.ExecResultStr("systemctl restart docker"))
c.JSON(common_err.SUCCESS, &model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: js})
}

View File

@@ -1,498 +0,0 @@
package v1
import (
"fmt"
"net/http"
"reflect"
"strconv"
"strings"
"time"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/model/notify"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/pkg/utils/encryption"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/service"
model2 "github.com/IceWhaleTech/CasaOS/service/model"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/v3/disk"
)
var diskMap = make(map[string]string)
// @Summary disk list
// @Produce application/json
// @Accept application/json
// @Tags disk
// @Security ApiKeyAuth
// @Success 200 {string} string "ok"
// @Router /disk/list [get]
func GetDiskList(c *gin.Context) {
path := c.Query("path")
if len(path) > 0 {
m := service.MyService.Disk().GetDiskInfo(path)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: m})
return
}
t := c.DefaultQuery("type", "")
list := service.MyService.Disk().LSBLK(false)
if t == "usb" {
data := []model.DriveUSB{}
for _, v := range list {
if v.Tran == "usb" {
temp := model.DriveUSB{}
temp.Model = v.Model
temp.Name = v.Name
temp.Size = v.Size
mountTemp := true
if len(v.Children) == 0 {
mountTemp = false
}
for _, child := range v.Children {
if len(child.MountPoint) > 0 {
avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
temp.Avail += avail
} else {
mountTemp = false
}
}
temp.Mount = mountTemp
data = append(data, temp)
}
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
return
}
dbList := service.MyService.Disk().GetSerialAll()
part := make(map[string]int64, len(dbList))
for _, v := range dbList {
part[v.MountPoint] = v.CreatedAt
}
findSystem := 0
disks := []model.Drive{}
storage := []model.Storage{}
avail := []model.Drive{}
for i := 0; i < len(list); i++ {
disk := model.Drive{}
if list[i].Rota {
disk.DiskType = "HDD"
} else {
disk.DiskType = "SSD"
}
disk.Serial = list[i].Serial
disk.Name = list[i].Name
disk.Size = list[i].Size
disk.Path = list[i].Path
disk.Model = list[i].Model
if len(list[i].Children) > 0 && findSystem == 0 {
for j := 0; j < len(list[i].Children); j++ {
if len(list[i].Children[j].Children) > 0 {
for _, v := range list[i].Children[j].Children {
if v.MountPoint == "/" {
stor := model.Storage{}
stor.Name = "System"
stor.MountPoint = v.MountPoint
stor.Size = v.FSSize
stor.Avail = v.FSAvail
stor.Path = v.Path
stor.Type = v.FsType
stor.DriveName = "System"
disk.Model = "System"
if strings.Contains(v.SubSystems, "mmc") {
disk.DiskType = "MMC"
} else if strings.Contains(v.SubSystems, "usb") {
disk.DiskType = "USB"
}
disk.Health = "true"
disks = append(disks, disk)
storage = append(storage, stor)
findSystem = 1
break
}
}
} else {
if list[i].Children[j].MountPoint == "/" {
stor := model.Storage{}
stor.Name = "System"
stor.MountPoint = list[i].Children[j].MountPoint
stor.Size = list[i].Children[j].FSSize
stor.Avail = list[i].Children[j].FSAvail
stor.Path = list[i].Children[j].Path
stor.Type = list[i].Children[j].FsType
stor.DriveName = "System"
disk.Model = "System"
if strings.Contains(list[i].Children[j].SubSystems, "mmc") {
disk.DiskType = "MMC"
} else if strings.Contains(list[i].Children[j].SubSystems, "usb") {
disk.DiskType = "USB"
}
disk.Health = "true"
disks = append(disks, disk)
storage = append(storage, stor)
findSystem = 1
break
}
}
}
}
if findSystem == 1 {
findSystem += 1
continue
}
if list[i].Tran == "sata" || list[i].Tran == "nvme" || list[i].Tran == "spi" || list[i].Tran == "sas" || strings.Contains(list[i].SubSystems, "virtio") || (list[i].Tran == "ata" && list[i].Type == "disk") {
temp := service.MyService.Disk().SmartCTL(list[i].Path)
if reflect.DeepEqual(temp, model.SmartctlA{}) {
temp.SmartStatus.Passed = true
}
if len(list[i].Children) == 1 && len(list[i].Children[0].MountPoint) > 0 {
stor := model.Storage{}
stor.MountPoint = list[i].Children[0].MountPoint
stor.Size = list[i].Children[0].FSSize
stor.Avail = list[i].Children[0].FSAvail
stor.Path = list[i].Children[0].Path
stor.Type = list[i].Children[0].FsType
stor.DriveName = list[i].Name
pathArr := strings.Split(list[i].Children[0].MountPoint, "/")
if len(pathArr) == 3 {
stor.Name = pathArr[2]
}
if t, ok := part[list[i].Children[0].MountPoint]; ok {
stor.CreatedAt = t
}
storage = append(storage, stor)
} else {
//todo 长度有问题
if len(list[i].Children) == 1 && list[i].Children[0].FsType == "ext4" {
disk.NeedFormat = false
avail = append(avail, disk)
} else {
disk.NeedFormat = true
avail = append(avail, disk)
}
}
disk.Temperature = temp.Temperature.Current
disk.Health = strconv.FormatBool(temp.SmartStatus.Passed)
disks = append(disks, disk)
}
}
data := make(map[string]interface{}, 3)
data["drive"] = disks
data["storage"] = storage
data["avail"] = avail
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
}
// @Summary get disk list
// @Produce application/json
// @Accept application/json
// @Tags disk
// @Security ApiKeyAuth
// @Success 200 {string} string "ok"
// @Router /disk/lists [get]
func GetPlugInDisks(c *gin.Context) {
list := service.MyService.Disk().LSBLK(true)
var result []*disk.UsageStat
for _, item := range list {
result = append(result, service.MyService.Disk().GetDiskInfoByPath(item.Path))
}
c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: result})
}
// @Summary disk detail
// @Produce application/json
// @Accept application/json
// @Tags disk
// @Security ApiKeyAuth
// @Param path query string true "for example /dev/sda"
// @Success 200 {string} string "ok"
// @Router /disk/info [get]
func GetDiskInfo(c *gin.Context) {
path := c.Query("path")
if len(path) == 0 {
c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
}
m := service.MyService.Disk().GetDiskInfo(path)
c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: m})
}
// @Summary 获取支持的格式
// @Produce application/json
// @Accept application/json
// @Tags disk
// @Security ApiKeyAuth
// @Success 200 {string} string "ok"
// @Router /disk/type [get]
func FormatDiskType(c *gin.Context) {
var strArr = [4]string{"fat32", "ntfs", "ext4", "exfat"}
c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: strArr})
}
// @Summary 删除分区
// @Produce application/json
// @Accept multipart/form-data
// @Tags disk
// @Security ApiKeyAuth
// @Param path formData string true "磁盘路径 例如/dev/sda1"
// @Success 200 {string} string "ok"
// @Router /disk/delpart [delete]
func RemovePartition(c *gin.Context) {
js := make(map[string]string)
c.ShouldBind(&js)
path := js["path"]
if len(path) == 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
}
var p = path[:len(path)-1]
var n = path[len(path)-1:]
service.MyService.Disk().DelPartition(p, n)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
// @Summary add storage
// @Produce application/json
// @Accept multipart/form-data
// @Tags disk
// @Security ApiKeyAuth
// @Param path formData string true "disk path e.g. /dev/sda"
// @Param serial formData string true "serial"
// @Param name formData string true "name"
// @Param format formData bool true "need format(true)"
// @Success 200 {string} string "ok"
// @Router /disk/storage [post]
func PostDiskAddPartition(c *gin.Context) {
js := make(map[string]interface{})
c.ShouldBind(&js)
path := js["path"].(string)
name := js["name"].(string)
format := js["format"].(bool)
if len(name) == 0 || len(path) == 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
if _, ok := diskMap[path]; ok {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DISK_BUSYING, Message: common_err.GetMsg(common_err.DISK_BUSYING)})
return
}
if !file.CheckNotExist("/DATA/" + name) {
// /mnt/name exist
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.NAME_NOT_AVAILABLE, Message: common_err.GetMsg(common_err.NAME_NOT_AVAILABLE)})
return
}
diskMap[path] = "busying"
currentDisk := service.MyService.Disk().GetDiskInfo(path)
if !format {
if len(currentDisk.Children) != 1 || !(len(currentDisk.Children) > 0 && currentDisk.Children[0].FsType == "ext4") {
delete(diskMap, path)
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DISK_NEEDS_FORMAT, Message: common_err.GetMsg(common_err.DISK_NEEDS_FORMAT)})
return
}
} else {
// format := service.MyService.Disk().FormatDisk(path+"1", "ext4")
// if len(format) == 0 {
// delete(diskMap, path)
// c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.FORMAT_ERROR, Message: common_err.GetMsg(common_err.FORMAT_ERROR)})
// return
// }
service.MyService.Disk().AddPartition(path)
}
formatBool := true
for formatBool {
currentDisk = service.MyService.Disk().GetDiskInfo(path)
fmt.Println(currentDisk.Children)
if len(currentDisk.Children) > 0 {
formatBool = false
break
}
time.Sleep(time.Second)
}
currentDisk = service.MyService.Disk().GetDiskInfo(path)
if len(currentDisk.Children) != 1 {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DISK_NEEDS_FORMAT, Message: common_err.GetMsg(common_err.DISK_NEEDS_FORMAT)})
return
}
mountPath := "/DATA/" + name
m := model2.SerialDisk{}
m.MountPoint = mountPath
m.Path = currentDisk.Children[0].Path
m.UUID = currentDisk.Children[0].UUID
m.State = 0
m.CreatedAt = time.Now().Unix()
service.MyService.Disk().SaveMountPoint(m)
//mount dir
service.MyService.Disk().MountDisk(currentDisk.Children[0].Path, mountPath)
service.MyService.Disk().RemoveLSBLKCache()
delete(diskMap, path)
//send notify to client
msg := notify.StorageMessage{}
msg.Action = "ADDED"
msg.Path = currentDisk.Children[0].Path
msg.Volume = mountPath
msg.Size = currentDisk.Children[0].Size
msg.Type = currentDisk.Children[0].Tran
service.MyService.Notify().SendStorageBySocket(msg)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
// @Param pwd formData string true "user password"
// @Param volume formData string true "mount point"
// @Success 200 {string} string "ok"
// @Router /disk/format [post]
func PostDiskFormat(c *gin.Context) {
id := c.GetHeader("user_id")
js := make(map[string]string)
c.ShouldBind(&js)
path := js["path"]
t := "ext4"
pwd := js["password"]
volume := js["volume"]
user := service.MyService.User().GetUserAllInfoById(id)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
if encryption.GetMD5ByStr(pwd) != user.Password {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.PWD_INVALID, Message: common_err.GetMsg(common_err.PWD_INVALID)})
return
}
if len(path) == 0 || len(t) == 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
if _, ok := diskMap[path]; ok {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DISK_BUSYING, Message: common_err.GetMsg(common_err.DISK_BUSYING)})
return
}
diskMap[path] = "busying"
service.MyService.Disk().UmountPointAndRemoveDir(path)
format := service.MyService.Disk().FormatDisk(path, t)
if len(format) == 0 {
delete(diskMap, path)
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.FORMAT_ERROR, Message: common_err.GetMsg(common_err.FORMAT_ERROR)})
return
}
service.MyService.Disk().MountDisk(path, volume)
service.MyService.Disk().RemoveLSBLKCache()
delete(diskMap, path)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
// @Summary remove mount point
// @Produce application/json
// @Accept multipart/form-data
// @Tags disk
// @Security ApiKeyAuth
// @Param path formData string true "e.g. /dev/sda1"
// @Param mount_point formData string true "e.g. /mnt/volume1"
// @Param pwd formData string true "user password"
// @Success 200 {string} string "ok"
// @Router /disk/umount [post]
func PostDiskUmount(c *gin.Context) {
id := c.GetHeader("user_id")
js := make(map[string]string)
c.ShouldBind(&js)
path := js["path"]
mountPoint := js["volume"]
pwd := js["password"]
if len(path) == 0 || len(mountPoint) == 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
user := service.MyService.User().GetUserAllInfoById(id)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
if encryption.GetMD5ByStr(pwd) != user.Password {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.PWD_INVALID, Message: common_err.GetMsg(common_err.PWD_INVALID)})
return
}
if _, ok := diskMap[path]; ok {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DISK_BUSYING, Message: common_err.GetMsg(common_err.DISK_BUSYING)})
return
}
service.MyService.Disk().UmountPointAndRemoveDir(path)
//delete data
service.MyService.Disk().DeleteMountPoint(path, mountPoint)
service.MyService.Disk().RemoveLSBLKCache()
//send notify to client
msg := notify.StorageMessage{}
msg.Action = "REMOVED"
msg.Path = path
msg.Volume = mountPoint
msg.Size = 0
msg.Type = ""
service.MyService.Notify().SendStorageBySocket(msg)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
// @Summary confirm delete disk
// @Produce application/json
// @Accept application/json
// @Tags disk
// @Security ApiKeyAuth
// @Param id path string true "id"
// @Success 200 {string} string "ok"
// @Router /disk/remove/{id} [delete]
func DeleteDisk(c *gin.Context) {
id := c.Param("id")
service.MyService.Disk().DeleteMount(id)
c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
// @Summary check mount point
// @Produce application/json
// @Accept application/json
// @Tags disk
// @Security ApiKeyAuth
// @Success 200 {string} string "ok"
// @Router /disk/init [get]
func GetDiskCheck(c *gin.Context) {
dbList := service.MyService.Disk().GetSerialAll()
list := service.MyService.Disk().LSBLK(true)
mapList := make(map[string]string)
for _, v := range list {
mapList[v.Serial] = "1"
}
for _, v := range dbList {
if _, ok := mapList[v.UUID]; !ok {
//disk undefind
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: "disk undefind"})
return
}
}
c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}

View File

@@ -38,7 +38,7 @@ var upgrader = websocket.Upgrader{
HandshakeTimeout: time.Duration(time.Second * 5),
}
//打开docker的terminal
// 打开docker的terminal
func DockerTerminal(c *gin.Context) {
col := c.DefaultQuery("cols", "100")
row := c.DefaultQuery("rows", "30")
@@ -209,7 +209,7 @@ func InstallApp(c *gin.Context) {
dockerImage = m.Image
dockerImageVersion = "latest"
}
m.Image = dockerImage + ":" + dockerImageVersion
for _, u := range m.Ports {
if u.Protocol == "udp" {
@@ -334,11 +334,11 @@ func InstallApp(c *gin.Context) {
return
}
for !service.MyService.Docker().IsExistImage(dockerImage + ":" + dockerImageVersion) {
for !service.MyService.Docker().IsExistImage(m.Image) {
time.Sleep(time.Second)
}
_, err = service.MyService.Docker().DockerContainerCreate(dockerImage+":"+dockerImageVersion, m)
_, err = service.MyService.Docker().DockerContainerCreate(m, "")
if err != nil {
//service.MyService.Redis().Set(id, "{\"id\"\""+id+"\",\"state\":false,\"message\":\""+err.Error()+"\",\"speed\":80}", 100)
notify := notify.Application{}
@@ -761,7 +761,7 @@ func ChangAppState(c *gin.Context) {
func ContainerLog(c *gin.Context) {
appId := c.Param("id")
log, _ := service.MyService.Docker().DockerContainerLog(appId)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: log})
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: string(log)})
}
// @Summary 获取容器状态
@@ -829,7 +829,6 @@ func UpdateSetting(c *gin.Context) {
// c.JSON(http.StatusOK, model.Result{Success: common_err.ERROR_APP_NAME_EXIST, Message: common_err.GetMsg(common_err.ERROR_APP_NAME_EXIST)})
// return
// }
service.MyService.Docker().DockerContainerStop(id)
portMap, _ := strconv.Atoi(m.PortMap)
if !port2.IsPortAvailable(portMap, "tcp") {
@@ -874,11 +873,11 @@ func UpdateSetting(c *gin.Context) {
service.MyService.Docker().DockerContainerUpdateName(id, id)
//service.MyService.Docker().DockerContainerRemove(id, true)
containerId, err := service.MyService.Docker().DockerContainerCreate(m.Image, m)
containerId, err := service.MyService.Docker().DockerContainerCreate(m, id)
if err != nil {
service.MyService.Docker().DockerContainerUpdateName(m.ContainerName, id)
service.MyService.Docker().DockerContainerStart(id)
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR)})
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
// echo -e "hellow\nworld" >>
@@ -1121,13 +1120,13 @@ func ContainerUpdateInfo(c *gin.Context) {
showENV := info.Config.Labels["show_env"]
showENVList := strings.Split(showENV, ",")
showENVMap := make(map[string]string)
if len(showENVList) > 1 {
if len(showENVList) > 0 && showENVList[0] != "" {
for _, name := range showENVList {
showENVMap[name] = "1"
}
}
for _, v := range info.Config.Env {
if len(showENVList) > 1 {
if len(showENVList) > 0 {
if _, ok := showENVMap[strings.Split(v, "=")[0]]; ok {
temp := model.Env{
Name: strings.Split(v, "=")[0],

View File

@@ -1,6 +1,7 @@
package v1
import (
"fmt"
"io"
"io/ioutil"
"log"
@@ -15,7 +16,6 @@ import (
"sync"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/service"
@@ -218,6 +218,11 @@ func GetDownloadSingleFile(c *gin.Context) {
func DirPath(c *gin.Context) {
path := c.DefaultQuery("path", "")
info := service.MyService.System().GetDirPath(path)
shares := service.MyService.Shares().GetSharesList()
sharesMap := make(map[string]string)
for _, v := range shares {
sharesMap[v.Path] = fmt.Sprint(v.ID)
}
if path == "/DATA/AppData" {
list := service.MyService.Docker().DockerContainerList()
apps := make(map[string]string, len(list))
@@ -230,34 +235,19 @@ func DirPath(c *gin.Context) {
info[i].Type = "application"
}
}
} else if path == "/DATA" {
disk := make(map[string]string)
lsblk := service.MyService.Disk().LSBLK(true)
for _, v := range lsblk {
if len(v.Children) > 0 {
t := v.Tran
for _, c := range v.Children {
if len(c.Children) > 0 {
for _, gc := range c.Children {
if len(gc.MountPoint) > 0 {
disk[gc.MountPoint] = t
}
}
}
if len(c.MountPoint) > 0 {
disk[c.MountPoint] = t
}
}
}
}
for i := 0; i < len(info); i++ {
if v, ok := disk[info[i].Path]; ok {
info[i].Type = v
}
}
}
for i := 0; i < len(info); i++ {
if v, ok := sharesMap[info[i].Path]; ok {
ex := make(map[string]interface{})
shareEx := make(map[string]string)
shareEx["shared"] = "true"
shareEx["id"] = v
ex["share"] = shareEx
info[i].Extensions = ex
}
}
//Hide the files or folders in operation
fileQueue := make(map[string]string)
if len(service.OpStrArr) > 0 {
@@ -276,6 +266,9 @@ func DirPath(c *gin.Context) {
pathList := []model.Path{}
for i := 0; i < len(info); i++ {
if info[i].Name == ".temp" && info[i].IsDir {
continue
}
if _, ok := fileQueue[info[i].Path]; !ok {
pathList = append(pathList, info[i])
}
@@ -376,7 +369,7 @@ func GetFileUpload(c *gin.Context) {
path := c.Query("path")
dirPath := ""
hash := file.GetHashByContent([]byte(fileName))
tempDir := config.AppInfo.TempPath + "/" + hash + strconv.Itoa(totalChunks) + "/"
tempDir := filepath.Join(path, ".temp", hash+strconv.Itoa(totalChunks)) + "/"
if fileName != relative {
dirPath = strings.TrimSuffix(relative, fileName)
tempDir += dirPath
@@ -415,7 +408,7 @@ func PostFileUpload(c *gin.Context) {
c.JSON(common_err.INVALID_PARAMS, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
tempDir := config.AppInfo.TempPath + "/" + hash + strconv.Itoa(totalChunks) + "/"
tempDir := filepath.Join(path, ".temp", hash+strconv.Itoa(totalChunks)) + "/"
if fileName != relative {
dirPath = strings.TrimSuffix(relative, fileName)

23
route/v1/notiry.go Normal file
View File

@@ -0,0 +1,23 @@
package v1
import (
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/gin-gonic/gin"
)
func PostNotifyMessage(c *gin.Context) {
path := c.Param("path")
message := make(map[string]interface{})
c.ShouldBind(&message)
service.MyService.Notify().SendNotify(path, message)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
func PostSystemStatusNotify(c *gin.Context) {
message := make(map[string]interface{})
c.ShouldBind(&message)
service.MyService.Notify().SettingSystemTempData(message)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}

181
route/v1/samba.go Normal file
View File

@@ -0,0 +1,181 @@
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-07-26 11:08:48
* @LastEditors: LinkLeong
* @LastEditTime: 2022-08-17 18:25:42
* @FilePath: /CasaOS/route/v1/samba.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package v1
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/samba"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/service"
model2 "github.com/IceWhaleTech/CasaOS/service/model"
"github.com/gin-gonic/gin"
)
// service
func GetSambaStatus(c *gin.Context) {
status := service.MyService.System().IsServiceRunning("smbd")
if !status {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_NOT_RUNNING, Message: common_err.GetMsg(common_err.SERVICE_NOT_RUNNING)})
return
}
needInit := true
if file.Exists("/etc/samba/smb.conf") {
str := file.ReadLine(1, "/etc/samba/smb.conf")
if strings.Contains(str, "# Copyright (c) 2021-2022 CasaOS Inc. All rights reserved.") {
needInit = false
}
}
data := make(map[string]string, 1)
data["need_init"] = fmt.Sprintf("%v", needInit)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
}
func GetSambaSharesList(c *gin.Context) {
shares := service.MyService.Shares().GetSharesList()
shareList := []model.Shares{}
for _, v := range shares {
shareList = append(shareList, model.Shares{
Anonymous: v.Anonymous,
Path: v.Path,
ID: v.ID,
})
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: shareList})
}
func PostSambaSharesCreate(c *gin.Context) {
shares := []model.Shares{}
c.ShouldBindJSON(&shares)
for _, v := range shares {
if v.Path == "" {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INSUFFICIENT_PERMISSIONS, Message: common_err.GetMsg(common_err.INSUFFICIENT_PERMISSIONS)})
return
}
if !file.Exists(v.Path) {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DIR_NOT_EXISTS, Message: common_err.GetMsg(common_err.DIR_NOT_EXISTS)})
return
}
if len(service.MyService.Shares().GetSharesByPath(v.Path)) > 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.SHARE_ALREADY_EXISTS, Message: common_err.GetMsg(common_err.SHARE_ALREADY_EXISTS)})
return
}
if len(service.MyService.Shares().GetSharesByPath(filepath.Base(v.Path))) > 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.SHARE_NAME_ALREADY_EXISTS, Message: common_err.GetMsg(common_err.SHARE_NAME_ALREADY_EXISTS)})
return
}
}
for _, v := range shares {
shareDBModel := model2.SharesDBModel{}
shareDBModel.Anonymous = true
shareDBModel.Path = v.Path
shareDBModel.Name = filepath.Base(v.Path)
os.Chmod(v.Path, 0777)
service.MyService.Shares().CreateShare(shareDBModel)
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: shares})
}
func DeleteSambaShares(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INSUFFICIENT_PERMISSIONS, Message: common_err.GetMsg(common_err.INSUFFICIENT_PERMISSIONS)})
return
}
service.MyService.Shares().DeleteShare(id)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: id})
}
//client
func GetSambaConnectionsList(c *gin.Context) {
connections := service.MyService.Connections().GetConnectionsList()
connectionList := []model.Connections{}
for _, v := range connections {
connectionList = append(connectionList, model.Connections{
ID: v.ID,
Username: v.Username,
Port: v.Port,
Host: v.Host,
MountPoint: v.MountPoint,
})
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: connectionList})
}
func PostSambaConnectionsCreate(c *gin.Context) {
connection := model.Connections{}
c.ShouldBindJSON(&connection)
if connection.Port == "" {
connection.Port = "445"
}
if connection.Username == "" || connection.Host == "" {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
connection.Host = strings.Split(connection.Host, "/")[0]
// check is exists
connections := service.MyService.Connections().GetConnectionByHost(connection.Host)
if len(connections) > 0 {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.Record_ALREADY_EXIST, Message: common_err.GetMsg(common_err.Record_ALREADY_EXIST), Data: common_err.GetMsg(common_err.Record_ALREADY_EXIST)})
return
}
// check connect is ok
directories, err := samba.GetSambaSharesList(connection.Host, connection.Port, connection.Username, connection.Password)
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
connectionDBModel := model2.ConnectionsDBModel{}
connectionDBModel.Username = connection.Username
connectionDBModel.Password = connection.Password
connectionDBModel.Host = connection.Host
connectionDBModel.Port = connection.Port
connectionDBModel.Directories = strings.Join(directories, ",")
baseHostPath := "/mnt/" + connection.Host
connectionDBModel.MountPoint = baseHostPath
connection.MountPoint = baseHostPath
file.IsNotExistMkDir(baseHostPath)
for _, v := range directories {
mountPoint := baseHostPath + "/" + v
file.IsNotExistMkDir(mountPoint)
service.MyService.Connections().MountSmaba(connectionDBModel.Username, connectionDBModel.Host, v, connectionDBModel.Port, mountPoint, connectionDBModel.Password)
}
service.MyService.Connections().CreateConnection(&connectionDBModel)
connection.ID = connectionDBModel.ID
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: connection})
}
func DeleteSambaConnections(c *gin.Context) {
id := c.Param("id")
connection := service.MyService.Connections().GetConnectionByID(id)
if connection.Username == "" {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.Record_NOT_EXIST, Message: common_err.GetMsg(common_err.Record_NOT_EXIST)})
return
}
mountPointList := service.MyService.System().GetDirPath(connection.MountPoint)
for _, v := range mountPointList {
service.MyService.Connections().UnmountSmaba(v.Path)
}
os.RemoveAll(connection.MountPoint)
service.MyService.Connections().DeleteConnection(id)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: id})
}

73
route/v1/samba_test.go Normal file
View File

@@ -0,0 +1,73 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-08-02 15:10:56
* @LastEditors: LinkLeong
* @LastEditTime: 2022-08-02 16:58:42
* @FilePath: /CasaOS/route/v1/samba_test.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package v1
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
)
func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder {
req, _ := http.NewRequest(method, path, nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
// func TestHelloWorld(t *testing.T) {
// // Build our expected body
// body := gin.H{
// "hello": "world",
// }
// // Grab our router
// router := "SetupRouter()"
// // Perform a GET request with that handler.
// w := performRequest(router, "GET", "/")
// // Assert we encoded correctly,
// // the request gives a 200
// assert.Equal(t, http.StatusOK, w.Code)
// // Convert the JSON response to a map
// var response map[string]string
// err := json.Unmarshal([]byte(w.Body.String()), &response)
// // Grab the value & whether or not it exists
// value, exists := response["hello"]
// // Make some assertions on the correctness of the response.
// assert.Nil(t, err)
// assert.True(t, exists)
// assert.Equal(t, body["hello"], value)
// }
func TestGetSambaSharesList(t *testing.T) {
gin.SetMode(gin.TestMode)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
executeWithContext := func() *httptest.ResponseRecorder {
response := httptest.NewRecorder()
con, ginEngine := gin.CreateTestContext(response)
requestUrl := "/v1/samba/shares"
httpRequest, _ := http.NewRequest("GET", requestUrl, nil)
GetSambaSharesList(con)
ginEngine.ServeHTTP(response, httpRequest)
return response
}
t.Run("Happy", func(t *testing.T) {
res := executeWithContext()
assert.Equal(t, http.StatusOK, res.Code)
})
}

View File

@@ -1,17 +0,0 @@
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-07-11 16:02:29
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-11 16:02:55
* @FilePath: /CasaOS/route/v1/storage.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package v1
import "github.com/gin-gonic/gin"
func GetStorageList(c *gin.Context) {
}

View File

@@ -1,10 +1,12 @@
package v1
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"reflect"
"strconv"
"strings"
"time"
@@ -13,14 +15,12 @@ import (
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/pkg/utils/loger"
port2 "github.com/IceWhaleTech/CasaOS/pkg/utils/port"
"github.com/IceWhaleTech/CasaOS/pkg/utils/version"
"github.com/IceWhaleTech/CasaOS/service"
model2 "github.com/IceWhaleTech/CasaOS/service/model"
"github.com/IceWhaleTech/CasaOS/types"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// @Summary check version
@@ -76,7 +76,7 @@ func GetCasaOSErrorLogs(c *gin.Context) {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: service.MyService.System().GetCasaOSLogs(line)})
}
//系统配置
// 系统配置
func GetSystemConfigDebug(c *gin.Context) {
array := service.MyService.System().GetSystemConfigDebug()
disk := service.MyService.System().GetDiskInfo()
@@ -174,7 +174,7 @@ func PostKillCasaOS(c *gin.Context) {
func PutSystemUSBAutoMount(c *gin.Context) {
js := make(map[string]string)
c.ShouldBind(&js)
status := js["status"]
status := js["state"]
if status == "on" {
service.MyService.System().UpdateUSBAutoMount("True")
service.MyService.System().ExecUSBAutoMountShell("True")
@@ -182,7 +182,6 @@ func PutSystemUSBAutoMount(c *gin.Context) {
service.MyService.System().UpdateUSBAutoMount("False")
service.MyService.System().ExecUSBAutoMountShell("False")
}
c.JSON(common_err.SUCCESS,
model.Result{
Success: common_err.SUCCESS,
@@ -202,7 +201,6 @@ func GetSystemUSBAutoMount(c *gin.Context) {
if config.ServerInfo.USBAutoMount == "False" {
state = "False"
}
c.JSON(common_err.SUCCESS,
model.Result{
Success: common_err.SUCCESS,
@@ -273,111 +271,14 @@ func GetSystemHardwareInfo(c *gin.Context) {
// @Router /sys/utilization [get]
func GetSystemUtilization(c *gin.Context) {
var data = make(map[string]interface{}, 6)
list := service.MyService.Disk().LSBLK(true)
summary := model.Summary{}
healthy := true
findSystem := 0
for i := 0; i < len(list); i++ {
if len(list[i].Children) > 0 && findSystem == 0 {
for j := 0; j < len(list[i].Children); j++ {
if len(list[i].Children[j].Children) > 0 {
for _, v := range list[i].Children[j].Children {
if v.MountPoint == "/" {
s, _ := strconv.ParseUint(v.FSSize, 10, 64)
a, _ := strconv.ParseUint(v.FSAvail, 10, 64)
u, _ := strconv.ParseUint(v.FSUsed, 10, 64)
loger.Info("disk info", zap.Any("/ total:", s))
loger.Info("disk path", zap.Any("path", v.Path))
summary.Size += s
summary.Avail += a
summary.Used += u
findSystem = 1
break
}
}
} else {
if list[i].Children[j].MountPoint == "/" {
s, _ := strconv.ParseUint(list[i].Children[j].FSSize, 10, 64)
a, _ := strconv.ParseUint(list[i].Children[j].FSAvail, 10, 64)
u, _ := strconv.ParseUint(list[i].Children[j].FSUsed, 10, 64)
loger.Info("disk info", zap.Any("/ total:", s))
loger.Info("disk path", zap.Any("path", list[i].Path))
summary.Size += s
summary.Avail += a
summary.Used += u
findSystem = 1
break
}
}
}
}
if findSystem == 1 {
findSystem += 1
continue
}
if list[i].Tran == "sata" || list[i].Tran == "nvme" || list[i].Tran == "spi" || list[i].Tran == "sas" || strings.Contains(list[i].SubSystems, "virtio") || (list[i].Tran == "ata" && list[i].Type == "disk") {
temp := service.MyService.Disk().SmartCTL(list[i].Path)
if reflect.DeepEqual(temp, model.SmartctlA{}) {
healthy = true
} else {
healthy = temp.SmartStatus.Passed
}
if len(list[i].Children) > 0 {
for _, v := range list[i].Children {
s, _ := strconv.ParseUint(v.FSSize, 10, 64)
a, _ := strconv.ParseUint(v.FSAvail, 10, 64)
u, _ := strconv.ParseUint(v.FSUsed, 10, 64)
loger.Info("disk info", zap.Any("/ total:", s))
loger.Info("disk path", zap.Any("path", list[i].Path))
summary.Size += s
summary.Avail += a
summary.Used += u
}
}
}
}
summary.Health = healthy
data["disk"] = summary
usbList := service.MyService.Disk().LSBLK(false)
usb := []model.DriveUSB{}
for _, v := range usbList {
if v.Tran == "usb" {
temp := model.DriveUSB{}
temp.Model = v.Model
temp.Name = v.Name
temp.Size = v.Size
mountTemp := true
if len(v.Children) == 0 {
mountTemp = false
}
for _, child := range v.Children {
if len(child.MountPoint) > 0 {
avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
temp.Avail += avail
used, _ := strconv.ParseUint(child.FSUsed, 10, 64)
temp.Used += used
} else {
mountTemp = false
}
}
temp.Mount = mountTemp
usb = append(usb, temp)
}
}
data["usb"] = usb
cpu := service.MyService.System().GetCpuPercent()
num := service.MyService.System().GetCpuCoreNum()
cpuData := make(map[string]interface{})
cpuData["percent"] = cpu
cpuData["num"] = num
cpuData["temperature"] = service.MyService.System().GetCPUTemperature()
cpuData["power"] = service.MyService.System().GetCPUPower()
data["cpu"] = cpuData
data["mem"] = service.MyService.System().GetMemInfo()
@@ -485,3 +386,21 @@ func GetSystemNetInfo(c *gin.Context) {
c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: newNet})
}
func GetSystemProxy(c *gin.Context) {
url := c.Query("url")
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
for k, v := range c.Request.Header {
c.Header(k, v[0])
}
rda, _ := ioutil.ReadAll(resp.Body)
// json.NewEncoder(c.Writer).Encode(json.RawMessage(string(rda)))
// 响应状态码
c.Writer.WriteHeader(resp.StatusCode)
// 复制转发的响应Body到响应Body
io.Copy(c.Writer, ioutil.NopCloser(bytes.NewBuffer(rda)))
}

View File

@@ -1,683 +0,0 @@
package v1
import (
json2 "encoding/json"
"io/ioutil"
"net/http"
url2 "net/url"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/model/system_model"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/pkg/utils/encryption"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/pkg/utils/jwt"
model2 "github.com/IceWhaleTech/CasaOS/service/model"
uuid "github.com/satori/go.uuid"
"github.com/tidwall/gjson"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/gin-gonic/gin"
)
// @Summary register user
// @Router /user/register/ [post]
func PostUserRegister(c *gin.Context) {
json := make(map[string]string)
c.ShouldBind(&json)
username := json["username"]
pwd := json["password"]
key := json["key"]
if _, ok := service.UserRegisterHash[key]; !ok {
c.JSON(common_err.CLIENT_ERROR,
model.Result{Success: common_err.KEY_NOT_EXIST, Message: common_err.GetMsg(common_err.KEY_NOT_EXIST)})
return
}
if len(username) == 0 || len(pwd) == 0 {
c.JSON(common_err.CLIENT_ERROR,
model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
if len(pwd) < 6 {
c.JSON(common_err.CLIENT_ERROR,
model.Result{Success: common_err.PWD_IS_TOO_SIMPLE, Message: common_err.GetMsg(common_err.PWD_IS_TOO_SIMPLE)})
return
}
oldUser := service.MyService.User().GetUserInfoByUserName(username)
if oldUser.Id > 0 {
c.JSON(common_err.CLIENT_ERROR,
model.Result{Success: common_err.USER_EXIST, Message: common_err.GetMsg(common_err.USER_EXIST)})
return
}
user := model2.UserDBModel{}
user.Username = username
user.Password = encryption.GetMD5ByStr(pwd)
user.Role = "admin"
user = service.MyService.User().CreateUser(user)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR)})
return
}
file.MkDir(config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id))
delete(service.UserRegisterHash, key)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
// @Summary login
// @Produce application/json
// @Accept application/json
// @Tags user
// @Param user_name query string true "User name"
// @Param pwd query string true "password"
// @Success 200 {string} string "ok"
// @Router /user/login [post]
func PostUserLogin(c *gin.Context) {
json := make(map[string]string)
c.ShouldBind(&json)
username := json["username"]
password := json["password"]
//check params is empty
if len(username) == 0 || len(password) == 0 {
c.JSON(common_err.CLIENT_ERROR,
model.Result{
Success: common_err.CLIENT_ERROR,
Message: common_err.GetMsg(common_err.INVALID_PARAMS),
})
return
}
user := service.MyService.User().GetUserAllInfoByName(username)
if user.Id == 0 {
c.JSON(common_err.CLIENT_ERROR,
model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
if user.Password != encryption.GetMD5ByStr(password) {
c.JSON(common_err.CLIENT_ERROR,
model.Result{Success: common_err.PWD_INVALID, Message: common_err.GetMsg(common_err.PWD_INVALID)})
return
}
token := system_model.VerifyInformation{}
token.AccessToken = jwt.GetAccessToken(user.Username, user.Password, user.Id)
token.RefreshToken = jwt.GetRefreshToken(user.Username, user.Password, user.Id)
token.ExpiresAt = time.Now().Add(3 * time.Hour * time.Duration(1)).Unix()
data := make(map[string]interface{}, 2)
user.Password = ""
data["token"] = token
// TODO:1 Database fields cannot be external
data["user"] = user
c.JSON(common_err.SUCCESS,
model.Result{
Success: common_err.SUCCESS,
Message: common_err.GetMsg(common_err.SUCCESS),
Data: data,
})
}
// @Summary edit user head
// @Produce application/json
// @Accept multipart/form-data
// @Tags user
// @Param file formData file true "用户头像"
// @Security ApiKeyAuth
// @Success 200 {string} string "ok"
// @Router /user/avatar [put]
func PutUserAvatar(c *gin.Context) {
id := c.GetHeader("user_id")
user := service.MyService.User().GetUserInfoById(id)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR,
model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
f, err := c.FormFile("file")
if err != nil {
c.JSON(common_err.CLIENT_ERROR,
model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
return
}
if len(user.Avatar) > 0 {
os.RemoveAll(config.AppInfo.UserDataPath + "/" + id + "/" + user.Avatar)
}
ext := filepath.Ext(f.Filename)
avatarPath := config.AppInfo.UserDataPath + "/" + id + "/avatar" + ext
c.SaveUploadedFile(f, avatarPath)
user.Avatar = avatarPath
service.MyService.User().UpdateUser(user)
c.JSON(http.StatusOK,
model.Result{
Success: common_err.SUCCESS,
Message: common_err.GetMsg(common_err.SUCCESS),
Data: user,
})
}
// @Summary edit user name
// @Produce application/json
// @Accept application/json
// @Tags user
// @Param old_name query string true "Old user name"
// @Security ApiKeyAuth
// @Success 200 {string} string "ok"
// @Router /user/name/:id [put]
func PutUserInfo(c *gin.Context) {
id := c.GetHeader("user_id")
json := model2.UserDBModel{}
c.ShouldBind(&json)
user := service.MyService.User().GetUserInfoById(id)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR,
model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
if len(json.Username) > 0 {
u := service.MyService.User().GetUserInfoByUserName(json.Username)
if u.Id > 0 {
c.JSON(common_err.CLIENT_ERROR,
model.Result{Success: common_err.USER_EXIST, Message: common_err.GetMsg(common_err.USER_EXIST)})
return
}
}
if len(json.Email) == 0 {
json.Email = user.Email
}
if len(json.Avatar) == 0 {
json.Avatar = user.Avatar
}
if len(json.Role) == 0 {
json.Role = user.Role
}
if len(json.Description) == 0 {
json.Description = user.Description
}
if len(json.Nickname) == 0 {
json.Nickname = user.Nickname
}
service.MyService.User().UpdateUser(json)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: json})
}
// @Summary edit user password
// @Produce application/json
// @Accept application/json
// @Tags user
// @Security ApiKeyAuth
// @Success 200 {string} string "ok"
// @Router /user/password/:id [put]
func PutUserPassword(c *gin.Context) {
id := c.GetHeader("user_id")
json := make(map[string]string)
c.ShouldBind(&json)
oldPwd := json["old_password"]
pwd := json["password"]
if len(oldPwd) == 0 || len(pwd) == 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
user := service.MyService.User().GetUserAllInfoById(id)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR,
model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
if user.Password != encryption.GetMD5ByStr(oldPwd) {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.PWD_INVALID_OLD, Message: common_err.GetMsg(common_err.PWD_INVALID_OLD)})
return
}
user.Password = encryption.GetMD5ByStr(pwd)
service.MyService.User().UpdateUserPassword(user)
user.Password = ""
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: user})
}
// @Summary edit user nick
// @Produce application/json
// @Accept application/json
// @Tags user
// @Param nick_name query string false "nick name"
// @Security ApiKeyAuth
// @Success 200 {string} string "ok"
// @Router /user/nick [put]
func PutUserNick(c *gin.Context) {
id := c.GetHeader("user_id")
json := make(map[string]string)
c.ShouldBind(&json)
Nickname := json["nick_name"]
if len(Nickname) == 0 {
c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
user := service.MyService.User().GetUserInfoById(id)
if user.Id == 0 {
c.JSON(http.StatusOK,
model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
user.Nickname = Nickname
service.MyService.User().UpdateUser(user)
c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: user})
}
// @Summary edit user description
// @Produce application/json
// @Accept multipart/form-data
// @Tags user
// @Param description formData string false "Description"
// @Security ApiKeyAuth
// @Success 200 {string} string "ok"
// @Router /user/desc [put]
func PutUserDesc(c *gin.Context) {
id := c.GetHeader("user_id")
json := make(map[string]string)
c.ShouldBind(&json)
desc := json["description"]
if len(desc) == 0 {
c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
user := service.MyService.User().GetUserInfoById(id)
if user.Id == 0 {
c.JSON(http.StatusOK,
model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
user.Description = desc
service.MyService.User().UpdateUser(user)
c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: user})
}
// @Summary get user info
// @Produce application/json
// @Accept application/json
// @Tags user
// @Success 200 {string} string "ok"
// @Router /user/info/:id [get]
func GetUserInfo(c *gin.Context) {
id := c.GetHeader("user_id")
user := service.MyService.User().GetUserInfoById(id)
c.JSON(common_err.SUCCESS,
model.Result{
Success: common_err.SUCCESS,
Message: common_err.GetMsg(common_err.SUCCESS),
Data: user,
})
}
/**
* @description:
* @param {*gin.Context} c
* @param {string} Username
* @return {*}
* @method:
* @router:
*/
func GetUserInfoByUsername(c *gin.Context) {
username := c.Param("username")
if len(username) == 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
user := service.MyService.User().GetUserInfoByUserName(username)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
c.JSON(common_err.SUCCESS,
model.Result{
Success: common_err.SUCCESS,
Message: common_err.GetMsg(common_err.SUCCESS),
Data: user,
})
}
/**
* @description: get all Usernames
* @method:GET
* @router:/user/all/name
*/
func GetUserAllUsername(c *gin.Context) {
users := service.MyService.User().GetAllUserName()
names := []string{}
for _, v := range users {
names = append(names, v.Username)
}
c.JSON(common_err.SUCCESS,
model.Result{
Success: common_err.SUCCESS,
Message: common_err.GetMsg(common_err.SUCCESS),
Data: names,
})
}
/**
* @description:get custom file by user
* @param {path} name string "file name"
* @method: GET
* @router: /user/custom/:key
*/
func GetUserCustomConf(c *gin.Context) {
name := c.Param("key")
if len(name) == 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
id := c.GetHeader("user_id")
user := service.MyService.User().GetUserInfoById(id)
// user := service.MyService.User().GetUserInfoByUsername(Username)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR,
model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
filePath := config.AppInfo.UserDataPath + "/" + id + "/" + name + ".json"
data := file.ReadFullFile(filePath)
if !gjson.ValidBytes(data) {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: string(data)})
return
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: json2.RawMessage(string(data))})
}
/**
* @description:create or update custom conf by user
* @param {path} name string "file name"
* @method:POST
* @router:/user/custom/:key
*/
func PostUserCustomConf(c *gin.Context) {
name := c.Param("key")
if len(name) == 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
id := c.GetHeader("user_id")
user := service.MyService.User().GetUserInfoById(id)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR,
model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
data, _ := ioutil.ReadAll(c.Request.Body)
filePath := config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id)
file.WriteToPath(data, filePath, name+".json")
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: json2.RawMessage(string(data))})
}
/**
* @description: delete user custom config
* @param {path} key string
* @method:delete
* @router:/user/custom/:key
*/
func DeleteUserCustomConf(c *gin.Context) {
name := c.Param("key")
if len(name) == 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
id := c.GetHeader("user_id")
user := service.MyService.User().GetUserInfoById(id)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR,
model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
filePath := config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id) + "/" + name + ".json"
err := os.Remove(filePath)
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR)})
return
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
/**
* @description:
* @param {path} id string "user id"
* @method:DELETE
* @router:/user/delete/:id
*/
func DeleteUser(c *gin.Context) {
id := c.Param("id")
service.MyService.User().DeleteUserById(id)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: id})
}
/**
* @description:update user image
* @method:POST
* @router:/user/current/image/:key
*/
func PutUserImage(c *gin.Context) {
id := c.GetHeader("user_id")
json := make(map[string]string)
c.ShouldBind(&json)
path := json["path"]
key := c.Param("key")
if len(path) == 0 || len(key) == 0 {
c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
if !file.Exists(path) {
c.JSON(http.StatusOK, model.Result{Success: common_err.FILE_DOES_NOT_EXIST, Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST)})
return
}
_, err := file.GetImageExt(path)
if err != nil {
c.JSON(http.StatusOK, model.Result{Success: common_err.NOT_IMAGE, Message: common_err.GetMsg(common_err.NOT_IMAGE)})
return
}
user := service.MyService.User().GetUserInfoById(id)
if user.Id == 0 {
c.JSON(http.StatusOK, model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
fstat, _ := os.Stat(path)
if fstat.Size() > 10<<20 {
c.JSON(http.StatusOK, model.Result{Success: common_err.IMAGE_TOO_LARGE, Message: common_err.GetMsg(common_err.IMAGE_TOO_LARGE)})
return
}
ext := file.GetExt(path)
filePath := config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id) + "/" + key + ext
file.CopySingleFile(path, filePath, "overwrite")
data := make(map[string]string, 3)
data["path"] = filePath
data["file_name"] = key + ext
data["online_path"] = "/v1/users/image?path=" + filePath
c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
}
/**
* @description:
* @param {*gin.Context} c
* @param {file} file
* @param {string} key
* @param {string} type:avatar,background
* @return {*}
* @method:
* @router:
*/
func PostUserUploadImage(c *gin.Context) {
id := c.GetHeader("user_id")
f, err := c.FormFile("file")
key := c.Param("key")
t := c.PostForm("type")
if len(key) == 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
if err != nil {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
return
}
_, err = file.GetImageExtByName(f.Filename)
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.NOT_IMAGE, Message: common_err.GetMsg(common_err.NOT_IMAGE)})
return
}
ext := filepath.Ext(f.Filename)
user := service.MyService.User().GetUserInfoById(id)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
if t == "avatar" {
key = "avatar"
}
path := config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id) + "/" + key + ext
c.SaveUploadedFile(f, path)
data := make(map[string]string, 3)
data["path"] = path
data["file_name"] = key + ext
data["online_path"] = "/v1/users/image?path=" + path
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
}
/**
* @description: get current user's image
* @method:GET
* @router:/user/image/:id
*/
func GetUserImage(c *gin.Context) {
filePath := c.Query("path")
if len(filePath) == 0 {
c.JSON(http.StatusNotFound, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
if !file.Exists(filePath) {
c.JSON(http.StatusNotFound, model.Result{Success: common_err.FILE_DOES_NOT_EXIST, Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST)})
return
}
if !strings.Contains(filePath, config.AppInfo.UserDataPath) {
c.JSON(http.StatusNotFound, model.Result{Success: common_err.INSUFFICIENT_PERMISSIONS, Message: common_err.GetMsg(common_err.INSUFFICIENT_PERMISSIONS)})
return
}
fileTmp, _ := os.Open(filePath)
defer fileTmp.Close()
fileName := path.Base(filePath)
// @tiger - RESTful 规范下不应该返回文件本身内容而是返回文件的静态URL由前端去解析
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url2.PathEscape(fileName))
c.File(filePath)
}
func DeleteUserImage(c *gin.Context) {
id := c.GetHeader("user_id")
path := c.Query("path")
if len(path) == 0 {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
user := service.MyService.User().GetUserInfoById(id)
if user.Id == 0 {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)})
return
}
if !file.Exists(path) {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.FILE_DOES_NOT_EXIST, Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST)})
return
}
if !strings.Contains(path, config.AppInfo.UserDataPath+"/"+strconv.Itoa(user.Id)) {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.INSUFFICIENT_PERMISSIONS, Message: common_err.GetMsg(common_err.INSUFFICIENT_PERMISSIONS)})
return
}
os.Remove(path)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
/**
* @description:
* @param {*gin.Context} c
* @param {string} refresh_token
* @return {*}
* @method:
* @router:
*/
func PostUserRefreshToken(c *gin.Context) {
js := make(map[string]string)
c.ShouldBind(&js)
refresh := js["refresh_token"]
claims, err := jwt.ParseToken(refresh, true)
if err != nil {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.VERIFICATION_FAILURE, Message: common_err.GetMsg(common_err.VERIFICATION_FAILURE), Data: err.Error()})
return
}
if !claims.VerifyExpiresAt(time.Now(), true) || !claims.VerifyIssuer("refresh", true) {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.VERIFICATION_FAILURE, Message: common_err.GetMsg(common_err.VERIFICATION_FAILURE)})
return
}
newToken := jwt.GetAccessToken(claims.Username, claims.PassWord, claims.Id)
verifyInfo := system_model.VerifyInformation{}
verifyInfo.AccessToken = newToken
verifyInfo.RefreshToken = jwt.GetRefreshToken(claims.Username, claims.PassWord, claims.Id)
verifyInfo.ExpiresAt = time.Now().Add(3 * time.Hour * time.Duration(1)).Unix()
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: verifyInfo})
}
func DeleteUserAll(c *gin.Context) {
service.MyService.User().DeleteAllUser()
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
// @Summary 检查是否进入引导状态
// @Produce application/json
// @Accept application/json
// @Tags sys
// @Security ApiKeyAuth
// @Success 200 {string} string "ok"
// @Router /sys/init/check [get]
func GetUserStatus(c *gin.Context) {
data := make(map[string]interface{}, 2)
if service.MyService.User().GetUserCount() > 0 {
data["initialized"] = true
data["key"] = ""
} else {
key := uuid.NewV4().String()
service.UserRegisterHash[key] = key
data["key"] = key
data["initialized"] = false
}
c.JSON(common_err.SUCCESS,
model.Result{
Success: common_err.SUCCESS,
Message: common_err.GetMsg(common_err.SUCCESS),
Data: data,
})
}

View File

@@ -439,7 +439,7 @@ func (a *appStruct) GetHardwareUsageStream() {
wg.Add(1)
go func(v types.Container, i int) {
defer wg.Done()
stats, err := cli.ContainerStats(ctx, v.ID, true)
stats, err := cli.ContainerStatsOneShot(ctx, v.ID)
if err != nil {
return
}

View File

@@ -127,15 +127,11 @@ func (o *casaService) GetServerList(index, size, tp, categoryId, key string) (mo
func (o *casaService) AsyncGetServerList() (collection model.ServerAppListCollection, err error) {
results := file.ReadFullFile(config.AppInfo.DBPath + "/app_list.json")
err = json2.Unmarshal(results, &collection)
if err != nil {
errr := json2.Unmarshal(results, &collection)
if errr != nil {
loger.Error("marshal error", zap.Any("err", err), zap.Any("content", string(results)))
}
if collection.Version == o.GetCasaosVersion().Version {
return collection, err
}
head := make(map[string]string)
head["Authorization"] = GetToken()
@@ -144,7 +140,7 @@ func (o *casaService) AsyncGetServerList() (collection model.ServerAppListCollec
listModel := []model.ServerAppList{}
communityModel := []model.ServerAppList{}
recommendModel := []model.ServerAppList{}
json2.Unmarshal([]byte(gjson.Get(listS, "data.list").String()), &listModel)
err = json2.Unmarshal([]byte(gjson.Get(listS, "data.list").String()), &listModel)
json2.Unmarshal([]byte(gjson.Get(listS, "data.recommend").String()), &recommendModel)
json2.Unmarshal([]byte(gjson.Get(listS, "data.community").String()), &communityModel)
@@ -153,7 +149,8 @@ func (o *casaService) AsyncGetServerList() (collection model.ServerAppListCollec
collection.List = listModel
collection.Recommend = recommendModel
collection.Version = o.GetCasaosVersion().Version
by, err := json.Marshal(collection)
var by []byte
by, err = json.Marshal(collection)
if err != nil {
loger.Error("marshal error", zap.Any("err", err))
}
@@ -203,10 +200,6 @@ func (o *casaService) AsyncGetServerCategoryList() ([]model.CategoryList, error)
err := json2.Unmarshal(results, &list)
if err != nil {
loger.Error("marshal error", zap.Any("err", err), zap.Any("content", string(results)))
} else {
if list.Version == o.GetCasaosVersion().Version {
return list.Item, nil
}
}
item := []model.CategoryList{}
head := make(map[string]string)
@@ -239,7 +232,11 @@ func (o *casaService) GetServerAppInfo(id, t string, language string) (model.Ser
if infoS == "" {
return info, errors.New("server error")
}
json2.Unmarshal([]byte(gjson.Get(infoS, "data").String()), &info)
err := json2.Unmarshal([]byte(gjson.Get(infoS, "data").String()), &info)
if err != nil {
fmt.Println(infoS)
return info, err
}
return info, nil
}

69
service/connections.go Normal file
View File

@@ -0,0 +1,69 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-07-26 18:13:22
* @LastEditors: LinkLeong
* @LastEditTime: 2022-08-04 20:10:31
* @FilePath: /CasaOS/service/connections.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package service
import (
"github.com/IceWhaleTech/CasaOS/pkg/config"
command2 "github.com/IceWhaleTech/CasaOS/pkg/utils/command"
"github.com/IceWhaleTech/CasaOS/service/model"
model2 "github.com/IceWhaleTech/CasaOS/service/model"
"gorm.io/gorm"
)
type ConnectionsService interface {
GetConnectionsList() (connections []model2.ConnectionsDBModel)
GetConnectionByHost(host string) (connections []model2.ConnectionsDBModel)
GetConnectionByID(id string) (connections model2.ConnectionsDBModel)
CreateConnection(connection *model2.ConnectionsDBModel)
DeleteConnection(id string)
UpdateConnection(connection *model2.ConnectionsDBModel)
MountSmaba(username, host, directory, port, mountPoint, password string) string
UnmountSmaba(mountPoint string) string
}
type connectionsStruct struct {
db *gorm.DB
}
func (s *connectionsStruct) GetConnectionByHost(host string) (connections []model2.ConnectionsDBModel) {
s.db.Select("username,host,status,id").Where("host = ?", host).Find(&connections)
return
}
func (s *connectionsStruct) GetConnectionByID(id string) (connections model2.ConnectionsDBModel) {
s.db.Select("username,password,host,status,id,directories,mount_point,port").Where("id = ?", id).First(&connections)
return
}
func (s *connectionsStruct) GetConnectionsList() (connections []model2.ConnectionsDBModel) {
s.db.Select("username,host,port,status,id,mount_point").Find(&connections)
return
}
func (s *connectionsStruct) CreateConnection(connection *model2.ConnectionsDBModel) {
s.db.Create(connection)
}
func (s *connectionsStruct) UpdateConnection(connection *model2.ConnectionsDBModel) {
s.db.Save(connection)
}
func (s *connectionsStruct) DeleteConnection(id string) {
s.db.Where("id= ?", id).Delete(&model.ConnectionsDBModel{})
}
func (s *connectionsStruct) MountSmaba(username, host, directory, port, mountPoint, password string) string {
str := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;MountCIFS " + username + " " + host + " " + directory + " " + port + " " + mountPoint + " " + password)
return str
}
func (s *connectionsStruct) UnmountSmaba(mountPoint string) string {
str := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;UMountPorintAndRemoveDir " + mountPoint)
return str
}
func NewConnectionsService(db *gorm.DB) ConnectionsService {
return &connectionsStruct{db: db}
}

View File

@@ -1,278 +0,0 @@
package service
import (
json2 "encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"time"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/config"
command2 "github.com/IceWhaleTech/CasaOS/pkg/utils/command"
"github.com/IceWhaleTech/CasaOS/pkg/utils/loger"
model2 "github.com/IceWhaleTech/CasaOS/service/model"
"github.com/shirou/gopsutil/v3/disk"
"github.com/tidwall/gjson"
"go.uber.org/zap"
"gorm.io/gorm"
)
type DiskService interface {
GetPlugInDisk() []string
LSBLK(isUseCache bool) []model.LSBLKModel
SmartCTL(path string) model.SmartctlA
FormatDisk(path, format string) []string
UmountPointAndRemoveDir(path string) []string
GetDiskInfo(path string) model.LSBLKModel
DelPartition(path, num string) string
AddPartition(path string) string
GetDiskInfoByPath(path string) *disk.UsageStat
MountDisk(path, volume string)
GetSerialAll() []model2.SerialDisk
SaveMountPoint(m model2.SerialDisk)
DeleteMountPoint(path, mountPoint string)
DeleteMount(id string)
UpdateMountPoint(m model2.SerialDisk)
RemoveLSBLKCache()
}
type diskService struct {
db *gorm.DB
}
func (d *diskService) RemoveLSBLKCache() {
key := "system_lsblk"
Cache.Delete(key)
}
func (d *diskService) SmartCTL(path string) model.SmartctlA {
key := "system_smart_" + path
if result, ok := Cache.Get(key); ok {
res, ok := result.(model.SmartctlA)
if ok {
return res
}
}
var m model.SmartctlA
str := command2.ExecSmartCTLByPath(path)
if str == nil {
loger.Error("failed to exec shell ", zap.Any("err", "smartctl exec error"))
return m
}
err := json2.Unmarshal([]byte(str), &m)
if err != nil {
loger.Error("Failed to unmarshal json", zap.Any("err", err))
}
if !reflect.DeepEqual(m, model.SmartctlA{}) {
Cache.Add(key, m, time.Hour*24)
}
return m
}
//通过脚本获取外挂磁盘
func (d *diskService) GetPlugInDisk() []string {
return command2.ExecResultStrArray("source " + config.AppInfo.ShellPath + "/helper.sh ;GetPlugInDisk")
}
//格式化硬盘
func (d *diskService) FormatDisk(path, format string) []string {
r := command2.ExecResultStrArray("source " + config.AppInfo.ShellPath + "/helper.sh ;FormatDisk " + path + " " + format)
return r
}
//移除挂载点,删除目录
func (d *diskService) UmountPointAndRemoveDir(path string) []string {
r := command2.ExecResultStrArray("source " + config.AppInfo.ShellPath + "/helper.sh ;UMountPorintAndRemoveDir " + path)
return r
}
//删除分区
func (d *diskService) DelPartition(path, num string) string {
r := command2.ExecResultStrArray("source " + config.AppInfo.ShellPath + "/helper.sh ;DelPartition " + path + " " + num)
fmt.Println(r)
return ""
}
//part
func (d *diskService) AddPartition(path string) string {
command2.ExecResultStrArray("source " + config.AppInfo.ShellPath + "/helper.sh ;AddPartition " + path)
return ""
}
func (d *diskService) AddAllPartition(path string) {
}
//获取硬盘详情
func (d *diskService) GetDiskInfoByPath(path string) *disk.UsageStat {
diskInfo, err := disk.Usage(path + "1")
if err != nil {
fmt.Println(err)
}
diskInfo.UsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.1f", diskInfo.UsedPercent), 64)
diskInfo.InodesUsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.1f", diskInfo.InodesUsedPercent), 64)
return diskInfo
}
//get disk details
func (d *diskService) LSBLK(isUseCache bool) []model.LSBLKModel {
key := "system_lsblk"
var n []model.LSBLKModel
if result, ok := Cache.Get(key); ok && isUseCache {
res, ok := result.([]model.LSBLKModel)
if ok {
return res
}
}
str := command2.ExecLSBLK()
if str == nil {
loger.Error("Failed to exec shell", zap.Any("err", "lsblk exec error"))
return nil
}
var m []model.LSBLKModel
err := json2.Unmarshal([]byte(gjson.Get(string(str), "blockdevices").String()), &m)
if err != nil {
loger.Error("Failed to unmarshal json", zap.Any("err", err))
}
var c []model.LSBLKModel
var fsused uint64
var health = true
for _, i := range m {
if i.Type != "loop" && !i.RO {
fsused = 0
for _, child := range i.Children {
if child.RM {
child.Health = strings.TrimSpace(command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;GetDiskHealthState " + child.Path))
if strings.ToLower(strings.TrimSpace(child.State)) != "ok" {
health = false
}
f, _ := strconv.ParseUint(child.FSUsed, 10, 64)
fsused += f
} else {
health = false
}
c = append(c, child)
}
//i.Format = strings.TrimSpace(command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;GetDiskType " + i.Path))
if health {
i.Health = "OK"
}
i.FSUsed = strconv.FormatUint(fsused, 10)
i.Children = c
if fsused > 0 {
i.UsedPercent, err = strconv.ParseFloat(fmt.Sprintf("%.4f", float64(fsused)/float64(i.Size)), 64)
if err != nil {
loger.Error("Failed to parse float", zap.Any("err", err))
}
}
n = append(n, i)
health = true
c = []model.LSBLKModel{}
fsused = 0
}
}
if len(n) > 0 {
Cache.Add(key, n, time.Second*100)
}
return n
}
func (d *diskService) GetDiskInfo(path string) model.LSBLKModel {
str := command2.ExecLSBLKByPath(path)
if str == nil {
loger.Error("Failed to exec shell", zap.Any("err", "lsblk exec error"))
return model.LSBLKModel{}
}
var ml []model.LSBLKModel
err := json2.Unmarshal([]byte(gjson.Get(string(str), "blockdevices").String()), &ml)
if err != nil {
loger.Error("Failed to unmarshal json", zap.Any("err", err))
return model.LSBLKModel{}
}
m := model.LSBLKModel{}
if len(ml) > 0 {
m = ml[0]
}
return m
// 下面为计算是否可以继续分区的部分,暂时不需要
chiArr := make(map[string]string)
chiList := command2.ExecResultStrArray("source " + config.AppInfo.ShellPath + "/helper.sh ;GetPartitionSectors " + m.Path)
if len(chiList) == 0 {
loger.Error("chiList length error", zap.Any("err", "chiList length error"))
}
for i := 0; i < len(chiList); i++ {
tempArr := strings.Split(chiList[i], ",")
chiArr[tempArr[0]] = chiList[i]
}
var maxSector uint64 = 0
for i := 0; i < len(m.Children); i++ {
tempArr := strings.Split(chiArr[m.Children[i].Path], ",")
m.Children[i].StartSector, _ = strconv.ParseUint(tempArr[1], 10, 64)
m.Children[i].EndSector, _ = strconv.ParseUint(tempArr[2], 10, 64)
if m.Children[i].EndSector > maxSector {
maxSector = m.Children[i].EndSector
}
}
diskEndSector := command2.ExecResultStrArray("source " + config.AppInfo.ShellPath + "/helper.sh ;GetDiskSizeAndSectors " + m.Path)
if len(diskEndSector) < 2 {
loger.Error("diskEndSector length error", zap.Any("err", "diskEndSector length error"))
}
diskEndSectorInt, _ := strconv.ParseUint(diskEndSector[len(diskEndSector)-1], 10, 64)
if (diskEndSectorInt-maxSector)*m.MinIO/1024/1024 > 100 {
//添加可以分区情况
p := model.LSBLKModel{}
p.Path = "可以添加"
m.Children = append(m.Children, p)
}
return m
}
func (d *diskService) MountDisk(path, volume string) {
r := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;do_mount " + path + " " + volume)
fmt.Print(r)
}
func (d *diskService) SaveMountPoint(m model2.SerialDisk) {
d.db.Where("uuid = ?", m.UUID).Delete(&model2.SerialDisk{})
d.db.Create(&m)
}
func (d *diskService) UpdateMountPoint(m model2.SerialDisk) {
d.db.Model(&model2.SerialDisk{}).Where("uui = ?", m.UUID).Update("mount_point", m.MountPoint)
}
func (d *diskService) DeleteMount(id string) {
d.db.Delete(&model2.SerialDisk{}).Where("id = ?", id)
}
func (d *diskService) DeleteMountPoint(path, mountPoint string) {
d.db.Where("path = ? AND mount_point = ?", path, mountPoint).Delete(&model2.SerialDisk{})
command2.OnlyExec("source " + config.AppInfo.ShellPath + "/helper.sh ;do_umount " + path)
}
func (d *diskService) GetSerialAll() []model2.SerialDisk {
var m []model2.SerialDisk
d.db.Find(&m)
return m
}
func NewDiskService(db *gorm.DB) DiskService {
return &diskService{db: db}
}

View File

@@ -7,13 +7,8 @@ import (
"encoding/binary"
json2 "encoding/json"
"fmt"
"syscall"
"github.com/IceWhaleTech/CasaOS/model/notify"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/oci"
"github.com/pkg/errors"
"go.uber.org/zap"
@@ -45,7 +40,7 @@ import (
type DockerService interface {
DockerPullImage(imageName string, icon, name string) error
IsExistImage(imageName string) bool
DockerContainerCreate(imageName string, m model.CustomizationPostData) (containerId string, err error)
DockerContainerCreate(m model.CustomizationPostData, id string) (containerId string, err error)
DockerContainerCopyCreate(info *types.ContainerJSON) (containerId string, err error)
DockerContainerStart(name string) error
DockerContainerStats(name string) (string, error)
@@ -57,13 +52,14 @@ type DockerService interface {
DockerContainerStop(id string) error
DockerContainerUpdateName(name, id string) (err error)
DockerContainerUpdate(m model.CustomizationPostData, id string) (err error)
DockerContainerLog(name string) (string, error)
DockerContainerLog(name string) ([]byte, error)
DockerContainerCommit(name string)
DockerContainerList() []types.Container
DockerNetworkModelList() []types.NetworkResource
DockerImageInfo(image string) (types.ImageInspect, error)
GetNetWorkNameByNetWorkID(id string) (string, error)
ContainerExecShell(container_id string) string
GetDockerInfo() (types.Info, error)
}
type dockerService struct {
@@ -99,7 +95,7 @@ func (ds *dockerService) ContainerExecShell(container_id string) string {
return exec.ID
}
//创建默认网络
// 创建默认网络
func DockerNetwork() {
cli, _ := client2.NewClientWithOpts(client2.FromEnv)
@@ -114,7 +110,7 @@ func DockerNetwork() {
cli.NetworkCreate(context.Background(), docker.NETWORKNAME, types.NetworkCreate{})
}
//根据网络id获取网络名
// 根据网络id获取网络名
func (ds *dockerService) GetNetWorkNameByNetWorkID(id string) (string, error) {
cli, _ := client2.NewClientWithOpts(client2.FromEnv)
defer cli.Close()
@@ -127,7 +123,7 @@ func (ds *dockerService) GetNetWorkNameByNetWorkID(id string) (string, error) {
return "", err
}
//拉取镜像
// 拉取镜像
func DockerPull() {
cli, _ := client2.NewClientWithOpts(client2.FromEnv)
@@ -146,7 +142,7 @@ func DockerPull() {
}
//拉取镜像
// 拉取镜像
func DockerEx() {
cli, _ := client2.NewClientWithOpts(client2.FromEnv)
@@ -297,7 +293,7 @@ func DockerLogs() {
//正式内容
//检查镜像是否存在
// 检查镜像是否存在
func (ds *dockerService) IsExistImage(imageName string) bool {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
@@ -316,7 +312,7 @@ func (ds *dockerService) IsExistImage(imageName string) bool {
return false
}
//安装镜像
// 安装镜像
func (ds *dockerService) DockerPullImage(imageName string, icon, name string) error {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
@@ -370,13 +366,13 @@ func (ds *dockerService) DockerContainerCopyCreate(info *types.ContainerJSON) (c
return container.ID, err
}
//param imageName 镜像名称
//param containerDbId 数据库的id
//param port 容器内部主端口
//param mapPort 容器主端口映射到外部的端口
//param tcp 容器其他tcp端口
//param udp 容器其他udp端口
func (ds *dockerService) DockerContainerCreate(imageName string, m model.CustomizationPostData) (containerId string, err error) {
// param imageName 镜像名称
// param containerDbId 数据库的id
// param port 容器内部主端口
// param mapPort 容器主端口映射到外部的端口
// param tcp 容器其他tcp端口
// param udp 容器其他udp端口
func (ds *dockerService) DockerContainerCreate(m model.CustomizationPostData, id string) (containerId string, err error) {
if len(m.NetworkModel) == 0 {
m.NetworkModel = "bridge"
}
@@ -385,6 +381,7 @@ func (ds *dockerService) DockerContainerCreate(imageName string, m model.Customi
if err != nil {
return "", err
}
defer cli.Close()
ports := make(nat.PortSet)
portMaps := make(nat.PortMap)
@@ -523,15 +520,34 @@ func (ds *dockerService) DockerContainerCreate(imageName string, m model.Customi
if len(m.HostName) == 0 {
m.HostName = m.Label
}
config := &container.Config{
Image: imageName,
Labels: map[string]string{"origin": m.Origin, m.Origin: m.Origin, "casaos": "casaos"},
Env: envArr,
// Healthcheck: health,
Hostname: m.HostName,
Cmd: m.Cmd,
info, err := cli.ContainerInspect(context.Background(), id)
hostConfig := &container.HostConfig{}
config := &container.Config{}
config.Labels = map[string]string{}
if err == nil {
// info.HostConfig = &container.HostConfig{}
// info.Config = &container.Config{}
// info.NetworkSettings = &types.NetworkSettings{}
hostConfig = info.HostConfig
config = info.Config
if config.Labels["casaos"] == "casaos" {
config.Cmd = m.Cmd
config.Image = m.Image
config.Env = envArr
config.Hostname = m.HostName
config.ExposedPorts = ports
}
} else {
config.Cmd = m.Cmd
config.Image = m.Image
config.Env = envArr
config.Hostname = m.HostName
config.ExposedPorts = ports
}
config.Labels["origin"] = m.Origin
config.Labels["casaos"] = "casaos"
config.Labels["web"] = m.PortMap
config.Labels["icon"] = m.Icon
config.Labels["desc"] = m.Description
@@ -541,12 +557,19 @@ func (ds *dockerService) DockerContainerCreate(imageName string, m model.Customi
config.Labels["protocol"] = m.Protocol
config.Labels["host"] = m.Host
config.Labels["name"] = m.Label
hostConfig := &container.HostConfig{Resources: res, Mounts: volumes, RestartPolicy: rp, NetworkMode: container.NetworkMode(m.NetworkModel), Privileged: m.Privileged, CapAdd: m.CapAdd}
//container, err := cli.ContainerCreate(context.Background(), info.Config, info.HostConfig, &network.NetworkingConfig{info.NetworkSettings.Networks}, nil, info.Name)
hostConfig.Mounts = volumes
hostConfig.Privileged = m.Privileged
hostConfig.CapAdd = m.CapAdd
hostConfig.NetworkMode = container.NetworkMode(m.NetworkModel)
hostConfig.RestartPolicy = rp
hostConfig.Resources = res
//hostConfig := &container.HostConfig{Resources: res, Mounts: volumes, RestartPolicy: rp, NetworkMode: , Privileged: m.Privileged, CapAdd: m.CapAdd}
//if net != "host" {
config.ExposedPorts = ports
hostConfig.PortBindings = portMaps
//}
containerDb, err := cli.ContainerCreate(context.Background(),
config,
hostConfig,
@@ -559,7 +582,7 @@ func (ds *dockerService) DockerContainerCreate(imageName string, m model.Customi
return containerDb.ID, err
}
//删除容器
// 删除容器
func (ds *dockerService) DockerContainerRemove(name string, update bool) error {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
@@ -583,7 +606,7 @@ func (ds *dockerService) DockerContainerRemove(name string, update bool) error {
return err
}
//删除镜像
// 删除镜像
func (ds *dockerService) DockerImageRemove(name string) error {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
@@ -631,7 +654,7 @@ Loop:
return err
}
//停止镜像
// 停止镜像
func (ds *dockerService) DockerContainerStop(id string) error {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
@@ -642,7 +665,7 @@ func (ds *dockerService) DockerContainerStop(id string) error {
return err
}
//启动容器
// 启动容器
func (ds *dockerService) DockerContainerStart(name string) error {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
@@ -653,24 +676,27 @@ func (ds *dockerService) DockerContainerStart(name string) error {
return err
}
//查看日志
func (ds *dockerService) DockerContainerLog(name string) (string, error) {
// 查看日志
func (ds *dockerService) DockerContainerLog(name string) ([]byte, error) {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
return "", err
return []byte(""), err
}
defer cli.Close()
body, err := cli.ContainerLogs(context.Background(), name, types.ContainerLogsOptions{ShowStderr: true, ShowStdout: true})
//body, err := cli.ContainerAttach(context.Background(), name, types.ContainerAttachOptions{Logs: true, Stream: false, Stdin: false, Stdout: false, Stderr: false})
body, err := cli.ContainerLogs(context.Background(), name, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true})
if err != nil {
return "", err
return []byte(""), err
}
defer body.Close()
content, err := ioutil.ReadAll(body)
//content, err := ioutil.ReadAll(body)
if err != nil {
return "", err
return []byte(""), err
}
return string(content), nil
return content, nil
}
func DockerContainerStats1() error {
@@ -692,7 +718,7 @@ func DockerContainerStats1() error {
return nil
}
//获取容器状态
// 获取容器状态
func (ds *dockerService) DockerContainerStats(name string) (string, error) {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
@@ -711,7 +737,7 @@ func (ds *dockerService) DockerContainerStats(name string) (string, error) {
return string(sts), nil
}
//备份容器
// 备份容器
func (ds *dockerService) DockerContainerCommit(name string) {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
@@ -756,7 +782,7 @@ func (ds *dockerService) DockerListByImage(image, version string) (*types.Contai
return &containers[0], nil
}
//获取容器详情
// 获取容器详情
func (ds *dockerService) DockerContainerInfo(name string) (*types.ContainerJSON, error) {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
@@ -771,13 +797,13 @@ func (ds *dockerService) DockerContainerInfo(name string) (*types.ContainerJSON,
return &d, nil
}
//更新容器
//param shares cpu优先级
//param containerDbId 数据库的id
//param port 容器内部主端口
//param mapPort 容器主端口映射到外部的端口
//param tcp 容器其他tcp端口
//param udp 容器其他udp端口
// 更新容器
// param shares cpu优先级
// param containerDbId 数据库的id
// param port 容器内部主端口
// param mapPort 容器主端口映射到外部的端口
// param tcp 容器其他tcp端口
// param udp 容器其他udp端口
func (ds *dockerService) DockerContainerUpdate(m model.CustomizationPostData, id string) (err error) {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
@@ -812,9 +838,9 @@ func (ds *dockerService) DockerContainerUpdate(m model.CustomizationPostData, id
return
}
//更新容器名称
//param name 容器名称
//param id 老的容器名称
// 更新容器名称
// param name 容器名称
// param id 老的容器名称
func (ds *dockerService) DockerContainerUpdateName(name, id string) (err error) {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
@@ -829,7 +855,7 @@ func (ds *dockerService) DockerContainerUpdateName(name, id string) (err error)
return
}
//获取网络列表
// 获取网络列表
func (ds *dockerService) DockerNetworkModelList() []types.NetworkResource {
cli, _ := client2.NewClientWithOpts(client2.FromEnv)
@@ -841,6 +867,17 @@ func NewDockerService() DockerService {
return &dockerService{rootDir: command2.ExecResultStr(`source ./shell/helper.sh ;GetDockerRootDir`)}
}
func (ds *dockerService) GetDockerInfo() (types.Info, error) {
cli, err := client2.NewClientWithOpts(client2.FromEnv)
if err != nil {
return types.Info{}, err
}
defer cli.Close()
return cli.Info(context.Background())
}
// ---------------------------------------test------------------------------------
//func ServiceCreate() {
// cli, err := client2.NewClientWithOpts(client2.FromEnv)
@@ -852,73 +889,73 @@ func NewDockerService() DockerService {
//
//}
func Containerd() {
// create a new client connected to the default socket path for containerd
cli, err := containerd.New("/run/containerd/containerd.sock")
if err != nil {
fmt.Println("111")
fmt.Println(err)
}
defer cli.Close()
// func Containerd() {
// // create a new client connected to the default socket path for containerd
// cli, err := containerd.New("/run/containerd/containerd.sock")
// if err != nil {
// fmt.Println("111")
// fmt.Println(err)
// }
// defer cli.Close()
// create a new context with an "example" namespace
ctx := namespaces.WithNamespace(context.Background(), "default")
// // create a new context with an "example" namespace
// ctx := namespaces.WithNamespace(context.Background(), "default")
// pull the redis image from DockerHub
image, err := cli.Pull(ctx, "docker.io/library/busybox:latest", containerd.WithPullUnpack)
if err != nil {
fmt.Println("222")
fmt.Println(err)
}
// // pull the redis image from DockerHub
// image, err := cli.Pull(ctx, "docker.io/library/busybox:latest", containerd.WithPullUnpack)
// if err != nil {
// fmt.Println("222")
// fmt.Println(err)
// }
// create a container
container, err := cli.NewContainer(
ctx,
"test1",
containerd.WithImage(image),
containerd.WithNewSnapshot("redis-server-snapshot1", image),
containerd.WithNewSpec(oci.WithImageConfig(image)),
)
// // create a container
// container, err := cli.NewContainer(
// ctx,
// "test1",
// containerd.WithImage(image),
// containerd.WithNewSnapshot("redis-server-snapshot1", image),
// containerd.WithNewSpec(oci.WithImageConfig(image)),
// )
if err != nil {
fmt.Println(err)
}
defer container.Delete(ctx, containerd.WithSnapshotCleanup)
// if err != nil {
// fmt.Println(err)
// }
// defer container.Delete(ctx, containerd.WithSnapshotCleanup)
// create a task from the container
task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
if err != nil {
fmt.Println(err)
}
defer task.Delete(ctx)
// // create a task from the container
// task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
// if err != nil {
// fmt.Println(err)
// }
// defer task.Delete(ctx)
// make sure we wait before calling start
exitStatusC, err := task.Wait(ctx)
if err != nil {
fmt.Println(err)
}
// // make sure we wait before calling start
// exitStatusC, err := task.Wait(ctx)
// if err != nil {
// fmt.Println(err)
// }
// call start on the task to execute the redis server
if err = task.Start(ctx); err != nil {
fmt.Println(err)
}
// // call start on the task to execute the redis server
// if err = task.Start(ctx); err != nil {
// fmt.Println(err)
// }
fmt.Println("执行完成等待")
// sleep for a lil bit to see the logs
time.Sleep(3 * time.Second)
// fmt.Println("执行完成等待")
// // sleep for a lil bit to see the logs
// time.Sleep(3 * time.Second)
// kill the process and get the exit status
if err = task.Kill(ctx, syscall.SIGTERM); err != nil {
fmt.Println(err)
}
// // kill the process and get the exit status
// if err = task.Kill(ctx, syscall.SIGTERM); err != nil {
// fmt.Println(err)
// }
// wait for the process to fully exit and print out the exit status
// // wait for the process to fully exit and print out the exit status
status := <-exitStatusC
code, _, err := status.Result()
if err != nil {
fmt.Println(err)
}
fmt.Printf("redis-server exited with status: %d\n", code)
// status := <-exitStatusC
// code, _, err := status.Result()
// if err != nil {
// fmt.Println(err)
// }
// fmt.Printf("redis-server exited with status: %d\n", code)
}
// }

View File

@@ -0,0 +1,28 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-07-26 17:17:57
* @LastEditors: LinkLeong
* @LastEditTime: 2022-08-01 17:08:08
* @FilePath: /CasaOS/service/model/o_connections.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package model
type ConnectionsDBModel struct {
ID uint `gorm:"column:id;primary_key" json:"id"`
Updated int64 `gorm:"autoUpdateTime"`
Created int64 `gorm:"autoCreateTime"`
Username string `json:"username"`
Password string `json:"password"`
Host string `json:"host"`
Port string `json:"port"`
Status string `json:"status"`
Directories string `json:"directories"` // string array
MountPoint string `json:"mount_point"` //parent directory of mount point
}
func (p *ConnectionsDBModel) TableName() string {
return "o_connections"
}

View File

@@ -1,15 +0,0 @@
package model
//SerialAdvanced Technology Attachment (STAT)
type SerialDisk struct {
Id uint `gorm:"column:id;primary_key" json:"id"`
UUID string `json:"uuid"`
Path string `json:"path"`
State int `json:"state"`
MountPoint string `json:"mount_point"`
CreatedAt int64 `json:"created_at"`
}
func (p *SerialDisk) TableName() string {
return "o_disk"
}

24
service/model/o_shares.go Normal file
View File

@@ -0,0 +1,24 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-07-26 11:17:17
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-27 15:25:07
* @FilePath: /CasaOS/service/model/o_shares.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package model
type SharesDBModel struct {
ID uint `gorm:"column:id;primary_key" json:"id"`
Anonymous bool `json:"anonymous"`
Path string `json:"path"`
Name string `json:"name"`
Updated int64 `gorm:"autoUpdateTime"`
Created int64 `gorm:"autoCreateTime"`
}
func (p *SharesDBModel) TableName() string {
return "o_shares"
}

View File

@@ -1,31 +0,0 @@
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-05-13 18:15:46
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-11 17:57:00
* @FilePath: /CasaOS/service/model/o_user.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package model
import "time"
//Soon to be removed
type UserDBModel struct {
Id int `gorm:"column:id;primary_key" json:"id"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
Role string `json:"role"`
Email string `json:"email"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Description string `json:"description"`
CreatedAt time.Time `gorm:"<-:create;autoCreateTime" json:"created_at,omitempty"`
UpdatedAt time.Time `gorm:"<-:create;<-:update;autoUpdateTime" json:"updated_at,omitempty"`
}
func (p *UserDBModel) TableName() string {
return "o_users"
}

View File

@@ -31,16 +31,37 @@ type NotifyServer interface {
SendNetInfoBySocket(netList []model2.IOCountersStat)
SendCPUInfoBySocket(cpu map[string]interface{})
SendMemInfoBySocket(mem map[string]interface{})
SendUSBInfoBySocket(list []model2.DriveUSB)
SendDiskInfoBySocket(disk model2.Summary)
SendFileOperateNotify(nowSend bool)
SendInstallAppBySocket(app notify.Application)
SendAllHardwareStatusBySocket(disk model2.Summary, list []model2.DriveUSB, mem map[string]interface{}, cpu map[string]interface{}, netList []model2.IOCountersStat)
SendAllHardwareStatusBySocket(mem map[string]interface{}, cpu map[string]interface{}, netList []model2.IOCountersStat)
SendStorageBySocket(message notify.StorageMessage)
SendNotify(path string, message map[string]interface{})
SettingSystemTempData(message map[string]interface{})
}
type notifyServer struct {
db *gorm.DB
db *gorm.DB
SystemTempMap map[string]interface{}
}
func (i *notifyServer) SettingSystemTempData(message map[string]interface{}) {
for k, v := range message {
i.SystemTempMap[k] = v
}
}
func (i *notifyServer) SendNotify(path string, message map[string]interface{}) {
msg := gosf.Message{}
msg.Body = message
msg.Success = true
msg.Text = path
notify := notify.Message{}
notify.Path = path
notify.Msg = msg
NotifyMsg <- notify
}
func (i *notifyServer) SendStorageBySocket(message notify.StorageMessage) {
@@ -58,12 +79,9 @@ func (i *notifyServer) SendStorageBySocket(message notify.StorageMessage) {
NotifyMsg <- notify
}
func (i *notifyServer) SendAllHardwareStatusBySocket(disk model2.Summary, list []model2.DriveUSB, mem map[string]interface{}, cpu map[string]interface{}, netList []model2.IOCountersStat) {
func (i *notifyServer) SendAllHardwareStatusBySocket(mem map[string]interface{}, cpu map[string]interface{}, netList []model2.IOCountersStat) {
body := make(map[string]interface{})
body["sys_disk"] = disk
body["sys_usb"] = list
body["sys_mem"] = mem
@@ -71,6 +89,10 @@ func (i *notifyServer) SendAllHardwareStatusBySocket(disk model2.Summary, list [
body["sys_net"] = netList
for k, v := range i.SystemTempMap {
body[k] = v
}
msg := gosf.Message{}
msg.Body = body
msg.Success = true
@@ -239,38 +261,6 @@ func (i *notifyServer) SendFileOperateNotify(nowSend bool) {
}
func (i *notifyServer) SendDiskInfoBySocket(disk model2.Summary) {
body := make(map[string]interface{})
body["data"] = disk
msg := gosf.Message{}
msg.Body = body
msg.Success = true
msg.Text = "sys_disk"
notify := notify.Message{}
notify.Path = "sys_disk"
notify.Msg = msg
NotifyMsg <- notify
}
func (i *notifyServer) SendUSBInfoBySocket(list []model2.DriveUSB) {
body := make(map[string]interface{})
body["data"] = list
msg := gosf.Message{}
msg.Body = body
msg.Success = true
msg.Text = "sys_usb"
notify := notify.Message{}
notify.Path = "sys_usb"
notify.Msg = msg
NotifyMsg <- notify
}
func (i *notifyServer) SendMemInfoBySocket(mem map[string]interface{}) {
body := make(map[string]interface{})
body["data"] = mem
@@ -454,5 +444,5 @@ func SendMeg() {
// }
func NewNotifyService(db *gorm.DB) NotifyServer {
return &notifyServer{db: db}
return &notifyServer{db: db, SystemTempMap: make(map[string]interface{})}
}

View File

@@ -2,7 +2,7 @@
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-07-12 09:48:56
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-15 10:58:54
* @LastEditTime: 2022-09-02 22:10:05
* @FilePath: /CasaOS/service/service.go
* @Description:
* @Website: https://www.casaos.io
@@ -11,6 +11,7 @@
package service
import (
gateway "github.com/IceWhaleTech/CasaOS-Gateway/common"
"github.com/gorilla/websocket"
"github.com/patrickmn/go-cache"
"gorm.io/gorm"
@@ -26,38 +27,58 @@ var SocketRun bool
type Repository interface {
App() AppService
User() UserService
//User() UserService
Docker() DockerService
Casa() CasaService
Disk() DiskService
Notify() NotifyServer
Rely() RelyService
System() SystemService
Shares() SharesService
Connections() ConnectionsService
Gateway() gateway.ManagementService
}
func NewService(db *gorm.DB) Repository {
func NewService(db *gorm.DB, RuntimePath string) Repository {
gatewayManagement, err := gateway.NewManagementService(RuntimePath)
if err != nil && len(RuntimePath) > 0 {
panic(err)
}
return &store{
app: NewAppService(db),
user: NewUserService(db),
docker: NewDockerService(),
casa: NewCasaService(),
disk: NewDiskService(db),
notify: NewNotifyService(db),
rely: NewRelyService(db),
system: NewSystemService(),
gateway: gatewayManagement,
app: NewAppService(db),
docker: NewDockerService(),
casa: NewCasaService(),
notify: NewNotifyService(db),
rely: NewRelyService(db),
system: NewSystemService(),
shares: NewSharesService(db),
connections: NewConnectionsService(db),
}
}
type store struct {
db *gorm.DB
app AppService
user UserService
docker DockerService
casa CasaService
disk DiskService
notify NotifyServer
rely RelyService
system SystemService
db *gorm.DB
app AppService
docker DockerService
casa CasaService
notify NotifyServer
rely RelyService
system SystemService
shares SharesService
connections ConnectionsService
gateway gateway.ManagementService
}
func (c *store) Gateway() gateway.ManagementService {
return c.gateway
}
func (s *store) Connections() ConnectionsService {
return s.connections
}
func (s *store) Shares() SharesService {
return s.shares
}
func (c *store) Rely() RelyService {
@@ -76,10 +97,6 @@ func (c *store) App() AppService {
return c.app
}
func (c *store) User() UserService {
return c.user
}
func (c *store) Docker() DockerService {
return c.docker
}
@@ -87,7 +104,3 @@ func (c *store) Docker() DockerService {
func (c *store) Casa() CasaService {
return c.casa
}
func (c *store) Disk() DiskService {
return c.disk
}

157
service/shares.go Normal file
View File

@@ -0,0 +1,157 @@
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-07-26 11:21:14
* @LastEditors: LinkLeong
* @LastEditTime: 2022-08-18 11:16:25
* @FilePath: /CasaOS/service/shares.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package service
import (
"path/filepath"
"strings"
"github.com/IceWhaleTech/CasaOS/pkg/config"
command2 "github.com/IceWhaleTech/CasaOS/pkg/utils/command"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/service/model"
model2 "github.com/IceWhaleTech/CasaOS/service/model"
"gorm.io/gorm"
)
type SharesService interface {
GetSharesList() (shares []model2.SharesDBModel)
GetSharesByPath(path string) (shares []model2.SharesDBModel)
GetSharesByName(name string) (shares []model2.SharesDBModel)
CreateShare(share model2.SharesDBModel)
DeleteShare(id string)
UpdateConfigFile()
InitSambaConfig()
DeleteShareByPath(path string)
}
type sharesStruct struct {
db *gorm.DB
}
func (s *sharesStruct) DeleteShareByPath(path string) {
s.db.Where("path LIKE ?", path+"%").Delete(&model.SharesDBModel{})
s.UpdateConfigFile()
}
func (s *sharesStruct) GetSharesByName(name string) (shares []model2.SharesDBModel) {
s.db.Select("anonymous,path,id").Where("name = ?", name).Find(&shares)
return
}
func (s *sharesStruct) GetSharesByPath(path string) (shares []model2.SharesDBModel) {
s.db.Select("anonymous,path,id").Where("path = ?", path).Find(&shares)
return
}
func (s *sharesStruct) GetSharesList() (shares []model2.SharesDBModel) {
s.db.Select("anonymous,path,id").Find(&shares)
return
}
func (s *sharesStruct) CreateShare(share model2.SharesDBModel) {
s.db.Create(&share)
s.InitSambaConfig()
s.UpdateConfigFile()
}
func (s *sharesStruct) DeleteShare(id string) {
s.db.Where("id= ?", id).Delete(&model.SharesDBModel{})
s.UpdateConfigFile()
}
func (s *sharesStruct) UpdateConfigFile() {
shares := []model2.SharesDBModel{}
s.db.Select("anonymous,path").Find(&shares)
//generated config file
var configStr = ""
for _, share := range shares {
dirName := filepath.Base(share.Path)
configStr += `
[` + dirName + `]
comment = CasaOS share ` + dirName + `
public = Yes
path = ` + share.Path + `
browseable = Yes
read only = No
guest ok = Yes
create mask = 0777
directory mask = 0777
`
}
//write config file
file.WriteToPath([]byte(configStr), "/etc/samba", "smb.casa.conf")
//restart samba
command2.ExecResultStrArray("source " + config.AppInfo.ShellPath + "/helper.sh ;RestartSMBD")
}
func (s *sharesStruct) InitSambaConfig() {
if file.Exists("/etc/samba/smb.conf") {
str := file.ReadLine(1, "/etc/samba/smb.conf")
if strings.Contains(str, "# Copyright (c) 2021-2022 CasaOS Inc. All rights reserved.") {
return
}
file.MoveFile("/etc/samba/smb.conf", "/etc/samba/smb.conf.bak")
var smbConf = ""
smbConf += `# Copyright (c) 2021-2022 CasaOS Inc. All rights reserved.
#
#
# ______ _______
# ( __ \ ( ___ )
# | ( \ ) | ( ) |
# | | ) | | | | |
# | | | | | | | |
# | | ) | | | | |
# | (__/ ) | (___) |
# (______/ (_______)
#
# _ _______ _________
# ( ( /| ( ___ ) \__ __/
# | \ ( | | ( ) | ) (
# | \ | | | | | | | |
# | (\ \) | | | | | | |
# | | \ | | | | | | |
# | ) \ | | (___) | | |
# |/ )_) (_______) )_(
#
# _______ _______ ______ _________ _______
# ( ) ( ___ ) ( __ \ \__ __/ ( ____ \ |\ /|
# | () () | | ( ) | | ( \ ) ) ( | ( \/ ( \ / )
# | || || | | | | | | | ) | | | | (__ \ (_) /
# | |(_)| | | | | | | | | | | | | __) \ /
# | | | | | | | | | | ) | | | | ( ) (
# | ) ( | | (___) | | (__/ ) ___) (___ | ) | |
# |/ \| (_______) (______/ \_______/ |/ \_/
#
#
# IMPORTANT: CasaOS will not provide technical support for any issues
# caused by unauthorized modification to the configuration.
[global]
## fruit settings
min protocol = SMB2
ea support = yes
## vfs objects = fruit streams_xattr
fruit:metadata = stream
fruit:model = Macmini
fruit:veto_appledouble = no
fruit:posix_rename = yes
fruit:zero_file_id = yes
fruit:wipe_intentionally_left_blank_rfork = yes
fruit:delete_empty_adfiles = yes
map to guest = bad user
include=/etc/samba/smb.casa.conf`
file.WriteToPath([]byte(smbConf), "/etc/samba", "smb.conf")
}
}
func NewSharesService(db *gorm.DB) SharesService {
return &sharesStruct{db: db}
}

View File

@@ -49,6 +49,9 @@ type SystemService interface {
CreateFile(path string) (int, error)
RenameFile(oldF, newF string) (int, error)
MkdirAll(path string) (int, error)
IsServiceRunning(name string) bool
GetCPUTemperature() int
GetCPUPower() map[string]string
}
type systemService struct {
}
@@ -213,7 +216,12 @@ func (c *systemService) GetNet(physics bool) []string {
}
func (s *systemService) UpdateSystemVersion(version string) {
command2.OnlyExec("curl -fsSL https://raw.githubusercontent.com/IceWhaleTech/get/main/update.sh | bash")
if file.Exists(config.AppInfo.LogPath + "/upgrade.log") {
os.Remove(config.AppInfo.LogPath + "/upgrade.log")
}
file.CreateFile(config.AppInfo.LogPath + "/upgrade.log")
//go command2.OnlyExec("curl -fsSL https://raw.githubusercontent.com/LinkLeong/casaos-alpha/main/update.sh | bash")
go command2.OnlyExec("curl -fsSL https://raw.githubusercontent.com/IceWhaleTech/get/main/update.sh | sudo bash")
//s.log.Error(config.AppInfo.ProjectPath + "/shell/tool.sh -r " + version)
//s.log.Error(command2.ExecResultStr(config.AppInfo.ProjectPath + "/shell/tool.sh -r " + version))
}
@@ -227,11 +235,10 @@ func (s *systemService) GetTimeZone() string {
func (s *systemService) ExecUSBAutoMountShell(state string) {
if state == "False" {
command2.OnlyExec("source " + config.AppInfo.ShellPath + "/helper.sh ;USB_Remove_File")
command2.OnlyExec("source " + config.AppInfo.ShellPath + "/helper.sh ;USB_Stop_Auto")
} else {
command2.OnlyExec("source " + config.AppInfo.ShellPath + "/helper.sh ;USB_Move_File")
command2.OnlyExec("source " + config.AppInfo.ShellPath + "/helper.sh ;USB_Start_Auto")
}
}
func (s *systemService) GetSystemConfigDebug() []string {
@@ -283,6 +290,39 @@ func GetDeviceAllIP() []string {
}
return address
}
func (s *systemService) IsServiceRunning(name string) bool {
status := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;CheckServiceStatus smbd")
return strings.TrimSpace(status) == "running"
}
func (s *systemService) GetCPUTemperature() int {
outPut := ""
if file.Exists("/sys/class/thermal/thermal_zone0/temp") {
outPut = string(file.ReadFullFile("/sys/class/thermal/thermal_zone0/temp"))
} else if file.Exists("/sys/class/hwmon/hwmon0/temp1_input") {
outPut = string(file.ReadFullFile("/sys/class/hwmon/hwmon0/temp1_input"))
} else {
outPut = "0"
}
celsius, _ := strconv.Atoi(strings.TrimSpace(outPut))
if celsius > 1000 {
celsius = celsius / 1000
}
return celsius
}
func (s *systemService) GetCPUPower() map[string]string {
data := make(map[string]string, 2)
data["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
if file.Exists("/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj") {
data["value"] = strings.TrimSpace(string(file.ReadFullFile("/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj")))
} else {
data["value"] = "0"
}
return data
}
func NewSystemService() SystemService {
return &systemService{}
}

View File

@@ -1,99 +0,0 @@
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-03-18 11:40:55
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-12 10:05:37
* @FilePath: /CasaOS/service/user.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package service
import (
"io"
"mime/multipart"
"os"
"github.com/IceWhaleTech/CasaOS/service/model"
"gorm.io/gorm"
)
type UserService interface {
UpLoadFile(file multipart.File, name string) error
CreateUser(m model.UserDBModel) model.UserDBModel
GetUserCount() (userCount int64)
UpdateUser(m model.UserDBModel)
UpdateUserPassword(m model.UserDBModel)
GetUserInfoById(id string) (m model.UserDBModel)
GetUserAllInfoById(id string) (m model.UserDBModel)
GetUserAllInfoByName(userName string) (m model.UserDBModel)
DeleteUserById(id string)
DeleteAllUser()
GetUserInfoByUserName(userName string) (m model.UserDBModel)
GetAllUserName() (list []model.UserDBModel)
}
var UserRegisterHash = make(map[string]string)
type userService struct {
db *gorm.DB
}
func (u *userService) DeleteAllUser() {
u.db.Where("1=1").Delete(&model.UserDBModel{})
}
func (u *userService) DeleteUserById(id string) {
u.db.Where("id= ?", id).Delete(&model.UserDBModel{})
}
func (u *userService) GetAllUserName() (list []model.UserDBModel) {
u.db.Select("username").Find(&list)
return
}
func (u *userService) CreateUser(m model.UserDBModel) model.UserDBModel {
u.db.Create(&m)
return m
}
func (u *userService) GetUserCount() (userCount int64) {
u.db.Find(&model.UserDBModel{}).Count(&userCount)
return
}
func (u *userService) UpdateUser(m model.UserDBModel) {
u.db.Model(&m).Omit("password").Updates(&m)
}
func (u *userService) UpdateUserPassword(m model.UserDBModel) {
u.db.Model(&m).Update("password", m.Password)
}
func (u *userService) GetUserAllInfoById(id string) (m model.UserDBModel) {
u.db.Where("id= ?", id).First(&m)
return
}
func (u *userService) GetUserAllInfoByName(userName string) (m model.UserDBModel) {
u.db.Where("username= ?", userName).First(&m)
return
}
func (u *userService) GetUserInfoById(id string) (m model.UserDBModel) {
u.db.Select("username", "id", "role", "nickname", "description", "avatar", "email").Where("id= ?", id).First(&m)
return
}
func (u *userService) GetUserInfoByUserName(userName string) (m model.UserDBModel) {
u.db.Select("username", "id", "role", "nickname", "description", "avatar", "email").Where("username= ?", userName).First(&m)
return
}
//上传文件
func (c *userService) UpLoadFile(file multipart.File, url string) error {
out, _ := os.OpenFile(url, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
defer out.Close()
io.Copy(out, file)
return nil
}
//获取用户Service
func NewUserService(db *gorm.DB) UserService {
return &userService{db: db}
}

View File

@@ -1,6 +0,0 @@
# copy to /etc/udev/rules.d path
KERNEL=="sd[a-z]*[0-9]", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/bin/systemctl start usb-mount@%k.service"
KERNEL=="sd[a-z]*[0-9]", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/bin/systemctl stop usb-mount@%k.service"

View File

@@ -1,268 +0,0 @@
#!/bin/bash
###
# @Author: LinkLeong link@icewhale.com
# @Date: 2022-06-30 10:08:33
# @LastEditors: LinkLeong
# @LastEditTime: 2022-07-01 11:18:07
# @FilePath: /CasaOS/shell/update.sh
# @Description:
###
((EUID)) && sudo_cmd="sudo"
# SYSTEM INFO
readonly UNAME_M="$(uname -m)"
# CasaOS PATHS
readonly CASA_REPO=IceWhaleTech/CasaOS
readonly CASA_UNZIP_TEMP_FOLDER=/tmp/casaos
readonly CASA_BIN=casaos
readonly CASA_BIN_PATH=/usr/bin/casaos
readonly CASA_CONF_PATH=/etc/casaos.conf
readonly CASA_SERVICE_PATH=/etc/systemd/system/casaos.service
readonly CASA_HELPER_PATH=/usr/share/casaos/shell/
readonly CASA_USER_CONF_PATH=/var/lib/casaos/conf/
readonly CASA_DB_PATH=/var/lib/casaos/db/
readonly CASA_TEMP_PATH=/var/lib/casaos/temp/
readonly CASA_LOGS_PATH=/var/log/casaos/
readonly CASA_PACKAGE_EXT=".tar.gz"
readonly CASA_RELEASE_API="https://api.github.com/repos/${CASA_REPO}/releases"
readonly CASA_OPENWRT_DOCS="https://github.com/IceWhaleTech/CasaOS-OpenWrt"
readonly COLOUR_RESET='\e[0m'
readonly aCOLOUR=(
'\e[38;5;154m' # green | Lines, bullets and separators
'\e[1m' # Bold white | Main descriptions
'\e[90m' # Grey | Credits
'\e[91m' # Red | Update notifications Alert
'\e[33m' # Yellow | Emphasis
)
Target_Arch=""
Target_Distro="debian"
Target_OS="linux"
Casa_Tag=""
#######################################
# Custom printing function
# Globals:
# None
# Arguments:
# $1 0:OK 1:FAILED 2:INFO 3:NOTICE
# message
# Returns:
# None
#######################################
Show() {
# OK
if (($1 == 0)); then
echo -e "${aCOLOUR[2]}[$COLOUR_RESET${aCOLOUR[0]} OK $COLOUR_RESET${aCOLOUR[2]}]$COLOUR_RESET $2"
# FAILED
elif (($1 == 1)); then
echo -e "${aCOLOUR[2]}[$COLOUR_RESET${aCOLOUR[3]}FAILED$COLOUR_RESET${aCOLOUR[2]}]$COLOUR_RESET $2"
# INFO
elif (($1 == 2)); then
echo -e "${aCOLOUR[2]}[$COLOUR_RESET${aCOLOUR[0]} INFO $COLOUR_RESET${aCOLOUR[2]}]$COLOUR_RESET $2"
# NOTICE
elif (($1 == 3)); then
echo -e "${aCOLOUR[2]}[$COLOUR_RESET${aCOLOUR[4]}NOTICE$COLOUR_RESET${aCOLOUR[2]}]$COLOUR_RESET $2"
fi
}
Warn() {
echo -e "${aCOLOUR[3]}$1$COLOUR_RESET"
}
# 0 Check_exist
Check_Exist() {
#Create Dir
Show 2 "Create Folders."
${sudo_cmd} mkdir -p ${CASA_HELPER_PATH}
${sudo_cmd} mkdir -p ${CASA_LOGS_PATH}
${sudo_cmd} mkdir -p ${CASA_USER_CONF_PATH}
${sudo_cmd} mkdir -p ${CASA_DB_PATH}
${sudo_cmd} mkdir -p ${CASA_TEMP_PATH}
Show 2 "Start cleaning up the old version."
${sudo_cmd} rm -rf /usr/lib/systemd/system/casaos.service
${sudo_cmd} rm -rf /lib/systemd/system/casaos.service
if [[ -f "/casaOS/server/conf/conf.ini" ]]; then
${sudo_cmd} cp -rf /casaOS/server/conf/conf.ini ${CASA_CONF_PATH}
${sudo_cmd} cp -rf /casaOS/server/conf/*.json ${CASA_USER_CONF_PATH}
fi
if [[ -d "/casaOS/server/db" ]]; then
${sudo_cmd} cp -rf /casaOS/server/db/* ${CASA_DB_PATH}
fi
Show 0 "Clearance completed."
}
# 1 Check Arch
Check_Arch() {
case $UNAME_M in
*aarch64*)
Target_Arch="arm64"
;;
*64*)
Target_Arch="amd64"
;;
*armv7*)
Target_Arch="arm-7"
;;
*)
Show 1 "Aborted, unsupported or unknown architecture: $UNAME_M"
exit 1
;;
esac
Show 0 "Your hardware architecture is : $UNAME_M"
}
#Download CasaOS Package
Download_CasaOS() {
Show 2 "Downloading CasaOS for ${Target_OS}/${Target_Arch}..."
Net_Getter="curl -fsSLk"
Casa_Package="${Target_OS}-${Target_Arch}-casaos${CASA_PACKAGE_EXT}"
if [[ ! -n "$version" ]]; then
Casa_Tag="$(${Net_Getter} ${CASA_RELEASE_API}/latest | grep -o '"tag_name": ".*"' | sed 's/"//g' | sed 's/tag_name: //g')"
elif [[ $version == "pre" ]]; then
Casa_Tag="$(${net_getter} ${CASA_RELEASE_API} | grep -o '"tag_name": ".*"' | sed 's/"//g' | sed 's/tag_name: //g' | sed -n '1p')"
else
Casa_Tag="$version"
fi
Casa_Package_URL="https://github.com/${CASA_REPO}/releases/download/${Casa_Tag}/${Casa_Package}"
echo
# Remove Temp File
${sudo_cmd} rm -rf "$PREFIX/tmp/${Casa_Package}"
# Download Package
${Net_Getter} "${Casa_Package_URL}" >"$PREFIX/tmp/${Casa_Package}"
if [[ $? -ne 0 ]]; then
Show 1 "Download failed, Please check if your internet connection is working and retry."
exit 1
else
Show 0 "Download successful!"
fi
#Extract CasaOS Package
Show 2 "Extracting..."
case "${Casa_Package}" in
*.zip) ${sudo_cmd} unzip -o "$PREFIX/tmp/${Casa_Package}" -d "$PREFIX/tmp/" ;;
*.tar.gz) ${sudo_cmd} tar -xzf "$PREFIX/tmp/${Casa_Package}" -C "$PREFIX/tmp/" ;;
esac
#Setting Executable Permissions
${sudo_cmd} chmod +x "$PREFIX${CASA_UNZIP_TEMP_FOLDER}/${CASA_BIN}"
}
#Install Addons
Install_Addons() {
Show 2 "Installing CasaOS Addons"
${sudo_cmd} cp -rf "$PREFIX${CASA_UNZIP_TEMP_FOLDER}/shell/11-usb-mount.rules" "/etc/udev/rules.d/"
${sudo_cmd} cp -rf "$PREFIX${CASA_UNZIP_TEMP_FOLDER}/shell/usb-mount@.service" "/etc/systemd/system/"
sync
}
#Clean Temp Files
Clean_Temp_Files() {
Show 0 "Clean..."
${sudo_cmd} rm -rf "$PREFIX${CASA_UNZIP_TEMP_FOLDER}"
sync
}
#Install CasaOS
Install_CasaOS() {
Show 2 "Installing..."
# Install Bin
${sudo_cmd} mv -f $PREFIX${CASA_UNZIP_TEMP_FOLDER}/${CASA_BIN} ${CASA_BIN_PATH}
# Install Helper
if [[ -d ${CASA_HELPER_PATH} ]]; then
${sudo_cmd} rm -rf ${CASA_HELPER_PATH}*
fi
${sudo_cmd} cp -rf $PREFIX${CASA_UNZIP_TEMP_FOLDER}/shell/* ${CASA_HELPER_PATH}
#Setting Executable Permissions
${sudo_cmd} chmod +x $PREFIX${CASA_HELPER_PATH}*
# Install Conf
if [[ ! -f ${CASA_CONF_PATH} ]]; then
if [[ -f $PREFIX${CASA_UNZIP_TEMP_FOLDER}/conf/conf.ini.sample ]]; then
${sudo_cmd} mv -f $PREFIX${CASA_UNZIP_TEMP_FOLDER}/conf/conf.ini.sample ${CASA_CONF_PATH}
else
${sudo_cmd} mv -f $PREFIX${CASA_UNZIP_TEMP_FOLDER}/conf/conf.conf.sample ${CASA_CONF_PATH}
fi
fi
sync
if [[ ! -x "$(command -v ${CASA_BIN})" ]]; then
Show 1 "Installation failed, please try again."
exit 1
else
Show 0 "CasaOS Successfully installed."
fi
}
#Generate Service File
Generate_Service() {
if [ -f ${CASA_SERVICE_PATH} ]; then
Show 2 "Try stop CasaOS system service."
# Stop before generation
if [[ $(systemctl is-active ${CASA_BIN} &>/dev/null) ]]; then
${sudo_cmd} systemctl stop ${CASA_BIN}
fi
fi
Show 2 "Create system service for CasaOS."
${sudo_cmd} tee ${CASA_SERVICE_PATH} >/dev/null <<EOF
[Unit]
Description=CasaOS Service
StartLimitIntervalSec=0
[Service]
Type=simple
LimitNOFILE=15210
Restart=always
RestartSec=1
User=root
ExecStart=${CASA_BIN_PATH} -c ${CASA_CONF_PATH}
[Install]
WantedBy=multi-user.target
EOF
Show 0 "CasaOS service Successfully created."
}
# Start CasaOS
Start_CasaOS() {
Show 2 "Create a system startup service for CasaOS."
$sudo_cmd systemctl daemon-reload
$sudo_cmd systemctl enable ${CASA_BIN}
}
Check_Arch
# Step 7: Download CasaOS
Check_Exist
Download_CasaOS
# Step 8: Install Addon
Install_Addons
# Step 9: Install CasaOS
Install_CasaOS
# Step 10: Generate_Service
Generate_Service
# Step 11: Start CasaOS
Start_CasaOS
Clean_Temp_Files

View File

@@ -1,15 +1,14 @@
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-02-17 18:53:22
* @LastEditors: LinkLeong
* @LastEditTime: 2022-07-18 18:47:15
* @FilePath: /CasaOS/types/system.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
/*@Author: LinkLeong link@icewhale.com
*@Date: 2022-02-17 18:53:22
*@LastEditors: LinkLeong
*@LastEditTime: 2022-09-06 14:27:42
*@FilePath: /CasaOS/types/system.go
*@Description:
*@Website: https://www.casaos.io
*Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package types
const CURRENTVERSION = "0.3.4"
const CURRENTVERSION = "0.3.7"
const BODY = ""
const BODY = " "

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/ui/img/icon/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#363636;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:2;}
@media ( prefers-color-scheme: dark ) {
.st0{fill:none;stroke:#FFFFFF;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:2;}
}
</style>
<path class="st0" d="M12,22c5.5,0,10-4.5,10-10S17.5,2,12,2S2,6.5,2,12S6.5,22,12,22z"/>
<path class="st0" d="M12,22c3.9,0,7-3.1,7-7s-3.1-7-7-7s-7,3.1-7,7S8.1,22,12,22z"/>
<path class="st0" d="M12,22c2.2,0,4-1.8,4-4s-1.8-4-4-4s-4,1.8-4,4S9.8,22,12,22z"/>
</svg>

Before

Width:  |  Height:  |  Size: 863 B

View File

@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="linearGradient1911" x1="25.085" x2="25.085" y1="24.031" y2="26.412" gradientTransform="translate(-35.822,-21.385)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fcbc19" stop-opacity=".99608" offset="0"/>
<stop stop-color="#f4b61f" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient4625" x1=".52918" x2="16.404" y1="5.0665" y2="5.0665" gradientTransform="translate(-17.925)" gradientUnits="userSpaceOnUse">
<stop stop-color="#b78815" offset="0"/>
<stop stop-color="#e2b24b" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient1951" x1="100" x2="133.19" y1="17.453" y2="51.606" gradientTransform="matrix(.26458 0 0 .26458 -38.033 -.13539)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fce798" offset="0"/>
<stop stop-color="#ffc937" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient11110" x1=".52917" x2="16.404" y1="5.3815" y2="5.3815" gradientTransform="translate(-17.925)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" offset="0"/>
<stop stop-color="#fff" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient1119" x1="8.4665" x2="8.4665" y1="6.0853" y2="9.4879" gradientUnits="userSpaceOnUse">
<stop stop-color="#fddb7a" offset="0"/>
<stop stop-color="#ffd970" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient1284" x1="8.4665" x2="8.4665" y1="10.26" y2="13.229" gradientUnits="userSpaceOnUse">
<stop stop-color="#ffd96e" offset="0"/>
<stop stop-color="#ffd561" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient1272" x1="-3.9033" x2="-3.9033" y1="7.7839" y2="15.613" gradientTransform="translate(12.362 -2.1249)" gradientUnits="userSpaceOnUse">
<stop stop-color="#c68d00" offset="0"/>
<stop stop-color="#a67100" offset="1"/>
</linearGradient>
</defs>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
</rdf:RDF>
</metadata>
<g transform="translate(17.925 4.2e-5)">
<path d="m-16.728 2.2489c-0.3653 0-0.66145 0.32575-0.66145 0.72759v2.9636c-0.0026 0.02629-0.0072 0.052-0.0072 0.07906v7.9373c0 0.40184 0.29614 0.72759 0.66144 0.72759h14.552c0.36531 0 0.66144-0.32575 0.66144-0.72759v-9.393c0-0.40184-0.29614-0.72759-0.66144-0.72759v5.16e-4h-8.1993l-0.89864-1.1095s-0.36607-0.478-1.0583-0.478h-1.3229z" color="#000000" color-rendering="auto" dominant-baseline="auto" fill="url(#linearGradient1911)" image-rendering="auto" shape-rendering="auto" solid-color="#000000" stop-color="#000000" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;font-variation-settings:normal;inline-size:0;isolation:auto;mix-blend-mode:normal;shape-margin:0;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal"/>
<path d="m-9.5646 3.8364c-0.11312 0.0018-0.30848-0.01542-0.51986 0.09508-0.73647 0.37946-0.77226 0.59366-1.557 0.71415h-4.9608c-0.21986 0-0.41824 0.08855-0.56171 0.23202-0.13937 0.13936-0.22041 0.33333-0.2253 0.54569v0.2775c0.0049-0.21236 0.08593-0.40633 0.2253-0.54569 0.14347-0.14347 0.34185-0.23202 0.56171-0.23202h4.9608c0.78472-0.1205 0.82051-0.33469 1.557-0.71415 0.21138-0.1105 0.40673-0.09328 0.51986-0.09508h7.3828c0.18216 3.65e-4 0.3472 0.08101 0.46663 0.21239 0.1197 0.13167 0.19378 0.31377 0.19378 0.51469v-0.2775c0-0.20092-0.07408-0.38302-0.19378-0.51469-0.11943-0.13137-0.28447-0.21202-0.46663-0.21239h-1.1652zm-7.8247 2.1037c-0.0026 0.02629-0.0067 0.05201-0.0067 0.07906v0.2775c0-0.02705 0.0041-0.05277 0.0067-0.07906z" fill="url(#linearGradient4625)" opacity=".001" stroke-width=".26458"/>
<path d="m-17.396 13.674v0.28215c0 0.20092 0.07357 0.3825 0.19327 0.51417s0.28553 0.21342 0.46818 0.21342h14.552c0.18265 0 0.34796-0.08175 0.46766-0.21342s0.19378-0.31325 0.19378-0.51417v-0.28215c0 0.20092-0.07409 0.3825-0.19378 0.51417-0.1197 0.13167-0.28501 0.21342-0.46766 0.21342h-14.552c-0.18265 0-0.34848-0.08176-0.46818-0.21342s-0.19327-0.31325-0.19327-0.51417z" fill="#e4a729" fill-opacity=".99608" stroke-width=".26458"/>
<path d="m-10.383 4.0979s-0.35919 0.47868-1.1575 0.79218c-0.03271 0.00587-0.06509 0.011575-0.10077 0.017052h-4.9608c-0.21986 0-0.41824 0.088552-0.56171 0.23202-0.13937 0.13936-0.22042 0.33333-0.2253 0.54569v0.51675c-0.0026 0.02629-0.0067 0.05201-0.0067 0.07906v7.3953c0 0.20092 0.07357 0.3825 0.19327 0.51417s0.28553 0.21342 0.46818 0.21342h14.552c0.18265 0 0.34796-0.08175 0.46766-0.21342s0.19378-0.31325 0.19378-0.51417v-8.851c0-0.20092-0.07408-0.38302-0.19378-0.51469-0.11945-0.13137-0.28449-0.21202-0.46665-0.21239h-7.4821z" fill="url(#linearGradient1951)"/>
<path d="m-10.383 4.0979s-0.35919 0.47868-1.1575 0.79218c-0.03271 0.00587-0.06509 0.011575-0.10077 0.017052h-4.9608c-0.21986 0-0.41824 0.088552-0.56171 0.23202-0.13937 0.13936-0.22041 0.33333-0.2253 0.54569v0.38446c0.0049-0.21236 0.08593-0.40633 0.2253-0.54569 0.14347-0.14347 0.34185-0.23202 0.56171-0.23202h4.9608c0.03568-0.00548 0.06806-0.011184 0.10077-0.017052 0.75911-0.26303 1.2883-0.79218 1.2883-0.79218h8.0707c0.18216 3.704e-4 0.34718 0.081016 0.46663 0.21239 0.1197 0.13167 0.19378 0.31377 0.19378 0.51469v-0.38446c0-0.20092-0.07408-0.38302-0.19378-0.51469-0.11945-0.13137-0.28447-0.21202-0.46663-0.21239h-7.4821zm-7.0062 2.1037c-0.0026 0.02629-0.0067 0.05201-0.0067 0.07906v0.38446c0-0.02705 0.0041-0.05277 0.0067-0.07906z" fill="url(#linearGradient11110)" opacity=".3"/>
</g>
<circle cx="8.4665" cy="9.525" r="3.9687" fill="url(#linearGradient1272)" stroke-linecap="round" stroke-linejoin="round" stroke-width=".1726"/>
<g transform="matrix(.77756 0 0 .77756 10.567 2.0159)">
<g transform="matrix(1.0741 0 0 1.0741 -11.796 -.7153)">
<circle cx="8.4665" cy="7.8051" r="1.7198" fill="url(#linearGradient1119)" stroke-linecap="round" stroke-linejoin="round" stroke-width=".34395" style="paint-order:stroke fill markers"/>
<path d="m5.2916 11.064 5.4e-6 -0.14432c0-0.43295 0.43294-0.72158 0.72157-0.72158h4.9067c0.28863 0 0.72158 0.28863 0.72158 0.72158v0.14432c0 1.3096-1.416 2.1647-3.1749 2.1647-1.7589 0-3.1749-0.85514-3.1749-2.1647z" fill="url(#linearGradient1284)" stroke-width=".14049"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -1,67 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css"/>
<linearGradient id="linearGradient1951" x1="100" x2="133.19" y1="17.453" y2="51.606" gradientTransform="matrix(.26458 0 0 .24792 -20.108 .79576)" gradientUnits="userSpaceOnUse">
<stop stop-color="#26abe7" offset="0"/>
<stop stop-color="#0669bc" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient11110" x1=".52917" x2="16.404" y1="5.3815" y2="5.3815" gradientTransform="matrix(1 0 0 .93702 6.5e-7 .92263)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" offset="0"/>
<stop stop-color="#fff" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient1911" x1="25.085" x2="25.085" y1="24.031" y2="26.412" gradientTransform="translate(-17.897,-21.369)" gradientUnits="userSpaceOnUse">
<stop stop-color="#0271ca" offset="0"/>
<stop stop-color="#0768ba" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient9169" x1="7.4082" x2="7.4082" y1="4.2333" y2="5.5561" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" offset="0"/>
<stop stop-color="#e3e4e5" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient23227" x1=".52916" x2="16.404" y1="14.196" y2="14.196" gradientTransform="translate(-18.282 -.015458)" gradientUnits="userSpaceOnUse">
<stop stop-color="#0a5ba8" stop-opacity=".99608" offset="0"/>
<stop stop-color="#104a8c" stop-opacity=".99608" offset="1"/>
</linearGradient>
</defs>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
</rdf:RDF>
</metadata>
<path d="m1.1977 2.2644c-0.3653 0-0.66145 0.32575-0.66145 0.72759v2.9636c-0.0026 0.02629-0.0072 0.052-0.0072 0.07906v7.9373c0 0.40184 0.29614 0.72759 0.66144 0.72759h14.552c0.36531 0 0.66144-0.32575 0.66144-0.72759v-9.393c0-0.40184-0.29614-0.72759-0.66144-0.72759v5.16e-4h-8.1993l-0.89864-1.1095s-0.36607-0.478-1.0583-0.478h-1.3229z" color="#000000" color-rendering="auto" dominant-baseline="auto" fill="url(#linearGradient1911)" image-rendering="auto" shape-rendering="auto" solid-color="#000000" stop-color="#000000" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;font-variation-settings:normal;inline-size:0;isolation:auto;mix-blend-mode:normal;shape-margin:0;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal"/>
<g fill="#5e4aa6" stroke-width=".26458">
<circle cx="-330.35" cy="-328.38" r="0"/>
<circle cx="-312.11" cy="-326.25" r="0"/>
<circle cx="-306.02" cy="-333.07" r="0"/>
<circle cx="-308.84" cy="-326.01" r="0"/>
<circle cx="-328.8" cy="-330.45" r="0"/>
<g transform="translate(24.108 -4.592)">
<circle cx="-330.35" cy="-328.38" r="0"/>
<circle cx="-312.11" cy="-326.25" r="0"/>
<circle cx="-306.02" cy="-333.07" r="0"/>
<circle cx="-308.84" cy="-326.01" r="0"/>
<circle cx="-328.8" cy="-330.45" r="0"/>
</g>
<g transform="translate(2.3479 -14.944)">
<circle cx="-330.35" cy="-328.38" r="0"/>
<circle cx="-312.11" cy="-326.25" r="0"/>
<circle cx="-306.02" cy="-333.07" r="0"/>
<circle cx="-308.84" cy="-326.01" r="0"/>
<circle cx="-328.8" cy="-330.45" r="0"/>
</g>
<g transform="translate(.000295 -.00036978)">
<circle cx="-330.35" cy="-328.38" r="0"/>
<circle cx="-312.11" cy="-326.25" r="0"/>
<circle cx="-306.02" cy="-333.07" r="0"/>
<circle cx="-308.84" cy="-326.01" r="0"/>
<circle cx="-328.8" cy="-330.45" r="0"/>
</g>
</g>
<path d="m0.52916 13.691v0.28215c0 0.20092 0.07357 0.3825 0.19327 0.51417s0.28553 0.21342 0.46818 0.21342h14.552c0.18265 0 0.34796-0.08175 0.46766-0.21342s0.19378-0.31325 0.19378-0.51417v-0.28215c0 0.20092-0.07409 0.3825-0.19378 0.51417-0.1197 0.13167-0.28501 0.21342-0.46766 0.21342h-14.552c-0.18265 0-0.34848-0.08176-0.46818-0.21342s-0.19327-0.31325-0.19327-0.51417z" fill="url(#linearGradient23227)" stroke-width=".26458"/>
<rect x=".79373" y="4.2333" width="15.346" height="8.9957" ry=".79373" fill="url(#linearGradient9169)" stroke-linecap="round" stroke-linejoin="round" stroke-width=".1077"/>
<path d="m7.542 4.7624s-0.35919 0.44853-1.1575 0.74229c-0.03271 0.0055-0.06509 0.010846-0.10077 0.015978h-5.4967c-0.21986 0-0.41824 0.082975-0.56171 0.21741-0.13937 0.13058-0.22042 0.31234-0.2253 0.51133v0.48421c0 0.02369-0.0021296 0.045593 0 0.073482l0.52916 6.9301c0.014339 0.18779 0.07357 0.35841 0.19327 0.48179s0.28553 0.19998 0.46818 0.19998h14.552c0.18265 0 0.34796-0.0766 0.46766-0.19998s0.18179-0.29386 0.19378-0.48179l0.52916-8.2935c0.011991-0.18793-0.07408-0.35889-0.19378-0.48227-0.11945-0.1231-0.28449-0.19866-0.46665-0.19901h-8.0112z" fill="url(#linearGradient1951)"/>
<path d="m7.542 4.7624s-0.35919 0.44853-1.1575 0.74229c-0.03271 0.0055-0.06509 0.010846-0.10077 0.015978h-5.49c-0.21986 0-0.41824 0.082975-0.56171 0.21741-0.13937 0.13058-0.22041 0.31234-0.2253 0.51132l-0.0067094 0.36074c0.0049-0.19899 0.092639-0.38123 0.23201-0.51181 0.14347-0.13443 0.34185-0.21741 0.56171-0.21741h5.49c0.03568-0.00513 0.06806-0.01048 0.10077-0.015978 0.75911-0.24646 1.2883-0.74229 1.2883-0.74229h8.5998c0.18216 3.471e-4 0.34718 0.075914 0.46663 0.19901 0.1197 0.12338 0.19378 0.29401 0.19378 0.48227v-0.36025c0-0.18827-0.07408-0.35889-0.19378-0.48227-0.11945-0.1231-0.28447-0.19866-0.46663-0.19901h-8.0112z" fill="url(#linearGradient11110)" opacity=".3"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48" height="48" fill="white" fill-opacity="0.01"/><path d="M24 44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44Z" stroke="#333" stroke-width="4" stroke-linejoin="round"/><path d="M24 44C31.732 44 38 37.732 38 30C38 22.268 31.732 16 24 16C16.268 16 10 22.268 10 30C10 37.732 16.268 44 24 44Z" stroke="#333" stroke-width="4" stroke-linejoin="round"/><path d="M24 44C28.4183 44 32 40.4183 32 36C32 31.5817 28.4183 28 24 28C19.5817 28 16 31.5817 16 36C16 40.4183 19.5817 44 24 44Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/></svg>

Before

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

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