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"
|
||||||
"ntech/internal/auth"
|
"ntech/internal/auth"
|
||||||
"ntech/internal/config"
|
"ntech/internal/config"
|
||||||
|
"ntech/internal/db"
|
||||||
"ntech/internal/db/sqlite"
|
"ntech/internal/db/sqlite"
|
||||||
"ntech/internal/handler"
|
"ntech/internal/handler"
|
||||||
ntechmw "ntech/internal/middleware"
|
ntechmw "ntech/internal/middleware"
|
||||||
@@ -47,7 +48,12 @@ func podesiLog() {
|
|||||||
func main() {
|
func main() {
|
||||||
mime.AddExtensionType(".js", "text/javascript")
|
mime.AddExtensionType(".js", "text/javascript")
|
||||||
mime.AddExtensionType(".css", "text/css")
|
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()
|
podesiLog()
|
||||||
auth.InitAuthLog()
|
auth.InitAuthLog()
|
||||||
|
|
||||||
@@ -70,8 +76,8 @@ func main() {
|
|||||||
staticFS = os.DirFS("web/static")
|
staticFS = os.DirFS("web/static")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.JelPrvoPokretanje() {
|
if config.JelPrvoPokretanje(envFajl) {
|
||||||
config.PokreniSetup(templFS)
|
config.PokreniSetup(templFS, envFajl)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,11 +86,6 @@ func main() {
|
|||||||
port = "8080"
|
port = "8080"
|
||||||
}
|
}
|
||||||
|
|
||||||
putanjaBaze := os.Getenv("NTECH_SQLITE")
|
|
||||||
if putanjaBaze == "" {
|
|
||||||
putanjaBaze = "ntech.db"
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := sqlite.OtvoriDB(putanjaBaze)
|
db, err := sqlite.OtvoriDB(putanjaBaze)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Greška pri otvaranju baze", "error", err)
|
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)
|
// ključ za šifrovanje TOTP tajni u mirovanju (AES-256-GCM)
|
||||||
totpKljuc, err := ucitajTotpKljuc()
|
totpKljuc, err := ucitajTotpKljuc(envFajl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Greška pri učitavanju ključa za TOTP", "error", err)
|
slog.Error("Greška pri učitavanju ključa za TOTP", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -130,6 +131,13 @@ func main() {
|
|||||||
|
|
||||||
h := handler.Novi(db, totpKljuc)
|
h := handler.Novi(db, totpKljuc)
|
||||||
h.Verzija = Verzija
|
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,
|
// 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)
|
// 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)
|
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
|
// 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).
|
// 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 != "" {
|
if v := os.Getenv("NTECH_TOTP_KEY"); v != "" {
|
||||||
kljuc, err := base64.StdEncoding.DecodeString(v)
|
kljuc, err := base64.StdEncoding.DecodeString(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -400,7 +408,7 @@ func ucitajTotpKljuc() ([]byte, error) {
|
|||||||
return nil, fmt.Errorf("generisanje ključa: %w", err)
|
return nil, fmt.Errorf("generisanje ključa: %w", err)
|
||||||
}
|
}
|
||||||
enkodiran := base64.StdEncoding.EncodeToString(kljuc)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("otvaranje ntech.env: %w", err)
|
return nil, fmt.Errorf("otvaranje ntech.env: %w", err)
|
||||||
}
|
}
|
||||||
@@ -413,6 +421,39 @@ func ucitajTotpKljuc() ([]byte, error) {
|
|||||||
return kljuc, nil
|
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.
|
// 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).
|
// Koristi već otvorenu vezu ka bazi (VACUUM INTO je bezbedan na pooled konekciji).
|
||||||
func napraviBackup(db *sql.DB, putanjaBaze string) {
|
func napraviBackup(db *sql.DB, putanjaBaze string) {
|
||||||
|
|||||||
@@ -4,8 +4,17 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"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
|
// lista portova koje proveravamo pri prvom pokretanju
|
||||||
var kandidatPortovi = []int{8080, 3000, 8000, 9090}
|
var kandidatPortovi = []int{8080, 3000, 8000, 9090}
|
||||||
|
|
||||||
@@ -32,8 +41,8 @@ func NadjiSlobodanPort() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// proverava da li je ovo prvo pokretanje programa
|
// proverava da li je ovo prvo pokretanje programa
|
||||||
func JelPrvoPokretanje() bool {
|
func JelPrvoPokretanje(envFajl string) bool {
|
||||||
_, err := os.Stat("ntech.env")
|
_, err := os.Stat(envFajl)
|
||||||
return os.IsNotExist(err)
|
return os.IsNotExist(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +65,7 @@ func StatusPortova() []PortStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SacuvajEnv upisuje izabrani port u ntech.env fajl
|
// 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)
|
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
|
// 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()
|
port := NadjiSlobodanPort()
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
slog.Error("setup: nije pronađen nijedan slobodan port"); os.Exit(1)
|
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"`
|
Port int `json:"port"`
|
||||||
}
|
}
|
||||||
json.NewDecoder(req.Body).Decode(&telo)
|
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)
|
http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ type Handler struct {
|
|||||||
PdvKprRepo db.PdvKprRepository
|
PdvKprRepo db.PdvKprRepository
|
||||||
NivelacijaRepo db.NivelacijaRepository
|
NivelacijaRepo db.NivelacijaRepository
|
||||||
Verzija string
|
Verzija string
|
||||||
|
JelDemo bool
|
||||||
AssetV string // verzija statičkih fajlova za cache-busting (postavlja se pri pokretanju)
|
AssetV string // verzija statičkih fajlova za cache-busting (postavlja se pri pokretanju)
|
||||||
Templates map[string]*template.Template
|
Templates map[string]*template.Template
|
||||||
TemplatesFS fs.FS
|
TemplatesFS fs.FS
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ func (h *Handler) PrikazPrijave(w http.ResponseWriter, r *http.Request) {
|
|||||||
"LoginPozadinaBlurKartice": loginBlurKartice,
|
"LoginPozadinaBlurKartice": loginBlurKartice,
|
||||||
"LoginPozadinaZatamnjenjeKartice": loginZatamnjenjeKartice,
|
"LoginPozadinaZatamnjenjeKartice": loginZatamnjenjeKartice,
|
||||||
"Verzija": h.Verzija,
|
"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>
|
<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>
|
<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}}">
|
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||||
<div class="polje">
|
<div class="polje">
|
||||||
<label for="korisnicko_ime">Korisničko ime</label>
|
<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>
|
||||||
<div class="polje">
|
<div class="polje">
|
||||||
<label for="lozinka">Lozinka</label>
|
<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>
|
</div>
|
||||||
<button type="submit" class="dugme">Prijavi se</button>
|
<button type="submit" class="dugme">Prijavi se</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user