Demo mod, favicon ispravka i putanja ntech.env uz bazu
- NTECH_ENV=demo aktivira demo mod: korisnik Demo/Demo1234 (admin) se kreira ili resetuje pri svakom pokretanju - Login ekran u demo modu prikazuje pre-popunjena polja i "DEMO verzija" - ntech.env se čuva u istom direktorijumu kao SQLite baza (umesto uvek u radnom direktorijumu) — rešava Docker volume problem - favicon.svg: uklonjen width="100%" koji je sprečavao prikaz ikone u brauzeru
This commit is contained in:
+52
-11
@@ -19,6 +19,7 @@ import (
|
||||
"ntech"
|
||||
"ntech/internal/auth"
|
||||
"ntech/internal/config"
|
||||
"ntech/internal/db"
|
||||
"ntech/internal/db/sqlite"
|
||||
"ntech/internal/handler"
|
||||
ntechmw "ntech/internal/middleware"
|
||||
@@ -47,7 +48,12 @@ func podesiLog() {
|
||||
func main() {
|
||||
mime.AddExtensionType(".js", "text/javascript")
|
||||
mime.AddExtensionType(".css", "text/css")
|
||||
godotenv.Load("ntech.env")
|
||||
putanjaBaze := os.Getenv("NTECH_SQLITE")
|
||||
if putanjaBaze == "" {
|
||||
putanjaBaze = "ntech.db"
|
||||
}
|
||||
envFajl := config.PutanjaNtechEnv(putanjaBaze)
|
||||
godotenv.Load(envFajl)
|
||||
podesiLog()
|
||||
auth.InitAuthLog()
|
||||
|
||||
@@ -70,8 +76,8 @@ func main() {
|
||||
staticFS = os.DirFS("web/static")
|
||||
}
|
||||
|
||||
if config.JelPrvoPokretanje() {
|
||||
config.PokreniSetup(templFS)
|
||||
if config.JelPrvoPokretanje(envFajl) {
|
||||
config.PokreniSetup(templFS, envFajl)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -80,11 +86,6 @@ func main() {
|
||||
port = "8080"
|
||||
}
|
||||
|
||||
putanjaBaze := os.Getenv("NTECH_SQLITE")
|
||||
if putanjaBaze == "" {
|
||||
putanjaBaze = "ntech.db"
|
||||
}
|
||||
|
||||
db, err := sqlite.OtvoriDB(putanjaBaze)
|
||||
if err != nil {
|
||||
slog.Error("Greška pri otvaranju baze", "error", err)
|
||||
@@ -111,7 +112,7 @@ func main() {
|
||||
}
|
||||
|
||||
// ključ za šifrovanje TOTP tajni u mirovanju (AES-256-GCM)
|
||||
totpKljuc, err := ucitajTotpKljuc()
|
||||
totpKljuc, err := ucitajTotpKljuc(envFajl)
|
||||
if err != nil {
|
||||
slog.Error("Greška pri učitavanju ključa za TOTP", "error", err)
|
||||
os.Exit(1)
|
||||
@@ -130,6 +131,13 @@ func main() {
|
||||
|
||||
h := handler.Novi(db, totpKljuc)
|
||||
h.Verzija = Verzija
|
||||
h.JelDemo = os.Getenv("NTECH_ENV") == "demo"
|
||||
if h.JelDemo {
|
||||
h.Verzija = "DEMO verzija"
|
||||
if err := postaviDemoKorisnika(context.Background(), h.KorisniciRepo); err != nil {
|
||||
slog.Warn("demo: greška pri postavljanju demo korisnika", "error", err)
|
||||
}
|
||||
}
|
||||
// verzija statičkih fajlova za cache-busting — menja se pri svakom pokretanju,
|
||||
// pa novi build/restart natera brauzer da povuče sveži CSS/JS (umesto starog iz keša)
|
||||
h.AssetV = strconv.FormatInt(time.Now().Unix(), 36)
|
||||
@@ -382,7 +390,7 @@ func main() {
|
||||
//
|
||||
// VAŽNO: ako se ovaj ključ izgubi ili promeni, postojeće šifrovane TOTP tajne se
|
||||
// više ne mogu dešifrovati (korisnici moraju ponovo aktivirati 2FA).
|
||||
func ucitajTotpKljuc() ([]byte, error) {
|
||||
func ucitajTotpKljuc(envFajl string) ([]byte, error) {
|
||||
if v := os.Getenv("NTECH_TOTP_KEY"); v != "" {
|
||||
kljuc, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
@@ -400,7 +408,7 @@ func ucitajTotpKljuc() ([]byte, error) {
|
||||
return nil, fmt.Errorf("generisanje ključa: %w", err)
|
||||
}
|
||||
enkodiran := base64.StdEncoding.EncodeToString(kljuc)
|
||||
f, err := os.OpenFile("ntech.env", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
|
||||
f, err := os.OpenFile(envFajl, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("otvaranje ntech.env: %w", err)
|
||||
}
|
||||
@@ -413,6 +421,39 @@ func ucitajTotpKljuc() ([]byte, error) {
|
||||
return kljuc, nil
|
||||
}
|
||||
|
||||
// postaviDemoKorisnika osigurava da pri pokretanju demo instance postoji korisnik "Demo"
|
||||
// sa ulogom "admin" i resetovanom lozinkom. Poziva se samo kada je NTECH_ENV=demo.
|
||||
func postaviDemoKorisnika(ctx context.Context, repo db.KorisniciRepository) error {
|
||||
const (
|
||||
demoIme = "Demo"
|
||||
demoLozinka = "Demo1234"
|
||||
)
|
||||
hash, err := auth.HashujLozinku(demoLozinka)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: postaviDemoKorisnika: %w", err)
|
||||
}
|
||||
korisnik, err := repo.DohvatiPoImenu(ctx, demoIme)
|
||||
if err != nil {
|
||||
// korisnik ne postoji — kreiraj ga
|
||||
if _, err := repo.Kreiraj(ctx, demoIme, hash, "admin"); err != nil {
|
||||
return fmt.Errorf("ntech: postaviDemoKorisnika: kreiranje: %w", err)
|
||||
}
|
||||
slog.Info("demo korisnik kreiran", "korisnik", demoIme)
|
||||
return nil
|
||||
}
|
||||
// korisnik postoji — resetuj lozinku i osiguraj da je aktivan
|
||||
if err := repo.PromeniLozinku(ctx, korisnik.ID, hash); err != nil {
|
||||
return fmt.Errorf("ntech: postaviDemoKorisnika: lozinka: %w", err)
|
||||
}
|
||||
if !korisnik.Aktivan {
|
||||
if err := repo.AzurirajAktivan(ctx, korisnik.ID, true); err != nil {
|
||||
return fmt.Errorf("ntech: postaviDemoKorisnika: aktivan: %w", err)
|
||||
}
|
||||
}
|
||||
slog.Info("demo korisnik resetovan", "korisnik", demoIme)
|
||||
return nil
|
||||
}
|
||||
|
||||
// napraviBackup kreira konzistentnu kopiju baze i briše najstarije preko zadatog broja kopija.
|
||||
// Koristi već otvorenu vezu ka bazi (VACUUM INTO je bezbedan na pooled konekciji).
|
||||
func napraviBackup(db *sql.DB, putanjaBaze string) {
|
||||
|
||||
@@ -4,8 +4,17 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// PutanjaNtechEnv vraća putanju do ntech.env fajla na osnovu putanje SQLite baze.
|
||||
// Fajl se čuva u istom direktorijumu kao i baza, tako da volume mount direktorijuma
|
||||
// pokriva i bazu i konfiguraciju.
|
||||
func PutanjaNtechEnv(sqlitePutanja string) string {
|
||||
dir := filepath.Dir(sqlitePutanja)
|
||||
return filepath.Join(dir, "ntech.env")
|
||||
}
|
||||
|
||||
// lista portova koje proveravamo pri prvom pokretanju
|
||||
var kandidatPortovi = []int{8080, 3000, 8000, 9090}
|
||||
|
||||
@@ -32,8 +41,8 @@ func NadjiSlobodanPort() int {
|
||||
}
|
||||
|
||||
// proverava da li je ovo prvo pokretanje programa
|
||||
func JelPrvoPokretanje() bool {
|
||||
_, err := os.Stat("ntech.env")
|
||||
func JelPrvoPokretanje(envFajl string) bool {
|
||||
_, err := os.Stat(envFajl)
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
|
||||
@@ -56,7 +65,7 @@ func StatusPortova() []PortStatus {
|
||||
}
|
||||
|
||||
// SacuvajEnv upisuje izabrani port u ntech.env fajl
|
||||
func SacuvajEnv(port int) error {
|
||||
func SacuvajEnv(port int, envFajl string) error {
|
||||
sadrzaj := fmt.Sprintf("NTECH_PORT=%d\n", port)
|
||||
return os.WriteFile("ntech.env", []byte(sadrzaj), 0600)
|
||||
return os.WriteFile(envFajl, []byte(sadrzaj), 0600)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func nadjiLokalneAdrese() []string {
|
||||
}
|
||||
|
||||
// PokreniSetup pokreće HTTP server za prvo podešavanje i čeka da korisnik završi
|
||||
func PokreniSetup(fsys fs.FS) {
|
||||
func PokreniSetup(fsys fs.FS, envFajl string) {
|
||||
port := NadjiSlobodanPort()
|
||||
if port == 0 {
|
||||
slog.Error("setup: nije pronađen nijedan slobodan port"); os.Exit(1)
|
||||
@@ -90,7 +90,7 @@ func PokreniSetup(fsys fs.FS) {
|
||||
Port int `json:"port"`
|
||||
}
|
||||
json.NewDecoder(req.Body).Decode(&telo)
|
||||
if err := SacuvajEnv(telo.Port); err != nil {
|
||||
if err := SacuvajEnv(telo.Port, envFajl); err != nil {
|
||||
http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ type Handler struct {
|
||||
PdvKprRepo db.PdvKprRepository
|
||||
NivelacijaRepo db.NivelacijaRepository
|
||||
Verzija string
|
||||
JelDemo bool
|
||||
AssetV string // verzija statičkih fajlova za cache-busting (postavlja se pri pokretanju)
|
||||
Templates map[string]*template.Template
|
||||
TemplatesFS fs.FS
|
||||
|
||||
@@ -64,6 +64,7 @@ func (h *Handler) PrikazPrijave(w http.ResponseWriter, r *http.Request) {
|
||||
"LoginPozadinaBlurKartice": loginBlurKartice,
|
||||
"LoginPozadinaZatamnjenjeKartice": loginZatamnjenjeKartice,
|
||||
"Verzija": h.Verzija,
|
||||
"JelDemo": h.JelDemo,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg width="100%" viewBox="0 0 750 690" role="img" style="" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 750 690" role="img" style="" xmlns="http://www.w3.org/2000/svg">
|
||||
<title style="fill:rgb(0, 0, 0);stroke:none;color:rgb(251, 251, 254);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">NTech favicon v4</title>
|
||||
<desc style="fill:rgb(0, 0, 0);stroke:none;color:rgb(251, 251, 254);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">Moderna ikonica sa NT slovima i tehnološkim akcentom</desc>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
@@ -57,11 +57,11 @@
|
||||
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||
<div class="polje">
|
||||
<label for="korisnicko_ime">Korisničko ime</label>
|
||||
<input type="text" id="korisnicko_ime" name="korisnicko_ime" autocomplete="username" autofocus required>
|
||||
<input type="text" id="korisnicko_ime" name="korisnicko_ime" autocomplete="username" autofocus required{{if .JelDemo}} value="Demo"{{end}}>
|
||||
</div>
|
||||
<div class="polje">
|
||||
<label for="lozinka">Lozinka</label>
|
||||
<input type="password" id="lozinka" name="lozinka" autocomplete="current-password" required>
|
||||
<input type="password" id="lozinka" name="lozinka" autocomplete="current-password" required{{if .JelDemo}} value="Demo1234"{{end}}>
|
||||
</div>
|
||||
<button type="submit" class="dugme">Prijavi se</button>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user