diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fbc60b3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5123 @@ +
+
+
+
+
+
+
A Community Legacy in Memory of @tteck
+ ++ Proxmox® is a registered trademark of Proxmox Server Solutions GmbH. +
+ -Scripts de Proxmox \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..cf5e732 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,23 @@ +## Supported Versions +This project currently supports the following versions of Proxmox VE: + +| Version | Supported | +| ------- | ------------------ | +| 8.3.x | :white_check_mark: | +| 8.2.x | :white_check_mark: | +| 8.1.x | :white_check_mark: | +| 8.0.x | Limited support* ❕| +| < 8.0 | :x: | + +*Version 8.0.x has limited support. Security updates may not be provided for all issues in this version. + +## Reporting a Vulnerability + +Security vulnerabilities shouldn’t be reported publicly to prevent potential exploitation. Instead, please report any vulnerabilities privately by reaching out directly to us. You can either join our [Discord server](https://discord.gg/jsYVk5JBxq) and send a direct message to a maintainer or contact us via email at contact@community-scripts.org. Be sure to include a detailed description of the vulnerability and the steps to reproduce it. Thank you for helping us keep our project secure! + +Once a vulnerability has been reported, the project maintainers will review it and acknowledge the report within 7 business days. We will then work to address the vulnerability and provide a fix as soon as possible. Depending on the severity of the issue, a patch may be released immediately or included in the next scheduled update. + +Please note that not all reported vulnerabilities may be accepted. The project maintainers reserve the right to decline a vulnerability report if it is deemed to be a low-risk issue or if it conflicts with the project's design or architecture. In such cases, we will provide an explanation for the decision. + +If you have any questions or concerns about this security policy, please don't hesitate to contact the project maintainers. + diff --git a/api/.env.example b/api/.env.example new file mode 100644 index 0000000..fc7bdbb --- /dev/null +++ b/api/.env.example @@ -0,0 +1,5 @@ +MONGO_USER= +MONGO_PASSWORD= +MONGO_IP= +MONGO_PORT= +MONGO_DATABASE= \ No newline at end of file diff --git a/api/go.mod b/api/go.mod new file mode 100644 index 0000000..0297b1a --- /dev/null +++ b/api/go.mod @@ -0,0 +1,32 @@ +module proxmox-api + +go 1.23.2 + +require go.mongodb.org/mongo-driver v1.17.2 + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.2 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/text v0.21.0 // indirect + gorm.io/driver/mysql v1.5.7 // indirect + gorm.io/driver/postgres v1.5.11 // indirect + gorm.io/gorm v1.25.12 // indirect +) diff --git a/api/go.sum b/api/go.sum new file mode 100644 index 0000000..ea649ff --- /dev/null +++ b/api/go.sum @@ -0,0 +1,83 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= +github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM= +go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/api/main.go b/api/main.go new file mode 100644 index 0000000..0227bf1 --- /dev/null +++ b/api/main.go @@ -0,0 +1,450 @@ +// Copyright (c) 2021-2025 community-scripts ORG +// Author: Michel Roegl-Brunner (michelroegl-brunner) +// License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "strconv" + "time" + + "github.com/gorilla/mux" + "github.com/joho/godotenv" + "github.com/rs/cors" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var client *mongo.Client +var collection *mongo.Collection + +func loadEnv() { + if err := godotenv.Load(); err != nil { + log.Fatal("Error loading .env file") + } +} + +// DataModel represents a single document in MongoDB +type DataModel struct { + ID primitive.ObjectID `json:"id" bson:"_id,omitempty"` + CT_TYPE uint `json:"ct_type" bson:"ct_type"` + DISK_SIZE float32 `json:"disk_size" bson:"disk_size"` + CORE_COUNT uint `json:"core_count" bson:"core_count"` + RAM_SIZE uint `json:"ram_size" bson:"ram_size"` + OS_TYPE string `json:"os_type" bson:"os_type"` + OS_VERSION string `json:"os_version" bson:"os_version"` + DISABLEIP6 string `json:"disableip6" bson:"disableip6"` + NSAPP string `json:"nsapp" bson:"nsapp"` + METHOD string `json:"method" bson:"method"` + CreatedAt time.Time `json:"created_at" bson:"created_at"` + PVEVERSION string `json:"pve_version" bson:"pve_version"` + STATUS string `json:"status" bson:"status"` + RANDOM_ID string `json:"random_id" bson:"random_id"` + TYPE string `json:"type" bson:"type"` + ERROR string `json:"error" bson:"error"` +} + +type StatusModel struct { + RANDOM_ID string `json:"random_id" bson:"random_id"` + ERROR string `json:"error" bson:"error"` + STATUS string `json:"status" bson:"status"` +} + +type CountResponse struct { + TotalEntries int64 `json:"total_entries"` + StatusCount map[string]int64 `json:"status_count"` + NSAPPCount map[string]int64 `json:"nsapp_count"` +} + +// ConnectDatabase initializes the MongoDB connection +func ConnectDatabase() { + loadEnv() + + mongoURI := fmt.Sprintf("mongodb://%s:%s@%s:%s", + os.Getenv("MONGO_USER"), + os.Getenv("MONGO_PASSWORD"), + os.Getenv("MONGO_IP"), + os.Getenv("MONGO_PORT")) + + database := os.Getenv("MONGO_DATABASE") + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + var err error + client, err = mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) + if err != nil { + log.Fatal("Failed to connect to MongoDB!", err) + } + collection = client.Database(database).Collection("data_models") + fmt.Println("Connected to MongoDB on 10.10.10.18") +} + +// UploadJSON handles API requests and stores data as a document in MongoDB +func UploadJSON(w http.ResponseWriter, r *http.Request) { + var input DataModel + + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + input.CreatedAt = time.Now() + + _, err := collection.InsertOne(context.Background(), input) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + log.Println("Received data:", input) + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]string{"message": "Data saved successfully"}) +} + +// UpdateStatus updates the status of a record based on RANDOM_ID +func UpdateStatus(w http.ResponseWriter, r *http.Request) { + var input StatusModel + + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + filter := bson.M{"random_id": input.RANDOM_ID} + update := bson.M{"$set": bson.M{"status": input.STATUS, "error": input.ERROR}} + + _, err := collection.UpdateOne(context.Background(), filter, update) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + log.Println("Updated data:", input) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"message": "Record updated successfully"}) +} + +// GetDataJSON fetches all data from MongoDB +func GetDataJSON(w http.ResponseWriter, r *http.Request) { + var records []DataModel + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + cursor, err := collection.Find(ctx, bson.M{}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer cursor.Close(ctx) + + for cursor.Next(ctx) { + var record DataModel + if err := cursor.Decode(&record); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + records = append(records, record) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(records) +} +func GetPaginatedData(w http.ResponseWriter, r *http.Request) { + page, _ := strconv.Atoi(r.URL.Query().Get("page")) + limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) + if page < 1 { + page = 1 + } + if limit < 1 { + limit = 10 + } + skip := (page - 1) * limit + var records []DataModel + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + options := options.Find().SetSkip(int64(skip)).SetLimit(int64(limit)) + cursor, err := collection.Find(ctx, bson.M{}, options) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer cursor.Close(ctx) + + for cursor.Next(ctx) { + var record DataModel + if err := cursor.Decode(&record); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + records = append(records, record) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(records) +} + +func GetSummary(w http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + totalCount, err := collection.CountDocuments(ctx, bson.M{}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + statusCount := make(map[string]int64) + nsappCount := make(map[string]int64) + + pipeline := []bson.M{ + {"$group": bson.M{"_id": "$status", "count": bson.M{"$sum": 1}}}, + } + cursor, err := collection.Aggregate(ctx, pipeline) + if err == nil { + for cursor.Next(ctx) { + var result struct { + ID string `bson:"_id"` + Count int64 `bson:"count"` + } + if err := cursor.Decode(&result); err == nil { + statusCount[result.ID] = result.Count + } + } + } + + pipeline = []bson.M{ + {"$group": bson.M{"_id": "$nsapp", "count": bson.M{"$sum": 1}}}, + } + cursor, err = collection.Aggregate(ctx, pipeline) + if err == nil { + for cursor.Next(ctx) { + var result struct { + ID string `bson:"_id"` + Count int64 `bson:"count"` + } + if err := cursor.Decode(&result); err == nil { + nsappCount[result.ID] = result.Count + } + } + } + + response := CountResponse{ + TotalEntries: totalCount, + StatusCount: statusCount, + NSAPPCount: nsappCount, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +func GetByNsapp(w http.ResponseWriter, r *http.Request) { + nsapp := r.URL.Query().Get("nsapp") + var records []DataModel + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + cursor, err := collection.Find(ctx, bson.M{"nsapp": nsapp}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer cursor.Close(ctx) + + for cursor.Next(ctx) { + var record DataModel + if err := cursor.Decode(&record); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + records = append(records, record) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(records) +} + +func GetByDateRange(w http.ResponseWriter, r *http.Request) { + + startDate := r.URL.Query().Get("start_date") + endDate := r.URL.Query().Get("end_date") + + if startDate == "" || endDate == "" { + http.Error(w, "Both start_date and end_date are required", http.StatusBadRequest) + return + } + + start, err := time.Parse("2006-01-02T15:04:05.999999+00:00", startDate+"T00:00:00+00:00") + if err != nil { + http.Error(w, "Invalid start_date format", http.StatusBadRequest) + return + } + + end, err := time.Parse("2006-01-02T15:04:05.999999+00:00", endDate+"T23:59:59+00:00") + if err != nil { + http.Error(w, "Invalid end_date format", http.StatusBadRequest) + return + } + + var records []DataModel + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + cursor, err := collection.Find(ctx, bson.M{ + "created_at": bson.M{ + "$gte": start, + "$lte": end, + }, + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer cursor.Close(ctx) + + for cursor.Next(ctx) { + var record DataModel + if err := cursor.Decode(&record); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + records = append(records, record) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(records) +} +func GetByStatus(w http.ResponseWriter, r *http.Request) { + status := r.URL.Query().Get("status") + var records []DataModel + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + cursor, err := collection.Find(ctx, bson.M{"status": status}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer cursor.Close(ctx) + + for cursor.Next(ctx) { + var record DataModel + if err := cursor.Decode(&record); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + records = append(records, record) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(records) +} + +func GetByOS(w http.ResponseWriter, r *http.Request) { + osType := r.URL.Query().Get("os_type") + osVersion := r.URL.Query().Get("os_version") + var records []DataModel + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + cursor, err := collection.Find(ctx, bson.M{"os_type": osType, "os_version": osVersion}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer cursor.Close(ctx) + + for cursor.Next(ctx) { + var record DataModel + if err := cursor.Decode(&record); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + records = append(records, record) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(records) +} + +func GetErrors(w http.ResponseWriter, r *http.Request) { + errorCount := make(map[string]int) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + cursor, err := collection.Find(ctx, bson.M{"error": bson.M{"$ne": ""}}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer cursor.Close(ctx) + + for cursor.Next(ctx) { + var record DataModel + if err := cursor.Decode(&record); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if record.ERROR != "" { + errorCount[record.ERROR]++ + } + } + + type ErrorCountResponse struct { + Error string `json:"error"` + Count int `json:"count"` + } + + var errorCounts []ErrorCountResponse + for err, count := range errorCount { + errorCounts = append(errorCounts, ErrorCountResponse{ + Error: err, + Count: count, + }) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(struct { + ErrorCounts []ErrorCountResponse `json:"error_counts"` + }{ + ErrorCounts: errorCounts, + }) +} + +func main() { + ConnectDatabase() + + router := mux.NewRouter() + router.HandleFunc("/upload", UploadJSON).Methods("POST") + router.HandleFunc("/upload/updatestatus", UpdateStatus).Methods("POST") + router.HandleFunc("/data/json", GetDataJSON).Methods("GET") + router.HandleFunc("/data/paginated", GetPaginatedData).Methods("GET") + router.HandleFunc("/data/summary", GetSummary).Methods("GET") + router.HandleFunc("/data/nsapp", GetByNsapp).Methods("GET") + router.HandleFunc("/data/date", GetByDateRange).Methods("GET") + router.HandleFunc("/data/status", GetByStatus).Methods("GET") + router.HandleFunc("/data/os", GetByOS).Methods("GET") + router.HandleFunc("/data/errors", GetErrors).Methods("GET") + + c := cors.New(cors.Options{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST"}, + AllowedHeaders: []string{"Content-Type", "Authorization"}, + AllowCredentials: true, + }) + + handler := c.Handler(router) + + fmt.Println("Server running on port 8080") + log.Fatal(http.ListenAndServe(":8080", handler)) +} diff --git a/ct/2fauth.sh b/ct/2fauth.sh new file mode 100644 index 0000000..4c9aab3 --- /dev/null +++ b/ct/2fauth.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2025 community-scripts ORG +# Author: jkrgr0 +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://docs.2fauth.app/ + +APP="2FAuth" +var_tags="2fa;authenticator" +var_cpu="1" +var_ram="512" +var_disk="2" +var_os="debian" +var_version="12" +var_unprivileged="1" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + # Check if installation is present | -f for file, -d for folder + if [[ ! -d "/opt/2fauth" ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + # Crawling the new version and checking whether an update is required + RELEASE=$(curl -fsSL https://api.github.com/repos/Bubka/2FAuth/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') + if [[ "${RELEASE}" != "$(cat /opt/2fauth_version.txt)" ]] || [[ ! -f /opt/2fauth_version.txt ]]; then + msg_info "Updating $APP to ${RELEASE}" + + $STD apt-get update + $STD apt-get -y upgrade + + # Creating Backup + msg_info "Creating Backup" + mv "/opt/2fauth" "/opt/2fauth-backup" + if ! dpkg -l | grep -q 'php8.3'; then + cp /etc/nginx/conf.d/2fauth.conf /etc/nginx/conf.d/2fauth.conf.bak + fi + msg_ok "Backup Created" + + # Upgrade PHP + if ! dpkg -l | grep -q 'php8.3'; then + $STD apt-get install -y \ + lsb-release \ + gpg + curl -fsSL https://packages.sury.org/php/apt.gpg | gpg --dearmor -o /usr/share/keyrings/deb.sury.org-php.gpg + echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list + $STD apt-get update + $STD apt-get install -y php8.3-{bcmath,common,ctype,curl,fileinfo,fpm,gd,mbstring,mysql,xml,cli,intl} + sed -i 's/php8.2/php8.3/g' /etc/nginx/conf.d/2fauth.conf + fi + + # Execute Update + curl -fsSL -o "${RELEASE}.zip" "https://github.com/Bubka/2FAuth/archive/refs/tags/${RELEASE}.zip" + unzip -q "${RELEASE}.zip" + mv "2FAuth-${RELEASE//v/}/" "/opt/2fauth" + mv "/opt/2fauth-backup/.env" "/opt/2fauth/.env" + mv "/opt/2fauth-backup/storage" "/opt/2fauth/storage" + cd "/opt/2fauth" || return + + chown -R www-data: "/opt/2fauth" + chmod -R 755 "/opt/2fauth" + + export COMPOSER_ALLOW_SUPERUSER=1 + $STD composer install --no-dev --prefer-source + + php artisan 2fauth:install + + $STD systemctl restart nginx + + # Cleaning up + msg_info "Cleaning Up" + rm -rf "v${RELEASE}.zip" + if dpkg -l | grep -q 'php8.2'; then + $STD apt-get remove --purge -y php8.2* + fi + $STD apt-get -y autoremove + $STD apt-get -y autoclean + msg_ok "Cleanup Completed" + + # Last Action + echo "${RELEASE}" >/opt/2fauth_version.txt + msg_ok "Updated $APP to ${RELEASE}" + else + msg_ok "No update required. ${APP} is already at ${RELEASE}" + fi + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:80${CL}" \ No newline at end of file diff --git a/ct/actualbudget.sh b/ct/actualbudget.sh new file mode 100644 index 0000000..10df637 --- /dev/null +++ b/ct/actualbudget.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2025 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://actualbudget.org/ + +APP="Actual Budget" +var_tags="finance" +var_cpu="2" +var_ram="4096" +var_disk="7" +var_os="debian" +var_version="12" +var_unprivileged="1" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + if [[ ! -d /opt/actualbudget ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + RELEASE=$(curl -fsSL https://api.github.com/repos/actualbudget/actual/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }') + if [[ ! -f /opt/actualbudget_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/actualbudget_version.txt)" ]]; then + msg_info "Stopping ${APP}" + systemctl stop actualbudget + msg_ok "${APP} Stopped" + + if ! command -v git &>/dev/null; then + msg_info "Installing git" + $STD apt-get update + $STD apt-get install -y git + msg_ok "Installed git" + fi + + msg_info "Updating ${APP} to ${RELEASE}" + cd /tmp || exit + curl -fsSL "https://github.com/actualbudget/actual/archive/refs/tags/v${RELEASE}.tar.gz" -o "v${RELEASE}.tar.gz" + + mv /opt/actualbudget /opt/actualbudget_bak + tar -xzf "v${RELEASE}.tar.gz" + mv "actual-${RELEASE}" /opt/actualbudget + + mkdir -p /opt/actualbudget-data/{server-files,upload,migrate,user-files,migrations,config} + for dir in server-files .migrate user-files migrations; do + if [[ -d /opt/actualbudget_bak/$dir ]]; then + mv /opt/actualbudget_bak/$dir/* /opt/actualbudget-data/$dir/ || true + fi + done + if [[ -f /opt/actualbudget-data/migrate/.migrations ]]; then + sed -i 's/null/1732656575219/g' /opt/actualbudget-data/migrate/.migrations + sed -i 's/null/1732656575220/g' /opt/actualbudget-data/migrate/.migrations + fi + if [[ -f /opt/actualbudget/server-files/account.sqlite ]] && [[ ! -f /opt/actualbudget-data/server-files/account.sqlite ]]; then + mv /opt/actualbudget/server-files/account.sqlite /opt/actualbudget-data/server-files/account.sqlite + fi + + if [[ -f /opt/actualbudget_bak/selfhost.key ]]; then + mv /opt/actualbudget_bak/selfhost.key /opt/actualbudget/selfhost.key + mv /opt/actualbudget_bak/selfhost.crt /opt/actualbudget/selfhost.crt + fi + + if [[ -f /opt/actualbudget_bak/.env ]]; then + mv /opt/actualbudget_bak/.env /opt/actualbudget-data/.env + else + cat <No categories available. Please check the API endpoint.
+ )} + {selectedCategoryIndex !== null ? ( ++ Created at: {script.date_created || "No date available"} +
++ {truncateDescription(script.description || "No description available.")} +
+ {renderResources(script)} ++ {categories.reduce((total, category) => total + (category.scripts?.length || 0), 0)} Total scripts +
++ {(category as any).description || "No description available."} +
+Loading...
; + if (error) returnError: {error}
; + + const requestSort = (key: string) => { + let direction: 'ascending' | 'descending' = 'ascending'; + if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') { + direction = 'descending'; + } + setSortConfig({ key, direction }); + }; + + const formatDate = (dateString: string): string => { + const date = new Date(dateString); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const timezoneOffset = dateString.slice(-6); + return `${day}.${month}.${year} ${hours}:${minutes} ${timezoneOffset} GMT`; + }; + + return ( ++
{summary?.total_entries} results found
+Status Legend: 🔄 installing {summary?.status_count["installing"] ?? 0} | ✔️ completed {summary?.status_count["done"] ?? 0} | ❌ failed {summary?.status_count["failed"] ?? 0} | ❓ unknown
+| requestSort('status')}>Status | +requestSort('type')}>Type | +requestSort('nsapp')}>Application | +requestSort('os_type')}>OS | +requestSort('os_version')}>OS Version | +requestSort('disk_size')}>Disk Size | +requestSort('core_count')}>Core Count | +requestSort('ram_size')}>RAM Size | +requestSort('method')}>Method | +requestSort('pve_version')}>PVE Version | +requestSort('error')}>Error Message | +requestSort('created_at')}>Created At | +
|---|---|---|---|---|---|---|---|---|---|---|---|
| + {item.status === "done" ? ( + "✔️" + ) : item.status === "failed" ? ( + "❌" + ) : item.status === "installing" ? ( + "🔄" + ) : ( + item.status + )} + | +{item.type === "lxc" ? ( + "📦" + ) : item.type === "vm" ? ( + "🖥️" + ) : ( + item.type + )} | +{item.nsapp} | +{item.os_type} | +{item.os_version} | +{item.disk_size} | +{item.core_count} | +{item.ram_size} | +{item.method} | +{item.pve_version} | +{item.error} | +{formatDate(item.created_at)} | +