CasaOS - 您的个人云操作系统,打造自主智能家居中心

0 阅读5分钟

CasaOS - 您的个人云操作系统

CasaOS
连接社区,建立自治,降低SaaS成本,最大化个性化助手的潜力。

CasaOS Version CasaOS License

功能特性

  • 个人云操作系统:提供完整的Web界面,将您的硬件设备转变为一个功能强大的个人云服务器。
  • 应用商店与Docker管理:轻松浏览、安装和管理Docker应用,支持应用克隆、单版本升级和安装进度展示。
  • 统一文件管理:支持本地磁盘、USB设备、Samba共享以及Google Drive、Dropbox、OneDrive等云盘的管理和挂载,实现多存储源的统一访问。
  • 磁盘存储管理:支持多磁盘合并存储空间,轻松管理磁盘挂载和分区。
  • 网络与远程访问:内置对ZeroTier的支持,方便组建虚拟局域网,实现安全远程访问。同时提供SSH Web终端功能。
  • 系统健康监控:实时监控CPU、内存、网络等系统状态,并提供系统日志打包下载功能。
  • 消息通知系统:内置WebSocket通知服务,实时推送系统事件、文件操作和应用安装状态。
  • 多架构支持:同时支持x86_64(amd64)、ARM64(aarch64)和ARMv7l架构设备,包括树莓派等开发板。

安装指南

系统要求

  • 操作系统:Linux (Debian, Ubuntu, Mint等)
  • 架构:amd64, arm64, arm-7, riscv64
  • 依赖:Go (> v1.17.0), Node.js, Yarn, Docker (可选,用于应用管理)

一键安装脚本

最简单的方式是使用官方安装脚本,在终端中执行以下命令:

curl -fsSL https://get.casaos.io | bash

从源码构建开发环境

  1. 克隆代码仓库

    git clone --recurse-submodules --remote-submodules https://github.com/<your GitHub Username>/CasaOS.git
    
  2. 安装依赖并构建前端

    cd CasaOS/UI
    yarn install
    yarn build
    cd ..
    
  3. 安装Go依赖并运行

    go get
    go run main.go
    

安装后的配置

安装完成后,CasaOS服务会自动作为systemd服务启用。您可以通过浏览器访问 http://<您的设备IP>:80 来开始使用。

配置文件位于 /etc/casaos/casaos.conf,您可以在此修改端口、数据库路径等设置。

使用说明

基础使用示例

  1. 访问仪表盘:打开浏览器,输入安装了CasaOS设备的IP地址。
  2. 安装应用:进入“应用商店”,浏览并点击您想要安装的应用(如Nextcloud、Jellyfin等),系统将自动通过Docker进行部署。
  3. 挂载云盘:进入“文件”模块,选择添加存储,根据指引(Google Drive, Dropbox, OneDrive)进行OAuth授权,即可将云盘挂载到本地文件系统。
  4. 管理共享:通过“Samba共享”功能,将本地目录通过SMB协议共享给局域网内的其他设备。

API 概览

CasaOS提供了完善的RESTful API v1/v2,用于系统、文件、应用等的管理。

获取系统版本

GET /v1/sys/version/current

获取存储列表

GET /v1/storages
Authorization: Bearer <your_jwt_token>

文件上传 (支持分块)

POST /v2/upload/file
Content-Type: multipart/form-data

参数:
- path: 目标路径
- identifier: 文件唯一标识
- filename: 文件名
- chunkNumber: 当前分块序号
- totalChunks: 总分块数
- file: 分块文件数据

WebSocket 通知

连接到 ws://<host>/v1/notify/ws 即可实时接收系统状态和应用安装进度的推送消息。

核心代码

主程序入口 (main.go)

负责初始化配置、数据库、日志,并启动HTTP路由服务。

//go:generate bash -c "mkdir -p codegen && go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4 -generate types,server,spec -package codegen api/casaos/openapi.yaml > codegen/casaos_api.go"
package main

import (
	"flag"
	"fmt"
	"github.com/IceWhaleTech/CasaOS/common"
	"github.com/IceWhaleTech/CasaOS/pkg/config"
	"github.com/IceWhaleTech/CasaOS/pkg/sqlite"
	"github.com/IceWhaleTech/CasaOS/route"
	"github.com/IceWhaleTech/CasaOS/service"
	"github.com/coreos/go-systemd/daemon"
	"go.uber.org/zap"
)

var (
	commit = "private build"
	date   = "private build"
	configFlag = flag.String("c", "", "config address")
	versionFlag = flag.Bool("v", false, "version")
)

