feat: DH-HIS Web Giao Ban Go v1.0.1 (Favicon, Exe icon, System Tray, Gitea auto-update)
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var secretKey = sha256.Sum256([]byte("dh-his-giaoban-secret-key-salt-2026-v1"))
|
||||
|
||||
type DatabaseInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int `json:"port"`
|
||||
AdminPath string `json:"admin_path"`
|
||||
AdminPassword string `json:"admin_password"` // stored as ENC(...)
|
||||
}
|
||||
|
||||
type DBConfig struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"` // stored as ENC(...)
|
||||
DefaultDB string `json:"default_db"`
|
||||
Databases []DatabaseInfo `json:"databases"`
|
||||
}
|
||||
|
||||
type UpdaterConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
GiteaURL string `json:"gitea_url"`
|
||||
Owner string `json:"owner"`
|
||||
Repo string `json:"repo"`
|
||||
Token string `json:"token"` // stored as ENC(...)
|
||||
CurrentVersion string `json:"current_version"`
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Server ServerConfig `json:"server"`
|
||||
Database DBConfig `json:"database"`
|
||||
Updater UpdaterConfig `json:"updater"`
|
||||
|
||||
// runtime plaintext fields (not exported to JSON if needed)
|
||||
DecryptedDBPassword string `json:"-"`
|
||||
DecryptedUpdaterToken string `json:"-"`
|
||||
DecryptedAdminPassword string `json:"-"`
|
||||
}
|
||||
|
||||
var (
|
||||
currentConfig *AppConfig
|
||||
configMutex sync.RWMutex
|
||||
configPath = "config.json"
|
||||
)
|
||||
|
||||
func Encrypt(plaintext string) (string, error) {
|
||||
if plaintext == "" {
|
||||
return "", nil
|
||||
}
|
||||
block, err := aes.NewCipher(secretKey[:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
|
||||
return "ENC(" + base64.StdEncoding.EncodeToString(ciphertext) + ")", nil
|
||||
}
|
||||
|
||||
func Decrypt(encText string) (string, error) {
|
||||
if encText == "" {
|
||||
return "", nil
|
||||
}
|
||||
if !strings.HasPrefix(encText, "ENC(") || !strings.HasSuffix(encText, ")") {
|
||||
// Already plaintext
|
||||
return encText, nil
|
||||
}
|
||||
|
||||
rawB64 := encText[4 : len(encText)-1]
|
||||
data, err := base64.StdEncoding.DecodeString(rawB64)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(secretKey[:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(data) < nonceSize {
|
||||
return "", errors.New("ciphertext quá ngắn")
|
||||
}
|
||||
|
||||
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(plaintext), nil
|
||||
}
|
||||
|
||||
func DefaultConfig() *AppConfig {
|
||||
cfg := &AppConfig{
|
||||
Server: ServerConfig{
|
||||
Port: 3000,
|
||||
AdminPath: "/admin",
|
||||
AdminPassword: "admin",
|
||||
},
|
||||
Database: DBConfig{
|
||||
Host: "192.168.50.79",
|
||||
Port: 5437,
|
||||
User: "postgres",
|
||||
Password: "123",
|
||||
DefaultDB: "an_omon_full_04052026",
|
||||
Databases: []DatabaseInfo{
|
||||
{ID: "an_omon_full_04052026", Name: "BVĐK Ô Môn - Cần Thơ (33 GB - Chuẩn khoa phòng)"},
|
||||
{ID: "tuan_dbgovap_full", Name: "BVĐK Gò Vấp - TP.HCM (65 GB - Dữ liệu lớn)"},
|
||||
{ID: "binh_bvq12_062026052026042026032026022026", Name: "BV Quận 12 - TP.HCM (21 GB)"},
|
||||
{ID: "bvq12_697", Name: "BV Quận 12 - Bản 697 (17 GB)"},
|
||||
{ID: "an_dalieu_full_05082026", Name: "BV Da Liễu (31 GB)"},
|
||||
},
|
||||
},
|
||||
Updater: UpdaterConfig{
|
||||
Enabled: true,
|
||||
GiteaURL: "https://i.dh-his.com",
|
||||
Owner: "hdhiswork",
|
||||
Repo: "DUAN",
|
||||
Token: "",
|
||||
CurrentVersion: "v1.0.0",
|
||||
},
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (*AppConfig, error) {
|
||||
configMutex.Lock()
|
||||
defer configMutex.Unlock()
|
||||
|
||||
if path != "" {
|
||||
configPath = path
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
cfg := DefaultConfig()
|
||||
cfg.DecryptedAdminPassword = cfg.Server.AdminPassword
|
||||
cfg.DecryptedDBPassword = cfg.Database.Password
|
||||
cfg.DecryptedUpdaterToken = cfg.Updater.Token
|
||||
|
||||
// Encrypt for disk save
|
||||
encAdmin, _ := Encrypt(cfg.Server.AdminPassword)
|
||||
encDB, _ := Encrypt(cfg.Database.Password)
|
||||
encTok, _ := Encrypt(cfg.Updater.Token)
|
||||
|
||||
cfg.Server.AdminPassword = encAdmin
|
||||
cfg.Database.Password = encDB
|
||||
cfg.Updater.Token = encTok
|
||||
|
||||
data, _ := json.MarshalIndent(cfg, "", " ")
|
||||
_ = os.MkdirAll(filepath.Dir(configPath), 0755)
|
||||
_ = os.WriteFile(configPath, data, 0644)
|
||||
|
||||
currentConfig = cfg
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &AppConfig{}
|
||||
if err := json.Unmarshal(data, cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decrypt sensitive fields for runtime use
|
||||
decAdmin, _ := Decrypt(cfg.Server.AdminPassword)
|
||||
decDB, _ := Decrypt(cfg.Database.Password)
|
||||
decTok, _ := Decrypt(cfg.Updater.Token)
|
||||
|
||||
cfg.DecryptedAdminPassword = decAdmin
|
||||
cfg.DecryptedDBPassword = decDB
|
||||
cfg.DecryptedUpdaterToken = decTok
|
||||
|
||||
currentConfig = cfg
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func SaveConfig(cfg *AppConfig) error {
|
||||
configMutex.Lock()
|
||||
defer configMutex.Unlock()
|
||||
|
||||
clone := *cfg
|
||||
|
||||
// Ensure passwords/tokens are stored encrypted
|
||||
if !strings.HasPrefix(clone.Server.AdminPassword, "ENC(") {
|
||||
clone.DecryptedAdminPassword = clone.Server.AdminPassword
|
||||
enc, _ := Encrypt(clone.Server.AdminPassword)
|
||||
clone.Server.AdminPassword = enc
|
||||
}
|
||||
if !strings.HasPrefix(clone.Database.Password, "ENC(") {
|
||||
clone.DecryptedDBPassword = clone.Database.Password
|
||||
enc, _ := Encrypt(clone.Database.Password)
|
||||
clone.Database.Password = enc
|
||||
}
|
||||
if !strings.HasPrefix(clone.Updater.Token, "ENC(") {
|
||||
clone.DecryptedUpdaterToken = clone.Updater.Token
|
||||
enc, _ := Encrypt(clone.Updater.Token)
|
||||
clone.Updater.Token = enc
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(&clone, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpPath := configPath + ".tmp"
|
||||
if err := os.WriteFile(tmpPath, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(tmpPath, configPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentConfig = &clone
|
||||
return nil
|
||||
}
|
||||
|
||||
func Get() *AppConfig {
|
||||
configMutex.RLock()
|
||||
defer configMutex.RUnlock()
|
||||
if currentConfig == nil {
|
||||
configMutex.RUnlock()
|
||||
cfg, _ := LoadConfig("config.json")
|
||||
return cfg
|
||||
}
|
||||
return currentConfig
|
||||
}
|
||||
Reference in New Issue
Block a user