├── model ├── receive │ └── app.go ├── heart.go ├── notify.go ├── search.go ├── system_app │ └── sync.go ├── ddns.go ├── user.go ├── version.go ├── share.go ├── notify │ ├── result.go │ ├── message.go │ ├── storage.go │ ├── application.go │ └── file.go ├── docker.go ├── system_model │ └── verify_information.go ├── connections.go ├── category.go ├── zima.go ├── net.go ├── file.go ├── sys_common.go ├── smartctl_model.go ├── disk.go ├── manifest.go └── app.go ├── shell ├── assist.sh ├── usb-mount@.service ├── 11-usb-mount.rules ├── delete-old-service.sh └── usb-mount.sh ├── service ├── data_ handling.go ├── app_test.go ├── docker_base │ ├── model.go │ ├── common.go │ └── mysql.go ├── model │ ├── o_notify.go │ ├── o_shares.go │ ├── o_disk.go │ ├── o_connections.go │ ├── o_user.go │ ├── o_rely.go │ └── o_container.go ├── docker_test.go ├── rely.go ├── file_test.go ├── service.go ├── connections.go ├── user.go ├── file.go └── shares.go ├── web ├── robots.txt ├── img │ ├── USB.3ba78dec.png │ ├── disk.573d4b55.png │ ├── default.be7833db.png │ ├── gradient.1b76cb09.png │ ├── storage.d487ddb6.png │ ├── icon │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ └── safari-pinned-tab.svg │ ├── casa-dark.b6d17cf2.svg │ ├── Files.svg │ └── CasaConnect.svg ├── static.go ├── browserconfig.xml ├── site.webmanifest ├── js │ └── 14.js ├── favicon.svg └── index.html ├── snapshot.png ├── types ├── rely.go ├── friend.go ├── search.go ├── person_download.go ├── task.go ├── notify.go ├── system.go └── person.go ├── pkg ├── docker │ ├── emum.go │ ├── volumes_test.go │ └── volumes.go ├── utils │ ├── random │ │ ├── random_test.go │ │ └── random.go │ ├── port │ │ ├── port_test.go │ │ └── port.go │ ├── encryption │ │ └── md5_helper.go │ ├── ip_helper │ │ ├── ip_test.go │ │ └── ip.go │ ├── env_helper │ │ └── env.go │ ├── network_detection_test.go │ ├── network_detection.go │ ├── udev_helper.go │ ├── version │ │ └── version.go │ ├── jwt │ │ ├── jwt.go │ │ └── jwt_helper.go │ ├── file │ │ ├── block.go │ │ ├── reader.go │ │ └── image.go │ ├── loger │ │ └── log.go │ ├── command │ │ └── command_helper.go │ ├── httper │ │ └── httper.go │ └── common_err │ │ └── e.go ├── cache │ └── cache.go ├── ddns │ └── emum.go ├── sqlite │ ├── db_test.go │ └── db.go ├── config │ ├── config.go │ ├── update.go │ └── init.go ├── github │ └── github.go ├── gredis │ └── redis.go ├── quic_helper │ └── config.go └── samba │ └── smaba.go ├── snapshot-dark.jpg ├── snapshot-light.jpg ├── snapshot-mobile.png ├── .gitmodules ├── Makefile ├── .github ├── ISSUE_TEMPLATE │ ├── submit-application.md │ ├── config.yml │ ├── feature_request.md │ ├── bug_report.md │ ├── alpha_bug_report.yaml │ └── app_request.yaml └── workflows │ ├── move_alpha_bug_to_project.yml │ ├── add_issues_to_projects.yml │ ├── push_events_to_discord.yml │ ├── demo.yml │ └── casa.yml ├── route ├── darwin.go ├── ui.go ├── v1 │ ├── notify_old.go │ ├── samba_test.go │ ├── storage.go │ └── samba.go ├── socket.go └── init.go ├── conf └── conf.conf.sample ├── .gitignore ├── DEVELOPING.md ├── middleware └── gin.go ├── go.mod ├── main.go └── .all-contributorsrc /model/receive/app.go: -------------------------------------------------------------------------------- 1 | package receive 2 | -------------------------------------------------------------------------------- /shell/assist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | -------------------------------------------------------------------------------- /service/data_ handling.go: -------------------------------------------------------------------------------- 1 | package service 2 | -------------------------------------------------------------------------------- /web/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/snapshot.png -------------------------------------------------------------------------------- /types/rely.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const RELY_TYPE_MYSQL = iota 4 | -------------------------------------------------------------------------------- /pkg/docker/emum.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | const NETWORKNAME = "oasis" 4 | -------------------------------------------------------------------------------- /snapshot-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/snapshot-dark.jpg -------------------------------------------------------------------------------- /snapshot-light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/snapshot-light.jpg -------------------------------------------------------------------------------- /snapshot-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/snapshot-mobile.png -------------------------------------------------------------------------------- /web/img/USB.3ba78dec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/web/img/USB.3ba78dec.png -------------------------------------------------------------------------------- /web/img/disk.573d4b55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/web/img/disk.573d4b55.png -------------------------------------------------------------------------------- /web/img/default.be7833db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/web/img/default.be7833db.png -------------------------------------------------------------------------------- /web/img/gradient.1b76cb09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/web/img/gradient.1b76cb09.png -------------------------------------------------------------------------------- /web/img/storage.d487ddb6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/web/img/storage.d487ddb6.png -------------------------------------------------------------------------------- /web/img/icon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/web/img/icon/favicon-16x16.png -------------------------------------------------------------------------------- /web/img/icon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/web/img/icon/favicon-32x32.png -------------------------------------------------------------------------------- /web/img/icon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/web/img/icon/mstile-150x150.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "UI"] 2 | path = UI 3 | url = https://github.com/IceWhaleTech/CasaOS-UI.git 4 | branch = main -------------------------------------------------------------------------------- /web/img/icon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/web/img/icon/apple-touch-icon.png -------------------------------------------------------------------------------- /web/img/icon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfang/CasaOS/main/web/img/icon/android-chrome-192x192.png -------------------------------------------------------------------------------- /types/friend.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | FRIENDSTATEDEFAULT = iota 5 | FRIENDSTATEWAIT 6 | FRIENDSTATEREQUEST 7 | ) 8 | -------------------------------------------------------------------------------- /model/heart.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type CasaOSHeart struct { 4 | UuId string `json:"uuid"` 5 | Type string `json:"type"` 6 | } 7 | -------------------------------------------------------------------------------- /model/notify.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type NotifyMssage struct { 4 | Type string `json:"type"` 5 | Data string `json:"data"` 6 | } 7 | -------------------------------------------------------------------------------- /service/app_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetCasaOSCount(t *testing.T) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /types/search.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | APPLICATION = iota 5 | MEDIA 6 | PICTURE 7 | MUSIC 8 | SEARCH 9 | UNKNOWN 10 | ) 11 | -------------------------------------------------------------------------------- /model/search.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type SearchFileInfo struct { 4 | Path string `json:"path"` 5 | Name string `json:"name"` 6 | Type int `json:"type"` 7 | } 8 | -------------------------------------------------------------------------------- /pkg/docker/volumes_test.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetDir(t *testing.T) { 9 | fmt.Println(GetDir("", "config")) 10 | } 11 | -------------------------------------------------------------------------------- /web/static.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import "embed" 4 | 5 | //go:embed index.html favicon.svg browserconfig.xml site.webmanifest robots.txt img js fonts css 6 | var Static embed.FS 7 | -------------------------------------------------------------------------------- /model/system_app/sync.go: -------------------------------------------------------------------------------- 1 | package system_app 2 | 3 | import "encoding/xml" 4 | 5 | type SyncConfig struct { 6 | XMLName xml.Name `xml:"configuration"` 7 | Key string `xml:"gui>apikey"` 8 | } 9 | -------------------------------------------------------------------------------- /pkg/utils/random/random_test.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestRandomString(t *testing.T) { 9 | fmt.Println(RandomString(6, true)) 10 | } 11 | -------------------------------------------------------------------------------- /types/person_download.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | DOWNLOADAWAIT = iota //default state 5 | DOWNLOADING 6 | DOWNLOADPAUSE 7 | DOWNLOADFINISH 8 | DOWNLOADERROR 9 | DOWNLOADFINISHED 10 | ) 11 | -------------------------------------------------------------------------------- /pkg/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/patrickmn/go-cache" 7 | ) 8 | 9 | func Init() *cache.Cache { 10 | return cache.New(5*time.Minute, 60*time.Second) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/ddns/emum.go: -------------------------------------------------------------------------------- 1 | package ddns 2 | 3 | const ( 4 | GOGADDY = iota 5 | GOOGLE 6 | ) 7 | 8 | const ( 9 | A = "A" 10 | AAAA = "AAAA" 11 | ) 12 | 13 | const ( 14 | GODADDYAPIURL = "https://api.godaddy.com" 15 | ) 16 | -------------------------------------------------------------------------------- /model/ddns.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type GoDaddyModel struct { 4 | Type uint `json:"type"` 5 | ApiHost string `json:"api_host"` 6 | Key string `json:"key"` 7 | Secret string `json:"secret"` 8 | Host string `json:"host"` 9 | } 10 | -------------------------------------------------------------------------------- /model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type UserInfo struct { 4 | NickName string `json:"nick_name"` 5 | Desc string `json:"desc"` 6 | ShareId string `json:"share_id"` 7 | Avatar string `json:"avatar"` 8 | Version int `json:"version,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /pkg/docker/volumes.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import "strings" 4 | 5 | func GetDir(id, envName string) string { 6 | 7 | if strings.Contains(envName, "$AppID") && len(id) > 0 { 8 | return strings.ReplaceAll(envName, "$AppID", id) 9 | } 10 | return envName 11 | } 12 | -------------------------------------------------------------------------------- /shell/usb-mount@.service: -------------------------------------------------------------------------------- 1 | # copy to /etc/systemd/system path 2 | [Unit] 3 | Description=Mount USB Drive on %i 4 | [Service] 5 | Type=oneshot 6 | RemainAfterExit=true 7 | ExecStart=/casaOS/server/shell/usb-mount.sh add %i 8 | ExecStop=/casaOS/server/shell/usb-mount.sh remove %i 9 | -------------------------------------------------------------------------------- /types/task.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | TASK_TYPE_USER = iota 5 | TASK_TYPE_APP 6 | ) 7 | 8 | const ( 9 | TASK_DATA_TYPE_LINK = iota 10 | TASK_DATA_TYPE_TEXT 11 | ) 12 | 13 | const ( 14 | TASK_STATE_UNCOMPLETE = iota 15 | TASK_STATE_COMPLETED 16 | ) 17 | -------------------------------------------------------------------------------- /shell/11-usb-mount.rules: -------------------------------------------------------------------------------- 1 | 2 | # copy to /etc/udev/rules.d path 3 | 4 | KERNEL=="sd[a-z]*[0-9]", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/bin/systemctl start usb-mount@%k.service" 5 | 6 | KERNEL=="sd[a-z]*[0-9]", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/bin/systemctl stop usb-mount@%k.service" -------------------------------------------------------------------------------- /pkg/sqlite/db_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetDb(t *testing.T) { 8 | // fmt.Println(GetDb()) 9 | // db:=GetDb() 10 | // d:=model.DDNSTypeDBModel{ 11 | // Name: "test", 12 | // ApiHost: "http://www.google.com", 13 | // } 14 | // db.Create(&d) 15 | } 16 | -------------------------------------------------------------------------------- /web/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY:build build-ui build-backend help 2 | 3 | build: build-ui build-backend 4 | 5 | 6 | build-ui: 7 | cd CasaOS-UI && yarn install && yarn build 8 | 9 | build-backend: 10 | export CGO_ENABLED=1;export CGO_LDFLAGS=-static;go build -o ./casa main.go;upx --lzma --best casa 11 | 12 | help: 13 | @echo "call john" 14 | -------------------------------------------------------------------------------- /model/version.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Version struct { 6 | Id uint `gorm:"column:id;primary_key" json:"id"` 7 | ChangeLog string `json:"change_log"` 8 | Version string `json:"version"` 9 | CreatedAt time.Time `json:"created_at"` 10 | UpdatedAt time.Time `json:"updated_at"` 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/submit-application.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Submit application 3 | about: Add an app to this project 4 | title: '' 5 | labels: '' 6 | assignees: LinkLeong 7 | 8 | --- 9 | 10 | Tested platform 11 | e.g. linux/amd64,linux/arm-v7,linux-arm64 12 | 13 | 14 | 15 | Please export and upload the configuration file of this application 16 | -------------------------------------------------------------------------------- /service/docker_base/model.go: -------------------------------------------------------------------------------- 1 | package docker_base 2 | 3 | type MysqlConfig struct { 4 | DataBaseHost string `json:"database_host"` 5 | DataBasePort string `json:"database_port"` 6 | DataBaseUser string `json:"database_user"` 7 | DataBasePassword string `json:"data_base_password"` 8 | DataBaseDB string `json:"data_base_db"` 9 | } 10 | -------------------------------------------------------------------------------- /web/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/ui/img/icon/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "theme_color": "#ffffff", 12 | "background_color": "#ffffff", 13 | "display": "standalone" 14 | } 15 | -------------------------------------------------------------------------------- /types/notify.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | NOTIFY_DYNAMICE = iota 5 | NOTIFY_UNREAD 6 | NOTIFY_READ 7 | ) 8 | const ( 9 | NOTIFY_TYPE_UNIMPORTANT = iota + 1 10 | NOTIFY_TYPE_NEED_CONFIRM 11 | NOTIFY_TYPE_ERROR 12 | NOTIFY_TYPE_INSTALL_LOG 13 | NOTIFY_TYPE_PERSION_FIRNED_LEAVE 14 | NOTIFY_TYPE_PERSION_FIRNED_LIVE 15 | NOTIFY_TYPE_HEALTH_CHECK 16 | ) 17 | 18 | const ( 19 | NOTIFY_APP = iota 20 | ) 21 | -------------------------------------------------------------------------------- /types/system.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-02-17 18:53:22 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-25 10:49:46 6 | * @FilePath: /CasaOS/types/system.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package types 12 | 13 | const CURRENTVERSION = "0.3.5.1" 14 | 15 | const BODY = " " 16 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2021-09-30 18:18:14 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-06-21 11:09:30 6 | * @FilePath: /CasaOS/pkg/config/config.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package config 12 | 13 | const ( 14 | USERCONFIGURL = "/etc/casaos.conf" 15 | ) 16 | -------------------------------------------------------------------------------- /pkg/utils/port/port_test.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestPortAvailable(t *testing.T) { 9 | // fmt.Println(PortAvailable()) 10 | //fmt.Println(IsPortAvailable(6881,"tcp")) 11 | p, _ := GetAvailablePort("udp") 12 | fmt.Println("udp", p) 13 | fmt.Println(IsPortAvailable(p, "udp")) 14 | 15 | t1, _ := GetAvailablePort("tcp") 16 | fmt.Println("tcp", t1) 17 | fmt.Println(IsPortAvailable(t1, "tcp")) 18 | } 19 | -------------------------------------------------------------------------------- /model/share.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.org 3 | * @Date: 2022-07-26 11:12:12 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-27 14:58:55 6 | * @FilePath: /CasaOS/model/share.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | type Shares struct { 14 | ID uint `json:"id"` 15 | Anonymous bool `json:"anonymous"` 16 | Path string `json:"path"` 17 | } 18 | -------------------------------------------------------------------------------- /web/js/14.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[14],{"./node_modules/vue-plyr/dist/vue-plyr.css": 2 | /*!*************************************************!*\ 3 | !*** ./node_modules/vue-plyr/dist/vue-plyr.css ***! 4 | \*************************************************/ 5 | /*! no static exports found */function(module,exports,__webpack_require__){eval("// extracted by mini-css-extract-plugin\n if(false) { var cssReload; }\n \n\n//# sourceURL=webpack:///./node_modules/vue-plyr/dist/vue-plyr.css?")}}]); -------------------------------------------------------------------------------- /model/notify/result.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-26 14:21:11 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-05-27 11:15:59 6 | * @FilePath: /CasaOS/model/notify/result.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | 12 | package notify 13 | 14 | // Notify struct for Notify 15 | type NotifyModel struct { 16 | Data interface{} `json:"data"` 17 | State string `json:"state"` 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature/Enhancement Ideas 4 | url: https://github.com/IceWhaleTech/CasaOS/discussions/164 5 | about: Have an idea for a new feature/enhancement? 6 | - name: Questions, Discussions 7 | url: https://github.com/IceWhaleTech/CasaOS/discussions 8 | about: Ask questions, propose ideas, or discuss anything related to CasaOS 9 | - name: Discord 10 | url: https://discord.gg/knqAbbBbeX 11 | about: Get help or share great ideas on Discord! -------------------------------------------------------------------------------- /model/notify/message.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-26 14:39:22 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-05-26 19:08:52 6 | * @FilePath: /CasaOS/model/notify/message.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package notify 12 | 13 | import ( 14 | f "github.com/ambelovsky/gosf" 15 | ) 16 | 17 | type Message struct { 18 | Path string `json:"path"` 19 | Msg f.Message `json:"msg"` 20 | } 21 | -------------------------------------------------------------------------------- /route/darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | // +build darwin 3 | 4 | /* 5 | * @Author: LinkLeong link@icewhale.org 6 | * @Date: 2022-08-12 14:22:28 7 | * @LastEditors: LinkLeong 8 | * @LastEditTime: 2022-08-12 18:41:14 9 | * @FilePath: /CasaOS/route/darwin.go 10 | * @Description: 11 | * @Website: https://www.casaos.io 12 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 13 | */ 14 | 15 | package route 16 | 17 | func MonitoryUSB() { 18 | 19 | } 20 | func SendAllHardwareStatusBySocket() { 21 | 22 | } 23 | func SendUSBBySocket() { 24 | 25 | } 26 | -------------------------------------------------------------------------------- /model/docker.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2021-12-08 18:10:25 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-13 10:49:16 6 | * @FilePath: /CasaOS/model/docker.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | type DockerStatsModel struct { 14 | Icon string `json:"icon"` 15 | Title string `json:"title"` 16 | Data interface{} `json:"data"` 17 | Previous interface{} `json:"previous"` 18 | } 19 | -------------------------------------------------------------------------------- /pkg/utils/random/random.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | func RandomString(n int, onlyLetter bool) string { 9 | 10 | var letters []rune 11 | 12 | if onlyLetter { 13 | letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 14 | } else { 15 | letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 16 | } 17 | 18 | b := make([]rune, n) 19 | rand.Seed(time.Now().UnixNano()) 20 | for i := range b { 21 | b[i] = letters[rand.Intn(len(letters))] 22 | } 23 | return string(b) 24 | } 25 | -------------------------------------------------------------------------------- /model/system_model/verify_information.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-06-15 11:30:47 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-06-23 18:40:40 6 | * @FilePath: /CasaOS/model/system_model/verify_information.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package system_model 12 | 13 | type VerifyInformation struct { 14 | RefreshToken string `json:"refresh_token"` 15 | AccessToken string `json:"access_token"` 16 | ExpiresAt int64 `json:"expires_at"` 17 | } 18 | -------------------------------------------------------------------------------- /pkg/github/github.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "github.com/google/go-github/v36/github" 6 | "golang.org/x/oauth2" 7 | ) 8 | 9 | func GetGithubClient() *github.Client { 10 | ctx := context.Background() 11 | ts := oauth2.StaticTokenSource( 12 | &oauth2.Token{AccessToken: ""}, 13 | ) 14 | tc := oauth2.NewClient(ctx, ts) 15 | client := github.NewClient(tc) 16 | return client 17 | 18 | // list all repositories for the authenticated user 19 | //repos, _, err := client.Repositories.List(ctx, "", nil) 20 | 21 | //fmt.Print(err) 22 | //fmt.Print(repos) 23 | 24 | } 25 | -------------------------------------------------------------------------------- /service/model/o_notify.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type AppNotify struct { 4 | State int `json:"state"` //0:一直在变动的未读消息 1:未读 2:已读 5 | Message string `json:"message"` 6 | CreatedAt string `json:"created_at"` 7 | UpdatedAt string `json:"updated_at"` 8 | Id string `json:"id"` 9 | Type int `json:"type"` 10 | Icon string `json:"icon"` 11 | Name string `json:"name"` 12 | Class int `json:"class"` 13 | CustomId string `gorm:"column:custom_id;primary_key" json:"custom_id"` 14 | } 15 | 16 | func (p *AppNotify) TableName() string { 17 | return "o_notify" 18 | } 19 | -------------------------------------------------------------------------------- /pkg/utils/encryption/md5_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-06-14 14:33:25 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-06-14 14:33:49 6 | * @FilePath: /CasaOS/pkg/utils/encryption/md5_helper.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package encryption 12 | 13 | import ( 14 | "crypto/md5" 15 | "encoding/hex" 16 | ) 17 | 18 | func GetMD5ByStr(str string) string { 19 | h := md5.New() 20 | h.Write([]byte(str)) 21 | return hex.EncodeToString(h.Sum(nil)) 22 | } 23 | -------------------------------------------------------------------------------- /model/notify/storage.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-07-15 10:43:00 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-15 10:56:17 6 | * @FilePath: /CasaOS/model/notify/storage.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package notify 12 | 13 | type StorageMessage struct { 14 | Type string `json:"type"` //sata,usb 15 | Action string `json:"action"` //remove add 16 | Path string `json:"path"` 17 | Volume string `json:"volume"` 18 | Size uint64 `json:"size"` 19 | } 20 | -------------------------------------------------------------------------------- /conf/conf.conf.sample: -------------------------------------------------------------------------------- 1 | [app] 2 | PAGE_SIZE = 10 3 | RuntimeRootPath = runtime/ 4 | LogPath = /var/log/casaos/ 5 | LogSaveName = log 6 | LogFileExt = log 7 | DateStrFormat = 20060102 8 | DateTimeFormat = 2006-01-02 15:04:05 9 | TimeFormat = 15:04:05 10 | DateFormat = 2006-01-02 11 | DBPath = /var/lib/casaos 12 | ShellPath = /usr/share/casaos/shell 13 | UserDataPath = /var/lib/casaos/conf 14 | TempPath = /var/lib/casaos/temp 15 | 16 | [server] 17 | HttpPort = 80 18 | RunMode = release 19 | ServerApi = https://api.casaos.io/casaos-api 20 | Handshake = socket.casaos.io 21 | Token = 22 | USBAutoMount = 23 | 24 | [system] -------------------------------------------------------------------------------- /pkg/utils/ip_helper/ip_test.go: -------------------------------------------------------------------------------- 1 | package ip_helper 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "testing" 7 | ) 8 | 9 | func TestGetExternalIPV4(t *testing.T) { 10 | ipv4 := make(chan string) 11 | go func() { ipv4 <- GetExternalIPV4() }() 12 | fmt.Println(<-ipv4) 13 | } 14 | func TestGetExternalIPV6(t *testing.T) { 15 | ipv6 := make(chan string) 16 | go func() { ipv6 <- GetExternalIPV6() }() 17 | fmt.Println(<-ipv6) 18 | 19 | } 20 | 21 | func TestGetLoclIp(t *testing.T) { 22 | fmt.Println(GetLoclIp()) 23 | } 24 | func TestHasLocalIP(t *testing.T) { 25 | fmt.Println("dddd") 26 | fmt.Println(HasLocalIP(net.ParseIP("192.168.2.10"))) 27 | } 28 | -------------------------------------------------------------------------------- /model/connections.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.org 3 | * @Date: 2022-07-27 10:30:43 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-04 20:06:04 6 | * @FilePath: /CasaOS/model/connections.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | type Connections struct { 14 | ID uint `json:"id"` 15 | Username string `json:"username"` 16 | Password string `json:"password,omitempty"` 17 | Host string `json:"host"` 18 | Port string `json:"port"` 19 | MountPoint string `json:"mount_point"` 20 | } 21 | -------------------------------------------------------------------------------- /model/notify/application.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-27 15:01:58 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-05-31 14:51:21 6 | * @FilePath: /CasaOS/model/notify/application.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package notify 12 | 13 | type Application struct { 14 | Name string `json:"name"` 15 | State string `json:"state"` 16 | Type string `json:"type"` 17 | Icon string `json:"icon"` 18 | Message string `json:"message"` 19 | Finished bool `json:"finished"` 20 | Success bool `json:"success"` 21 | } 22 | -------------------------------------------------------------------------------- /pkg/utils/env_helper/env.go: -------------------------------------------------------------------------------- 1 | package env_helper 2 | 3 | import "strings" 4 | 5 | func ReplaceDefaultENV(key, tz string) string { 6 | temp := "" 7 | switch key { 8 | case "$DefaultPassword": 9 | temp = "casaos" 10 | case "$DefaultUserName": 11 | temp = "admin" 12 | 13 | case "$PUID": 14 | temp = "1000" 15 | case "$PGID": 16 | temp = "1000" 17 | case "$TZ": 18 | temp = tz 19 | } 20 | return temp 21 | } 22 | 23 | //replace env default setting 24 | func ReplaceStringDefaultENV(str string) string { 25 | return strings.ReplaceAll(strings.ReplaceAll(str, "$DefaultPassword", ReplaceDefaultENV("$DefaultPassword", "")), "$DefaultUserName", ReplaceDefaultENV("$DefaultUserName", "")) 26 | } 27 | -------------------------------------------------------------------------------- /route/ui.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-06-23 17:27:43 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-06-23 17:27:48 6 | * @FilePath: /CasaOS/route/ui.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package route 12 | 13 | import ( 14 | "html/template" 15 | 16 | "github.com/IceWhaleTech/CasaOS/web" 17 | "github.com/gin-gonic/gin" 18 | ) 19 | 20 | func WebUIHome(c *gin.Context) { 21 | c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8") 22 | index, _ := template.ParseFS(web.Static, "index.html") 23 | index.Execute(c.Writer, nil) 24 | return 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /service/docker_base/common.go: -------------------------------------------------------------------------------- 1 | package docker_base 2 | 3 | import "github.com/IceWhaleTech/CasaOS/model" 4 | 5 | //过滤mysql关键字 6 | func MysqlFilter(c MysqlConfig, envs model.EnvArray) model.EnvArray { 7 | for i := 0; i < len(envs); i++ { 8 | switch envs[i].Value { 9 | case "$MYSQL_HOST": 10 | envs[i].Value = c.DataBaseHost 11 | case "$MYSQL_PORT": 12 | envs[i].Value = c.DataBasePort 13 | case "$MYSQL_USERNAME": 14 | envs[i].Value = c.DataBaseUser 15 | case "$MYSQL_PASSWORD": 16 | envs[i].Value = c.DataBasePassword 17 | case "$MYSQL_DBNAME": 18 | envs[i].Value = c.DataBaseDB 19 | case "$MYSQL_HOST_AND_PORT": 20 | envs[i].Value = c.DataBaseHost + ":" + c.DataBasePort 21 | } 22 | } 23 | return envs 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Go template 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | ### Example user template template 19 | ### Example user template 20 | 21 | # IntelliJ project files 22 | .idea 23 | .vscode 24 | *.iml 25 | out 26 | gen 27 | /logs/ 28 | /sql/ 29 | /out/ 30 | /db/ 31 | /docs/ 32 | /web/ 33 | /conf/conf.ini 34 | /conf/conf.conf 35 | /conf/conf.json 36 | __debug_bin 37 | main 38 | CasaOS 39 | github.com 40 | .all-contributorsrc 41 | build 42 | dist 43 | .goreleaser.yaml -------------------------------------------------------------------------------- /model/notify/file.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-26 14:21:57 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-06-02 11:14:15 6 | * @FilePath: /CasaOS/model/notify/file.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package notify 12 | 13 | type File struct { 14 | Finished bool `json:"finished"` 15 | ProcessedSize int64 `json:"processed_size"` 16 | ProcessingPath string `json:"processing_path"` 17 | Status string `json:"status"` 18 | TotalSize int64 `json:"total_size"` 19 | Id string `json:"id"` 20 | To string `json:"to"` 21 | Type string `json:"type"` 22 | } 23 | -------------------------------------------------------------------------------- /web/img/casa-dark.b6d17cf2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /service/model/o_shares.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.org 3 | * @Date: 2022-07-26 11:17:17 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-27 15:25:07 6 | * @FilePath: /CasaOS/service/model/o_shares.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | type SharesDBModel struct { 14 | ID uint `gorm:"column:id;primary_key" json:"id"` 15 | Anonymous bool `json:"anonymous"` 16 | Path string `json:"path"` 17 | Name string `json:"name"` 18 | Updated int64 `gorm:"autoUpdateTime"` 19 | Created int64 `gorm:"autoCreateTime"` 20 | } 21 | 22 | func (p *SharesDBModel) TableName() string { 23 | return "o_shares" 24 | } 25 | -------------------------------------------------------------------------------- /model/category.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: link a624669980@163.com 3 | * @Date: 2022-05-16 17:37:08 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-13 10:46:38 6 | * @FilePath: /CasaOS/model/category.go 7 | * @Description: 8 | */ 9 | package model 10 | 11 | type ServerCategoryList struct { 12 | Version string `json:"version"` 13 | Item []CategoryList `json:"item"` 14 | } 15 | type CategoryList struct { 16 | Id uint `gorm:"column:id;primary_key" json:"id"` 17 | //CreatedAt time.Time `json:"created_at"` 18 | // 19 | //UpdatedAt time.Time `json:"updated_at"` 20 | Font string `json:"font"` // @tiger - 如果这个和前端有关,应该不属于后端的出参范围,而是前端去界定 21 | Name string `json:"name"` 22 | Count uint `json:"count"` // @tiger - count 属于动态信息,应该单独放在一个出参结构中(原因见另外一个关于 静态/动态 出参的注释) 23 | } 24 | -------------------------------------------------------------------------------- /types/person.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const PERSONADDFRIEND = "add_user" 4 | const PERSONAGREEFRIEND = "agree_user" 5 | const PERSONDOWNLOAD = "file_data" 6 | const PERSONSUMMARY = "summary" 7 | const PERSONGETIP = "get_ip" 8 | const PERSONCONNECTION = "connection" 9 | const PERSONDIRECTORY = "directory" 10 | const PERSONHELLO = "hello" 11 | const PERSONSHAREID = "share_id" 12 | const PERSONUPLOAD = "upload" 13 | const PERSONUPLOADDATA = "upload_data" 14 | const PERSONINTERNALINSPECTION = "internal_inspection" 15 | const PERSONPING = "ping" 16 | const PERSONIMAGETHUMBNAIL = "image_thumbnail" 17 | 18 | const PERSONCANCEL = "cancel" // Cancel Download 19 | 20 | const ( 21 | PERSONFILEDOWNLOAD = iota //default state 22 | PERSONFILEUPLOAD 23 | PERSONFILERECEIVEUPLOAD //receive upload file 24 | ) 25 | -------------------------------------------------------------------------------- /service/model/o_disk.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.org 3 | * @Date: 2021-12-07 17:14:41 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-17 18:46:43 6 | * @FilePath: /CasaOS/service/model/o_disk.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | //SerialAdvanced Technology Attachment (STAT) 14 | type SerialDisk struct { 15 | Id uint `gorm:"column:id;primary_key" json:"id"` 16 | UUID string `json:"uuid"` 17 | Path string `json:"path"` 18 | State int `json:"state"` 19 | MountPoint string `json:"mount_point"` 20 | CreatedAt int64 `json:"created_at"` 21 | } 22 | 23 | func (p *SerialDisk) TableName() string { 24 | return "o_disk" 25 | } 26 | -------------------------------------------------------------------------------- /pkg/utils/network_detection_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong a624669980@163.com 3 | * @Date: 2022-05-08 15:07:31 4 | * @LastEditors: LinkLeong a624669980@163.com 5 | * @LastEditTime: 2022-05-09 11:43:30 6 | * @FilePath: /CasaOS/pkg/utils/network_detection_test.go 7 | * @Description: 8 | * 9 | * Copyright (c) 2022 by LinkLeong a624669980@163.com, All Rights Reserved. 10 | */ 11 | 12 | package utils 13 | 14 | import ( 15 | "fmt" 16 | "testing" 17 | ) 18 | 19 | func TestGetResultTest(t *testing.T) { 20 | list := []string{"https://www.google.com", "https://www.bing.com", "https://www.baidu.com"} 21 | data := make(chan string) 22 | //data <- "init" 23 | for _, v := range list { 24 | go GetNetWorkTypeDetection(data, v) 25 | } 26 | result := <-data 27 | close(data) 28 | fmt.Println(result) 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '[Bug] ' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /pkg/gredis/redis.go: -------------------------------------------------------------------------------- 1 | package gredis 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/IceWhaleTech/CasaOS/model" 7 | "github.com/gomodule/redigo/redis" 8 | ) 9 | 10 | func GetRedisConn(m *model.RedisModel) *redis.Pool { 11 | redisConn := &redis.Pool{ 12 | MaxIdle: m.MaxIdle, 13 | MaxActive: m.MaxActive, 14 | IdleTimeout: m.IdleTimeout * time.Second, 15 | Dial: func() (redis.Conn, error) { 16 | c, err := redis.Dial("tcp", m.Host) 17 | if err != nil { 18 | return nil, err 19 | } 20 | if m.Password != "" { 21 | if _, err := c.Do("AUTH", m.Password); err != nil { 22 | c.Close() 23 | return nil, err 24 | } 25 | } 26 | return c, err 27 | }, 28 | TestOnBorrow: func(c redis.Conn, t time.Time) error { 29 | _, err := c.Do("PING") 30 | return err 31 | }, 32 | } 33 | return redisConn 34 | } 35 | -------------------------------------------------------------------------------- /pkg/utils/network_detection.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong a624669980@163.com 3 | * @Date: 2022-05-08 14:58:46 4 | * @LastEditors: LinkLeong a624669980@163.com 5 | * @LastEditTime: 2022-05-09 13:42:26 6 | * @FilePath: /CasaOS/pkg/utils/network_detection.go 7 | * @Description: 8 | * 9 | * Copyright (c) 2022 by LinkLeong a624669980@163.com, All Rights Reserved. 10 | */ 11 | package utils 12 | 13 | import natType "github.com/Curtis-Milo/nat-type-identifier-go" 14 | 15 | /** 16 | * @description: 17 | * @param {chanstring} data 18 | * @param {string} url 19 | * @return {*} 20 | */ 21 | func GetNetWorkTypeDetection(data chan string, url string) { 22 | // fmt.Println("url:", url) 23 | // httper.Get(url, nil) 24 | // aaa <- url 25 | result, err := natType.GetDeterminedNatType(true, 5, url) 26 | if err == nil { 27 | data <- result 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/move_alpha_bug_to_project.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Move alpha bug to project 4 | 5 | on: 6 | issues: 7 | types: 8 | - opened 9 | 10 | jobs: 11 | track_issue: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Generate token 15 | id: generate_token 16 | uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0 17 | with: 18 | app_id: ${{ secrets.ALPHA_BOT_ID }} 19 | private_key: ${{ secrets.ALPHA_BOT_PEM }} 20 | 21 | - name: Add Issue To GitHub Projects Beta 22 | uses: actions/add-to-project@v0.1.0 23 | with: 24 | project-url: https://github.com/orgs/IceWhaleTech/projects/5 25 | github-token: ${{ steps.generate_token.outputs.token }} 26 | labeled: alpha, bug 27 | label-operator: AND 28 | -------------------------------------------------------------------------------- /web/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /model/zima.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.org 3 | * @Date: 2022-05-13 18:15:46 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-01 18:32:57 6 | * @FilePath: /CasaOS/model/zima.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | import "time" 14 | 15 | type Path struct { 16 | Name string `json:"name"` //File name or document name 17 | Path string `json:"path"` //Full path to file or folder 18 | IsDir bool `json:"is_dir"` //Is it a folder 19 | Date time.Time `json:"date"` 20 | Size int64 `json:"size"` //File Size 21 | Type string `json:"type,omitempty"` 22 | Label string `json:"label,omitempty"` 23 | Write bool `json:"write"` 24 | Extensions map[string]interface{} `json:"extensions"` 25 | } 26 | -------------------------------------------------------------------------------- /pkg/utils/udev_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.org 3 | * @Date: 2022-08-10 16:06:12 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-10 16:11:37 6 | * @FilePath: /CasaOS/pkg/utils/udev_helper.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package utils 12 | 13 | // func getOptionnalMatcher() (matcher netlink.Matcher, err error) { 14 | // if filePath == nil || *filePath == "" { 15 | // return nil, nil 16 | // } 17 | 18 | // stream, err := ioutil.ReadFile(*filePath) 19 | // if err != nil { 20 | // return nil, err 21 | // } 22 | 23 | // if stream == nil { 24 | // return nil, fmt.Errorf("Empty, no rules provided in \"%s\", err: %w", *filePath, err) 25 | // } 26 | 27 | // var rules netlink.RuleDefinitions 28 | // if err := json.Unmarshal(stream, &rules); err != nil { 29 | // return nil, fmt.Errorf("Wrong rule syntax, err: %w", err) 30 | // } 31 | 32 | // return &rules, nil 33 | // } 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/alpha_bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: "[Alpha Only] Bug Report" 2 | description: CasaOS Alpha Testing specific bug report form. 3 | title: "[Alpha][Bug] " 4 | labels: ["alpha", "bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | > If you haven't joined CasaOS Alpha Team yet. 11 | > Please join first on [Discord](https://discord.gg/knqAbbBbeX) to be updated on the Alpha test plan and test scope. 12 | - type: textarea 13 | id: what-happened 14 | attributes: 15 | label: What happened? 16 | description: Also tell us, what did you expect to happen? 17 | placeholder: Tell us what you see! 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: screenshots 22 | attributes: 23 | label: Screenshots 24 | description: If applicable, add screenshots to help explain your problem. 25 | placeholder: Screenshots would be very helpful! 26 | 27 | -------------------------------------------------------------------------------- /service/model/o_connections.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.org 3 | * @Date: 2022-07-26 17:17:57 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-01 17:08:08 6 | * @FilePath: /CasaOS/service/model/o_connections.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | type ConnectionsDBModel struct { 14 | ID uint `gorm:"column:id;primary_key" json:"id"` 15 | Updated int64 `gorm:"autoUpdateTime"` 16 | Created int64 `gorm:"autoCreateTime"` 17 | Username string `json:"username"` 18 | Password string `json:"password"` 19 | Host string `json:"host"` 20 | Port string `json:"port"` 21 | Status string `json:"status"` 22 | Directories string `json:"directories"` // string array 23 | MountPoint string `json:"mount_point"` //parent directory of mount point 24 | } 25 | 26 | func (p *ConnectionsDBModel) TableName() string { 27 | return "o_connections" 28 | } 29 | -------------------------------------------------------------------------------- /model/net.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type IOCountersStat struct { 4 | Name string `json:"name"` // interface name 5 | BytesSent uint64 `json:"bytesSent"` // number of bytes sent 6 | BytesRecv uint64 `json:"bytesRecv"` // number of bytes received 7 | PacketsSent uint64 `json:"packetsSent"` // number of packets sent 8 | PacketsRecv uint64 `json:"packetsRecv"` // number of packets received 9 | Errin uint64 `json:"errin"` // total number of errors while receiving 10 | Errout uint64 `json:"errout"` // total number of errors while sending 11 | Dropin uint64 `json:"dropin"` // total number of incoming packets which were dropped 12 | Dropout uint64 `json:"dropout"` // total number of outgoing packets which were dropped (always 0 on OSX and BSD) 13 | Fifoin uint64 `json:"fifoin"` // total number of FIFO buffers errors while receiving 14 | Fifoout uint64 `json:"fifoout"` // total number of FIFO buffers errors while sending 15 | State string `json:"state"` 16 | Time int64 `json:"time"` 17 | } 18 | -------------------------------------------------------------------------------- /pkg/utils/version/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-13 18:15:46 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-21 15:27:53 6 | * @FilePath: /CasaOS/pkg/utils/version/version.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package version 12 | 13 | import ( 14 | "strconv" 15 | "strings" 16 | 17 | "github.com/IceWhaleTech/CasaOS/model" 18 | "github.com/IceWhaleTech/CasaOS/types" 19 | ) 20 | 21 | func IsNeedUpdate(version model.Version) (bool, model.Version) { 22 | 23 | v1 := strings.Split(version.Version, ".") 24 | 25 | v2 := strings.Split(types.CURRENTVERSION, ".") 26 | 27 | for len(v1) < len(v2) { 28 | v1 = append(v1, "0") 29 | } 30 | for len(v2) < len(v1) { 31 | v2 = append(v2, "0") 32 | } 33 | for i := 0; i < len(v1); i++ { 34 | a, _ := strconv.Atoi(v1[i]) 35 | b, _ := strconv.Atoi(v2[i]) 36 | if a > b { 37 | return true, version 38 | } 39 | if a < b { 40 | return false, version 41 | } 42 | } 43 | return false, version 44 | } 45 | -------------------------------------------------------------------------------- /service/model/o_user.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-13 18:15:46 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-11 17:57:00 6 | * @FilePath: /CasaOS/service/model/o_user.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | import "time" 14 | 15 | //Soon to be removed 16 | type UserDBModel struct { 17 | Id int `gorm:"column:id;primary_key" json:"id"` 18 | Username string `json:"username"` 19 | Password string `json:"password,omitempty"` 20 | Role string `json:"role"` 21 | Email string `json:"email"` 22 | Nickname string `json:"nickname"` 23 | Avatar string `json:"avatar"` 24 | Description string `json:"description"` 25 | CreatedAt time.Time `gorm:"<-:create;autoCreateTime" json:"created_at,omitempty"` 26 | UpdatedAt time.Time `gorm:"<-:create;<-:update;autoUpdateTime" json:"updated_at,omitempty"` 27 | } 28 | 29 | func (p *UserDBModel) TableName() string { 30 | return "o_users" 31 | } 32 | -------------------------------------------------------------------------------- /pkg/config/update.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/IceWhaleTech/CasaOS/pkg/utils/file" 7 | ) 8 | 9 | //检查目录是否存在 10 | func mkdirDATAAll() { 11 | sysType := runtime.GOOS 12 | var dirArray []string 13 | if sysType == "linux" { 14 | dirArray = []string{"/DATA/AppData", "/DATA/Documents", "/DATA/Downloads", "/DATA/Gallery", "/DATA/Media/Movies", "/DATA/Media/TV Shows", "/DATA/Media/Music"} 15 | } 16 | 17 | if sysType == "windows" { 18 | 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"} 19 | } 20 | if sysType == "darwin" { 21 | 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"} 22 | } 23 | 24 | for _, v := range dirArray { 25 | file.IsNotExistMkDir(v) 26 | } 27 | 28 | } 29 | 30 | func UpdateSetup() { 31 | mkdirDATAAll() 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/add_issues_to_projects.yml: -------------------------------------------------------------------------------- 1 | name: Add Issues To Projects 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | add-issues: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Generate token 13 | id: generate_token 14 | uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0 15 | with: 16 | app_id: ${{ secrets.ALPHA_BOT_ID }} 17 | private_key: ${{ secrets.ALPHA_BOT_PEM }} 18 | 19 | - name: Add Alpha Bug Issue To project 20 | uses: actions/add-to-project@v0.3.0 21 | with: 22 | github-token: ${{ steps.generate_token.outputs.token }} 23 | project-url: https://github.com/orgs/IceWhaleTech/projects/5 24 | labeled: alpha, bug 25 | label-operator: AND 26 | 27 | - name: Add App Request Issue To project 28 | uses: actions/add-to-project@v0.3.0 29 | with: 30 | github-token: ${{ steps.generate_token.outputs.token }} 31 | project-url: https://github.com/orgs/IceWhaleTech/projects/8 32 | labeled: "App Request" 33 | label-operator: AND 34 | -------------------------------------------------------------------------------- /model/file.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-20 16:27:12 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-06-09 18:18:46 6 | * @FilePath: /CasaOS/model/file.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | type FileOperate struct { 14 | Type string `json:"type" binding:"required"` 15 | Item []FileItem `json:"item" binding:"required"` 16 | TotalSize int64 `json:"total_size"` 17 | ProcessedSize int64 `json:"processed_size"` 18 | To string `json:"to" binding:"required"` 19 | Style string `json:"style"` 20 | Finished bool `json:"finished"` 21 | } 22 | 23 | type FileItem struct { 24 | From string `json:"from" binding:"required"` 25 | Finished bool `json:"finished"` 26 | Size int64 `json:"size"` 27 | ProcessedSize int64 `json:"processed_size"` 28 | } 29 | 30 | type FileUpdate struct { 31 | FilePath string `json:"path" binding:"required"` 32 | FileContent string `json:"content" binding:"required"` 33 | } 34 | -------------------------------------------------------------------------------- /service/docker_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | //func TestDockerImageInfo(t *testing.T) { 9 | // //DockerImageInfo() 10 | // 11 | // address, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:0", "0.0.0.0")) 12 | // if err != nil { 13 | // fmt.Println(0, err) 14 | // } 15 | // 16 | // listener, err := net.ListenTCP("tcp", address) 17 | // if err != nil { 18 | // fmt.Println(0, err) 19 | // } 20 | // 21 | // defer listener.Close() 22 | // fmt.Println(listener.Addr().(*net.TCPAddr).Port, nil) 23 | // 24 | //} 25 | 26 | //func TestDockerNetwork(t *testing.T) { 27 | // DockerNetwork() 28 | //} 29 | // 30 | //func TestDockerPull(t *testing.T) { 31 | // DockerPull() 32 | //} 33 | // 34 | //func TestDockerLog(t *testing.T) { 35 | // DockerLog() 36 | //} 37 | //func TestDockerLogs(t *testing.T) { 38 | // DockerLogs() 39 | //} 40 | 41 | func TestDockerContainerStats(t *testing.T) { 42 | fmt.Println(DockerContainerStats1()) 43 | } 44 | 45 | //func TestDockerImageRemove(t *testing.T) { 46 | // host, domain, tld := gotld.GetSubdomain("aaa.liru-05.top", 1) 47 | // fmt.Println(host) 48 | // fmt.Println(domain) 49 | // fmt.Println(tld) 50 | //} 51 | -------------------------------------------------------------------------------- /service/model/o_rely.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | "github.com/IceWhaleTech/CasaOS/service/docker_base" 7 | "time" 8 | ) 9 | 10 | type RelyDBModel struct { 11 | Id uint `gorm:"column:id;primary_key" json:"id"` 12 | CustomId string ` json:"custom_id"` 13 | ContainerCustomId string `json:"container_custom_id"` 14 | Config MysqlConfigs `json:"config"` 15 | ContainerId string `json:"container_id,omitempty"` 16 | Type int `json:"type"` //目前暂未使用 17 | CreatedAt time.Time `gorm:"<-:create" json:"created_at"` 18 | UpdatedAt time.Time `gorm:"<-:create;<-:update" json:"updated_at"` 19 | } 20 | 21 | /****************使gorm支持[]string结构*******************/ 22 | type MysqlConfigs docker_base.MysqlConfig 23 | 24 | func (c MysqlConfigs) Value() (driver.Value, error) { 25 | b, err := json.Marshal(c) 26 | return string(b), err 27 | } 28 | 29 | func (c *MysqlConfigs) Scan(input interface{}) error { 30 | return json.Unmarshal(input.([]byte), c) 31 | } 32 | 33 | /****************使gorm支持[]string结构*******************/ 34 | 35 | func (p RelyDBModel) TableName() string { 36 | return "o_rely" 37 | } 38 | -------------------------------------------------------------------------------- /service/rely.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2021-09-30 18:18:14 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-06-02 18:00:57 6 | * @FilePath: /CasaOS/service/rely.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package service 12 | 13 | import ( 14 | model2 "github.com/IceWhaleTech/CasaOS/service/model" 15 | "gorm.io/gorm" 16 | ) 17 | 18 | type RelyService interface { 19 | Create(rely model2.RelyDBModel) 20 | Delete(id string) 21 | GetInfo(id string) model2.RelyDBModel 22 | } 23 | 24 | type relyService struct { 25 | db *gorm.DB 26 | } 27 | 28 | func (r *relyService) Create(rely model2.RelyDBModel) { 29 | 30 | r.db.Create(&rely) 31 | 32 | } 33 | 34 | //获取我的应用列表 35 | func (r *relyService) GetInfo(id string) model2.RelyDBModel { 36 | var m model2.RelyDBModel 37 | r.db.Where("custom_id = ?", id).First(&m) 38 | 39 | // @tiger - 作为出参不应该直接返回数据库内的格式(见类似问题的注释) 40 | return m 41 | } 42 | 43 | func (r *relyService) Delete(id string) { 44 | var c model2.RelyDBModel 45 | r.db.Where("custom_id = ?", id).Delete(&c) 46 | } 47 | 48 | func NewRelyService(db *gorm.DB) RelyService { 49 | return &relyService{db: db} 50 | } 51 | -------------------------------------------------------------------------------- /pkg/quic_helper/config.go: -------------------------------------------------------------------------------- 1 | package quic_helper 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "math/big" 10 | 11 | "github.com/lucas-clemente/quic-go" 12 | ) 13 | 14 | // Setup a bare-bones TLS config for the server 15 | func GetGenerateTLSConfig(token string) *tls.Config { 16 | key, err := rsa.GenerateKey(rand.Reader, 1024) 17 | if err != nil { 18 | panic(err) 19 | } 20 | template := x509.Certificate{SerialNumber: big.NewInt(1)} 21 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) 22 | if err != nil { 23 | panic(err) 24 | } 25 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) 26 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 27 | 28 | tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) 29 | if err != nil { 30 | panic(err) 31 | } 32 | return &tls.Config{ 33 | Certificates: []tls.Certificate{tlsCert}, 34 | NextProtos: []string{token}, 35 | SessionTicketsDisabled: true, 36 | } 37 | } 38 | func GetClientTlsConfig(otherToken string) *tls.Config { 39 | return &tls.Config{ 40 | InsecureSkipVerify: true, 41 | NextProtos: []string{otherToken}, 42 | SessionTicketsDisabled: true, 43 | } 44 | } 45 | 46 | func GetQUICConfig() *quic.Config { 47 | return &quic.Config{ 48 | ConnectionIDLength: 4, 49 | KeepAlive: true, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /web/img/icon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /pkg/utils/port/port.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // 获取可用端口 9 | func GetAvailablePort(t string) (int, error) { 10 | address := fmt.Sprintf("%s:0", "0.0.0.0") 11 | if t == "udp" { 12 | add, err := net.ResolveUDPAddr(t, address) 13 | if err != nil { 14 | return 0, err 15 | } 16 | 17 | listener, err := net.ListenUDP(t, add) 18 | if err != nil { 19 | return 0, err 20 | } 21 | 22 | defer listener.Close() 23 | return listener.LocalAddr().(*net.UDPAddr).Port, nil 24 | } else { 25 | add, err := net.ResolveTCPAddr(t, address) 26 | if err != nil { 27 | return 0, err 28 | } 29 | 30 | listener, err := net.ListenTCP(t, add) 31 | if err != nil { 32 | return 0, err 33 | } 34 | 35 | defer listener.Close() 36 | return listener.Addr().(*net.TCPAddr).Port, nil 37 | } 38 | 39 | } 40 | 41 | // 判断端口是否可以(未被占用) 42 | // param t tcp/udp 43 | func IsPortAvailable(port int, t string) bool { 44 | address := fmt.Sprintf("%s:%d", "0.0.0.0", port) 45 | if t == "udp" { 46 | sadd, err := net.ResolveUDPAddr("udp", address) 47 | uc, err := net.ListenUDP("udp", sadd) 48 | 49 | if err != nil { 50 | fmt.Println(err.Error()) 51 | return false 52 | } else { 53 | defer uc.Close() 54 | return true 55 | } 56 | 57 | } else { 58 | listener, err := net.Listen(t, address) 59 | 60 | if err != nil { 61 | //log.Infof("port %s is taken: %s", address, err) 62 | return false 63 | } 64 | defer listener.Close() 65 | return true 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # CasaOS Development 2 | Here we will describe the steps required to setup a development environment with CasaOS. 3 | 4 | - [Setting up development environment](#setting-up-development-environment) 5 | - [Pre-requisites](#pre-requisites) 6 | - [1. Fork the Repo](#1.-fork-the-repo) 7 | - [2. Clone the repo down](#2.-clone-the-repo-down) 8 | - [3. Install dependencies](#3.-install-dependencies) 9 | 10 | 11 | ## Setting up a development environment 12 | In this section we will walk you through the general process of setting up your development environment to get started. 13 | 14 | ### Pre-requisites 15 | The following must be installed in order to get started. The details of how to install them is outside the scope of this doc, but generally they should be able to be installed with your systems package manager (apt, yum, brew, choco, etc). 16 | - Go > v1.17.0 17 | - yarn 18 | - node.js 19 | 20 | ### 1. Fork the Repo 21 | [Fork the repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo) onto your own GitHub account for developing. 22 | 23 | ### 2. Clone the repo down 24 | 1. Navigate into your go workspace (check with `go env GOPATH`). 25 | 2. Navigate to the appropriate path for github. It should look something like this: `/github.com//`. If it doesn't exist create it. 26 | 3. Clone down the repo with the following: `git clone --recurse-submodules --remote-submodules https://github.com//CasaOS.git` 27 | 28 | ### 3. Install dependencies 29 | 1. `cd UI` 30 | 2. `yarn install` 31 | 3. `yarn build` 32 | 4. `cd ..` 33 | 5. `go get` 34 | -------------------------------------------------------------------------------- /route/v1/notify_old.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/IceWhaleTech/CasaOS/service" 8 | "github.com/IceWhaleTech/CasaOS/types" 9 | "github.com/gin-gonic/gin" 10 | "github.com/gorilla/websocket" 11 | ) 12 | 13 | var upGrader = websocket.Upgrader{ 14 | CheckOrigin: func(r *http.Request) bool { 15 | return true 16 | }, 17 | } 18 | 19 | // @Summary websocket 接口,连接成功后发送一个"notify"字符串 20 | // @Produce application/json 21 | // @Accept application/json 22 | // @Tags notify 23 | // @Security ApiKeyAuth 24 | // @Param token path string true "token" 25 | // @Success 200 {string} string "ok" 26 | // @Router /notify/ws [get] 27 | func NotifyWS(c *gin.Context) { 28 | //升级get请求为webSocket协议 29 | ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) 30 | if err != nil { 31 | return 32 | } 33 | defer ws.Close() 34 | service.WebSocketConns = append(service.WebSocketConns, ws) 35 | 36 | if !service.SocketRun { 37 | service.SocketRun = true 38 | service.SendMeg() 39 | } 40 | for { 41 | mt, message, err := ws.ReadMessage() 42 | fmt.Println(mt, message, err) 43 | } 44 | 45 | } 46 | 47 | // @Summary 标记notify已读 48 | // @Produce application/json 49 | // @Accept application/json 50 | // @Tags notify 51 | // @Security ApiKeyAuth 52 | // @Success 200 {string} string "ok" 53 | // @Router /notify/read/{id} [put] 54 | func PutNotifyRead(c *gin.Context) { 55 | id := c.Param("id") 56 | // if len(id) == 0 { 57 | // c.JSON(http.StatusOK, model.Result{Success: oasis_err.INVALID_PARAMS, Message: oasis_err.GetMsg(oasis_err.INVALID_PARAMS)}) 58 | // return 59 | // } 60 | fmt.Println(id) 61 | service.MyService.Notify().MarkRead(id, types.NOTIFY_READ) 62 | } 63 | -------------------------------------------------------------------------------- /pkg/utils/jwt/jwt.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2021-09-30 18:18:14 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-18 17:30:38 6 | * @FilePath: /CasaOS/pkg/utils/jwt/jwt.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package jwt 12 | 13 | import ( 14 | "time" 15 | 16 | jwt "github.com/golang-jwt/jwt/v4" 17 | ) 18 | 19 | type Claims struct { 20 | Username string `json:"username"` 21 | PassWord string `json:"password"` 22 | Id int `json:"id"` 23 | jwt.RegisteredClaims 24 | } 25 | 26 | var jwtSecret []byte 27 | 28 | //创建token 29 | func GenerateToken(username, password string, id int, issuer string, t time.Duration) (string, error) { 30 | clims := Claims{ 31 | username, 32 | password, 33 | id, 34 | jwt.RegisteredClaims{ 35 | ExpiresAt: jwt.NewNumericDate(time.Now().Add(t)), 36 | IssuedAt: jwt.NewNumericDate(time.Now()), 37 | NotBefore: jwt.NewNumericDate(time.Now()), 38 | Issuer: issuer, 39 | }, 40 | } 41 | 42 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, clims) 43 | token, err := tokenClaims.SignedString(jwtSecret) 44 | return token, err 45 | 46 | } 47 | 48 | //解析token 49 | func ParseToken(token string, valid bool) (*Claims, error) { 50 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { 51 | return jwtSecret, nil 52 | }) 53 | if tokenClaims != nil { 54 | if clims, ok := tokenClaims.Claims.(*Claims); ok { 55 | if valid && tokenClaims.Valid { 56 | return clims, nil 57 | } else if !valid { 58 | return clims, nil 59 | } 60 | } 61 | } 62 | return nil, err 63 | } 64 | -------------------------------------------------------------------------------- /pkg/samba/smaba.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.org 3 | * @Date: 2022-07-27 10:35:29 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-01 13:56:44 6 | * @FilePath: /CasaOS/pkg/samba/smaba.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package samba 12 | 13 | import ( 14 | "errors" 15 | "net" 16 | 17 | "github.com/hirochachacha/go-smb2" 18 | ) 19 | 20 | func ConnectSambaService(host, port, username, password, directory string) error { 21 | conn, err := net.Dial("tcp", host+":"+port) 22 | if err != nil { 23 | return err 24 | } 25 | defer conn.Close() 26 | d := &smb2.Dialer{ 27 | Initiator: &smb2.NTLMInitiator{ 28 | User: username, 29 | Password: password, 30 | }, 31 | } 32 | 33 | s, err := d.Dial(conn) 34 | if err != nil { 35 | return err 36 | } 37 | defer s.Logoff() 38 | names, err := s.ListSharenames() 39 | if err != nil { 40 | return err 41 | } 42 | 43 | for _, name := range names { 44 | if name == directory { 45 | return nil 46 | } 47 | } 48 | return errors.New("directory not found") 49 | } 50 | 51 | //get share name list 52 | func GetSambaSharesList(host, port, username, password string) ([]string, error) { 53 | conn, err := net.Dial("tcp", host+":"+port) 54 | if err != nil { 55 | return nil, err 56 | } 57 | defer conn.Close() 58 | d := &smb2.Dialer{ 59 | Initiator: &smb2.NTLMInitiator{ 60 | User: username, 61 | Password: password, 62 | }, 63 | } 64 | 65 | s, err := d.Dial(conn) 66 | if err != nil { 67 | return nil, err 68 | } 69 | defer s.Logoff() 70 | names, err := s.ListSharenames() 71 | if err != nil { 72 | return nil, err 73 | } 74 | return names, err 75 | } 76 | -------------------------------------------------------------------------------- /service/file_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | var ctx context.Context 14 | var cancel context.CancelFunc 15 | 16 | func TestNewInteruptReader(t *testing.T) { 17 | ctx, cancel = context.WithCancel(context.Background()) 18 | 19 | go func() { 20 | // 在初始上下文的基础上创建一个有取消功能的上下文 21 | // ctx, cancel := context.WithCancel(ctx) 22 | fmt.Println("开始") 23 | fIn, err := os.Open("/Users/liangjianli/Downloads/demo_data.tar.gz") 24 | if err != nil { 25 | 26 | } 27 | defer fIn.Close() 28 | fmt.Println("创建新文件") 29 | fOut, err := os.Create("/Users/liangjianli/Downloads/demo_data1.tar.gz") 30 | if err != nil { 31 | fmt.Println(err) 32 | } 33 | 34 | defer fOut.Close() 35 | 36 | fmt.Println("准备复制") 37 | // _, err = io.Copy(out, NewReader(ctx, f)) 38 | // time.Sleep(time.Second * 2) 39 | //ctx.Done() 40 | // cancel() 41 | 42 | // interrupt context after 500ms 43 | 44 | // interrupt context with SIGTERM (CTRL+C) 45 | //sigs := make(chan os.Signal, 1) 46 | //signal.Notify(sigs, os.Interrupt) 47 | 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | 52 | // Reader that fails when context is canceled 53 | in := NewReader(ctx, fIn) 54 | // Writer that fails when context is canceled 55 | out := NewWriter(ctx, fOut) 56 | 57 | //time.Sleep(2 * time.Second) 58 | 59 | //cancel() 60 | 61 | n, err := io.Copy(out, in) 62 | log.Println(n, "bytes copied.") 63 | if err != nil { 64 | fmt.Println("Err:", err) 65 | } 66 | 67 | fmt.Println("Closing.") 68 | }() 69 | 70 | go func() { 71 | //<-sigs 72 | time.Sleep(time.Second) 73 | fmt.Println("退出") 74 | ddd() 75 | }() 76 | time.Sleep(time.Second * 10) 77 | } 78 | 79 | func ddd() { 80 | cancel() 81 | } 82 | -------------------------------------------------------------------------------- /pkg/utils/ip_helper/ip.go: -------------------------------------------------------------------------------- 1 | package ip_helper 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | httper2 "github.com/IceWhaleTech/CasaOS/pkg/utils/httper" 8 | ) 9 | 10 | func IsIPv4(address string) bool { 11 | return strings.Count(address, ":") < 2 12 | } 13 | func IsIPv6(address string) bool { 14 | return strings.Count(address, ":") >= 2 15 | } 16 | 17 | //获取外网ip 18 | func GetExternalIPV4() string { 19 | return httper2.Get("https://api.ipify.org", nil) 20 | } 21 | 22 | //获取外网ip 23 | func GetExternalIPV6() string { 24 | return httper2.Get("https://api6.ipify.org", nil) 25 | } 26 | 27 | //获取本地ip 28 | func GetLoclIp() string { 29 | addrs, err := net.InterfaceAddrs() 30 | if err != nil { 31 | return "127.0.0.1" 32 | } 33 | for _, address := range addrs { 34 | if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 35 | if ipnet.IP.To4() != nil { 36 | return ipnet.IP.String() 37 | } 38 | 39 | } 40 | } 41 | return "127.0.0.1" 42 | } 43 | func GetDeviceAllIP(port string) []string { 44 | var address []string 45 | addrs, err := net.InterfaceAddrs() 46 | if err != nil { 47 | return address 48 | } 49 | for _, a := range addrs { 50 | if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { 51 | if ipNet.IP.To16() != nil { 52 | address = append(address, ipNet.IP.String()+":"+port) 53 | } 54 | } 55 | } 56 | return address 57 | } 58 | 59 | func HasLocalIP(ip net.IP) bool { 60 | if ip.IsLoopback() { 61 | return true 62 | } 63 | ip.String() 64 | 65 | ip4 := ip.To4() 66 | if ip4 == nil { 67 | return false 68 | } 69 | 70 | return ip4[0] == 10 || // 10.0.0.0/8 71 | (ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31) || // 172.16.0.0/12 72 | (ip4[0] == 169 && ip4[1] == 254) || // 169.254.0.0/16 73 | (ip4[0] == 192 && ip4[1] == 168) // 192.168.0.0/16 74 | } 75 | -------------------------------------------------------------------------------- /route/socket.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-23 17:18:56 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-06-09 21:48:10 6 | * @FilePath: /CasaOS/route/socket.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package route 12 | 13 | import ( 14 | "strconv" 15 | "time" 16 | 17 | "github.com/IceWhaleTech/CasaOS/model/notify" 18 | "github.com/IceWhaleTech/CasaOS/pkg/config" 19 | "github.com/IceWhaleTech/CasaOS/pkg/utils/port" 20 | "github.com/IceWhaleTech/CasaOS/service" 21 | f "github.com/ambelovsky/gosf" 22 | ) 23 | 24 | func SocketInit(msg chan notify.Message) { 25 | 26 | // set socket port 27 | socketPort := 0 28 | if len(config.ServerInfo.SocketPort) == 0 { 29 | socketPort, _ = port.GetAvailablePort("tcp") 30 | config.ServerInfo.SocketPort = strconv.Itoa(socketPort) 31 | config.Cfg.Section("server").Key("SocketPort").SetValue(strconv.Itoa(socketPort)) 32 | config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath) 33 | } else { 34 | socketPort, _ = strconv.Atoi(config.ServerInfo.SocketPort) 35 | if !port.IsPortAvailable(socketPort, "tcp") { 36 | socketPort, _ := port.GetAvailablePort("tcp") 37 | config.ServerInfo.SocketPort = strconv.Itoa(socketPort) 38 | config.Cfg.Section("server").Key("SocketPort").SetValue(strconv.Itoa(socketPort)) 39 | config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath) 40 | } 41 | } 42 | 43 | f.OnConnect(func(c *f.Client, request *f.Request) { 44 | service.ClientCount += 1 45 | }) 46 | f.OnDisconnect(func(c *f.Client, request *f.Request) { 47 | service.ClientCount -= 1 48 | }) 49 | go func(msg chan notify.Message) { 50 | for v := range msg { 51 | f.Broadcast("", v.Path, &v.Msg) 52 | time.Sleep(time.Millisecond * 100) 53 | } 54 | 55 | }(msg) 56 | 57 | f.Startup(map[string]interface{}{ 58 | "port": socketPort}) 59 | 60 | } 61 | -------------------------------------------------------------------------------- /.github/workflows/push_events_to_discord.yml: -------------------------------------------------------------------------------- 1 | name: Push Events to Discord 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | issue_comment: 8 | types: 9 | - created 10 | discussion: 11 | types: 12 | - created 13 | - transferred 14 | - answered 15 | discussion_comment: 16 | types: 17 | - created 18 | 19 | jobs: 20 | push-events: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | 26 | - name: General Discussions & Comments 27 | if: ${{ ( github.event_name == 'discussion' || github.event_name == 'discussion_comment' ) && github.event.discussion.category.name == 'General' }} 28 | uses: joseph-montanez/forward-event-action@v3.0.0 29 | with: 30 | webhook: ${{ secrets.Discord_CasaOS_General_Webhook }} 31 | 32 | - name: App Request Issues & Comments 33 | if: ${{ ( github.event_name == 'issues' || github.event_name == 'issue_comment' ) && contains(github.event.issue.labels.*.name, 'App Request') }} 34 | uses: joseph-montanez/forward-event-action@v3.0.0 35 | with: 36 | webhook: ${{ secrets.Discord_CasaOS_App_Request_Webhook }} 37 | 38 | - name: Bug Issues & Comments 39 | if: ${{ ( github.event_name == 'issues' || github.event_name == 'issue_comment' ) && contains(github.event.issue.labels.*.name, 'bug') && !contains(github.event.issue.labels.*.name, 'alpha') }} 40 | uses: joseph-montanez/forward-event-action@v3.0.0 41 | with: 42 | webhook: ${{ secrets.Discord_CasaOS_Bug_Webhook }} 43 | 44 | - name: Alpha Issues & Comments 45 | if: ${{ ( github.event_name == 'issues' || github.event_name == 'issue_comment' ) && contains(github.event.issue.labels.*.name, 'alpha') }} 46 | uses: joseph-montanez/forward-event-action@v3.0.0 47 | with: 48 | webhook: ${{ secrets.Discord_CasaOS_Alpha_Webhook }} 49 | -------------------------------------------------------------------------------- /model/sys_common.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-13 18:15:46 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-14 11:02:06 6 | * @FilePath: /CasaOS/model/sys_common.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | import "time" 14 | 15 | //系统配置 16 | type SysInfoModel struct { 17 | Name string //系统名称 18 | } 19 | 20 | //用户相关 21 | type UserModel struct { 22 | UserName string 23 | PWD string 24 | Token string 25 | Head string 26 | Email string 27 | Description string 28 | Initialized bool 29 | Avatar string 30 | NickName string 31 | Public string 32 | } 33 | 34 | //服务配置 35 | type ServerModel struct { 36 | HttpPort string 37 | RunMode string 38 | ServerApi string 39 | LockAccount bool 40 | Token string 41 | USBAutoMount string 42 | SocketPort string 43 | } 44 | 45 | //服务配置 46 | type APPModel struct { 47 | LogPath string 48 | LogSaveName string 49 | LogFileExt string 50 | DateStrFormat string 51 | DateTimeFormat string 52 | UserDataPath string 53 | TimeFormat string 54 | DateFormat string 55 | DBPath string 56 | ShellPath string 57 | TempPath string 58 | } 59 | 60 | //公共返回模型 61 | type Result struct { 62 | Success int `json:"success" example:"200"` 63 | Message string `json:"message" example:"ok"` 64 | Data interface{} `json:"data" example:"返回结果"` 65 | } 66 | 67 | //redis配置文件 68 | type RedisModel struct { 69 | Host string 70 | Password string 71 | MaxIdle int 72 | MaxActive int 73 | IdleTimeout time.Duration 74 | } 75 | 76 | type SystemConfig struct { 77 | ConfigPath string `json:"config_path"` 78 | } 79 | 80 | type CasaOSGlobalVariables struct { 81 | AppChange bool 82 | } 83 | 84 | type FileSetting struct { 85 | ShareDir []string `json:"share_dir" delim:"|"` 86 | DownloadDir string `json:"download_dir"` 87 | } 88 | -------------------------------------------------------------------------------- /pkg/utils/file/block.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "io" 7 | "math" 8 | "os" 9 | "strconv" 10 | ) 11 | 12 | // Get info of block 13 | func GetBlockInfo(fileSize int64) (blockSize int, length int) { 14 | switch { 15 | case fileSize <= 1<<28: //256M 16 | blockSize = 1 << 17 //128kb 17 | case fileSize <= 1<<29: //512M 18 | blockSize = 1 << 18 //256kb 19 | case fileSize <= 1<<30: //1G 20 | blockSize = 1 << 19 //512kb 21 | case fileSize <= 1<<31: //2G 22 | blockSize = 1 << 20 //(mb) 23 | case fileSize <= 1<<32: //4G 24 | blockSize = 1 << 21 //2mb 25 | case fileSize <= 1<<33: //8G 26 | blockSize = 1 << 22 //4mb 27 | case fileSize <= 1<<34: //16g 28 | blockSize = 1 << 23 //8mb 29 | default: 30 | blockSize = 1 << 24 //16mb 31 | } 32 | temp := float64(fileSize) / float64(blockSize) 33 | length = int(math.Ceil(temp)) 34 | return 35 | } 36 | 37 | //get the hash of the data 38 | func GetHashByContent(data []byte) string { 39 | sum := md5.Sum(data) 40 | return hex.EncodeToString(sum[:]) 41 | } 42 | 43 | //get the hash of the data 44 | func GetHashByPath(path string) string { 45 | pFile, err := os.Open(path) 46 | if err != nil { 47 | return "" 48 | } 49 | defer pFile.Close() 50 | md5h := md5.New() 51 | io.Copy(md5h, pFile) 52 | return hex.EncodeToString(md5h.Sum(nil)) 53 | } 54 | 55 | //Comparison data hash 56 | func ComparisonHash(data []byte, hash string) bool { 57 | sum := md5.Sum(data) 58 | return hex.EncodeToString(sum[:]) == hash 59 | } 60 | 61 | //get prefix byte length 62 | func PrefixLength(byteLength int) []byte { 63 | lengthByte := []byte{'0', '0', '0', '0', '0', '0'} 64 | bSize := strconv.Itoa(byteLength) 65 | cha := 6 - len(bSize) 66 | for i := len(bSize); i > 0; i-- { 67 | lengthByte[cha+i-1] = bSize[i-1] 68 | } 69 | return lengthByte 70 | } 71 | 72 | //get data byte length 73 | func DataLength(length int) []byte { 74 | lengthByte := []byte{'0', '0', '0', '0', '0', '0', '0', '0'} 75 | bSize := strconv.Itoa(length) 76 | cha := 8 - len(bSize) 77 | for i := len(bSize); i > 0; i-- { 78 | lengthByte[cha+i-1] = bSize[i-1] 79 | } 80 | return lengthByte 81 | } 82 | -------------------------------------------------------------------------------- /middleware/gin.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2021-10-08 10:29:08 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-22 11:06:07 6 | * @FilePath: /CasaOS/middleware/gin.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package middleware 12 | 13 | import ( 14 | "fmt" 15 | "net/http" 16 | "strings" 17 | 18 | "github.com/IceWhaleTech/CasaOS/pkg/utils/loger" 19 | "github.com/gin-gonic/gin" 20 | "go.uber.org/zap" 21 | ) 22 | 23 | func Cors() gin.HandlerFunc { 24 | return func(c *gin.Context) { 25 | method := c.Request.Method 26 | 27 | c.Header("Access-Control-Allow-Origin", "*") 28 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") 29 | //允许跨域设置可以返回其他子段,可以自定义字段 30 | c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,Language,Content-Type,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Access-Control-Allow-Methods,Connection,Host,Origin,Referer,User-Agent,X-Requested-With") 31 | // 允许浏览器(客户端)可以解析的头部 (重要) 32 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers") 33 | //c.Writer.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Content-Length, X-CSRF-Token, Token, session, Origin, Host, Connection, Accept-Encoding, Accept-Language, X-Requested-With") 34 | //设置缓存时间 35 | c.Header("Access-Control-Max-Age", "172800") 36 | c.Header("Access-Control-Allow-Credentials", "true") 37 | c.Set("Content-Type", "application/json") 38 | //} 39 | 40 | //允许类型校验 41 | if method == "OPTIONS" { 42 | c.JSON(http.StatusOK, "ok!") 43 | } 44 | 45 | defer func() { 46 | if err := recover(); err != nil { 47 | fmt.Println(err) 48 | } 49 | }() 50 | 51 | c.Next() 52 | } 53 | } 54 | func WriteLog() gin.HandlerFunc { 55 | return func(c *gin.Context) { 56 | if !strings.Contains(c.Request.URL.String(), "password") { 57 | loger.Info("request:", zap.Any("path", c.Request.URL.String()), zap.Any("param", c.Params), zap.Any("query", c.Request.URL.Query()), zap.Any("method", c.Request.Method)) 58 | c.Next() 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /route/v1/samba_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.org 3 | * @Date: 2022-08-02 15:10:56 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-02 16:58:42 6 | * @FilePath: /CasaOS/route/v1/samba_test.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package v1 12 | 13 | import ( 14 | "net/http" 15 | "net/http/httptest" 16 | "testing" 17 | 18 | "github.com/gin-gonic/gin" 19 | "github.com/golang/mock/gomock" 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder { 24 | req, _ := http.NewRequest(method, path, nil) 25 | w := httptest.NewRecorder() 26 | r.ServeHTTP(w, req) 27 | return w 28 | } 29 | 30 | // func TestHelloWorld(t *testing.T) { 31 | // // Build our expected body 32 | // body := gin.H{ 33 | // "hello": "world", 34 | // } 35 | // // Grab our router 36 | // router := "SetupRouter()" 37 | // // Perform a GET request with that handler. 38 | // w := performRequest(router, "GET", "/") 39 | // // Assert we encoded correctly, 40 | // // the request gives a 200 41 | // assert.Equal(t, http.StatusOK, w.Code) 42 | // // Convert the JSON response to a map 43 | // var response map[string]string 44 | // err := json.Unmarshal([]byte(w.Body.String()), &response) 45 | // // Grab the value & whether or not it exists 46 | // value, exists := response["hello"] 47 | // // Make some assertions on the correctness of the response. 48 | // assert.Nil(t, err) 49 | // assert.True(t, exists) 50 | // assert.Equal(t, body["hello"], value) 51 | // } 52 | 53 | func TestGetSambaSharesList(t *testing.T) { 54 | gin.SetMode(gin.TestMode) 55 | ctrl := gomock.NewController(t) 56 | defer ctrl.Finish() 57 | executeWithContext := func() *httptest.ResponseRecorder { 58 | response := httptest.NewRecorder() 59 | con, ginEngine := gin.CreateTestContext(response) 60 | 61 | requestUrl := "/v1/samba/shares" 62 | httpRequest, _ := http.NewRequest("GET", requestUrl, nil) 63 | GetSambaSharesList(con) 64 | ginEngine.ServeHTTP(response, httpRequest) 65 | return response 66 | } 67 | 68 | t.Run("Happy", func(t *testing.T) { 69 | res := executeWithContext() 70 | assert.Equal(t, http.StatusOK, res.Code) 71 | }) 72 | 73 | } 74 | -------------------------------------------------------------------------------- /pkg/sqlite/db.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-13 18:15:46 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-27 11:25:26 6 | * @FilePath: /CasaOS/pkg/sqlite/db.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package sqlite 12 | 13 | import ( 14 | "time" 15 | 16 | "github.com/IceWhaleTech/CasaOS/pkg/utils/file" 17 | "github.com/IceWhaleTech/CasaOS/pkg/utils/loger" 18 | model2 "github.com/IceWhaleTech/CasaOS/service/model" 19 | "go.uber.org/zap" 20 | "gorm.io/driver/sqlite" 21 | "gorm.io/gorm" 22 | ) 23 | 24 | var gdb *gorm.DB 25 | 26 | func GetDb(dbPath string) *gorm.DB { 27 | if gdb != nil { 28 | return gdb 29 | } 30 | // Refer https://github.com/go-sql-driver/mysql#dsn-data-source-name 31 | //dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", m.User, m.PWD, m.IP, m.Port, m.DBName) 32 | //db, err := gorm.Open(mysql2.Open(dsn), &gorm.Config{}) 33 | file.IsNotExistMkDir(dbPath) 34 | db, err := gorm.Open(sqlite.Open(dbPath+"/casaOS.db"), &gorm.Config{}) 35 | c, _ := db.DB() 36 | c.SetMaxIdleConns(10) 37 | c.SetMaxOpenConns(100) 38 | c.SetConnMaxIdleTime(time.Second * 1000) 39 | if err != nil { 40 | loger.Error("sqlite connect error", zap.Any("db connect error", err)) 41 | panic("sqlite connect error") 42 | return nil 43 | } 44 | gdb = db 45 | 46 | db.Exec(`alter table o_user rename to old_user; 47 | 48 | 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); 49 | 50 | insert into o_users select id,user_name,password,role,email,nick_name,avatar,description,created_at,updated_at from old_user; 51 | 52 | drop table old_user; 53 | drop table o_user; 54 | `) 55 | 56 | err = db.AutoMigrate(&model2.AppNotify{}, &model2.AppListDBModel{}, &model2.SerialDisk{}, model2.UserDBModel{}, model2.SharesDBModel{}, model2.ConnectionsDBModel{}) 57 | db.Exec("DROP TABLE IF EXISTS o_application") 58 | db.Exec("DROP TABLE IF EXISTS o_friend") 59 | db.Exec("DROP TABLE IF EXISTS o_person_download") 60 | db.Exec("DROP TABLE IF EXISTS o_person_down_record") 61 | if err != nil { 62 | loger.Error("check or create db error", zap.Any("error", err)) 63 | } 64 | return db 65 | } 66 | -------------------------------------------------------------------------------- /pkg/utils/jwt/jwt_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-06-17 14:01:25 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-29 16:22:25 6 | * @FilePath: /CasaOS/pkg/utils/jwt/jwt_helper.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package jwt 12 | 13 | import ( 14 | "fmt" 15 | "strconv" 16 | "time" 17 | 18 | "github.com/IceWhaleTech/CasaOS/model" 19 | "github.com/IceWhaleTech/CasaOS/pkg/utils/common_err" 20 | loger2 "github.com/IceWhaleTech/CasaOS/pkg/utils/loger" 21 | "github.com/gin-gonic/gin" 22 | ) 23 | 24 | func JWT() gin.HandlerFunc { 25 | return func(c *gin.Context) { 26 | var code int 27 | code = common_err.SUCCESS 28 | token := c.GetHeader("Authorization") 29 | if len(token) == 0 { 30 | token = c.Query("token") 31 | } 32 | if token == "" { 33 | code = common_err.INVALID_PARAMS 34 | } 35 | 36 | claims, err := ParseToken(token, false) 37 | 38 | //_, err := ParseToken(token) 39 | if err != nil { 40 | code = common_err.ERROR_AUTH_TOKEN 41 | } 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) { 42 | //Special treatment 43 | } else if !claims.VerifyExpiresAt(time.Now(), true) || !claims.VerifyIssuer("casaos", true) { 44 | code = common_err.ERROR_AUTH_TOKEN 45 | } 46 | if code != common_err.SUCCESS { 47 | c.JSON(code, model.Result{Success: code, Message: common_err.GetMsg(code)}) 48 | c.Abort() 49 | return 50 | } 51 | c.Request.Header.Add("user_id", strconv.Itoa(claims.Id)) 52 | c.Next() 53 | } 54 | } 55 | 56 | //get AccessToken 57 | func GetAccessToken(username, pwd string, id int) string { 58 | token, err := GenerateToken(username, pwd, id, "casaos", 3*time.Hour*time.Duration(1)) 59 | if err == nil { 60 | return token 61 | } else { 62 | loger2.Error(fmt.Sprintf("Get Token Fail: %V", err)) 63 | return "" 64 | } 65 | } 66 | 67 | func GetRefreshToken(username, pwd string, id int) string { 68 | token, err := GenerateToken(username, pwd, id, "refresh", 7*24*time.Hour*time.Duration(1)) 69 | if err == nil { 70 | return token 71 | } else { 72 | loger2.Error(fmt.Sprintf("Get Token Fail: %V", err)) 73 | return "" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | CasaOS 22 | 23 | 24 | 25 | 26 | 30 |
31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/app_request.yaml: -------------------------------------------------------------------------------- 1 | name: "App Request" 2 | description: "Request to add an app to the app store." 3 | title: "[App Request] AppName" 4 | labels: ["App Request"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | ### ❤ Thanks for taking the time to fill out this app request! 10 | > 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. 11 | > 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. 12 | > If you have any questions, please ask them on [Discord](https://discord.gg/knqAbbBbeX) or [Github Discussions](https://github.com/IceWhaleTech/CasaOS/discussions). 13 | 14 | - type: textarea 15 | id: app-info 16 | attributes: 17 | label: "App Information" 18 | description: "The formal information of this app, as detailed as possible." 19 | value: | 20 | - Name: 21 | - Short Description: 22 | - Official Website: 23 | - GitHub Repository: 24 | - Docker Image: 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | id: why 30 | attributes: 31 | label: "Why do you want this app?" 32 | description: "Detailed notes can help developers and others understand the importance of this app." 33 | placeholder: | 34 | As a [what role], it helps me solve [what problem], and especially [what function] is great! 35 | or 36 | It solves [what problem] and especially [what feature] works well, which is hard to do with other app. 37 | or 38 | This is the app that [some device/service] must use and will not work without it. 39 | or 40 | others 41 | 42 | - type: textarea 43 | id: additional-info 44 | attributes: 45 | label: "Additional information?" 46 | description: "Anything else you want to share with the developers and others?" 47 | placeholder: | 48 | Example: 49 | - Noteworthy matters. 50 | - Recommended Docker image. 51 | - Validated Docker deployment instructions. 52 | - Notable Docker setup details. 53 | - Recommended config files, user data, accessible directory settings. -------------------------------------------------------------------------------- /service/service.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-07-12 09:48:56 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-27 10:28:48 6 | * @FilePath: /CasaOS/service/service.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package service 12 | 13 | import ( 14 | "github.com/gorilla/websocket" 15 | "github.com/patrickmn/go-cache" 16 | "gorm.io/gorm" 17 | ) 18 | 19 | var Cache *cache.Cache 20 | 21 | var MyService Repository 22 | 23 | var WebSocketConns []*websocket.Conn 24 | var NewVersionApp map[string]string 25 | var SocketRun bool 26 | 27 | type Repository interface { 28 | App() AppService 29 | User() UserService 30 | Docker() DockerService 31 | Casa() CasaService 32 | Disk() DiskService 33 | Notify() NotifyServer 34 | Rely() RelyService 35 | System() SystemService 36 | Shares() SharesService 37 | Connections() ConnectionsService 38 | } 39 | 40 | func NewService(db *gorm.DB) Repository { 41 | return &store{ 42 | app: NewAppService(db), 43 | user: NewUserService(db), 44 | docker: NewDockerService(), 45 | casa: NewCasaService(), 46 | disk: NewDiskService(db), 47 | notify: NewNotifyService(db), 48 | rely: NewRelyService(db), 49 | system: NewSystemService(), 50 | shares: NewSharesService(db), 51 | connections: NewConnectionsService(db), 52 | } 53 | } 54 | 55 | type store struct { 56 | db *gorm.DB 57 | app AppService 58 | user UserService 59 | docker DockerService 60 | casa CasaService 61 | disk DiskService 62 | notify NotifyServer 63 | rely RelyService 64 | system SystemService 65 | shares SharesService 66 | connections ConnectionsService 67 | } 68 | 69 | func (s *store) Connections() ConnectionsService { 70 | return s.connections 71 | } 72 | func (s *store) Shares() SharesService { 73 | return s.shares 74 | } 75 | 76 | func (c *store) Rely() RelyService { 77 | return c.rely 78 | } 79 | 80 | func (c *store) System() SystemService { 81 | return c.system 82 | } 83 | func (c *store) Notify() NotifyServer { 84 | 85 | return c.notify 86 | } 87 | 88 | func (c *store) App() AppService { 89 | return c.app 90 | } 91 | 92 | func (c *store) User() UserService { 93 | return c.user 94 | } 95 | 96 | func (c *store) Docker() DockerService { 97 | return c.docker 98 | } 99 | 100 | func (c *store) Casa() CasaService { 101 | return c.casa 102 | } 103 | 104 | func (c *store) Disk() DiskService { 105 | return c.disk 106 | } 107 | -------------------------------------------------------------------------------- /service/docker_base/mysql.go: -------------------------------------------------------------------------------- 1 | package docker_base 2 | 3 | import ( 4 | "context" 5 | "github.com/docker/docker/api/types" 6 | "github.com/docker/docker/api/types/container" 7 | "github.com/docker/docker/api/types/filters" 8 | "github.com/docker/docker/api/types/network" 9 | client2 "github.com/docker/docker/client" 10 | "time" 11 | ) 12 | 13 | //创建一个mysql数据库 14 | func MysqlCreate(mysqlConfig MysqlConfig, dbId string, cpuShares int64, memory int64) (string, error) { 15 | const imageName = "mysql" 16 | const imageVersion = "8" 17 | const imageNet = "oasis" 18 | 19 | cli, err := client2.NewClientWithOpts(client2.FromEnv) 20 | if err != nil { 21 | return "", err 22 | } 23 | defer cli.Close() 24 | _, err = cli.ImagePull(context.Background(), imageName+":"+imageVersion, types.ImagePullOptions{}) 25 | 26 | isExist := true 27 | //检查到镜像才继续 28 | for isExist { 29 | filter := filters.NewArgs() 30 | filter.Add("before", imageName+":"+imageVersion) 31 | list, e := cli.ImageList(context.Background(), types.ImageListOptions{Filters: filter}) 32 | if e == nil && len(list) > 0 { 33 | isExist = false 34 | } 35 | time.Sleep(time.Second) 36 | } 37 | 38 | var envArr = []string{"MYSQL_ROOT_PASSWORD=" + mysqlConfig.DataBasePassword, "MYSQL_DATABASE=" + mysqlConfig.DataBaseDB} 39 | 40 | res := container.Resources{} 41 | if cpuShares > 0 { 42 | res.CPUShares = cpuShares 43 | } 44 | if memory > 0 { 45 | res.Memory = memory << 20 46 | } 47 | 48 | rp := container.RestartPolicy{} 49 | 50 | rp.Name = "always" 51 | 52 | config := &container.Config{ 53 | Image: imageName, 54 | Labels: map[string]string{"version": imageVersion, "author": "official"}, 55 | Env: envArr, 56 | } 57 | hostConfig := &container.HostConfig{Resources: res, RestartPolicy: rp, NetworkMode: container.NetworkMode(imageNet)} 58 | 59 | containerCreate, err := cli.ContainerCreate(context.Background(), 60 | config, 61 | hostConfig, 62 | &network.NetworkingConfig{EndpointsConfig: map[string]*network.EndpointSettings{imageNet: {NetworkID: ""}}}, 63 | nil, 64 | dbId) 65 | 66 | containerId := containerCreate.ID 67 | 68 | //启动容器 69 | err = cli.ContainerStart(context.Background(), dbId, types.ContainerStartOptions{}) 70 | if err != nil { 71 | return containerId, err 72 | } 73 | 74 | return containerId, nil 75 | 76 | } 77 | 78 | func MysqlDelete(dbId string) error { 79 | 80 | cli, err := client2.NewClientWithOpts(client2.FromEnv) 81 | if err != nil { 82 | return err 83 | } 84 | defer cli.Close() 85 | err = cli.ContainerStop(context.Background(), dbId, nil) 86 | if err != nil { 87 | return err 88 | } 89 | 90 | err = cli.ContainerRemove(context.Background(), dbId, types.ContainerRemoveOptions{}) 91 | return err 92 | 93 | } 94 | -------------------------------------------------------------------------------- /model/smartctl_model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // 4 | type SmartctlA struct { 5 | Smartctl struct { 6 | Version []int `json:"version"` 7 | SvnRevision string `json:"svn_revision"` 8 | PlatformInfo string `json:"platform_info"` 9 | BuildInfo string `json:"build_info"` 10 | Argv []string `json:"argv"` 11 | ExitStatus int `json:"exit_status"` 12 | } `json:"smartctl"` 13 | Device struct { 14 | Name string `json:"name"` 15 | InfoName string `json:"info_name"` 16 | Type string `json:"type"` 17 | Protocol string `json:"protocol"` 18 | } `json:"device"` 19 | ModelName string `json:"model_name"` 20 | SerialNumber string `json:"serial_number"` 21 | FirmwareVersion string `json:"firmware_version"` 22 | UserCapacity struct { 23 | Blocks int `json:"blocks"` 24 | Bytes int64 `json:"bytes"` 25 | } `json:"user_capacity"` 26 | SmartStatus struct { 27 | Passed bool `json:"passed"` 28 | } `json:"smart_status"` 29 | AtaSmartData struct { 30 | OfflineDataCollection struct { 31 | Status struct { 32 | Value int `json:"value"` 33 | String string `json:"string"` 34 | } `json:"status"` 35 | CompletionSeconds int `json:"completion_seconds"` 36 | } `json:"offline_data_collection"` 37 | SelfTest struct { 38 | Status struct { 39 | Value int `json:"value"` 40 | String string `json:"string"` 41 | Passed bool `json:"passed"` 42 | } `json:"status"` 43 | PollingMinutes struct { 44 | Short int `json:"short"` 45 | Extended int `json:"extended"` 46 | Conveyance int `json:"conveyance"` 47 | } `json:"polling_minutes"` 48 | } `json:"self_test"` 49 | Capabilities struct { 50 | Values []int `json:"values"` 51 | ExecOfflineImmediateSupported bool `json:"exec_offline_immediate_supported"` 52 | OfflineIsAbortedUponNewCmd bool `json:"offline_is_aborted_upon_new_cmd"` 53 | OfflineSurfaceScanSupported bool `json:"offline_surface_scan_supported"` 54 | SelfTestsSupported bool `json:"self_tests_supported"` 55 | ConveyanceSelfTestSupported bool `json:"conveyance_self_test_supported"` 56 | SelectiveSelfTestSupported bool `json:"selective_self_test_supported"` 57 | AttributeAutosaveEnabled bool `json:"attribute_autosave_enabled"` 58 | ErrorLoggingSupported bool `json:"error_logging_supported"` 59 | GpLoggingSupported bool `json:"gp_logging_supported"` 60 | } `json:"capabilities"` 61 | } `json:"ata_smart_data"` 62 | PowerOnTime struct { 63 | Hours int `json:"hours"` 64 | } `json:"power_on_time"` 65 | PowerCycleCount int `json:"power_cycle_count"` 66 | Temperature struct { 67 | Current int `json:"current"` 68 | } `json:"temperature"` 69 | } 70 | -------------------------------------------------------------------------------- /pkg/utils/loger/log.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-06-02 15:09:38 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-06-27 15:47:49 6 | * @FilePath: /CasaOS/pkg/utils/loger/log.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package loger 12 | 13 | import ( 14 | "fmt" 15 | "os" 16 | "path" 17 | "path/filepath" 18 | "runtime" 19 | 20 | "github.com/IceWhaleTech/CasaOS/pkg/config" 21 | "go.uber.org/zap" 22 | "go.uber.org/zap/zapcore" 23 | "gopkg.in/natefinch/lumberjack.v2" 24 | ) 25 | 26 | var loggers *zap.Logger 27 | 28 | func getFileLogWriter() (writeSyncer zapcore.WriteSyncer) { 29 | // 使用 lumberjack 实现 log rotate 30 | lumberJackLogger := &lumberjack.Logger{ 31 | Filename: filepath.Join(config.AppInfo.LogPath, fmt.Sprintf("%s.%s", 32 | config.AppInfo.LogSaveName, 33 | config.AppInfo.LogFileExt, 34 | )), 35 | MaxSize: 10, 36 | MaxBackups: 60, 37 | MaxAge: 1, 38 | Compress: true, 39 | } 40 | 41 | return zapcore.AddSync(lumberJackLogger) 42 | } 43 | 44 | func LogInit() { 45 | encoderConfig := zap.NewProductionEncoderConfig() 46 | encoderConfig.EncodeTime = zapcore.EpochTimeEncoder 47 | encoder := zapcore.NewJSONEncoder(encoderConfig) 48 | fileWriteSyncer := getFileLogWriter() 49 | core := zapcore.NewTee( 50 | zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.InfoLevel), 51 | zapcore.NewCore(encoder, fileWriteSyncer, zapcore.InfoLevel), 52 | ) 53 | loggers = zap.New(core) 54 | 55 | } 56 | 57 | func Info(message string, fields ...zap.Field) { 58 | callerFields := getCallerInfoForLog() 59 | fields = append(fields, callerFields...) 60 | loggers.Info(message, fields...) 61 | } 62 | 63 | func Debug(message string, fields ...zap.Field) { 64 | callerFields := getCallerInfoForLog() 65 | fields = append(fields, callerFields...) 66 | loggers.Debug(message, fields...) 67 | } 68 | 69 | func Error(message string, fields ...zap.Field) { 70 | callerFields := getCallerInfoForLog() 71 | fields = append(fields, callerFields...) 72 | loggers.Error(message, fields...) 73 | } 74 | 75 | func Warn(message string, fields ...zap.Field) { 76 | callerFields := getCallerInfoForLog() 77 | fields = append(fields, callerFields...) 78 | loggers.Warn(message, fields...) 79 | } 80 | 81 | func getCallerInfoForLog() (callerFields []zap.Field) { 82 | 83 | pc, file, line, ok := runtime.Caller(2) // 回溯两层,拿到写日志的调用方的函数信息 84 | if !ok { 85 | return 86 | } 87 | funcName := runtime.FuncForPC(pc).Name() 88 | funcName = path.Base(funcName) //Base函数返回路径的最后一个元素,只保留函数名 89 | 90 | callerFields = append(callerFields, zap.String("func", funcName), zap.String("file", file), zap.Int("line", line)) 91 | return 92 | } 93 | -------------------------------------------------------------------------------- /pkg/utils/command/command_helper.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "io/ioutil" 8 | "os/exec" 9 | "time" 10 | ) 11 | 12 | func OnlyExec(cmdStr string) { 13 | cmd := exec.Command("/bin/bash", "-c", cmdStr) 14 | stdout, err := cmd.StdoutPipe() 15 | if err != nil { 16 | return 17 | } 18 | defer stdout.Close() 19 | if err := cmd.Start(); err != nil { 20 | return 21 | } 22 | cmd.Wait() 23 | return 24 | } 25 | 26 | func ExecResultStrArray(cmdStr string) []string { 27 | cmd := exec.Command("/bin/bash", "-c", cmdStr) 28 | stdout, err := cmd.StdoutPipe() 29 | if err != nil { 30 | fmt.Println(err) 31 | return nil 32 | } 33 | defer stdout.Close() 34 | if err = cmd.Start(); err != nil { 35 | fmt.Println(err) 36 | return nil 37 | } 38 | //str, err := ioutil.ReadAll(stdout) 39 | var networklist = []string{} 40 | outputBuf := bufio.NewReader(stdout) 41 | for { 42 | output, _, err := outputBuf.ReadLine() 43 | if err != nil { 44 | if err.Error() != "EOF" { 45 | fmt.Printf("Error :%s\n", err) 46 | } 47 | break 48 | } 49 | networklist = append(networklist, string(output)) 50 | } 51 | cmd.Wait() 52 | return networklist 53 | } 54 | 55 | func ExecResultStr(cmdStr string) string { 56 | cmd := exec.Command("/bin/bash", "-c", cmdStr) 57 | stdout, err := cmd.StdoutPipe() 58 | if err != nil { 59 | fmt.Println(err) 60 | return "" 61 | } 62 | defer stdout.Close() 63 | if err := cmd.Start(); err != nil { 64 | fmt.Println(err) 65 | return "" 66 | } 67 | str, err := ioutil.ReadAll(stdout) 68 | cmd.Wait() 69 | if err != nil { 70 | fmt.Println(err) 71 | return "" 72 | } 73 | return string(str) 74 | } 75 | 76 | //执行 lsblk 命令 77 | func ExecLSBLK() []byte { 78 | output, err := exec.Command("lsblk", "-O", "-J", "-b").Output() 79 | if err != nil { 80 | fmt.Println("lsblk", err) 81 | return nil 82 | } 83 | return output 84 | } 85 | 86 | //执行 lsblk 命令 87 | func ExecLSBLKByPath(path string) []byte { 88 | output, err := exec.Command("lsblk", path, "-O", "-J", "-b").Output() 89 | if err != nil { 90 | fmt.Println("lsblk", err) 91 | return nil 92 | } 93 | return output 94 | } 95 | 96 | //exec smart 97 | func ExecSmartCTLByPath(path string) []byte { 98 | timeout := 3 99 | ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) 100 | defer cancel() 101 | output, err := exec.CommandContext(ctx, "smartctl", "-a", path, "-j").Output() 102 | if err != nil { 103 | fmt.Println("smartctl", err) 104 | return nil 105 | } 106 | return output 107 | } 108 | 109 | func ExecEnabledSMART(path string) { 110 | 111 | exec.Command("smartctl", "-s on", path).Output() 112 | } 113 | -------------------------------------------------------------------------------- /service/connections.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.org 3 | * @Date: 2022-07-26 18:13:22 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-04 20:10:31 6 | * @FilePath: /CasaOS/service/connections.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package service 12 | 13 | import ( 14 | "github.com/IceWhaleTech/CasaOS/pkg/config" 15 | command2 "github.com/IceWhaleTech/CasaOS/pkg/utils/command" 16 | "github.com/IceWhaleTech/CasaOS/service/model" 17 | model2 "github.com/IceWhaleTech/CasaOS/service/model" 18 | "gorm.io/gorm" 19 | ) 20 | 21 | type ConnectionsService interface { 22 | GetConnectionsList() (connections []model2.ConnectionsDBModel) 23 | GetConnectionByHost(host string) (connections []model2.ConnectionsDBModel) 24 | GetConnectionByID(id string) (connections model2.ConnectionsDBModel) 25 | CreateConnection(connection *model2.ConnectionsDBModel) 26 | DeleteConnection(id string) 27 | UpdateConnection(connection *model2.ConnectionsDBModel) 28 | MountSmaba(username, host, directory, port, mountPoint, password string) string 29 | UnmountSmaba(mountPoint string) string 30 | } 31 | 32 | type connectionsStruct struct { 33 | db *gorm.DB 34 | } 35 | 36 | func (s *connectionsStruct) GetConnectionByHost(host string) (connections []model2.ConnectionsDBModel) { 37 | s.db.Select("username,host,status,id").Where("host = ?", host).Find(&connections) 38 | return 39 | } 40 | func (s *connectionsStruct) GetConnectionByID(id string) (connections model2.ConnectionsDBModel) { 41 | s.db.Select("username,password,host,status,id,directories,mount_point,port").Where("id = ?", id).First(&connections) 42 | return 43 | } 44 | func (s *connectionsStruct) GetConnectionsList() (connections []model2.ConnectionsDBModel) { 45 | s.db.Select("username,host,port,status,id,mount_point").Find(&connections) 46 | return 47 | } 48 | func (s *connectionsStruct) CreateConnection(connection *model2.ConnectionsDBModel) { 49 | s.db.Create(connection) 50 | } 51 | func (s *connectionsStruct) UpdateConnection(connection *model2.ConnectionsDBModel) { 52 | s.db.Save(connection) 53 | } 54 | func (s *connectionsStruct) DeleteConnection(id string) { 55 | s.db.Where("id= ?", id).Delete(&model.ConnectionsDBModel{}) 56 | } 57 | 58 | func (s *connectionsStruct) MountSmaba(username, host, directory, port, mountPoint, password string) string { 59 | str := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;MountCIFS " + username + " " + host + " " + directory + " " + port + " " + mountPoint + " " + password) 60 | return str 61 | } 62 | func (s *connectionsStruct) UnmountSmaba(mountPoint string) string { 63 | str := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;UMountPorintAndRemoveDir " + mountPoint) 64 | return str 65 | } 66 | 67 | func NewConnectionsService(db *gorm.DB) ConnectionsService { 68 | return &connectionsStruct{db: db} 69 | } 70 | -------------------------------------------------------------------------------- /pkg/config/init.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-13 18:15:46 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-14 10:58:45 6 | * @FilePath: /CasaOS/pkg/config/init.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package config 12 | 13 | import ( 14 | "fmt" 15 | "log" 16 | "os" 17 | "path" 18 | "path/filepath" 19 | "runtime" 20 | "strings" 21 | 22 | "github.com/IceWhaleTech/CasaOS/model" 23 | "github.com/go-ini/ini" 24 | ) 25 | 26 | //系统配置 27 | var SysInfo = &model.SysInfoModel{} 28 | 29 | //用户相关 30 | var UserInfo = &model.UserModel{} 31 | 32 | //用户相关 33 | var AppInfo = &model.APPModel{} 34 | 35 | //var RedisInfo = &model.RedisModel{} 36 | 37 | //server相关 38 | var ServerInfo = &model.ServerModel{} 39 | 40 | var SystemConfigInfo = &model.SystemConfig{} 41 | 42 | var CasaOSGlobalVariables = &model.CasaOSGlobalVariables{} 43 | 44 | var FileSettingInfo = &model.FileSetting{} 45 | 46 | var Cfg *ini.File 47 | 48 | //初始化设置,获取系统的部分信息。 49 | func InitSetup(config string) { 50 | 51 | var configDir = USERCONFIGURL 52 | if len(config) > 0 { 53 | configDir = config 54 | } 55 | if runtime.GOOS == "darwin" { 56 | configDir = "./conf/conf.conf" 57 | } 58 | var err error 59 | //读取文件 60 | Cfg, err = ini.Load(configDir) 61 | if err != nil { 62 | fmt.Printf("Fail to read file: %v", err) 63 | os.Exit(1) 64 | } 65 | 66 | mapTo("user", UserInfo) 67 | mapTo("app", AppInfo) 68 | //mapTo("redis", RedisInfo) 69 | mapTo("server", ServerInfo) 70 | mapTo("system", SystemConfigInfo) 71 | mapTo("file", FileSettingInfo) 72 | SystemConfigInfo.ConfigPath = configDir 73 | if len(AppInfo.DBPath) == 0 { 74 | AppInfo.DBPath = "/var/lib/casaos" 75 | Cfg.SaveTo(configDir) 76 | } 77 | if len(AppInfo.LogPath) == 0 { 78 | AppInfo.LogPath = "/var/log/casaos/" 79 | Cfg.SaveTo(configDir) 80 | } 81 | if len(AppInfo.ShellPath) == 0 { 82 | AppInfo.ShellPath = "/usr/share/casaos/shell" 83 | Cfg.SaveTo(configDir) 84 | } 85 | if len(AppInfo.UserDataPath) == 0 { 86 | AppInfo.UserDataPath = "/var/lib/casaos/conf" 87 | Cfg.SaveTo(configDir) 88 | } 89 | if len(AppInfo.TempPath) == 0 { 90 | AppInfo.TempPath = "/var/lib/casaos/temp" 91 | Cfg.SaveTo(configDir) 92 | } 93 | // AppInfo.ProjectPath = getCurrentDirectory() //os.Getwd() 94 | 95 | } 96 | 97 | //映射 98 | func mapTo(section string, v interface{}) { 99 | err := Cfg.Section(section).MapTo(v) 100 | if err != nil { 101 | log.Fatalf("Cfg.MapTo %s err: %v", section, err) 102 | } 103 | } 104 | 105 | // 获取当前执行文件绝对路径(go run) 106 | func getCurrentAbPathByCaller() string { 107 | var abPath string 108 | _, filename, _, ok := runtime.Caller(0) 109 | if ok { 110 | abPath = path.Dir(filename) 111 | } 112 | return abPath 113 | } 114 | func getCurrentDirectory() string { 115 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 116 | if err != nil { 117 | log.Fatal(err) 118 | } 119 | return strings.Replace(dir, "\\", "/", -1) 120 | } 121 | -------------------------------------------------------------------------------- /service/model/o_container.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-05-13 18:15:46 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-13 10:56:34 6 | * @FilePath: /CasaOS/service/model/o_container.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | const CONTAINERTABLENAME = "o_container" 14 | 15 | //Soon to be removed 16 | type AppListDBModel struct { 17 | CustomId string `gorm:"column:custom_id;primary_key" json:"custom_id"` 18 | Title string `json:"title"` 19 | // ScreenshotLink model.Strings `gorm:"type:json" json:"screenshot_link,omitempty"` 20 | ScreenshotLink string `json:"screenshot_link"` 21 | Slogan string `json:"slogan"` 22 | Description string `json:"description"` 23 | //Tags model.Strings `gorm:"type:json" json:"tags"` 24 | Tags string `json:"tags"` 25 | Icon string `json:"icon"` 26 | Version string `json:"version"` 27 | ContainerId string `json:"container_id,omitempty"` 28 | Image string `json:"image,omitempty"` 29 | Index string `json:"index"` 30 | CreatedAt string `gorm:"<-:create;autoCreateTime" json:"created_at"` 31 | UpdatedAt string `gorm:"<-:create;<-:update;autoUpdateTime" json:"updated_at"` 32 | //Port string `json:"port,omitempty"` 33 | PortMap string `json:"port_map"` 34 | Label string `json:"label"` 35 | EnableUPNP bool `json:"enable_upnp"` 36 | Envs string `json:"envs"` 37 | Ports string `json:"ports"` 38 | Volumes string `json:"volumes"` 39 | Devices string `json:"devices"` 40 | //Envs []model.Env `json:"envs"` 41 | //Ports []model.PortMap `gorm:"type:json" json:"ports"` 42 | //Volumes []model.PathMap `gorm:"type:json" json:"volumes"` 43 | //Devices []model.PathMap `gorm:"type:json" json:"device"` 44 | Position bool `json:"position"` 45 | NetModel string `json:"net_model"` 46 | CpuShares int64 `json:"cpu_shares"` 47 | Memory int64 `json:"memory"` 48 | Restart string `json:"restart"` 49 | //Rely model.MapStrings `gorm:"type:json" json:"rely"` //[{"mysql":"id"},{"mysql":"id"}] 50 | Origin string `json:"origin"` 51 | HostName string `json:"host_name"` 52 | Privileged bool `json:"privileged"` 53 | CapAdd string `json:"cap_add"` 54 | Cmd string `gorm:"type:json" json:"cmd"` 55 | } 56 | 57 | func (p *AppListDBModel) TableName() string { 58 | return "o_container" 59 | } 60 | 61 | type MyAppList struct { 62 | Id string `json:"id"` 63 | Name string `json:"name"` 64 | Icon string `json:"icon"` 65 | State string `json:"state"` 66 | CustomId string `gorm:"column:custom_id;primary_key" json:"custom_id"` 67 | Index string `json:"index"` 68 | //Order string `json:"order"` 69 | Port string `json:"port"` 70 | Slogan string `json:"slogan"` 71 | Type string `json:"type"` 72 | //Rely model.MapStrings `json:"rely"` //[{"mysql":"id"},{"mysql":"id"}] 73 | Image string `json:"image"` 74 | Volumes string `json:"volumes"` 75 | Latest bool `json:"latest"` 76 | Host string `json:"host"` 77 | Protocol string `json:"protocol"` 78 | } 79 | -------------------------------------------------------------------------------- /service/user.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-03-18 11:40:55 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-12 10:05:37 6 | * @FilePath: /CasaOS/service/user.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package service 12 | 13 | import ( 14 | "io" 15 | "mime/multipart" 16 | "os" 17 | 18 | "github.com/IceWhaleTech/CasaOS/service/model" 19 | "gorm.io/gorm" 20 | ) 21 | 22 | type UserService interface { 23 | UpLoadFile(file multipart.File, name string) error 24 | CreateUser(m model.UserDBModel) model.UserDBModel 25 | GetUserCount() (userCount int64) 26 | UpdateUser(m model.UserDBModel) 27 | UpdateUserPassword(m model.UserDBModel) 28 | GetUserInfoById(id string) (m model.UserDBModel) 29 | GetUserAllInfoById(id string) (m model.UserDBModel) 30 | GetUserAllInfoByName(userName string) (m model.UserDBModel) 31 | DeleteUserById(id string) 32 | DeleteAllUser() 33 | GetUserInfoByUserName(userName string) (m model.UserDBModel) 34 | GetAllUserName() (list []model.UserDBModel) 35 | } 36 | 37 | var UserRegisterHash = make(map[string]string) 38 | 39 | type userService struct { 40 | db *gorm.DB 41 | } 42 | 43 | func (u *userService) DeleteAllUser() { 44 | u.db.Where("1=1").Delete(&model.UserDBModel{}) 45 | } 46 | func (u *userService) DeleteUserById(id string) { 47 | u.db.Where("id= ?", id).Delete(&model.UserDBModel{}) 48 | } 49 | 50 | func (u *userService) GetAllUserName() (list []model.UserDBModel) { 51 | u.db.Select("username").Find(&list) 52 | return 53 | } 54 | func (u *userService) CreateUser(m model.UserDBModel) model.UserDBModel { 55 | u.db.Create(&m) 56 | return m 57 | } 58 | 59 | func (u *userService) GetUserCount() (userCount int64) { 60 | u.db.Find(&model.UserDBModel{}).Count(&userCount) 61 | return 62 | } 63 | 64 | func (u *userService) UpdateUser(m model.UserDBModel) { 65 | u.db.Model(&m).Omit("password").Updates(&m) 66 | } 67 | func (u *userService) UpdateUserPassword(m model.UserDBModel) { 68 | u.db.Model(&m).Update("password", m.Password) 69 | } 70 | func (u *userService) GetUserAllInfoById(id string) (m model.UserDBModel) { 71 | u.db.Where("id= ?", id).First(&m) 72 | return 73 | } 74 | func (u *userService) GetUserAllInfoByName(userName string) (m model.UserDBModel) { 75 | u.db.Where("username= ?", userName).First(&m) 76 | return 77 | } 78 | func (u *userService) GetUserInfoById(id string) (m model.UserDBModel) { 79 | u.db.Select("username", "id", "role", "nickname", "description", "avatar", "email").Where("id= ?", id).First(&m) 80 | return 81 | } 82 | 83 | func (u *userService) GetUserInfoByUserName(userName string) (m model.UserDBModel) { 84 | u.db.Select("username", "id", "role", "nickname", "description", "avatar", "email").Where("username= ?", userName).First(&m) 85 | return 86 | } 87 | 88 | //上传文件 89 | func (c *userService) UpLoadFile(file multipart.File, url string) error { 90 | out, _ := os.OpenFile(url, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) 91 | defer out.Close() 92 | io.Copy(out, file) 93 | return nil 94 | } 95 | 96 | //获取用户Service 97 | func NewUserService(db *gorm.DB) UserService { 98 | return &userService{db: db} 99 | } 100 | -------------------------------------------------------------------------------- /route/v1/storage.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-07-11 16:02:29 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-17 19:14:50 6 | * @FilePath: /CasaOS/route/v1/storage.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package v1 12 | 13 | import ( 14 | "path/filepath" 15 | "reflect" 16 | 17 | "github.com/IceWhaleTech/CasaOS/model" 18 | "github.com/IceWhaleTech/CasaOS/pkg/utils/common_err" 19 | "github.com/IceWhaleTech/CasaOS/service" 20 | "github.com/gin-gonic/gin" 21 | ) 22 | 23 | func GetStorageList(c *gin.Context) { 24 | system := c.Query("system") 25 | storages := []model.Storages{} 26 | disks := service.MyService.Disk().LSBLK(false) 27 | diskNumber := 1 28 | children := 1 29 | findSystem := 0 30 | for _, d := range disks { 31 | if d.Tran != "usb" { 32 | tempSystemDisk := false 33 | children = 1 34 | tempDisk := model.Storages{ 35 | DiskName: d.Model, 36 | Path: d.Path, 37 | Size: d.Size, 38 | } 39 | 40 | storageArr := []model.Storage{} 41 | temp := service.MyService.Disk().SmartCTL(d.Path) 42 | if reflect.DeepEqual(temp, model.SmartctlA{}) { 43 | temp.SmartStatus.Passed = true 44 | } 45 | for _, v := range d.Children { 46 | if v.MountPoint != "" { 47 | if findSystem == 0 { 48 | if v.MountPoint == "/" { 49 | tempDisk.DiskName = "System" 50 | findSystem = 1 51 | tempSystemDisk = true 52 | } 53 | if len(v.Children) > 0 { 54 | for _, c := range v.Children { 55 | if c.MountPoint == "/" { 56 | tempDisk.DiskName = "System" 57 | findSystem = 1 58 | tempSystemDisk = true 59 | break 60 | } 61 | } 62 | } 63 | } 64 | 65 | stor := model.Storage{} 66 | stor.MountPoint = v.MountPoint 67 | stor.Size = v.FSSize 68 | stor.Avail = v.FSAvail 69 | stor.Path = v.Path 70 | stor.Type = v.FsType 71 | stor.DriveName = v.Name 72 | if len(v.Label) == 0 { 73 | if stor.MountPoint == "/" { 74 | stor.Label = "System" 75 | } else { 76 | stor.Label = filepath.Base(stor.MountPoint) 77 | } 78 | 79 | children += 1 80 | } else { 81 | stor.Label = v.Label 82 | } 83 | storageArr = append(storageArr, stor) 84 | } 85 | } 86 | 87 | if len(storageArr) > 0 { 88 | if tempSystemDisk && len(system) > 0 { 89 | tempStorageArr := []model.Storage{} 90 | for i := 0; i < len(storageArr); i++ { 91 | if storageArr[i].MountPoint != "/boot/efi" && storageArr[i].Type != "swap" { 92 | tempStorageArr = append(tempStorageArr, storageArr[i]) 93 | } 94 | } 95 | tempDisk.Children = tempStorageArr 96 | storages = append(storages, tempDisk) 97 | diskNumber += 1 98 | } else if !tempSystemDisk { 99 | tempDisk.Children = storageArr 100 | storages = append(storages, tempDisk) 101 | diskNumber += 1 102 | } 103 | 104 | } 105 | } 106 | } 107 | 108 | c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: storages}) 109 | } 110 | -------------------------------------------------------------------------------- /shell/delete-old-service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### 3 | # @Author: LinkLeong link@icewhale.com 4 | # @Date: 2022-06-30 10:08:33 5 | # @LastEditors: LinkLeong 6 | # @LastEditTime: 2022-07-01 11:17:54 7 | # @FilePath: /CasaOS/shell/delete-old-service.sh 8 | # @Description: 9 | ### 10 | 11 | ((EUID)) && sudo_cmd="sudo" 12 | 13 | # SYSTEM INFO 14 | readonly UNAME_M="$(uname -m)" 15 | 16 | # CasaOS PATHS 17 | readonly CASA_REPO=IceWhaleTech/CasaOS 18 | readonly CASA_UNZIP_TEMP_FOLDER=/tmp/casaos 19 | readonly CASA_BIN=casaos 20 | readonly CASA_BIN_PATH=/usr/bin/casaos 21 | readonly CASA_CONF_PATH=/etc/casaos.conf 22 | readonly CASA_SERVICE_PATH=/etc/systemd/system/casaos.service 23 | readonly CASA_HELPER_PATH=/usr/share/casaos/shell/ 24 | readonly CASA_USER_CONF_PATH=/var/lib/casaos/conf/ 25 | readonly CASA_DB_PATH=/var/lib/casaos/db/ 26 | readonly CASA_TEMP_PATH=/var/lib/casaos/temp/ 27 | readonly CASA_LOGS_PATH=/var/log/casaos/ 28 | readonly CASA_PACKAGE_EXT=".tar.gz" 29 | readonly CASA_RELEASE_API="https://api.github.com/repos/${CASA_REPO}/releases" 30 | readonly CASA_OPENWRT_DOCS="https://github.com/IceWhaleTech/CasaOS-OpenWrt" 31 | 32 | readonly COLOUR_RESET='\e[0m' 33 | readonly aCOLOUR=( 34 | '\e[38;5;154m' # green | Lines, bullets and separators 35 | '\e[1m' # Bold white | Main descriptions 36 | '\e[90m' # Grey | Credits 37 | '\e[91m' # Red | Update notifications Alert 38 | '\e[33m' # Yellow | Emphasis 39 | ) 40 | 41 | Target_Arch="" 42 | Target_Distro="debian" 43 | Target_OS="linux" 44 | Casa_Tag="" 45 | 46 | 47 | ####################################### 48 | # Custom printing function 49 | # Globals: 50 | # None 51 | # Arguments: 52 | # $1 0:OK 1:FAILED 2:INFO 3:NOTICE 53 | # message 54 | # Returns: 55 | # None 56 | ####################################### 57 | 58 | Show() { 59 | # OK 60 | if (($1 == 0)); then 61 | echo -e "${aCOLOUR[2]}[$COLOUR_RESET${aCOLOUR[0]} OK $COLOUR_RESET${aCOLOUR[2]}]$COLOUR_RESET $2" 62 | # FAILED 63 | elif (($1 == 1)); then 64 | echo -e "${aCOLOUR[2]}[$COLOUR_RESET${aCOLOUR[3]}FAILED$COLOUR_RESET${aCOLOUR[2]}]$COLOUR_RESET $2" 65 | # INFO 66 | elif (($1 == 2)); then 67 | echo -e "${aCOLOUR[2]}[$COLOUR_RESET${aCOLOUR[0]} INFO $COLOUR_RESET${aCOLOUR[2]}]$COLOUR_RESET $2" 68 | # NOTICE 69 | elif (($1 == 3)); then 70 | echo -e "${aCOLOUR[2]}[$COLOUR_RESET${aCOLOUR[4]}NOTICE$COLOUR_RESET${aCOLOUR[2]}]$COLOUR_RESET $2" 71 | fi 72 | } 73 | 74 | Warn() { 75 | echo -e "${aCOLOUR[3]}$1$COLOUR_RESET" 76 | } 77 | 78 | # 0 Check_exist 79 | Check_Exist() { 80 | #Create Dir 81 | Show 2 "Create Folders." 82 | ${sudo_cmd} mkdir -p ${CASA_HELPER_PATH} 83 | ${sudo_cmd} mkdir -p ${CASA_LOGS_PATH} 84 | ${sudo_cmd} mkdir -p ${CASA_USER_CONF_PATH} 85 | ${sudo_cmd} mkdir -p ${CASA_DB_PATH} 86 | ${sudo_cmd} mkdir -p ${CASA_TEMP_PATH} 87 | 88 | 89 | Show 2 "Start cleaning up the old version." 90 | 91 | ${sudo_cmd} rm -rf /usr/lib/systemd/system/casaos.service 92 | 93 | ${sudo_cmd} rm -rf /lib/systemd/system/casaos.service 94 | 95 | ${sudo_cmd} rm -rf /usr/local/bin/${CASA_BIN} 96 | 97 | #Clean 98 | if [[ -d "/casaOS" ]]; then 99 | ${sudo_cmd} rm -rf /casaOS 100 | fi 101 | Show 0 "Clearance completed." 102 | 103 | $sudo_cmd systemctl restart ${CASA_BIN} 104 | } 105 | Check_Exist 106 | -------------------------------------------------------------------------------- /model/disk.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-07-13 10:43:45 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-03 14:45:35 6 | * @FilePath: /CasaOS/model/disk.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package model 12 | 13 | type LSBLKModel struct { 14 | Name string `json:"name"` 15 | FsType string `json:"fstype"` 16 | Size uint64 `json:"size"` 17 | FSSize string `json:"fssize"` 18 | Path string `json:"path"` 19 | Model string `json:"model"` //设备标识符 20 | RM bool `json:"rm"` //是否为可移动设备 21 | RO bool `json:"ro"` //是否为只读设备 22 | State string `json:"state"` 23 | PhySec int `json:"phy-sec"` //物理扇区大小 24 | Type string `json:"type"` 25 | Vendor string `json:"vendor"` //供应商 26 | Rev string `json:"rev"` //修订版本 27 | FSAvail string `json:"fsavail"` //可用空间 28 | FSUse string `json:"fsuse%"` //已用百分比 29 | MountPoint string `json:"mountpoint"` 30 | Format string `json:"format"` 31 | Health string `json:"health"` 32 | HotPlug bool `json:"hotplug"` 33 | UUID string `json:"uuid"` 34 | FSUsed string `json:"fsused"` 35 | Temperature int `json:"temperature"` 36 | Tran string `json:"tran"` 37 | MinIO uint64 `json:"min-io"` 38 | UsedPercent float64 `json:"used_percent"` 39 | Serial string `json:"serial"` 40 | Children []LSBLKModel `json:"children"` 41 | SubSystems string `json:"subsystems"` 42 | Label string `json:"label"` 43 | //详情特有 44 | StartSector uint64 `json:"start_sector,omitempty"` 45 | Rota bool `json:"rota"` //true(hhd) false(ssd) 46 | DiskType string `json:"disk_type"` 47 | EndSector uint64 `json:"end_sector,omitempty"` 48 | } 49 | 50 | type Drive struct { 51 | Name string `json:"name"` 52 | Size uint64 `json:"size"` 53 | Model string `json:"model"` 54 | Health string `json:"health"` 55 | Temperature int `json:"temperature"` 56 | DiskType string `json:"disk_type"` 57 | NeedFormat bool `json:"need_format"` 58 | Serial string `json:"serial"` 59 | Path string `json:"path"` 60 | ChildrenNumber int `json:"children_number"` 61 | } 62 | 63 | type DriveUSB struct { 64 | Name string `json:"name"` 65 | Size uint64 `json:"size"` 66 | Model string `json:"model"` 67 | Avail uint64 `json:"avail"` 68 | Children []USBChildren `json:"children"` 69 | } 70 | type USBChildren struct { 71 | Name string `json:"name"` 72 | Size uint64 `json:"size"` 73 | Avail uint64 `json:"avail"` 74 | MountPoint string `json:"mount_point"` 75 | } 76 | 77 | type Storage struct { 78 | MountPoint string `json:"mount_point"` 79 | Size string `json:"size"` 80 | Avail string `json:"avail"` //可用空间 81 | Type string `json:"type"` 82 | Path string `json:"path"` 83 | DriveName string `json:"drive_name"` 84 | Label string `json:"label"` 85 | } 86 | type Storages struct { 87 | DiskName string `json:"disk_name"` 88 | Size uint64 `json:"size"` 89 | Path string `json:"path"` 90 | Children []Storage `json:"children"` 91 | } 92 | 93 | type Summary struct { 94 | Size uint64 `json:"size"` 95 | Avail uint64 `json:"avail"` //可用空间 96 | Health bool `json:"health"` 97 | Used uint64 `json:"used"` 98 | } 99 | -------------------------------------------------------------------------------- /.github/workflows/demo.yml: -------------------------------------------------------------------------------- 1 | name: Demo Reset 2 | 3 | # Controls when the workflow will run 4 | on: 5 | schedule: 6 | - cron: "0 * * * *" 7 | 8 | # Allows you to run this workflow manually from the Actions tab 9 | workflow_dispatch: 10 | 11 | # OLD_INSTANCE_SNAPSHOT_NAME=$(aws lightsail get-instance-snapshots | grep '"name": "CasaOS-Demo-Snapshot-[0-9]' | sed 's/ //g' | sed 's/"//g' | sed 's/,//g' | sed 's/name://g') 12 | # OLD_INSTANCE_NAME=$(aws lightsail get-instances | grep '"name": "CasaOS-Demo-[0-9]' | sed 's/ //g' | sed 's/"//g' | sed 's/,//g' | sed 's/name://g') 13 | # NEW_INSTANCE_NAME=CasaOS-Demo-$(date +%s) 14 | 15 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 16 | jobs: 17 | # This workflow contains a single job called "build" 18 | reset: 19 | # The type of runner that the job will run on 20 | runs-on: ubuntu-latest 21 | 22 | # Steps represent a sequence of tasks that will be executed as part of the job 23 | steps: 24 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 25 | - uses: actions/checkout@v2 26 | 27 | - name: Configure AWS credentials from Test account 28 | uses: aws-actions/configure-aws-credentials@v1 29 | with: 30 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 31 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 32 | aws-region: us-west-2 33 | 34 | - name: Get old instance and snapshot name, create new instance name 35 | run: | 36 | echo "OLD_INSTANCE_SNAPSHOT_NAME=$(aws lightsail get-instance-snapshots | grep '"name": "0.3.3-demo-1658402149' | sed 's/ //g' | sed 's/"//g' | sed 's/,//g' | sed 's/name://g')" >> $GITHUB_ENV 37 | echo "OLD_INSTANCE_NAME=$(aws lightsail get-instances | grep '"name": "CasaOS-Demo-[0-9]' | sed 's/ //g' | sed 's/"//g' | sed 's/,//g' | sed 's/name://g')" >> $GITHUB_ENV 38 | echo "NEW_INSTANCE_NAME=CasaOS-Demo-$(date +%s)" >> $GITHUB_ENV 39 | 40 | - name: Create instances from snapshot 41 | run: | 42 | aws lightsail create-instances-from-snapshot \ 43 | --instance-snapshot-name ${{ env.OLD_INSTANCE_SNAPSHOT_NAME }} \ 44 | --instance-names ${{ env.NEW_INSTANCE_NAME }} \ 45 | --availability-zone us-west-2a \ 46 | --bundle-id medium_2_0 47 | 48 | - name: Wait for new instance running 49 | run: | 50 | TIMEOUT=$(($(date +%s)+600)) 51 | while [ $TIMEOUT -gt $(date +%s) ] 52 | do 53 | NEW_INSTANCE_STATE=$(aws lightsail get-instance-state --instance-name ${{ env.NEW_INSTANCE_NAME }} | grep '"name":' | sed 's/ //g' | sed 's/"//g' | sed 's/name://g') 54 | if [ $NEW_INSTANCE_STATE == running ] 55 | then 56 | echo "New instance is running now" 57 | sleep 10s 58 | break 59 | fi 60 | done 61 | 62 | - name: Put instance public ports 63 | run: | 64 | aws lightsail put-instance-public-ports \ 65 | --port-infos fromPort=0,toPort=65535,protocol=all \ 66 | --instance-name ${{ env.NEW_INSTANCE_NAME }} 67 | 68 | - name: Attach static ip 69 | run: | 70 | aws lightsail attach-static-ip \ 71 | --static-ip-name CasaOS-Demo-IP \ 72 | --instance-name ${{ env.NEW_INSTANCE_NAME }} 73 | 74 | - name: Delete old instance 75 | run: | 76 | aws lightsail delete-instance \ 77 | --instance-name ${{ env.OLD_INSTANCE_NAME }} 78 | 79 | 80 | -------------------------------------------------------------------------------- /pkg/utils/file/reader.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | ) 9 | 10 | var ( 11 | buffSize = 1 << 20 12 | ) 13 | 14 | // ReadLineFromEnd -- 15 | type ReadLineFromEnd struct { 16 | f *os.File 17 | 18 | fileSize int 19 | bwr *bytes.Buffer 20 | lineBuff []byte 21 | swapBuff []byte 22 | 23 | isFirst bool 24 | } 25 | 26 | // NewReadLineFromEnd -- 27 | func NewReadLineFromEnd(name string) (rd *ReadLineFromEnd, err error) { 28 | f, err := os.Open(name) 29 | if err != nil { 30 | return nil, err 31 | } 32 | info, err := f.Stat() 33 | if info.IsDir() { 34 | return nil, fmt.Errorf("not file") 35 | } 36 | fileSize := int(info.Size()) 37 | rd = &ReadLineFromEnd{ 38 | f: f, 39 | fileSize: fileSize, 40 | bwr: bytes.NewBuffer([]byte{}), 41 | lineBuff: make([]byte, 0), 42 | swapBuff: make([]byte, buffSize), 43 | isFirst: true, 44 | } 45 | return rd, nil 46 | } 47 | 48 | // ReadLine 结尾包含'\n' 49 | func (c *ReadLineFromEnd) ReadLine() (line []byte, err error) { 50 | var ok bool 51 | for { 52 | ok, err = c.buff() 53 | if err != nil { 54 | return nil, err 55 | } 56 | if ok { 57 | break 58 | } 59 | } 60 | line, err = c.bwr.ReadBytes('\n') 61 | if err == io.EOF && c.fileSize > 0 { 62 | err = nil 63 | } 64 | return line, err 65 | } 66 | 67 | // Close -- 68 | func (c *ReadLineFromEnd) Close() (err error) { 69 | return c.f.Close() 70 | } 71 | 72 | func (c *ReadLineFromEnd) buff() (ok bool, err error) { 73 | if c.fileSize == 0 { 74 | return true, nil 75 | } 76 | 77 | if c.bwr.Len() >= buffSize { 78 | return true, nil 79 | } 80 | 81 | offset := 0 82 | if c.fileSize > buffSize { 83 | offset = c.fileSize - buffSize 84 | } 85 | _, err = c.f.Seek(int64(offset), 0) 86 | if err != nil { 87 | return false, err 88 | } 89 | 90 | n, err := c.f.Read(c.swapBuff) 91 | if err != nil && err != io.EOF { 92 | return false, err 93 | } 94 | if c.fileSize < n { 95 | n = c.fileSize 96 | } 97 | if n == 0 { 98 | return true, nil 99 | } 100 | 101 | for { 102 | m := bytes.LastIndex(c.swapBuff[:n], []byte{'\n'}) 103 | if m == -1 { 104 | break 105 | } 106 | if m < n-1 { 107 | err = c.writeLine(c.swapBuff[m+1 : n]) 108 | if err != nil { 109 | return false, err 110 | } 111 | ok = true 112 | } else if m == n-1 && !c.isFirst { 113 | err = c.writeLine(nil) 114 | if err != nil { 115 | return false, err 116 | } 117 | ok = true 118 | } 119 | n = m 120 | if n == 0 { 121 | break 122 | } 123 | } 124 | if n > 0 { 125 | reverseBytes(c.swapBuff[:n]) 126 | c.lineBuff = append(c.lineBuff, c.swapBuff[:n]...) 127 | } 128 | if offset == 0 { 129 | err = c.writeLine(nil) 130 | if err != nil { 131 | return false, err 132 | } 133 | ok = true 134 | } 135 | c.fileSize = offset 136 | if c.isFirst { 137 | c.isFirst = false 138 | } 139 | return ok, nil 140 | } 141 | 142 | func (c *ReadLineFromEnd) writeLine(b []byte) (err error) { 143 | if len(b) > 0 { 144 | _, err = c.bwr.Write(b) 145 | if err != nil { 146 | return err 147 | } 148 | } 149 | if len(c.lineBuff) > 0 { 150 | reverseBytes(c.lineBuff) 151 | _, err = c.bwr.Write(c.lineBuff) 152 | if err != nil { 153 | return err 154 | } 155 | c.lineBuff = c.lineBuff[:0] 156 | } 157 | _, err = c.bwr.Write([]byte{'\n'}) 158 | if err != nil { 159 | return err 160 | } 161 | return nil 162 | } 163 | 164 | func reverseBytes(b []byte) { 165 | n := len(b) 166 | if n <= 1 { 167 | return 168 | } 169 | for i := 0; i < n; i++ { 170 | k := n - 1 171 | if k != i { 172 | b[i], b[k] = b[k], b[i] 173 | } 174 | n-- 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/IceWhaleTech/CasaOS 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Curtis-Milo/nat-type-identifier-go v0.0.0-20220215191915-18d42168c63d 7 | github.com/Microsoft/go-winio v0.5.0 // indirect 8 | github.com/Microsoft/hcsshim v0.8.22 // indirect 9 | github.com/ambelovsky/go-structs v1.1.0 // indirect 10 | github.com/ambelovsky/gosf v0.0.0-20201109201340-237aea4d6109 11 | github.com/ambelovsky/gosf-socketio v0.0.0-20201109193639-add9d32f8b19 // indirect 12 | github.com/bits-and-blooms/bitset v1.2.1 // indirect 13 | github.com/containerd/containerd v1.5.7 14 | github.com/containerd/continuity v0.2.0 // indirect 15 | github.com/disintegration/imaging v1.6.2 16 | github.com/docker/distribution v2.8.0+incompatible // indirect 17 | github.com/docker/docker v20.10.7+incompatible 18 | github.com/docker/go-connections v0.4.0 19 | github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b 20 | github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect 21 | github.com/gin-contrib/gzip v0.0.2 22 | github.com/gin-gonic/gin v1.7.2 23 | github.com/go-ini/ini v1.62.0 24 | github.com/go-playground/validator/v10 v10.6.1 // indirect 25 | github.com/gogo/googleapis v1.4.1 // indirect 26 | github.com/golang-jwt/jwt/v4 v4.4.1 27 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 28 | github.com/golang/mock v1.6.0 29 | github.com/gomodule/redigo v1.8.5 30 | github.com/google/go-github/v36 v36.0.0 31 | github.com/google/uuid v1.3.0 // indirect 32 | github.com/googollee/go-socket.io v1.6.2 33 | github.com/gorilla/mux v1.8.0 // indirect 34 | github.com/gorilla/websocket v1.4.2 35 | github.com/hirochachacha/go-smb2 v1.1.0 36 | github.com/jinzhu/copier v0.3.2 37 | github.com/json-iterator/go v1.1.11 // indirect 38 | github.com/klauspost/compress v1.13.6 // indirect 39 | github.com/kr/text v0.2.0 // indirect 40 | github.com/leodido/go-urn v1.2.1 // indirect 41 | github.com/lucas-clemente/quic-go v0.25.0 42 | github.com/mattn/go-isatty v0.0.14 // indirect 43 | github.com/mattn/go-sqlite3 v1.14.11 // indirect 44 | github.com/mholt/archiver/v3 v3.5.1 45 | github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect 46 | github.com/morikuni/aec v1.0.0 // indirect 47 | github.com/opencontainers/image-spec v1.0.2 // indirect 48 | github.com/opencontainers/selinux v1.8.5 // indirect 49 | github.com/patrickmn/go-cache v2.1.0+incompatible 50 | github.com/pilebones/go-udev v0.9.0 51 | github.com/pkg/errors v0.9.1 52 | github.com/prometheus/procfs v0.7.3 // indirect 53 | github.com/robfig/cron v1.2.0 54 | github.com/satori/go.uuid v1.2.0 55 | github.com/shirou/gopsutil/v3 v3.22.7 56 | github.com/sirupsen/logrus v1.8.1 57 | github.com/smartystreets/assertions v1.2.0 // indirect 58 | github.com/smartystreets/goconvey v1.6.4 // indirect 59 | github.com/stretchr/testify v1.8.0 60 | github.com/tidwall/gjson v1.10.2 61 | github.com/ugorji/go v1.2.6 // indirect 62 | go.opencensus.io v0.23.0 // indirect 63 | go.uber.org/zap v1.10.0 64 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 65 | golang.org/x/mod v0.5.0 // indirect 66 | golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect 67 | golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f 68 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect 69 | golang.org/x/text v0.3.7 // indirect 70 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect 71 | golang.org/x/tools v0.1.7 // indirect 72 | google.golang.org/appengine v1.6.7 // indirect 73 | google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0 // indirect 74 | google.golang.org/grpc v1.41.0 // indirect 75 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 76 | gopkg.in/ini.v1 v1.62.0 // indirect 77 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 78 | gorm.io/driver/sqlite v1.2.6 79 | gorm.io/gorm v1.22.5 80 | ) 81 | -------------------------------------------------------------------------------- /shell/usb-mount.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # copy to /casaOS/util/shell path 4 | # chmod 755 5 | 6 | log="logger -t usb-mount.sh -s " 7 | 8 | ACTION=$1 9 | 10 | DEVBASE=$2 11 | 12 | DEVICE="/dev/${DEVBASE}" 13 | 14 | # See if this drive is already mounted, and if so where 15 | MOUNT_POINT=$(lsblk -l -p -o name,mountpoint | grep ${DEVICE} | awk '{print $2}') 16 | 17 | do_mount() { 18 | 19 | if [ -n "${MOUNT_POINT}" ]; then 20 | ${log} "Warning: ${DEVICE} is already mounted at ${MOUNT_POINT}" 21 | exit 1 22 | fi 23 | 24 | # Get info for this drive: $ID_FS_LABEL and $ID_FS_TYPE 25 | eval $(blkid -o udev ${DEVICE} | grep -i -e "ID_FS_LABEL" -e "ID_FS_TYPE") 26 | 27 | #ID_FS_LABEL=新加卷 28 | #ID_FS_LABEL_ENC=新加卷 29 | #ID_FS_TYPE=ntfs 30 | 31 | # Figure out a mount point to use 32 | # LABEL=${ID_FS_LABEL} 33 | LABEL=${DEVBASE} 34 | if grep -q " /DATA/USB_Storage_${LABEL} " /etc/mtab; then 35 | # Already in use, make a unique one 36 | LABEL+="_${DEVBASE}" 37 | fi 38 | DEV_LABEL="${LABEL}" 39 | 40 | # Use the device name in case the drive doesn't have label 41 | if [ -z ${DEV_LABEL} ]; then 42 | DEV_LABEL="${DEVBASE}" 43 | fi 44 | 45 | 46 | MOUNT_POINT="/DATA/USB_Storage_${DEV_LABEL}" 47 | 48 | ${log} "Mount point: ${MOUNT_POINT}" 49 | 50 | mkdir -p ${MOUNT_POINT} 51 | 52 | 53 | # MOUNT_POINT="/DATA/USB_Storage1" 54 | # arr=("/DATA/USB_Storage1" "/DATA/USB_Storage2" "/DATA/USB_Storage3" "/DATA/USB_Storage4" "/DATA/USB_Storage5" "/DATA/USB_Storage6" "/DATA/USB_Storage7" "/DATA/USB_Storage8" "/DATA/USB_Storage9" "/DATA/USB_Storage10" "/DATA/USB_Storage11" "/DATA/USB_Storage12") 55 | # for folder in ${arr[@]}; do 56 | # #如果文件夹不存在,创建文件夹 57 | # if [ ! -d "$folder" ]; then 58 | # mkdir -p ${folder} 59 | # MOUNT_POINT=$folder 60 | # break 61 | # fi 62 | # done 63 | 64 | # ${log} "Mount point: ${MOUNT_POINT}" 65 | 66 | 67 | 68 | # # Global mount options 69 | # OPTS="rw,relatime" 70 | # 71 | # # File system type specific mount options 72 | # if [[ ${ID_FS_TYPE} == "vfat" ]]; then 73 | # OPTS+=",users,gid=100,umask=000,shortname=mixed,utf8=1,flush" 74 | # fi 75 | 76 | # if ! mount -o ${OPTS} ${DEVICE} ${MOUNT_POINT}; then 77 | # ${log} "Error mounting ${DEVICE} (status = $?)" 78 | # rmdir "${MOUNT_POINT}" 79 | # exit 1 80 | # else 81 | # # Track the mounted drives 82 | # echo "${MOUNT_POINT}:${DEVBASE}" | cat >>"/var/log/usb-mount.track" 83 | # fi 84 | # 85 | # ${log} "Mounted ${DEVICE} at ${MOUNT_POINT}" 86 | 87 | case ${ID_FS_TYPE} in 88 | vfat) 89 | mount -t vfat -o rw,relatime,users,gid=100,umask=000,shortname=mixed,utf8=1,flush ${DEVICE} ${MOUNT_POINT} 90 | ;; 91 | ext[2-4]) 92 | mount -o noatime ${DEVICE} ${MOUNT_POINT} >/dev/null 2>&1 93 | ;; 94 | exfat) 95 | mount -t exfat ${DEVICE} ${MOUNT_POINT} >/dev/null 2>&1 96 | ;; 97 | ntfs) 98 | ntfs-3g ${DEVICE} ${MOUNT_POINT} 99 | ;; 100 | iso9660) 101 | mount -t iso9660 ${DEVICE} ${MOUNT_POINT} 102 | ;; 103 | *) 104 | /bin/rmdir "${MOUNT_POINT}" 105 | exit 0 106 | ;; 107 | esac 108 | } 109 | 110 | do_umount() { 111 | 112 | if [[ -z ${MOUNT_POINT} ]]; then 113 | ${log} "Warning: ${DEVICE} is not mounted" 114 | else 115 | #/bin/kill -9 $(lsof ${MOUNT_POINT}) 116 | umount -l ${DEVICE} 117 | ${log} "Unmounted ${DEVICE} from ${MOUNT_POINT}" 118 | if [ "`ls -A ${MOUNT_POINT}`" = "" ]; then 119 | /bin/rm -fr "${MOUNT_POINT}" 120 | fi 121 | sed -i.bak "\@${MOUNT_POINT}@d" /var/log/usb-mount.track 122 | fi 123 | 124 | } 125 | 126 | case "${ACTION}" in 127 | add) 128 | do_mount 129 | ;; 130 | remove) 131 | do_umount 132 | ;; 133 | *) 134 | exit 1 135 | ;; 136 | esac 137 | -------------------------------------------------------------------------------- /pkg/utils/httper/httper.go: -------------------------------------------------------------------------------- 1 | package httper 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/IceWhaleTech/CasaOS/pkg/config" 12 | "github.com/tidwall/gjson" 13 | ) 14 | 15 | //发送GET请求 16 | //url:请求地址 17 | //response:请求返回的内容 18 | func Get(url string, head map[string]string) (response string) { 19 | client := &http.Client{Timeout: 30 * time.Second} 20 | req, err := http.NewRequest("GET", url, nil) 21 | 22 | for k, v := range head { 23 | req.Header.Add(k, v) 24 | } 25 | if err != nil { 26 | return "" 27 | } 28 | resp, err := client.Do(req) 29 | if err != nil { 30 | fmt.Println(err) 31 | //需要错误日志的处理 32 | //loger.Error(error) 33 | return "" 34 | //panic(error) 35 | } 36 | defer resp.Body.Close() 37 | var buffer [512]byte 38 | result := bytes.NewBuffer(nil) 39 | for { 40 | n, err := resp.Body.Read(buffer[0:]) 41 | result.Write(buffer[0:n]) 42 | if err != nil && err == io.EOF { 43 | break 44 | } else if err != nil { 45 | //loger.Error(err) 46 | return "" 47 | // panic(err) 48 | } 49 | } 50 | response = result.String() 51 | return 52 | } 53 | 54 | //发送GET请求 55 | //url:请求地址 56 | //response:请求返回的内容 57 | func PersonGet(url string) (response string) { 58 | client := &http.Client{Timeout: 5 * time.Second} 59 | req, err := http.NewRequest("GET", url, nil) 60 | 61 | if err != nil { 62 | return "" 63 | } 64 | resp, err := client.Do(req) 65 | if err != nil { 66 | //需要错误日志的处理 67 | //loger.Error(error) 68 | return "" 69 | //panic(error) 70 | } 71 | defer resp.Body.Close() 72 | var buffer [512]byte 73 | result := bytes.NewBuffer(nil) 74 | for { 75 | n, err := resp.Body.Read(buffer[0:]) 76 | result.Write(buffer[0:n]) 77 | if err != nil && err == io.EOF { 78 | break 79 | } else if err != nil { 80 | //loger.Error(err) 81 | return "" 82 | // panic(err) 83 | } 84 | } 85 | response = result.String() 86 | return 87 | } 88 | 89 | //发送POST请求 90 | //url:请求地址,data:POST请求提交的数据,contentType:请求体格式,如:application/json 91 | //content:请求放回的内容 92 | func Post(url string, data []byte, contentType string, head map[string]string) (content string) { 93 | 94 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) 95 | req.Header.Add("content-type", contentType) 96 | for k, v := range head { 97 | req.Header.Add(k, v) 98 | } 99 | if err != nil { 100 | panic(err) 101 | } 102 | 103 | client := &http.Client{Timeout: 5 * time.Second} 104 | resp, error := client.Do(req) 105 | if error != nil { 106 | fmt.Println(error) 107 | return 108 | } 109 | defer resp.Body.Close() 110 | 111 | result, _ := ioutil.ReadAll(resp.Body) 112 | content = string(result) 113 | return 114 | } 115 | 116 | //发送POST请求 117 | //url:请求地址,data:POST请求提交的数据,contentType:请求体格式,如:application/json 118 | //content:请求放回的内容 119 | func ZeroTierGet(url string, head map[string]string) (content string, code int) { 120 | req, err := http.NewRequest(http.MethodGet, url, nil) 121 | for k, v := range head { 122 | req.Header.Add(k, v) 123 | } 124 | if err != nil { 125 | panic(err) 126 | } 127 | 128 | client := &http.Client{Timeout: 20 * time.Second} 129 | resp, error := client.Do(req) 130 | 131 | if error != nil { 132 | panic(error) 133 | } 134 | defer resp.Body.Close() 135 | code = resp.StatusCode 136 | result, _ := ioutil.ReadAll(resp.Body) 137 | content = string(result) 138 | return 139 | } 140 | 141 | //发送GET请求 142 | //url:请求地址 143 | //response:请求返回的内容 144 | func OasisGet(url string) (response string) { 145 | 146 | head := make(map[string]string) 147 | 148 | t := make(chan string) 149 | 150 | go func() { 151 | str := Get(config.ServerInfo.ServerApi+"/token", nil) 152 | 153 | t <- gjson.Get(str, "data").String() 154 | }() 155 | head["Authorization"] = <-t 156 | 157 | return Get(url, head) 158 | 159 | } 160 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/IceWhaleTech/CasaOS/model/notify" 10 | "github.com/IceWhaleTech/CasaOS/pkg/cache" 11 | "github.com/IceWhaleTech/CasaOS/pkg/config" 12 | "github.com/IceWhaleTech/CasaOS/pkg/sqlite" 13 | "github.com/IceWhaleTech/CasaOS/pkg/utils/encryption" 14 | "github.com/IceWhaleTech/CasaOS/pkg/utils/loger" 15 | "github.com/IceWhaleTech/CasaOS/pkg/utils/random" 16 | "github.com/IceWhaleTech/CasaOS/route" 17 | "github.com/IceWhaleTech/CasaOS/service" 18 | "github.com/IceWhaleTech/CasaOS/types" 19 | 20 | "github.com/robfig/cron" 21 | "gorm.io/gorm" 22 | ) 23 | 24 | var sqliteDB *gorm.DB 25 | 26 | var configFlag = flag.String("c", "", "config address") 27 | var dbFlag = flag.String("db", "", "db path") 28 | var resetUser = flag.Bool("ru", false, "reset user") 29 | var user = flag.String("user", "", "user name") 30 | var version = flag.Bool("v", false, "show version") 31 | 32 | func init() { 33 | flag.Parse() 34 | if *version { 35 | fmt.Println("v" + types.CURRENTVERSION) 36 | return 37 | } 38 | config.InitSetup(*configFlag) 39 | config.UpdateSetup() 40 | 41 | loger.LogInit() 42 | if len(*dbFlag) == 0 { 43 | *dbFlag = config.AppInfo.DBPath + "/db" 44 | } 45 | 46 | sqliteDB = sqlite.GetDb(*dbFlag) 47 | //gredis.GetRedisConn(config.RedisInfo), 48 | 49 | service.MyService = service.NewService(sqliteDB) 50 | 51 | service.Cache = cache.Init() 52 | 53 | service.GetToken() 54 | 55 | service.NewVersionApp = make(map[string]string) 56 | route.InitFunction() 57 | 58 | // go service.LoopFriend() 59 | // go service.MyService.App().CheckNewImage() 60 | 61 | } 62 | 63 | // @title casaOS API 64 | // @version 1.0.0 65 | // @contact.name lauren.pan 66 | // @contact.url https://www.zimaboard.com 67 | // @contact.email lauren.pan@icewhale.org 68 | // @description casaOS v1版本api 69 | // @host 192.168.2.217:8089 70 | // @securityDefinitions.apikey ApiKeyAuth 71 | // @in header 72 | // @name Authorization 73 | // @BasePath /v1 74 | func main() { 75 | service.NotifyMsg = make(chan notify.Message, 10) 76 | if *version { 77 | return 78 | } 79 | if *resetUser { 80 | if user == nil || len(*user) == 0 { 81 | fmt.Println("user is empty") 82 | return 83 | } 84 | userData := service.MyService.User().GetUserAllInfoByName(*user) 85 | if userData.Id == 0 { 86 | fmt.Println("user not exist") 87 | return 88 | } 89 | password := random.RandomString(6, false) 90 | userData.Password = encryption.GetMD5ByStr(password) 91 | service.MyService.User().UpdateUserPassword(userData) 92 | fmt.Println("User reset successful") 93 | fmt.Println("UserName:" + userData.Username) 94 | fmt.Println("Password:" + password) 95 | return 96 | } 97 | go route.SocketInit(service.NotifyMsg) 98 | go route.MonitoryUSB() 99 | //model.Setup() 100 | //gredis.Setup() 101 | r := route.InitRouter() 102 | //service.SyncTask(sqliteDB) 103 | cron2 := cron.New() 104 | //every day execution 105 | 106 | err := cron2.AddFunc("0/5 * * * * *", func() { 107 | if service.ClientCount > 0 { 108 | //route.SendNetINfoBySocket() 109 | //route.SendCPUBySocket() 110 | //route.SendMemBySocket() 111 | // route.SendDiskBySocket() 112 | //route.SendUSBBySocket() 113 | route.SendAllHardwareStatusBySocket() 114 | } 115 | }) 116 | if err != nil { 117 | fmt.Println(err) 118 | } 119 | cron2.Start() 120 | defer cron2.Stop() 121 | s := &http.Server{ 122 | Addr: fmt.Sprintf(":%v", config.ServerInfo.HttpPort), 123 | Handler: r, 124 | ReadTimeout: 60 * time.Second, 125 | WriteTimeout: 60 * time.Second, 126 | MaxHeaderBytes: 1 << 20, 127 | } 128 | 129 | s.ListenAndServe() 130 | 131 | // if err := r.Run(fmt.Sprintf(":%v", config.ServerInfo.HttpPort)); err != nil { 132 | // fmt.Println("failed run app: ", err) 133 | // } 134 | } 135 | -------------------------------------------------------------------------------- /pkg/utils/common_err/e.go: -------------------------------------------------------------------------------- 1 | package common_err 2 | 3 | const ( 4 | SUCCESS = 200 5 | SERVICE_ERROR = 500 6 | CLIENT_ERROR = 400 7 | ERROR_AUTH_TOKEN = 401 8 | 9 | INVALID_PARAMS = 4000 10 | //user 11 | PWD_INVALID = 10001 12 | PWD_IS_EMPTY = 10002 13 | PWD_INVALID_OLD = 10003 14 | ACCOUNT_LOCK = 10004 15 | PWD_IS_TOO_SIMPLE = 10005 16 | USER_NOT_EXIST = 10006 17 | USER_EXIST = 10007 18 | KEY_NOT_EXIST = 10008 19 | NOT_IMAGE = 10009 20 | IMAGE_TOO_LARGE = 10010 21 | INSUFFICIENT_PERMISSIONS = 10011 22 | 23 | //system 24 | DIR_ALREADY_EXISTS = 20001 25 | FILE_ALREADY_EXISTS = 20002 26 | FILE_OR_DIR_EXISTS = 20003 27 | PORT_IS_OCCUPIED = 20004 28 | COMMAND_ERROR_INVALID_OPERATION = 20005 29 | VERIFICATION_FAILURE = 20006 30 | Record_NOT_EXIST = 20007 31 | Record_ALREADY_EXIST = 20008 32 | SERVICE_NOT_RUNNING = 20009 33 | 34 | //disk 35 | NAME_NOT_AVAILABLE = 40001 36 | DISK_NEEDS_FORMAT = 40002 37 | DISK_BUSYING = 40003 38 | REMOVE_MOUNT_POINT_ERROR = 40004 39 | FORMAT_ERROR = 40005 40 | 41 | //app 42 | UNINSTALL_APP_ERROR = 50001 43 | PULL_IMAGE_ERROR = 50002 44 | DEVICE_NOT_EXIST = 50003 45 | ERROR_APP_NAME_EXIST = 50004 46 | 47 | //file 48 | FILE_DOES_NOT_EXIST = 60001 49 | FILE_READ_ERROR = 60002 50 | FILE_DELETE_ERROR = 60003 51 | DIR_NOT_EXISTS = 60004 52 | SOURCE_DES_SAME = 60005 53 | 54 | //share 55 | SHARE_ALREADY_EXISTS = 70001 56 | SHARE_NAME_ALREADY_EXISTS = 70002 57 | ) 58 | 59 | var MsgFlags = map[int]string{ 60 | SUCCESS: "ok", 61 | SERVICE_ERROR: "Fail", 62 | CLIENT_ERROR: "Fail", 63 | INVALID_PARAMS: "Parameters Error", 64 | ERROR_AUTH_TOKEN: "Error auth token", 65 | 66 | //user 67 | PWD_INVALID: "Invalid password", 68 | PWD_IS_EMPTY: "Password is empty", 69 | PWD_INVALID_OLD: "Invalid old password", 70 | ACCOUNT_LOCK: "Account is locked", 71 | PWD_IS_TOO_SIMPLE: "Password is too simple", 72 | USER_NOT_EXIST: "User does not exist", 73 | USER_EXIST: "User already exists", 74 | KEY_NOT_EXIST: "Key does not exist", 75 | IMAGE_TOO_LARGE: "Image is too large", 76 | NOT_IMAGE: "Not an image", 77 | INSUFFICIENT_PERMISSIONS: "Insufficient permissions", 78 | 79 | //system 80 | DIR_ALREADY_EXISTS: "Folder already exists", 81 | FILE_ALREADY_EXISTS: "File already exists", 82 | FILE_OR_DIR_EXISTS: "File or folder already exists", 83 | PORT_IS_OCCUPIED: "Port is occupied", 84 | VERIFICATION_FAILURE: "Verification failure", 85 | Record_ALREADY_EXIST: "Record already exists", 86 | Record_NOT_EXIST: "Record does not exist", 87 | SERVICE_NOT_RUNNING: "Service is not running", 88 | 89 | //app 90 | UNINSTALL_APP_ERROR: "Error uninstalling app", 91 | PULL_IMAGE_ERROR: "Error pulling image", 92 | DEVICE_NOT_EXIST: "Device does not exist", 93 | ERROR_APP_NAME_EXIST: "App name already exists", 94 | 95 | //disk 96 | NAME_NOT_AVAILABLE: "Name not available", 97 | DISK_NEEDS_FORMAT: "Drive needs to be formatted", 98 | REMOVE_MOUNT_POINT_ERROR: "Failed to remove mount point", 99 | DISK_BUSYING: "Drive is busy", 100 | FORMAT_ERROR: "Formatting failed, please check if the directory is occupied", 101 | //share 102 | SHARE_ALREADY_EXISTS: "Share already exists", 103 | SHARE_NAME_ALREADY_EXISTS: "Share name already exists", 104 | // 105 | SOURCE_DES_SAME: "Source and destination cannot be the same.", 106 | FILE_DOES_NOT_EXIST: "File does not exist", 107 | 108 | DIR_NOT_EXISTS: "Directory does not exist", 109 | 110 | FILE_READ_ERROR: "File read error", 111 | FILE_DELETE_ERROR: "Delete error", 112 | 113 | COMMAND_ERROR_INVALID_OPERATION: "invalid operation", 114 | } 115 | 116 | //获取错误信息 117 | func GetMsg(code int) string { 118 | msg, ok := MsgFlags[code] 119 | if ok { 120 | return msg 121 | } 122 | return MsgFlags[SERVICE_ERROR] 123 | } 124 | -------------------------------------------------------------------------------- /model/manifest.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | ) 7 | 8 | type TcpPorts struct { 9 | Desc string `json:"desc"` 10 | ContainerPort int `json:"container_port"` 11 | } 12 | type UdpPorts struct { 13 | Desc string `json:"desc"` 14 | ContainerPort int `json:"container_port"` 15 | } 16 | 17 | /*******************使用gorm支持json************************************/ 18 | 19 | type PortMap struct { 20 | ContainerPort string `json:"container"` 21 | CommendPort string `json:"host"` 22 | Protocol string `json:"protocol"` 23 | Desc string `json:"desc"` 24 | Type int `json:"type"` 25 | } 26 | 27 | type PortArray []PortMap 28 | 29 | // Value 实现方法 30 | func (p PortArray) Value() (driver.Value, error) { 31 | return json.Marshal(p) 32 | } 33 | 34 | // Scan 实现方法 35 | func (p *PortArray) Scan(input interface{}) error { 36 | return json.Unmarshal(input.([]byte), p) 37 | } 38 | 39 | /************************************************************************/ 40 | 41 | /*******************使用gorm支持json************************************/ 42 | 43 | type Env struct { 44 | Name string `json:"container"` 45 | Value string `json:"host"` 46 | Desc string `json:"desc"` 47 | Type int `json:"type"` 48 | } 49 | 50 | type JSON json.RawMessage 51 | 52 | type EnvArray []Env 53 | 54 | // Value 实现方法 55 | func (p EnvArray) Value() (driver.Value, error) { 56 | return json.Marshal(p) 57 | //return .MarshalJSON() 58 | } 59 | 60 | // Scan 实现方法 61 | func (p *EnvArray) Scan(input interface{}) error { 62 | return json.Unmarshal(input.([]byte), p) 63 | } 64 | 65 | /************************************************************************/ 66 | 67 | /*******************使用gorm支持json************************************/ 68 | 69 | type PathMap struct { 70 | ContainerPath string `json:"container"` 71 | Path string `json:"host"` 72 | Type int `json:"type"` 73 | Desc string `json:"desc"` 74 | } 75 | 76 | type PathArray []PathMap 77 | 78 | // Value 实现方法 79 | func (p PathArray) Value() (driver.Value, error) { 80 | return json.Marshal(p) 81 | } 82 | 83 | // Scan 实现方法 84 | func (p *PathArray) Scan(input interface{}) error { 85 | return json.Unmarshal(input.([]byte), p) 86 | } 87 | 88 | /************************************************************************/ 89 | 90 | //type PostData struct { 91 | // Envs EnvArrey `json:"envs,omitempty"` 92 | // Udp PortArrey `json:"udp_ports"` 93 | // Tcp PortArrey `json:"tcp_ports"` 94 | // Volumes PathArrey `json:"volumes"` 95 | // Devices PathArrey `json:"devices"` 96 | // Port string `json:"port,omitempty"` 97 | // PortMap string `json:"port_map"` 98 | // CpuShares int64 `json:"cpu_shares,omitempty"` 99 | // Memory int64 `json:"memory,omitempty"` 100 | // Restart string `json:"restart,omitempty"` 101 | // EnableUPNP bool `json:"enable_upnp"` 102 | // Label string `json:"label"` 103 | // Position bool `json:"position"` 104 | //} 105 | 106 | type CustomizationPostData struct { 107 | ContainerName string `json:"container_name"` 108 | CustomId string `json:"custom_id"` 109 | Origin string `json:"origin"` 110 | NetworkModel string `json:"network_model"` 111 | Index string `json:"index"` 112 | Icon string `json:"icon"` 113 | Image string `json:"image"` 114 | Envs EnvArray `json:"envs"` 115 | Ports PortArray `json:"ports"` 116 | Volumes PathArray `json:"volumes"` 117 | Devices PathArray `json:"devices"` 118 | //Port string `json:"port,omitempty"` 119 | PortMap string `json:"port_map"` 120 | CpuShares int64 `json:"cpu_shares"` 121 | Memory int64 `json:"memory"` 122 | Restart string `json:"restart"` 123 | EnableUPNP bool `json:"enable_upnp"` 124 | Label string `json:"label"` 125 | Description string `json:"description"` 126 | Position bool `json:"position"` 127 | HostName string `json:"host_name"` 128 | Privileged bool `json:"privileged"` 129 | CapAdd []string `json:"cap_add"` 130 | Cmd []string `json:"cmd"` 131 | Protocol string `json:"protocol"` 132 | Host string `json:"host"` 133 | } 134 | -------------------------------------------------------------------------------- /service/file.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2021-12-20 14:15:46 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-07-04 16:18:23 6 | * @FilePath: /CasaOS/service/file.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package service 12 | 13 | import ( 14 | "context" 15 | "io" 16 | "os" 17 | "path/filepath" 18 | "strings" 19 | "sync" 20 | "time" 21 | 22 | "github.com/IceWhaleTech/CasaOS/model" 23 | "github.com/IceWhaleTech/CasaOS/pkg/utils/file" 24 | "github.com/IceWhaleTech/CasaOS/pkg/utils/loger" 25 | "go.uber.org/zap" 26 | ) 27 | 28 | var FileQueue sync.Map 29 | 30 | var OpStrArr []string 31 | 32 | type reader struct { 33 | ctx context.Context 34 | r io.Reader 35 | } 36 | 37 | // NewReader wraps an io.Reader to handle context cancellation. 38 | // 39 | // Context state is checked BEFORE every Read. 40 | func NewReader(ctx context.Context, r io.Reader) io.Reader { 41 | if r, ok := r.(*reader); ok && ctx == r.ctx { 42 | return r 43 | } 44 | return &reader{ctx: ctx, r: r} 45 | } 46 | 47 | func (r *reader) Read(p []byte) (n int, err error) { 48 | select { 49 | case <-r.ctx.Done(): 50 | return 0, r.ctx.Err() 51 | default: 52 | return r.r.Read(p) 53 | } 54 | } 55 | 56 | type writer struct { 57 | ctx context.Context 58 | w io.Writer 59 | } 60 | 61 | type copier struct { 62 | writer 63 | } 64 | 65 | func NewWriter(ctx context.Context, w io.Writer) io.Writer { 66 | if w, ok := w.(*copier); ok && ctx == w.ctx { 67 | return w 68 | } 69 | return &copier{writer{ctx: ctx, w: w}} 70 | } 71 | 72 | // Write implements io.Writer, but with context awareness. 73 | func (w *writer) Write(p []byte) (n int, err error) { 74 | select { 75 | case <-w.ctx.Done(): 76 | return 0, w.ctx.Err() 77 | default: 78 | return w.w.Write(p) 79 | } 80 | } 81 | func FileOperate(k string) { 82 | 83 | list, ok := FileQueue.Load(k) 84 | if !ok { 85 | return 86 | } 87 | 88 | temp := list.(model.FileOperate) 89 | if temp.ProcessedSize > 0 { 90 | return 91 | } 92 | for i := 0; i < len(temp.Item); i++ { 93 | v := temp.Item[i] 94 | if temp.Type == "move" { 95 | lastPath := v.From[strings.LastIndex(v.From, "/")+1:] 96 | if !file.CheckNotExist(temp.To + "/" + lastPath) { 97 | if temp.Style == "skip" { 98 | temp.Item[i].Finished = true 99 | continue 100 | } else { 101 | os.RemoveAll(temp.To + "/" + lastPath) 102 | } 103 | } 104 | err := os.Rename(v.From, temp.To+"/"+lastPath) 105 | if err != nil { 106 | loger.Error("file move error", zap.Any("err", err)) 107 | err = file.MoveFile(v.From, temp.To+"/"+lastPath) 108 | if err != nil { 109 | loger.Error("MoveFile error", zap.Any("err", err)) 110 | continue 111 | } 112 | 113 | } 114 | } else if temp.Type == "copy" { 115 | err := file.CopyDir(v.From, temp.To, temp.Style) 116 | if err != nil { 117 | continue 118 | } 119 | } else { 120 | continue 121 | } 122 | 123 | } 124 | temp.Finished = true 125 | FileQueue.Store(k, temp) 126 | } 127 | 128 | func ExecOpFile() { 129 | len := len(OpStrArr) 130 | if len == 0 { 131 | return 132 | } 133 | if len > 1 { 134 | len = 1 135 | } 136 | for i := 0; i < len; i++ { 137 | go FileOperate(OpStrArr[i]) 138 | } 139 | } 140 | 141 | // file move or copy and send notify 142 | func CheckFileStatus() { 143 | for { 144 | if len(OpStrArr) == 0 { 145 | return 146 | } 147 | for _, v := range OpStrArr { 148 | var total int64 = 0 149 | item, ok := FileQueue.Load(v) 150 | if !ok { 151 | continue 152 | } 153 | temp := item.(model.FileOperate) 154 | for i := 0; i < len(temp.Item); i++ { 155 | 156 | if !temp.Item[i].Finished { 157 | size, err := file.GetFileOrDirSize(temp.To + "/" + filepath.Base(temp.Item[i].From)) 158 | if err != nil { 159 | continue 160 | } 161 | temp.Item[i].ProcessedSize = size 162 | if size == temp.Item[i].Size { 163 | temp.Item[i].Finished = true 164 | } 165 | total += size 166 | } else { 167 | total += temp.Item[i].ProcessedSize 168 | } 169 | 170 | } 171 | temp.ProcessedSize = total 172 | FileQueue.Store(v, temp) 173 | } 174 | time.Sleep(time.Second * 3) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /.github/workflows/casa.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Build CasaOS 4 | 5 | on: 6 | repository_dispatch: 7 | workflow_dispatch: 8 | inputs: 9 | ssh: 10 | description: 'SSH connection to Actions' 11 | required: false 12 | default: 'false' 13 | 14 | #on: 15 | # push: 16 | # branches: 17 | # - 'main' 18 | # tags: 19 | # - 'v*' 20 | env: 21 | REPO_URL: https://github.com/IceWhaleTech/CasaOS.git 22 | REPO_BRANCH: main 23 | PACK_SH_URL: https://raw.githubusercontent.com/jerrykuku/actions-casa/main/pack.sh 24 | PACK_SH: pack.sh 25 | TZ: Asia/Shanghai 26 | 27 | jobs: 28 | xgo: 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | go_version: 33 | - 1.17.1 34 | runs-on: ubuntu-latest 35 | steps: 36 | 37 | # - name: Get release 38 | # id: get_release 39 | # uses: bruceadams/get-release@v1.2.3 40 | # env: 41 | # GITHUB_TOKEN: ${{ github.token }} 42 | 43 | - uses: actions/checkout@v2 44 | with: 45 | fetch-depth: 0 46 | submodules: true 47 | # - name: Initialization environment 48 | # env: 49 | # DEBIAN_FRONTEND: noninteractive 50 | # run: | 51 | # sudo timedatectl set-timezone "$TZ" 52 | # sudo mkdir -p /workdir 53 | # sudo chown $USER:$GROUPS /workdir 54 | 55 | 56 | 57 | 58 | 59 | # - name: Clone source code 60 | # working-directory: /workdir 61 | # run: | 62 | # df -hT $PWD 63 | # git clone $REPO_URL -b $REPO_BRANCH --recursive casa 64 | # ln -sf /workdir/casa $GITHUB_WORKSPACE/casa 65 | # ls 66 | 67 | 68 | - name: Set enviroment for github-release 69 | run: | 70 | echo "VERSION=$(cat types/system.go | grep CURRENTVERSION | awk '$2 == "CURRENTVERSION"{print $4}' | sed 's/"//g')" >>$GITHUB_ENV 71 | echo "BODY=$(cat types/system.go | grep BODY | awk -F= '{print $2}' | sed 's/"//g')" >>$GITHUB_ENV 72 | 73 | 74 | 75 | - name: Use Node.js 76 | uses: actions/setup-node@v2 77 | with: 78 | node-version: '14' 79 | 80 | - name: Build frontend with nodejs and yarn 81 | run: | 82 | cd UI 83 | ls 84 | yarn install 85 | yarn build 86 | 87 | - name: list work 88 | run: pwd 89 | 90 | - name: Build with xgo 91 | uses: crazy-max/ghaction-xgo@v1 92 | with: 93 | xgo_version: v0.7.5 94 | go_version: ${{ matrix.go_version }} 95 | dest: build 96 | prefix: casa 97 | targets: linux/amd64,linux/arm64,linux/arm-7 98 | v: true 99 | x: false 100 | race: false 101 | ldflags: -s -w 102 | buildmode: default 103 | # 104 | # - name: List Files 105 | # run: | 106 | # ls 107 | # mkdir build 108 | # ls 109 | # echo "::set-output name=status::success" 110 | 111 | - name: Pack builds 112 | run: | 113 | wget $PACK_SH_URL 114 | chmod +x $PACK_SH 115 | ./$PACK_SH 116 | echo "::set-output name=status::success" 117 | - name: list work 118 | run: ls 119 | 120 | 121 | - name: Update release 122 | uses: meeDamian/github-release@2.0 123 | with: 124 | token: ${{ secrets.GITHUB_TOKEN }} 125 | files: > 126 | linux-amd64-casaos.tar.gz 127 | linux-arm64-casaos.tar.gz 128 | linux-arm-7-casaos.tar.gz 129 | tag: v${{ env.VERSION }} 130 | body: > 131 | ${{ env.BODY }} 132 | name: v${{ env.VERSION }} 133 | gzip: false 134 | allow_override: false 135 | prerelease: true 136 | # - name: Upload linux-amd64-casaos.tar.gz 137 | # id: upload_assets_amd64 138 | # uses: shogo82148/actions-upload-release-asset@v1 139 | # with: 140 | # upload_url: ${{ steps.get_release.outputs.upload_url }} 141 | # asset_path: /workdir/casa/upload/linux-amd64-casaos.tar.gz 142 | # 143 | # - name: Upload linux-arm64-casaos.tar.gz 144 | # id: upload_assets_arm64 145 | # uses: shogo82148/actions-upload-release-asset@v1 146 | # with: 147 | # upload_url: ${{ steps.get_release.outputs.upload_url }} 148 | # asset_path: /workdir/casa/upload/linux-arm64-casaos.tar.gz 149 | -------------------------------------------------------------------------------- /model/app.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | "time" 7 | ) 8 | 9 | type ServerAppListCollection struct { 10 | List []ServerAppList `json:"list"` 11 | Recommend []ServerAppList `json:"recommend"` 12 | Community []ServerAppList `json:"community"` 13 | Version string `json:"version"` 14 | } 15 | 16 | // @tiger - 对于用于出参的数据结构,静态信息(例如 title)和 17 | // 动态信息(例如 state、query_count)应该划分到不同的数据结构中 18 | // 19 | // 这样的好处是 20 | // 1 - 多次获取动态信息时可以减少出参复杂度,因为静态信息只获取一次就好 21 | // 2 - 在未来的迭代中,可以降低维护成本(所有字段都展开放在一个层级维护成本略高) 22 | // 23 | // 另外,一些针对性字段,例如 Docker 相关的,可以用 map 来保存。 24 | // 这样在未来增加多态 App,例如 Snap,不需要维护多个结构,或者一个结构保存不必要的字段 25 | type ServerAppList struct { 26 | Id uint `gorm:"column:id;primary_key" json:"id"` 27 | Title string `json:"title"` 28 | Description string `json:"description"` 29 | Tagline string `json:"tagline"` 30 | Tags Strings `gorm:"type:json" json:"tags"` 31 | Icon string `json:"icon"` 32 | ScreenshotLink Strings `gorm:"type:json" json:"screenshot_link"` 33 | Category string `json:"category"` 34 | CategoryId int `json:"category_id"` 35 | CategoryFont string `json:"category_font"` 36 | PortMap string `json:"port_map"` 37 | ImageVersion string `json:"image_version"` 38 | Tip string `json:"tip"` 39 | Envs EnvArray `json:"envs"` 40 | Ports PortArray `json:"ports"` 41 | Volumes PathArray `json:"volumes"` 42 | Devices PathArray `json:"devices"` 43 | NetworkModel string `json:"network_model"` 44 | Image string `json:"image"` 45 | Index string `json:"index"` 46 | CreatedAt time.Time `json:"created_at"` 47 | UpdatedAt time.Time `json:"updated_at"` 48 | State int `json:"state"` 49 | Author string `json:"author"` 50 | MinMemory int `json:"min_memory"` 51 | MinDisk int `json:"min_disk"` 52 | MaxMemory uint64 `json:"max_memory"` 53 | Thumbnail string `json:"thumbnail"` 54 | Healthy string `json:"healthy"` 55 | Plugins Strings `json:"plugins"` 56 | Origin string `json:"origin"` 57 | Type int `json:"type"` 58 | QueryCount int `json:"query_count"` 59 | Developer string `json:"developer"` 60 | HostName string `json:"host_name"` 61 | Privileged bool `json:"privileged"` 62 | CapAdd Strings `json:"cap_add"` 63 | Cmd Strings `json:"cmd"` 64 | } 65 | 66 | type Ports struct { 67 | ContainerPort uint `json:"container_port"` 68 | CommendPort int `json:"commend_port"` 69 | Desc string `json:"desc"` 70 | Type int `json:"type"` // 1:必选 2:可选 3:默认值不必显示 4:系统处理 5:container内容也可编辑 71 | } 72 | 73 | type Volume struct { 74 | ContainerPath string `json:"container_path"` 75 | Path string `json:"path"` 76 | Desc string `json:"desc"` 77 | Type int `json:"type"` // 1:必选 2:可选 3:默认值不必显示 4:系统处理 5:container内容也可编辑 78 | } 79 | 80 | type Envs struct { 81 | Name string `json:"name"` 82 | Value string `json:"value"` 83 | Desc string `json:"desc"` 84 | Type int `json:"type"` // 1:必选 2:可选 3:默认值不必显示 4:系统处理 5:container内容也可编辑 85 | } 86 | 87 | type Devices struct { 88 | ContainerPath string `json:"container_path"` 89 | Path string `json:"path"` 90 | Desc string `json:"desc"` 91 | Type int `json:"type"` // 1:必选 2:可选 3:默认值不必显示 4:系统处理 5:container内容也可编辑 92 | } 93 | 94 | type configures struct { 95 | TcpPorts []Ports `json:"tcp_ports"` 96 | UdpPorts []Ports `json:"udp_ports"` 97 | Envs []Envs `json:"envs"` 98 | Volumes []Volume `json:"volumes"` 99 | Devices []Devices `json:"devices"` 100 | } 101 | 102 | /****************使gorm支持[]string结构*******************/ 103 | type Strings []string 104 | 105 | func (c Strings) Value() (driver.Value, error) { 106 | b, err := json.Marshal(c) 107 | return string(b), err 108 | } 109 | 110 | func (c *Strings) Scan(input interface{}) error { 111 | return json.Unmarshal(input.([]byte), c) 112 | } 113 | 114 | /****************使gorm支持[]string结构*******************/ 115 | 116 | /****************使gorm支持[]string结构*******************/ 117 | type MapStrings []map[string]string 118 | 119 | func (c MapStrings) Value() (driver.Value, error) { 120 | b, err := json.Marshal(c) 121 | return string(b), err 122 | } 123 | 124 | func (c *MapStrings) Scan(input interface{}) error { 125 | return json.Unmarshal(input.([]byte), c) 126 | } 127 | 128 | /****************使gorm支持[]string结构*******************/ 129 | -------------------------------------------------------------------------------- /pkg/utils/file/image.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "github.com/disintegration/imaging" 14 | "github.com/dsoprea/go-exif/v3" 15 | exifcommon "github.com/dsoprea/go-exif/v3/common" 16 | ) 17 | 18 | func GetImage(path string, width, height int) ([]byte, error) { 19 | if thumbnail, err := GetThumbnailByOwnerPhotos(path); err == nil { 20 | return thumbnail, nil 21 | } else { 22 | return GetThumbnailByWebPhoto(path, width, height) 23 | } 24 | } 25 | func GetThumbnailByOwnerPhotos(path string) ([]byte, error) { 26 | file, err := os.Open(path) 27 | if err != nil { 28 | return nil, err 29 | } 30 | buff := &bytes.Buffer{} 31 | 32 | defer file.Close() 33 | offset := 0 34 | offsets := []int{12, 30} 35 | 36 | head := make([]byte, 0xffff) 37 | 38 | r := io.TeeReader(file, buff) 39 | _, err = r.Read(head) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | for _, offset = range offsets { 45 | if _, err = exif.ParseExifHeader(head[offset:]); err == nil { 46 | break 47 | } 48 | } 49 | if err != nil { 50 | return nil, err 51 | } 52 | im, err := exifcommon.NewIfdMappingWithStandard() 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | _, index, err := exif.Collect(im, exif.NewTagIndex(), head[offset:]) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | ifd := index.RootIfd.NextIfd() 63 | if ifd == nil { 64 | return nil, exif.ErrNoThumbnail 65 | } 66 | thumbnail, err := ifd.Thumbnail() 67 | if err != nil { 68 | return nil, err 69 | } 70 | return thumbnail, nil 71 | } 72 | func GetThumbnailByWebPhoto(path string, width, height int) ([]byte, error) { 73 | src, err := imaging.Open(path) 74 | if err != nil { 75 | fmt.Println(err) 76 | return nil, err 77 | } 78 | 79 | img := imaging.Resize(src, width, 0, imaging.Lanczos) 80 | 81 | f, err := imaging.FormatFromFilename(path) 82 | if err != nil { 83 | return nil, err 84 | } 85 | buf := bytes.Buffer{} 86 | imaging.Encode(&buf, img, f) 87 | return buf.Bytes(), nil 88 | } 89 | 90 | func ImageExtArray() []string { 91 | 92 | ext := []string{ 93 | "ase", 94 | "art", 95 | "bmp", 96 | "blp", 97 | "cd5", 98 | "cit", 99 | "cpt", 100 | "cr2", 101 | "cut", 102 | "dds", 103 | "dib", 104 | "djvu", 105 | "egt", 106 | "exif", 107 | "gif", 108 | "gpl", 109 | "grf", 110 | "icns", 111 | "ico", 112 | "iff", 113 | "jng", 114 | "jpeg", 115 | "jpg", 116 | "jfif", 117 | "jp2", 118 | "jps", 119 | "lbm", 120 | "max", 121 | "miff", 122 | "mng", 123 | "msp", 124 | "nitf", 125 | "ota", 126 | "pbm", 127 | "pc1", 128 | "pc2", 129 | "pc3", 130 | "pcf", 131 | "pcx", 132 | "pdn", 133 | "pgm", 134 | "PI1", 135 | "PI2", 136 | "PI3", 137 | "pict", 138 | "pct", 139 | "pnm", 140 | "pns", 141 | "ppm", 142 | "psb", 143 | "psd", 144 | "pdd", 145 | "psp", 146 | "px", 147 | "pxm", 148 | "pxr", 149 | "qfx", 150 | "raw", 151 | "rle", 152 | "sct", 153 | "sgi", 154 | "rgb", 155 | "int", 156 | "bw", 157 | "tga", 158 | "tiff", 159 | "tif", 160 | "vtf", 161 | "xbm", 162 | "xcf", 163 | "xpm", 164 | "3dv", 165 | "amf", 166 | "ai", 167 | "awg", 168 | "cgm", 169 | "cdr", 170 | "cmx", 171 | "dxf", 172 | "e2d", 173 | "egt", 174 | "eps", 175 | "fs", 176 | "gbr", 177 | "odg", 178 | "svg", 179 | "stl", 180 | "vrml", 181 | "x3d", 182 | "sxd", 183 | "v2d", 184 | "vnd", 185 | "wmf", 186 | "emf", 187 | "art", 188 | "xar", 189 | "png", 190 | "webp", 191 | "jxr", 192 | "hdp", 193 | "wdp", 194 | "cur", 195 | "ecw", 196 | "iff", 197 | "lbm", 198 | "liff", 199 | "nrrd", 200 | "pam", 201 | "pcx", 202 | "pgf", 203 | "sgi", 204 | "rgb", 205 | "rgba", 206 | "bw", 207 | "int", 208 | "inta", 209 | "sid", 210 | "ras", 211 | "sun", 212 | "tga", 213 | } 214 | 215 | return ext 216 | } 217 | 218 | /** 219 | * @description:get a image's ext 220 | * @param {string} path "file path" 221 | * @return {string} ext "file ext" 222 | * @return {error} err "error info" 223 | */ 224 | func GetImageExt(p string) (string, error) { 225 | file, err := os.Open(p) 226 | if err != nil { 227 | return "", err 228 | } 229 | 230 | buff := make([]byte, 512) 231 | 232 | _, err = file.Read(buff) 233 | 234 | if err != nil { 235 | return "", err 236 | } 237 | 238 | filetype := http.DetectContentType(buff) 239 | 240 | ext := ImageExtArray() 241 | 242 | for i := 0; i < len(ext); i++ { 243 | if strings.Contains(ext[i], filetype[6:]) { 244 | return ext[i], nil 245 | } 246 | } 247 | 248 | return "", errors.New("invalid image type") 249 | } 250 | 251 | func GetImageExtByName(p string) (string, error) { 252 | 253 | extArr := ImageExtArray() 254 | ext := filepath.Ext(p) 255 | for i := 0; i < len(extArr); i++ { 256 | if strings.Contains(ext, extArr[i]) { 257 | return extArr[i], nil 258 | } 259 | } 260 | return "", errors.New("invalid image type") 261 | } 262 | -------------------------------------------------------------------------------- /service/shares.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.org 3 | * @Date: 2022-07-26 11:21:14 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-18 11:16:25 6 | * @FilePath: /CasaOS/service/shares.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package service 12 | 13 | import ( 14 | "path/filepath" 15 | "strings" 16 | 17 | "github.com/IceWhaleTech/CasaOS/pkg/config" 18 | command2 "github.com/IceWhaleTech/CasaOS/pkg/utils/command" 19 | "github.com/IceWhaleTech/CasaOS/pkg/utils/file" 20 | "github.com/IceWhaleTech/CasaOS/service/model" 21 | model2 "github.com/IceWhaleTech/CasaOS/service/model" 22 | "gorm.io/gorm" 23 | ) 24 | 25 | type SharesService interface { 26 | GetSharesList() (shares []model2.SharesDBModel) 27 | GetSharesByPath(path string) (shares []model2.SharesDBModel) 28 | GetSharesByName(name string) (shares []model2.SharesDBModel) 29 | CreateShare(share model2.SharesDBModel) 30 | DeleteShare(id string) 31 | UpdateConfigFile() 32 | InitSambaConfig() 33 | DeleteShareByPath(path string) 34 | } 35 | 36 | type sharesStruct struct { 37 | db *gorm.DB 38 | } 39 | 40 | func (s *sharesStruct) DeleteShareByPath(path string) { 41 | s.db.Where("path LIKE ?", path+"%").Delete(&model.SharesDBModel{}) 42 | s.UpdateConfigFile() 43 | } 44 | 45 | func (s *sharesStruct) GetSharesByName(name string) (shares []model2.SharesDBModel) { 46 | s.db.Select("anonymous,path,id").Where("name = ?", name).Find(&shares) 47 | 48 | return 49 | } 50 | func (s *sharesStruct) GetSharesByPath(path string) (shares []model2.SharesDBModel) { 51 | s.db.Select("anonymous,path,id").Where("path = ?", path).Find(&shares) 52 | return 53 | } 54 | func (s *sharesStruct) GetSharesList() (shares []model2.SharesDBModel) { 55 | s.db.Select("anonymous,path,id").Find(&shares) 56 | return 57 | } 58 | func (s *sharesStruct) CreateShare(share model2.SharesDBModel) { 59 | s.db.Create(&share) 60 | s.InitSambaConfig() 61 | s.UpdateConfigFile() 62 | } 63 | func (s *sharesStruct) DeleteShare(id string) { 64 | s.db.Where("id= ?", id).Delete(&model.SharesDBModel{}) 65 | s.UpdateConfigFile() 66 | } 67 | 68 | func (s *sharesStruct) UpdateConfigFile() { 69 | shares := []model2.SharesDBModel{} 70 | s.db.Select("anonymous,path").Find(&shares) 71 | //generated config file 72 | var configStr = "" 73 | for _, share := range shares { 74 | dirName := filepath.Base(share.Path) 75 | configStr += ` 76 | [` + dirName + `] 77 | comment = CasaOS share ` + dirName + ` 78 | public = Yes 79 | path = ` + share.Path + ` 80 | browseable = Yes 81 | read only = No 82 | guest ok = Yes 83 | create mask = 0777 84 | directory mask = 0777 85 | 86 | ` 87 | } 88 | //write config file 89 | file.WriteToPath([]byte(configStr), "/etc/samba", "smb.casa.conf") 90 | //restart samba 91 | command2.ExecResultStrArray("source " + config.AppInfo.ShellPath + "/helper.sh ;RestartSMBD") 92 | 93 | } 94 | func (s *sharesStruct) InitSambaConfig() { 95 | if file.Exists("/etc/samba/smb.conf") { 96 | str := file.ReadLine(1, "/etc/samba/smb.conf") 97 | if strings.Contains(str, "# Copyright (c) 2021-2022 CasaOS Inc. All rights reserved.") { 98 | return 99 | } 100 | file.MoveFile("/etc/samba/smb.conf", "/etc/samba/smb.conf.bak") 101 | var smbConf = "" 102 | smbConf += `# Copyright (c) 2021-2022 CasaOS Inc. All rights reserved. 103 | # 104 | # 105 | # ______ _______ 106 | # ( __ \ ( ___ ) 107 | # | ( \ ) | ( ) | 108 | # | | ) | | | | | 109 | # | | | | | | | | 110 | # | | ) | | | | | 111 | # | (__/ ) | (___) | 112 | # (______/ (_______) 113 | # 114 | # _ _______ _________ 115 | # ( ( /| ( ___ ) \__ __/ 116 | # | \ ( | | ( ) | ) ( 117 | # | \ | | | | | | | | 118 | # | (\ \) | | | | | | | 119 | # | | \ | | | | | | | 120 | # | ) \ | | (___) | | | 121 | # |/ )_) (_______) )_( 122 | # 123 | # _______ _______ ______ _________ _______ 124 | # ( ) ( ___ ) ( __ \ \__ __/ ( ____ \ |\ /| 125 | # | () () | | ( ) | | ( \ ) ) ( | ( \/ ( \ / ) 126 | # | || || | | | | | | | ) | | | | (__ \ (_) / 127 | # | |(_)| | | | | | | | | | | | | __) \ / 128 | # | | | | | | | | | | ) | | | | ( ) ( 129 | # | ) ( | | (___) | | (__/ ) ___) (___ | ) | | 130 | # |/ \| (_______) (______/ \_______/ |/ \_/ 131 | # 132 | # 133 | # IMPORTANT: CasaOS will not provide technical support for any issues 134 | # caused by unauthorized modification to the configuration. 135 | 136 | [global] 137 | ## fruit settings 138 | min protocol = SMB2 139 | ea support = yes 140 | ## vfs objects = fruit streams_xattr 141 | fruit:metadata = stream 142 | fruit:model = Macmini 143 | fruit:veto_appledouble = no 144 | fruit:posix_rename = yes 145 | fruit:zero_file_id = yes 146 | fruit:wipe_intentionally_left_blank_rfork = yes 147 | fruit:delete_empty_adfiles = yes 148 | map to guest = bad user 149 | include=/etc/samba/smb.casa.conf` 150 | file.WriteToPath([]byte(smbConf), "/etc/samba", "smb.conf") 151 | } 152 | 153 | } 154 | 155 | func NewSharesService(db *gorm.DB) SharesService { 156 | return &sharesStruct{db: db} 157 | } 158 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "badgeTemplate": "\"All&color=162453&style=flat-square&logo=Handshake&logoColor=fff\" />", 7 | "commit": false, 8 | "contributors": [ 9 | { 10 | "login": "jerrykuku", 11 | "name": "老竭力", 12 | "avatar_url": "https://avatars.githubusercontent.com/u/9485680?v=4", 13 | "profile": "https://github.com/jerrykuku", 14 | "contributions": [ 15 | "code", 16 | "doc", 17 | "ideas", 18 | "infra", 19 | "maintenance", 20 | "platform", 21 | "question", 22 | "review" 23 | ] 24 | }, 25 | { 26 | "login": "LinkLeong", 27 | "name": "link", 28 | "avatar_url": "https://avatars.githubusercontent.com/u/13556972?v=4", 29 | "profile": "https://github.com/LinkLeong", 30 | "contributions": [ 31 | "code", 32 | "doc", 33 | "ideas", 34 | "infra", 35 | "maintenance", 36 | "question", 37 | "review" 38 | ] 39 | }, 40 | { 41 | "login": "tigerinus", 42 | "name": "Tiger Wang (王豫)", 43 | "avatar_url": "https://avatars.githubusercontent.com/u/7172560?v=4", 44 | "profile": "https://github.com/tigerinus", 45 | "contributions": [ 46 | "code", 47 | "doc", 48 | "ideas", 49 | "infra", 50 | "maintenance", 51 | "mentoring", 52 | "security", 53 | "question", 54 | "review" 55 | ] 56 | }, 57 | { 58 | "login": "Lauren-ED209", 59 | "name": "Lauren", 60 | "avatar_url": "https://avatars.githubusercontent.com/u/8243355?v=4", 61 | "profile": "https://github.com/Lauren-ED209", 62 | "contributions": [ 63 | "ideas", 64 | "fundingFinding", 65 | "projectManagement", 66 | "question", 67 | "test" 68 | ] 69 | }, 70 | { 71 | "login": "JohnGuan", 72 | "name": "John Guan", 73 | "avatar_url": "https://avatars.githubusercontent.com/u/3358477?v=4", 74 | "profile": "https://JohnGuan.Cn", 75 | "contributions": [ 76 | "blog", 77 | "content", 78 | "doc", 79 | "ideas", 80 | "eventOrganizing", 81 | "mentoring", 82 | "question", 83 | "review" 84 | ] 85 | }, 86 | { 87 | "login": "dtaivpp", 88 | "name": "David Tippett", 89 | "avatar_url": "https://avatars.githubusercontent.com/u/17506770?v=4", 90 | "profile": "https://blog.tippybits.com", 91 | "contributions": [ 92 | "doc", 93 | "ideas", 94 | "question" 95 | ] 96 | }, 97 | { 98 | "login": "zarevskaya", 99 | "name": "Skaya", 100 | "avatar_url": "https://avatars.githubusercontent.com/u/60230221?v=4", 101 | "profile": "https://github.com/zarevskaya", 102 | "contributions": [ 103 | "mentoring", 104 | "question", 105 | "tutorial", 106 | "translation" 107 | ] 108 | }, 109 | { 110 | "login": "AuthorShin", 111 | "name": "AuthorShin", 112 | "avatar_url": "https://avatars.githubusercontent.com/u/4959043?v=4", 113 | "profile": "https://github.com/AuthorShin", 114 | "contributions": [ 115 | "test", 116 | "bug", 117 | "question", 118 | "ideas" 119 | ] 120 | }, 121 | { 122 | "login": "baptiste313", 123 | "name": "baptiste313", 124 | "avatar_url": "https://avatars.githubusercontent.com/u/93325157?v=4", 125 | "profile": "https://github.com/baptiste313", 126 | "contributions": [ 127 | "translation" 128 | ] 129 | }, 130 | { 131 | "login": "DrMxrcy", 132 | "name": "DrMxrcy", 133 | "avatar_url": "https://avatars.githubusercontent.com/u/58747968?v=4", 134 | "profile": "https://github.com/DrMxrcy", 135 | "contributions": [ 136 | "test", 137 | "ideas", 138 | "question" 139 | ] 140 | }, 141 | { 142 | "login": "Joooost", 143 | "name": "Joooost", 144 | "avatar_url": "https://avatars.githubusercontent.com/u/12090673?v=4", 145 | "profile": "https://github.com/Joooost", 146 | "contributions": [ 147 | "ideas" 148 | ] 149 | }, 150 | { 151 | "login": "sio", 152 | "name": "Vitaly Potyarkin", 153 | "avatar_url": "https://avatars.githubusercontent.com/u/334908?v=4", 154 | "profile": "https://potyarkin.ml", 155 | "contributions": [ 156 | "ideas" 157 | ] 158 | }, 159 | { 160 | "login": "bearfrieze", 161 | "name": "Bjørn Friese", 162 | "avatar_url": "https://avatars.githubusercontent.com/u/1023813?v=4", 163 | "profile": "https://github.com/bearfrieze", 164 | "contributions": [ 165 | "ideas" 166 | ] 167 | }, 168 | { 169 | "login": "Protektor-Desura", 170 | "name": "Protektor", 171 | "avatar_url": "https://avatars.githubusercontent.com/u/1195496?v=4", 172 | "profile": "https://github.com/Protektor-Desura", 173 | "contributions": [ 174 | "bug", 175 | "ideas", 176 | "question" 177 | ] 178 | }, 179 | { 180 | "login": "llwaini", 181 | "name": "llwaini", 182 | "avatar_url": "https://avatars.githubusercontent.com/u/59589857?v=4", 183 | "profile": "https://github.com/llwaini", 184 | "contributions": [ 185 | "projectManagement", 186 | "test", 187 | "tutorial" 188 | ] 189 | } 190 | ], 191 | "contributorsPerLine": 7, 192 | "projectName": "CasaOS", 193 | "projectOwner": "IceWhaleTech", 194 | "repoType": "github", 195 | "repoHost": "https://github.com", 196 | "skipCi": true 197 | } 198 | -------------------------------------------------------------------------------- /web/img/Files.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | image/svg+xml 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /route/init.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "github.com/IceWhaleTech/CasaOS/pkg/config" 11 | "github.com/IceWhaleTech/CasaOS/pkg/samba" 12 | "github.com/IceWhaleTech/CasaOS/pkg/utils/command" 13 | "github.com/IceWhaleTech/CasaOS/pkg/utils/encryption" 14 | "github.com/IceWhaleTech/CasaOS/pkg/utils/file" 15 | "github.com/IceWhaleTech/CasaOS/pkg/utils/loger" 16 | "github.com/IceWhaleTech/CasaOS/service" 17 | model2 "github.com/IceWhaleTech/CasaOS/service/model" 18 | uuid "github.com/satori/go.uuid" 19 | "go.uber.org/zap" 20 | ) 21 | 22 | func InitFunction() { 23 | ShellInit() 24 | CheckSerialDiskMount() 25 | CheckToken2_11() 26 | ImportApplications() 27 | // Soon to be removed 28 | ChangeAPIUrl() 29 | MoveUserToDB() 30 | go InitNetworkMount() 31 | 32 | } 33 | 34 | func CheckSerialDiskMount() { 35 | // check mount point 36 | dbList := service.MyService.Disk().GetSerialAll() 37 | 38 | list := service.MyService.Disk().LSBLK(true) 39 | mountPoint := make(map[string]string, len(dbList)) 40 | //remount 41 | for _, v := range dbList { 42 | mountPoint[v.UUID] = v.MountPoint 43 | } 44 | for _, v := range list { 45 | command.ExecEnabledSMART(v.Path) 46 | if v.Children != nil { 47 | for _, h := range v.Children { 48 | //if len(h.MountPoint) == 0 && len(v.Children) == 1 && h.FsType == "ext4" { 49 | if m, ok := mountPoint[h.UUID]; ok { 50 | //mount point check 51 | volume := m 52 | if !file.CheckNotExist(m) { 53 | for i := 0; file.CheckNotExist(volume); i++ { 54 | volume = m + strconv.Itoa(i+1) 55 | } 56 | } 57 | service.MyService.Disk().MountDisk(h.Path, volume) 58 | if volume != m { 59 | ms := model2.SerialDisk{} 60 | ms.UUID = v.UUID 61 | ms.MountPoint = volume 62 | service.MyService.Disk().UpdateMountPoint(ms) 63 | } 64 | 65 | } 66 | //} 67 | } 68 | } 69 | } 70 | service.MyService.Disk().RemoveLSBLKCache() 71 | command.OnlyExec("source " + config.AppInfo.ShellPath + "/helper.sh ;AutoRemoveUnuseDir") 72 | } 73 | func ShellInit() { 74 | command.OnlyExec("curl -fsSL https://raw.githubusercontent.com/IceWhaleTech/get/main/assist.sh | bash") 75 | if !file.CheckNotExist("/casaOS") { 76 | command.OnlyExec("source /casaOS/server/shell/update.sh ;") 77 | command.OnlyExec("source " + config.AppInfo.ShellPath + "/delete-old-service.sh ;") 78 | } 79 | 80 | } 81 | func CheckToken2_11() { 82 | if len(config.ServerInfo.Token) == 0 { 83 | token := uuid.NewV4().String 84 | config.ServerInfo.Token = token() 85 | config.Cfg.Section("server").Key("Token").SetValue(token()) 86 | config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath) 87 | } 88 | 89 | if len(config.UserInfo.Description) == 0 { 90 | config.Cfg.Section("user").Key("Description").SetValue("nothing") 91 | config.UserInfo.Description = "nothing" 92 | config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath) 93 | } 94 | 95 | if service.MyService.System().GetSysInfo().KernelArch == "aarch64" && config.ServerInfo.USBAutoMount != "True" && strings.Contains(service.MyService.System().GetDeviceTree(), "Raspberry Pi") { 96 | service.MyService.System().UpdateUSBAutoMount("False") 97 | service.MyService.System().ExecUSBAutoMountShell("False") 98 | } 99 | 100 | // str := []string{} 101 | // str = append(str, "ddd") 102 | // str = append(str, "aaa") 103 | // ddd := strings.Join(str, "|") 104 | // config.Cfg.Section("file").Key("ShareDir").SetValue(ddd) 105 | 106 | // config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath) 107 | 108 | } 109 | 110 | func ImportApplications() { 111 | service.MyService.App().ImportApplications(true) 112 | } 113 | 114 | // 0.3.1 115 | func ChangeAPIUrl() { 116 | 117 | newAPIUrl := "https://api.casaos.io/casaos-api" 118 | if config.ServerInfo.ServerApi == "https://api.casaos.zimaboard.com" { 119 | config.ServerInfo.ServerApi = newAPIUrl 120 | config.Cfg.Section("server").Key("ServerApi").SetValue(newAPIUrl) 121 | config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath) 122 | } 123 | 124 | } 125 | 126 | //0.3.3 127 | //Transferring user data to the database 128 | func MoveUserToDB() { 129 | 130 | if len(config.UserInfo.UserName) > 0 && service.MyService.User().GetUserInfoByUserName(config.UserInfo.UserName).Id == 0 { 131 | user := model2.UserDBModel{} 132 | user.Username = config.UserInfo.UserName 133 | user.Email = config.UserInfo.Email 134 | user.Nickname = config.UserInfo.NickName 135 | user.Password = encryption.GetMD5ByStr(config.UserInfo.PWD) 136 | user.Role = "admin" 137 | user = service.MyService.User().CreateUser(user) 138 | if user.Id > 0 { 139 | userPath := config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id) 140 | file.MkDir(userPath) 141 | os.Rename("/casaOS/server/conf/app_order.json", userPath+"/app_order.json") 142 | } 143 | 144 | } 145 | } 146 | 147 | func InitNetworkMount() { 148 | time.Sleep(time.Second * 10) 149 | connections := service.MyService.Connections().GetConnectionsList() 150 | for _, v := range connections { 151 | connection := service.MyService.Connections().GetConnectionByID(fmt.Sprint(v.ID)) 152 | directories, err := samba.GetSambaSharesList(connection.Host, connection.Port, connection.Username, connection.Password) 153 | if err != nil { 154 | service.MyService.Connections().DeleteConnection(fmt.Sprint(connection.ID)) 155 | loger.Error("mount samba err", zap.Any("err", err), zap.Any("info", connection)) 156 | continue 157 | } 158 | baseHostPath := "/mnt/" + connection.Host 159 | 160 | mountPointList := service.MyService.System().GetDirPath(baseHostPath) 161 | for _, v := range mountPointList { 162 | service.MyService.Connections().UnmountSmaba(v.Path) 163 | } 164 | 165 | os.RemoveAll(baseHostPath) 166 | 167 | file.IsNotExistMkDir(baseHostPath) 168 | for _, v := range directories { 169 | mountPoint := baseHostPath + "/" + v 170 | file.IsNotExistMkDir(mountPoint) 171 | service.MyService.Connections().MountSmaba(connection.Username, connection.Host, v, connection.Port, mountPoint, connection.Password) 172 | } 173 | connection.Directories = strings.Join(directories, ",") 174 | service.MyService.Connections().UpdateConnection(&connection) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /web/img/CasaConnect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | image/svg+xml 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /route/v1/samba.go: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinkLeong link@icewhale.com 3 | * @Date: 2022-07-26 11:08:48 4 | * @LastEditors: LinkLeong 5 | * @LastEditTime: 2022-08-17 18:25:42 6 | * @FilePath: /CasaOS/route/v1/samba.go 7 | * @Description: 8 | * @Website: https://www.casaos.io 9 | * Copyright (c) 2022 by icewhale, All Rights Reserved. 10 | */ 11 | package v1 12 | 13 | import ( 14 | "fmt" 15 | "os" 16 | "path/filepath" 17 | "strings" 18 | 19 | "github.com/IceWhaleTech/CasaOS/model" 20 | "github.com/IceWhaleTech/CasaOS/pkg/samba" 21 | "github.com/IceWhaleTech/CasaOS/pkg/utils/common_err" 22 | "github.com/IceWhaleTech/CasaOS/pkg/utils/file" 23 | "github.com/IceWhaleTech/CasaOS/service" 24 | model2 "github.com/IceWhaleTech/CasaOS/service/model" 25 | "github.com/gin-gonic/gin" 26 | ) 27 | 28 | // service 29 | 30 | func GetSambaStatus(c *gin.Context) { 31 | status := service.MyService.System().IsServiceRunning("smbd") 32 | 33 | if !status { 34 | c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_NOT_RUNNING, Message: common_err.GetMsg(common_err.SERVICE_NOT_RUNNING)}) 35 | return 36 | } 37 | needInit := true 38 | if file.Exists("/etc/samba/smb.conf") { 39 | str := file.ReadLine(1, "/etc/samba/smb.conf") 40 | if strings.Contains(str, "# Copyright (c) 2021-2022 CasaOS Inc. All rights reserved.") { 41 | needInit = false 42 | } 43 | } 44 | data := make(map[string]string, 1) 45 | data["need_init"] = fmt.Sprintf("%v", needInit) 46 | c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data}) 47 | } 48 | 49 | func GetSambaSharesList(c *gin.Context) { 50 | shares := service.MyService.Shares().GetSharesList() 51 | shareList := []model.Shares{} 52 | for _, v := range shares { 53 | shareList = append(shareList, model.Shares{ 54 | Anonymous: v.Anonymous, 55 | Path: v.Path, 56 | ID: v.ID, 57 | }) 58 | } 59 | c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: shareList}) 60 | } 61 | 62 | func PostSambaSharesCreate(c *gin.Context) { 63 | shares := []model.Shares{} 64 | c.ShouldBindJSON(&shares) 65 | for _, v := range shares { 66 | if v.Path == "" { 67 | c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INSUFFICIENT_PERMISSIONS, Message: common_err.GetMsg(common_err.INSUFFICIENT_PERMISSIONS)}) 68 | return 69 | } 70 | if !file.Exists(v.Path) { 71 | c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.DIR_NOT_EXISTS, Message: common_err.GetMsg(common_err.DIR_NOT_EXISTS)}) 72 | return 73 | } 74 | if len(service.MyService.Shares().GetSharesByPath(v.Path)) > 0 { 75 | c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.SHARE_ALREADY_EXISTS, Message: common_err.GetMsg(common_err.SHARE_ALREADY_EXISTS)}) 76 | return 77 | } 78 | if len(service.MyService.Shares().GetSharesByPath(filepath.Base(v.Path))) > 0 { 79 | 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)}) 80 | return 81 | } 82 | } 83 | for _, v := range shares { 84 | shareDBModel := model2.SharesDBModel{} 85 | shareDBModel.Anonymous = true 86 | shareDBModel.Path = v.Path 87 | shareDBModel.Name = filepath.Base(v.Path) 88 | os.Chmod(v.Path, 0777) 89 | service.MyService.Shares().CreateShare(shareDBModel) 90 | } 91 | 92 | c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: shares}) 93 | } 94 | func DeleteSambaShares(c *gin.Context) { 95 | id := c.Param("id") 96 | if id == "" { 97 | c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INSUFFICIENT_PERMISSIONS, Message: common_err.GetMsg(common_err.INSUFFICIENT_PERMISSIONS)}) 98 | return 99 | } 100 | service.MyService.Shares().DeleteShare(id) 101 | c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: id}) 102 | } 103 | 104 | //client 105 | 106 | func GetSambaConnectionsList(c *gin.Context) { 107 | connections := service.MyService.Connections().GetConnectionsList() 108 | connectionList := []model.Connections{} 109 | for _, v := range connections { 110 | connectionList = append(connectionList, model.Connections{ 111 | ID: v.ID, 112 | Username: v.Username, 113 | Port: v.Port, 114 | Host: v.Host, 115 | MountPoint: v.MountPoint, 116 | }) 117 | } 118 | c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: connectionList}) 119 | } 120 | 121 | func PostSambaConnectionsCreate(c *gin.Context) { 122 | connection := model.Connections{} 123 | c.ShouldBindJSON(&connection) 124 | if connection.Port == "" { 125 | connection.Port = "445" 126 | } 127 | if connection.Username == "" || connection.Host == "" { 128 | c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) 129 | return 130 | } 131 | connection.Host = strings.Split(connection.Host, "/")[0] 132 | // check is exists 133 | connections := service.MyService.Connections().GetConnectionByHost(connection.Host) 134 | if len(connections) > 0 { 135 | 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)}) 136 | return 137 | } 138 | // check connect is ok 139 | directories, err := samba.GetSambaSharesList(connection.Host, connection.Port, connection.Username, connection.Password) 140 | if err != nil { 141 | c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()}) 142 | return 143 | } 144 | 145 | connectionDBModel := model2.ConnectionsDBModel{} 146 | connectionDBModel.Username = connection.Username 147 | connectionDBModel.Password = connection.Password 148 | connectionDBModel.Host = connection.Host 149 | connectionDBModel.Port = connection.Port 150 | connectionDBModel.Directories = strings.Join(directories, ",") 151 | baseHostPath := "/mnt/" + connection.Host 152 | connectionDBModel.MountPoint = baseHostPath 153 | connection.MountPoint = baseHostPath 154 | file.IsNotExistMkDir(baseHostPath) 155 | for _, v := range directories { 156 | mountPoint := baseHostPath + "/" + v 157 | file.IsNotExistMkDir(mountPoint) 158 | service.MyService.Connections().MountSmaba(connectionDBModel.Username, connectionDBModel.Host, v, connectionDBModel.Port, mountPoint, connectionDBModel.Password) 159 | } 160 | 161 | service.MyService.Connections().CreateConnection(&connectionDBModel) 162 | 163 | connection.ID = connectionDBModel.ID 164 | c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: connection}) 165 | } 166 | 167 | func DeleteSambaConnections(c *gin.Context) { 168 | id := c.Param("id") 169 | connection := service.MyService.Connections().GetConnectionByID(id) 170 | if connection.Username == "" { 171 | c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.Record_NOT_EXIST, Message: common_err.GetMsg(common_err.Record_NOT_EXIST)}) 172 | return 173 | } 174 | mountPointList := service.MyService.System().GetDirPath(connection.MountPoint) 175 | for _, v := range mountPointList { 176 | service.MyService.Connections().UnmountSmaba(v.Path) 177 | } 178 | os.RemoveAll(connection.MountPoint) 179 | service.MyService.Connections().DeleteConnection(id) 180 | c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: id}) 181 | } 182 | --------------------------------------------------------------------------------