func init() {
	flag.Parse()
	if *versionFlag {
		fmt.Println("v" + common.VERSION)
		return
	}
	config.InitSetup(*configFlag, _confSample)
	// ... 初始化日志、数据库等
}

func main() {
	// 初始化数据库连接
	sqliteDB := sqlite.GetDb(config.AppInfo.DBPath + "/db")
	
	// 初始化服务层
	service.MyService = service.NewService(sqliteDB, "")
	
	// 启动后台任务(如网络挂载检查)
	go route.InitFunction()
	
	// 启动HTTP服务器
	r := route.InitV2Router()
	// ... 监听端口并启动服务
}

WebSocket通知服务 (notify.go)

通过WebSocket向所有连接的客户端推送系统事件。

package service

import (
	"encoding/json"
	"github.com/gorilla/websocket"
	"gorm.io/gorm"
)

var WebSocketConns []*websocket.Conn

type notifyServer struct {
	db *gorm.DB
}

func (i *notifyServer) SendNotify(name string, message map[string]interface{}) {
	// 将消息封装并通过消息总线发布
	msg := make(map[string]string)
	for k, v := range message {
		bt, _ := json.Marshal(v)
		msg[k] = string(bt)
	}
	// 发布事件到内部消息总线
	MyService.MessageBus().PublishEventWithResponse(context.Background(), common.SERVICENAME, name, msg)
	
	// 可选:直接通过WebSocket推送
	for _, wsConn := range WebSocketConns {
		wsConn.WriteJSON(map[string]interface{}{
			"name":    name,
			"message": message,
		})
	}
}

云盘驱动实现 (Google Drive)

实现了对Google Drive的挂载、文件列表读取和下载链接生成功能。

package google_drive

import (
	"context"
	"fmt"
	"net/http"
	"github.com/IceWhaleTech/CasaOS/internal/driver"
	"github.com/IceWhaleTech/CasaOS/model"
)

type GoogleDrive struct {
	model.StorageA
	Addition
	AccessToken string
}

// 初始化并进行OAuth2.0令牌刷新
func (d *GoogleDrive) Init(ctx context.Context) error {
	if len(d.RefreshToken) == 0 {
		d.getRefreshToken()
	}
	return d.refreshToken()
}

// 获取指定目录下的文件列表
func (d *GoogleDrive) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
	files, err := d.getFiles(dir.GetID())
	if err != nil {
		return nil, err
	}
	return utils.SliceConvert(files, func(src File) (model.Obj, error) {
		return fileToObj(src), nil
	})
}

// 生成文件下载链接
func (d *GoogleDrive) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
	url := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?alt=media", file.GetID())
	return &model.Link{
		Method: http.MethodGet,
		URL:    url,
		Header: http.Header{"Authorization": []string{"Bearer " + d.AccessToken}},
	}, nil
}

文件上传分块处理 (file_upload.go)

支持大文件的分块上传,提高上传稳定性和用户体验。

package service

import (
	"io"
	"mime/multipart"
	"os"
	"sync"
)

type FileUploadService struct {
	uploadStatus sync.Map
}

// 检查指定分块是否已上传
func (s *FileUploadService) TestChunk(identifier string, chunkNumber int64) error {
	fileInfoTemp, ok := s.uploadStatus.Load(identifier)
	if !ok {
		return fmt.Errorf("file not found")
	}
	fileInfo := fileInfoTemp.(*FileInfo)
	if !fileInfo.uploaded[chunkNumber-1] {
		return fmt.Errorf("chunk not found")
	}
	return nil
}

// 保存上传的分块数据
func (s *FileUploadService) UploadFile(..., bin *multipart.FileHeader) error {
	// 打开临时文件,在指定偏移量写入分块
	file, err := os.OpenFile(path+"/"+relativePath+".tmp", os.O_WRONLY|os.O_CREATE, 0644)
	defer file.Close()
	
	_, err = file.Seek((chunkNumber-1)*chunkSize, io.SeekStart)
	src, _ := bin.Open()
	defer src.Close()
	io.Copy(file, src)
	
	// 记录已上传的分块
	fileInfo.uploaded[chunkNumber-1] = true
	fileInfo.uploadedChunkNum++
	
	// 所有分块上传完成后,重命名临时文件为正式文件
	if fileInfo.uploadedChunkNum == totalChunks {
		os.Rename(path+"/"+relativePath+".tmp", path+"/"+relativePath)
		s.uploadStatus.Delete(identifier)
	}
	return nil
}

0HBzHnHDYfukGJRlJ1fmsw1nPo5qypnb74SWxuanHBw=