Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
package init
import (
"fmt"
"io"
"log"
"os"
"os/exec"
"path"
"time"
)
func checkConfigPath(configPath string) error {
targetPath := path.Join(configPath, configFilename)
if _, err := os.Stat(targetPath); err == nil {
return fmt.Errorf("config in %s already exists", targetPath)
}
return nil
}
func configExists(configPath string) bool {
targetPath := path.Join(configPath, configFilename)
if _, err := os.Stat(targetPath); err == nil {
return true
}
return false
}
func backupQsferaConfigFile(configPath string) (string, error) {
sourceConfig := path.Join(configPath, configFilename)
targetBackupConfig := path.Join(configPath, configFilename+"."+time.Now().Format("2006-01-02-15-04-05")+".backup")
source, err := os.Open(sourceConfig)
if err != nil {
log.Fatalf("Could not read %s (%s)", sourceConfig, err)
}
defer source.Close()
target, err := os.Create(targetBackupConfig)
if err != nil {
log.Fatalf("Could not generate backup %s (%s)", targetBackupConfig, err)
}
defer target.Close()
_, err = io.Copy(target, source)
if err != nil {
log.Fatalf("Could not write backup %s (%s)", targetBackupConfig, err)
}
return targetBackupConfig, nil
}
// printBanner prints the generated qsfera config banner.
func printBanner(targetPath, ocAdminServicePassword, targetBackupConfig string) {
fmt.Printf(
"\n=========================================\n"+
" generated КуСфера Config\n"+
"=========================================\n"+
" configpath : %s\n"+
" user : admin\n"+
" password : %s\n\n",
targetPath, ocAdminServicePassword)
if targetBackupConfig != "" {
fmt.Printf("\n=========================================\n"+
"An older config file has been backed up to\n %s\n\n",
targetBackupConfig)
}
}
// writeConfig writes the config to the target path and prints a banner
func writeConfig(configPath, ocAdminServicePassword, targetBackupConfig string, yamlOutput []byte) error {
targetPath := path.Join(configPath, configFilename)
err := os.WriteFile(targetPath, yamlOutput, 0600)
if err != nil {
return err
}
printBanner(targetPath, ocAdminServicePassword, targetBackupConfig)
return nil
}
// writePatch writes the diff to a file
func writePatch(configPath string, yamlOutput []byte) error {
fmt.Println("running in diff mode")
tmpFile := path.Join(configPath, "qsfera.yaml.tmp")
err := os.WriteFile(tmpFile, yamlOutput, 0600)
if err != nil {
return err
}
fmt.Println("diff -u " + path.Join(configPath, configFilename) + " " + tmpFile)
cmd := exec.Command("diff", "-u", path.Join(configPath, configFilename), tmpFile)
stdout, err := cmd.Output()
if err == nil {
err = os.Remove(tmpFile)
if err != nil {
return err
}
fmt.Println("no changes, your config is up to date")
return nil
}
fmt.Println(string(stdout))
err = os.Remove(tmpFile)
if err != nil {
return err
}
patchPath := path.Join(configPath, "qsfera.config.patch")
err = os.WriteFile(patchPath, stdout, 0600)
if err != nil {
return err
}
fmt.Printf("diff written to %s\n", patchPath)
return nil
}
+315
View File
@@ -0,0 +1,315 @@
package init
import (
"fmt"
"os"
"path"
"github.com/google/uuid"
"gopkg.in/yaml.v2"
"github.com/qsfera/server/pkg/generators"
)
const (
configFilename = "qsfera.yaml"
passwordLength = 32
)
var (
_insecureService = InsecureService{Insecure: true}
_insecureEvents = Events{TLSInsecure: true}
)
// CreateConfig creates a config file with random passwords at configPath
func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword string) error {
if diff && forceOverwrite {
return fmt.Errorf("diff and force-overwrite flags are mutually exclusive")
}
if diff && adminPassword != "" {
return fmt.Errorf("diff and admin-password flags are mutually exclusive")
}
if configExists(configPath) && !forceOverwrite && !diff {
return fmt.Errorf("config file already exists, use --force-overwrite to overwrite or --diff to show diff")
}
err := checkConfigPath(configPath)
if err != nil && (!forceOverwrite && !diff) {
fmt.Println("off")
return err
}
targetBackupConfig := ""
if err != nil {
targetBackupConfig, err = backupQsferaConfigFile(configPath)
if err != nil {
return err
}
}
err = os.MkdirAll(configPath, 0700)
if err != nil {
return err
}
// Load old config
var oldCfg QsferaConfig
if diff {
fp, err := os.ReadFile(path.Join(configPath, configFilename))
if err != nil {
return err
}
err = yaml.Unmarshal(fp, &oldCfg)
if err != nil {
return err
}
}
var (
systemUserID, adminUserID, graphApplicationID, storageUsersMountID, serviceAccountID string
idmServicePassword, idpServicePassword, ocAdminServicePassword, revaServicePassword string
tokenManagerJwtSecret, collaborationWOPISecret, machineAuthAPIKey, systemUserAPIKey string
revaTransferSecret, thumbnailsTransferSecret, serviceAccountSecret, urlSigningSecret string
)
if diff {
systemUserID = oldCfg.SystemUserID
adminUserID = oldCfg.AdminUserID
graphApplicationID = oldCfg.Graph.Application.ID
storageUsersMountID = oldCfg.Gateway.StorageRegistry.StorageUsersMountID
serviceAccountID = oldCfg.Graph.ServiceAccount.ServiceAccountID
idmServicePassword = oldCfg.Idm.ServiceUserPasswords.IdmPassword
idpServicePassword = oldCfg.Idm.ServiceUserPasswords.IdpPassword
ocAdminServicePassword = oldCfg.Idm.ServiceUserPasswords.AdminPassword
revaServicePassword = oldCfg.Idm.ServiceUserPasswords.RevaPassword
tokenManagerJwtSecret = oldCfg.TokenManager.JWTSecret
collaborationWOPISecret = oldCfg.Collaboration.WopiApp.Secret
if collaborationWOPISecret == "" {
collaborationWOPISecret, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random wopi secret for collaboration service: %s", err)
}
}
machineAuthAPIKey = oldCfg.MachineAuthAPIKey
systemUserAPIKey = oldCfg.SystemUserAPIKey
revaTransferSecret = oldCfg.TransferSecret
thumbnailsTransferSecret = oldCfg.Thumbnails.Thumbnail.TransferSecret
serviceAccountSecret = oldCfg.Graph.ServiceAccount.ServiceAccountSecret
urlSigningSecret = oldCfg.URLSigningSecret
if urlSigningSecret == "" {
urlSigningSecret, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random secret for urlSigningSecret: %s", err)
}
}
} else {
systemUserID = uuid.NewString()
adminUserID = uuid.NewString()
graphApplicationID = uuid.NewString()
storageUsersMountID = uuid.NewString()
serviceAccountID = uuid.NewString()
idmServicePassword, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random password for idm: %s", err)
}
idpServicePassword, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random password for idp: %s", err)
}
ocAdminServicePassword = adminPassword
if ocAdminServicePassword == "" {
ocAdminServicePassword, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random password for qsfera admin: %s", err)
}
}
revaServicePassword, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random password for reva: %s", err)
}
tokenManagerJwtSecret, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random password for tokenmanager: %s", err)
}
collaborationWOPISecret, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random wopi secret for collaboration service: %s", err)
}
machineAuthAPIKey, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random password for machineauthsecret: %s", err)
}
systemUserAPIKey, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random system user API key: %s", err)
}
revaTransferSecret, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random password for revaTransferSecret: %s", err)
}
urlSigningSecret, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random secret for urlSigningSecret: %s", err)
}
thumbnailsTransferSecret, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random password for thumbnailsTransferSecret: %s", err)
}
serviceAccountSecret, err = generators.GenerateRandomPassword(passwordLength)
if err != nil {
return fmt.Errorf("could not generate random secret for serviceAccountSecret: %s", err)
}
}
serviceAccount := ServiceAccount{
ServiceAccountID: serviceAccountID,
ServiceAccountSecret: serviceAccountSecret,
}
cfg := QsferaConfig{
TokenManager: TokenManager{
JWTSecret: tokenManagerJwtSecret,
},
MachineAuthAPIKey: machineAuthAPIKey,
SystemUserAPIKey: systemUserAPIKey,
TransferSecret: revaTransferSecret,
URLSigningSecret: urlSigningSecret,
SystemUserID: systemUserID,
AdminUserID: adminUserID,
Idm: IdmService{
ServiceUserPasswords: ServiceUserPasswordsSettings{
AdminPassword: ocAdminServicePassword,
IdpPassword: idpServicePassword,
RevaPassword: revaServicePassword,
IdmPassword: idmServicePassword,
},
},
Idp: LdapBasedService{
Ldap: LdapSettings{
BindPassword: idpServicePassword,
},
},
AuthBasic: AuthbasicService{
AuthProviders: LdapBasedService{
Ldap: LdapSettings{
BindPassword: revaServicePassword,
},
},
},
Collaboration: Collaboration{
WopiApp: WopiApp{
Secret: collaborationWOPISecret,
},
},
Groups: UsersAndGroupsService{
Drivers: LdapBasedService{
Ldap: LdapSettings{
BindPassword: revaServicePassword,
},
},
},
Users: UsersAndGroupsService{
Drivers: LdapBasedService{
Ldap: LdapSettings{
BindPassword: revaServicePassword,
},
},
},
Graph: GraphService{
Application: GraphApplication{
ID: graphApplicationID,
},
Identity: LdapBasedService{
Ldap: LdapSettings{
BindPassword: idmServicePassword,
},
},
ServiceAccount: serviceAccount,
},
Thumbnails: ThumbnailService{
Thumbnail: ThumbnailSettings{
TransferSecret: thumbnailsTransferSecret,
},
},
Gateway: Gateway{
StorageRegistry: StorageRegistry{
StorageUsersMountID: storageUsersMountID,
},
},
StorageUsers: StorageUsers{
MountID: storageUsersMountID,
ServiceAccount: serviceAccount,
},
Userlog: Userlog{
ServiceAccount: serviceAccount,
},
AuthService: AuthService{
ServiceAccount: serviceAccount,
},
Search: Search{
ServiceAccount: serviceAccount,
},
Notifications: Notifications{
ServiceAccount: serviceAccount,
},
Frontend: FrontendService{
ServiceAccount: serviceAccount,
},
Ocm: OcmService{
ServiceAccount: serviceAccount,
},
Clientlog: Clientlog{
ServiceAccount: serviceAccount,
},
Proxy: ProxyService{
ServiceAccount: serviceAccount,
},
Settings: SettingsService{
ServiceAccountIDs: []string{serviceAccount.ServiceAccountID},
},
Activitylog: Activitylog{
ServiceAccount: serviceAccount,
},
Sharing: Sharing{
ServiceAccount: serviceAccount,
},
}
if insecure {
cfg.AuthBearer = AuthbearerService{
AuthProviders: AuthProviderSettings{Oidc: _insecureService},
}
cfg.Collaboration.App.Insecure = true
cfg.Frontend.AppHandler = _insecureService
cfg.Frontend.Archiver = _insecureService
cfg.Frontend.OCDav = _insecureService
cfg.Graph.Spaces = _insecureService
cfg.Graph.Events = _insecureEvents
cfg.Notifications.Notifications.Events = _insecureEvents
cfg.Search.Events = _insecureEvents
cfg.Audit.Events = _insecureEvents
cfg.Sharing.Events = _insecureEvents
cfg.StorageUsers.Events = _insecureEvents
cfg.Nats.Nats.TLSSkipVerifyClientCert = true
cfg.Proxy = ProxyService{
InsecureBackends: true,
OIDC: InsecureProxyOIDC{
Insecure: true,
},
ServiceAccount: serviceAccount,
}
cfg.Thumbnails.Thumbnail.WebdavAllowInsecure = true
cfg.Thumbnails.Thumbnail.Cs3AllowInsecure = true
}
yamlOutput, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("could not marshall config into yaml: %s", err)
}
if diff {
return writePatch(configPath, yamlOutput)
}
return writeConfig(configPath, ocAdminServicePassword, targetBackupConfig, yamlOutput)
}
+253
View File
@@ -0,0 +1,253 @@
package init
// TODO: use the qsfera config struct instead of this custom struct
// We can't use it right now, because it would need "omitempty" on
// all elements, in order to produce a slim config file with `qsfera init`.
// We can't just add these "omitempty" tags, since we want to generate
// full example configuration files with that struct, too.
// Proposed solution to get rid of this temporary solution:
// - use the qsfera config struct
// - set the needed values like below
// - marshal it to yaml
// - unmarshal it into yaml.Node
// - recurse through the nodes and delete empty / default ones
// - marshal it to yaml
// QsferaConfig is the configuration for the КуСфера services
type QsferaConfig struct {
TokenManager TokenManager `yaml:"token_manager"`
MachineAuthAPIKey string `yaml:"machine_auth_api_key"`
SystemUserAPIKey string `yaml:"system_user_api_key"`
TransferSecret string `yaml:"transfer_secret"`
URLSigningSecret string `yaml:"url_signing_secret"`
SystemUserID string `yaml:"system_user_id"`
AdminUserID string `yaml:"admin_user_id"`
Graph GraphService `yaml:"graph"`
Idp LdapBasedService `yaml:"idp"`
Idm IdmService `yaml:"idm"`
Collaboration Collaboration `yaml:"collaboration"`
Proxy ProxyService `yaml:"proxy"`
Frontend FrontendService `yaml:"frontend"`
AuthBasic AuthbasicService `yaml:"auth_basic"`
AuthBearer AuthbearerService `yaml:"auth_bearer"`
Users UsersAndGroupsService `yaml:"users"`
Groups UsersAndGroupsService `yaml:"groups"`
Ocm OcmService `yaml:"ocm"`
Thumbnails ThumbnailService `yaml:"thumbnails"`
Search Search `yaml:"search"`
Audit Audit `yaml:"audit"`
Settings SettingsService `yaml:"settings"`
Sharing Sharing `yaml:"sharing"`
StorageUsers StorageUsers `yaml:"storage_users"`
Notifications Notifications `yaml:"notifications"`
Nats Nats `yaml:"nats"`
Gateway Gateway `yaml:"gateway"`
Userlog Userlog `yaml:"userlog"`
AuthService AuthService `yaml:"auth_service"`
Clientlog Clientlog `yaml:"clientlog"`
Activitylog Activitylog `yaml:"activitylog"`
}
// Activitylog is the configuration for the activitylog service
type Activitylog struct {
ServiceAccount ServiceAccount `yaml:"service_account"`
}
// App is the configuration for the collaboration service
type App struct {
Insecure bool `yaml:"insecure"`
}
// Audit is the configuration for the audit service
type Audit struct {
Events Events
}
// AuthbasicService is the configuration for the authbasic service
type AuthbasicService struct {
AuthProviders LdapBasedService `yaml:"auth_providers"`
}
// AuthbearerService is the configuration for the authbearer service
type AuthbearerService struct {
AuthProviders AuthProviderSettings `yaml:"auth_providers"`
}
// AuthProviderSettings is the configuration for the auth provider settings
type AuthProviderSettings struct {
Oidc InsecureService
}
// AuthService is the configuration for the auth service
type AuthService struct {
ServiceAccount ServiceAccount `yaml:"service_account"`
}
// Clientlog is the configuration for the clientlog service
type Clientlog struct {
ServiceAccount ServiceAccount `yaml:"service_account"`
}
// Collaboration is the configuration for the collaboration service
type Collaboration struct {
WopiApp WopiApp `yaml:"wopi"`
App App `yaml:"app"`
}
// Events is the configuration for events
type Events struct {
TLSInsecure bool `yaml:"tls_insecure"`
}
// FrontendService is the configuration for the frontend service
type FrontendService struct {
AppHandler InsecureService `yaml:"app_handler"`
Archiver InsecureService
ServiceAccount ServiceAccount `yaml:"service_account"`
OCDav InsecureService
}
// Gateway is the configuration for the gateway
type Gateway struct {
StorageRegistry StorageRegistry `yaml:"storage_registry"`
}
// GraphApplication is the configuration for the graph application
type GraphApplication struct {
ID string `yaml:"id"`
}
// GraphService is the configuration for the graph service
type GraphService struct {
Application GraphApplication
Events Events
Spaces InsecureService
Identity LdapBasedService
ServiceAccount ServiceAccount `yaml:"service_account"`
}
// IdmService is the configuration for the IDM service
type IdmService struct {
ServiceUserPasswords ServiceUserPasswordsSettings `yaml:"service_user_passwords"`
}
// InsecureProxyOIDC is the configuration for the insecure proxy OIDC
type InsecureProxyOIDC struct {
Insecure bool `yaml:"insecure"`
}
// InsecureService is the configuration for services that can be insecure
type InsecureService struct {
Insecure bool
}
// LdapBasedService is the configuration for LDAP based services
type LdapBasedService struct {
Ldap LdapSettings
}
// LdapSettings is the configuration for LDAP settings
type LdapSettings struct {
BindPassword string `yaml:"bind_password"`
}
// Nats is the configuration for the nats service
type Nats struct {
// The nats config has a field called nats
Nats struct {
TLSSkipVerifyClientCert bool `yaml:"tls_skip_verify_client_cert"`
}
}
// Notifications is the configuration for the notifications service
type Notifications struct {
Notifications struct{ Events Events } // The notifications config has a field called notifications
ServiceAccount ServiceAccount `yaml:"service_account"`
}
// OcmService is the configuration for the OCM service
type OcmService struct {
ServiceAccount ServiceAccount `yaml:"service_account"`
}
// ProxyService is the configuration for the proxy service
type ProxyService struct {
OIDC InsecureProxyOIDC `yaml:"oidc"`
InsecureBackends bool `yaml:"insecure_backends"`
ServiceAccount ServiceAccount `yaml:"service_account"`
}
// Search is the configuration for the search service
type Search struct {
Events Events
ServiceAccount ServiceAccount `yaml:"service_account"`
}
// ServiceAccount is the configuration for the used service account
type ServiceAccount struct {
ServiceAccountID string `yaml:"service_account_id"`
ServiceAccountSecret string `yaml:"service_account_secret"`
}
// ServiceUserPasswordsSettings is the configuration for service user passwords
type ServiceUserPasswordsSettings struct {
AdminPassword string `yaml:"admin_password"`
IdmPassword string `yaml:"idm_password"`
RevaPassword string `yaml:"reva_password"`
IdpPassword string `yaml:"idp_password"`
}
// SettingsService is the configuration for the settings service
type SettingsService struct {
ServiceAccountIDs []string `yaml:"service_account_ids"`
}
// Sharing is the configuration for the sharing service
type Sharing struct {
Events Events
ServiceAccount ServiceAccount `yaml:"service_account"`
}
// StorageRegistry is the configuration for the storage registry
type StorageRegistry struct {
StorageUsersMountID string `yaml:"storage_users_mount_id"`
}
// StorageUsers is the configuration for the storage users
type StorageUsers struct {
Events Events
MountID string `yaml:"mount_id"`
ServiceAccount ServiceAccount `yaml:"service_account"`
}
// ThumbnailSettings is the configuration for the thumbnail settings
type ThumbnailSettings struct {
TransferSecret string `yaml:"transfer_secret"`
WebdavAllowInsecure bool `yaml:"webdav_allow_insecure"`
Cs3AllowInsecure bool `yaml:"cs3_allow_insecure"`
}
// ThumbnailService is the configuration for the thumbnail service
type ThumbnailService struct {
Thumbnail ThumbnailSettings
}
// TokenManager is the configuration for the token manager
type TokenManager struct {
JWTSecret string `yaml:"jwt_secret"`
}
// UsersAndGroupsService is the configuration for the users and groups service
type UsersAndGroupsService struct {
Drivers LdapBasedService
}
// Userlog is the configuration for the userlog service
type Userlog struct {
ServiceAccount ServiceAccount `yaml:"service_account"`
}
// WopiApp is the configuration for the WOPI app
type WopiApp struct {
Secret string `yaml:"secret"`
}