commit daf390bf6c78c8de8f2e810898dd7a81bb499b5c Author: serr Date: Sat Apr 5 18:26:56 2025 +0300 new diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0cffcb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.json \ No newline at end of file diff --git a/assets/css/styles.css b/assets/css/styles.css new file mode 100644 index 0000000..61dada0 --- /dev/null +++ b/assets/css/styles.css @@ -0,0 +1,57 @@ +/* Design idea from here https://mo.rijndael.cc/ */ + +body { + display: flex; + flex-wrap: wrap; + margin: 0; + align-items: flex-start; + padding: 10px; +} + +header, main, footer { + margin: 5px; + display: flex; + flex-direction: column; + gap: 10px; +} + +header, footer { + flex: 2; +} + +main { + flex: 3; +} + +header > div, +footer > div, +main > div { + box-shadow: 5px 5px 0 0 lightgrey; + box-sizing: border-box; + border: 1px solid; + width: 100%; + text-align: center; + color: black; + padding-left: 10px; + padding-right: 10px; +} + +header > div > ul, +footer > div > ul, +main > div > ul { + text-align: left; +} + +header > div > h1, +footer > div > h1, +main > div > h1 { + box-sizing: border-box; + border-top: 1px solid; + border-bottom: 1px solid; +} + +@media (max-width: 1200px) { + header, footer, main { + flex: 1 100%; + } +} \ No newline at end of file diff --git a/assets/pic/favicon.webp b/assets/pic/favicon.webp new file mode 100644 index 0000000..b2cc3c1 Binary files /dev/null and b/assets/pic/favicon.webp differ diff --git a/assets/pic/footer.webp b/assets/pic/footer.webp new file mode 100644 index 0000000..ecc763e Binary files /dev/null and b/assets/pic/footer.webp differ diff --git a/assets/pic/header.webp b/assets/pic/header.webp new file mode 100644 index 0000000..dbec776 Binary files /dev/null and b/assets/pic/header.webp differ diff --git a/assets/templates/main.gohtml b/assets/templates/main.gohtml new file mode 100644 index 0000000..ac62c4d --- /dev/null +++ b/assets/templates/main.gohtml @@ -0,0 +1,117 @@ + + + + hikan.ru + + + + + + +
+
+ +
+
+

+ contacts +

+

+ you can message me on telegram or mastodon +

+
+
+

+ some system information +

+
    +
  • unix timestamp of page rendering - {{ .renderingTimestamp }}
  • +
+
+
+
+
+

+ $whoami +

+

+ my name is serr (you can easily guess my real name if you speak Russian :d), and i didn't come up with that nickname, i just started being called it +

+

+ i was born in 2003, i'm currently a cybersecurity major at university +

+

+ pronouns: he/him +

+
+
+

+ what do i do? +

+

+ programming is my everything - my job, my hobby, my lifelong obsession +

+

+ i love growing in all areas of programming - i am literally interested in everything: cybersecurity (chaotically breaking things, analyzing code, writing automated analyzers, and moving bytes back and forth), concurrency/multithreading, web development, low-level programming, cryptography and a lot more! +

+

+ i like the idea of symbolic/concolic execution and virtual code execution in general +

+
+
+

+ things i love +

+
    +
  • coffee. i REALLY love coffee. almost any. and a lot of
  • +
  • movies and TV series (especially TV series). i watch something almost every day
  • +
  • true crime. i'm obsessed with serial killer cases, mysterious disappearances, unsolved murders - all that dark stuff
  • +
  • russian underground rap like Slava KPSS, Zamay, MB Packet, Ovsyankin etc.
  • +
  • simple and extensible code. i think if your code is overly complex, it means you are doing something wrong. most things are simpler than they seem
  • +
+
+
+

+ projects +

+ +
+
+

+ nice links +

+ +
+
+ + + \ No newline at end of file diff --git a/cache/cache.go b/cache/cache.go new file mode 100644 index 0000000..9c5a616 --- /dev/null +++ b/cache/cache.go @@ -0,0 +1,25 @@ +package cache + +import "sync" + +type Cache struct { + Data map[string]any + Mu sync.RWMutex +} + +func Init() *Cache { + return &Cache{Data: make(map[string]any)} +} + +func (c *Cache) Get(key string) (any, bool) { + c.Mu.RLock() + pageData, ok := c.Data[key] + c.Mu.RUnlock() + return pageData, ok +} + +func (c *Cache) Set(key string, data any) { + c.Mu.Lock() + c.Data[key] = data + c.Mu.Unlock() +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..21a7a36 --- /dev/null +++ b/config/config.go @@ -0,0 +1,40 @@ +package config + +import ( + "encoding/json" + "os" +) + +/* + For server start (author's note to self): + +sudo systemctl stop server.service +go build main.go +sudo systemctl start server.service + +*/ + +type Config struct { + AssetsPath string + TemplatesPath string + TemplatesExt string + LocalIP string + ServerIP string + Port string +} + +func Init() *Config { + return &Config{} +} + +func (c *Config) Load(configPath string) error { + configFile, err := os.ReadFile(configPath) + if err != nil { + return err + } + err = json.Unmarshal(configFile, c) + if err != nil { + return err + } + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c8beda6 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module main + +go 1.23.2 diff --git a/main.go b/main.go new file mode 100644 index 0000000..9ac77ea --- /dev/null +++ b/main.go @@ -0,0 +1,176 @@ +package main + +import ( + "bytes" + "log" + "main/cache" + "main/config" + "main/tools" + "net/http" + "os" + "path/filepath" + "strings" + "text/template" + "time" +) + +// App представляет основное состояние приложения +type App struct { + Templates *template.Template // Шаблоны страниц + Config *config.Config // Конфиг + StartTime int64 // Время запуска + Cache *cache.Cache // Кэш (отрендеренные странички) +} + +func main() { + var app *App + var err error + + // Инициализация приложения + if app, err = AppInit("config.json"); err != nil { + log.Fatal(err) + } + + // Настройка маршрутов и запуск + if app.SetupRouterAndRun() != nil { + log.Fatal(err) + } +} + +// Запускает сервер на указанном IP и порту +func RunServer(ip, port string, router http.Handler) { + addr := ip + port + log.Println("Run on", addr) + log.Fatal(http.ListenAndServe(addr, router)) +} + +// Инициализирует приложение +func AppInit(configPath string) (*App, error) { + + a := &App{ + StartTime: time.Now().Unix(), + Config: config.Init(), + Cache: cache.Init(), + } + + // Загрузка конфига + if err := a.Config.Load(configPath); err != nil { + return nil, err + } + + // Загрузка шаблонов + if err := a.LoadTemplates(a.Config.TemplatesPath, a.Config.TemplatesExt); err != nil { + log.Fatal(err) + } + + return a, nil +} + +func (a *App) SetupRouterAndRun() error { + // Настройка маршрутов + router := a.SetupRouter() + + // Запуск сервера + if ok, err := tools.IsIPInUse(a.Config.ServerIP); err != nil { + return err + } else if ok { + RunServer(a.Config.ServerIP, a.Config.Port, router) + } else { + RunServer(a.Config.LocalIP, a.Config.Port, router) + } + + return nil +} + +// Настраивает маршруты +func (a *App) SetupRouter() *http.ServeMux { + router := http.NewServeMux() + + // Обработка статических файлов с кэшированием + router.Handle(a.Config.AssetsPath, a.StaticHandler()) + + // Обработка главной страницы + router.Handle("/", a.MainPageHandler()) + + return router +} + +// Обработчик статических файлов с кэшированием +func (a *App) StaticHandler() http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Cache-Control", "public, max-age=31536000, immutable") + // Здесь используется встроенный файловый сервер Go (http.FileServer), который: + // Реализует интерфейс http.Handler (и поэтому имеет метод ServeHTTP) + // Автоматически обслуживает статические файлы из файловой системы + // Сам обрабатывает HTTP-запросы, определяет MIME-типы, отправляет правильные заголовки и т.д. + http.FileServer(http.Dir(".")).ServeHTTP(w, r) + }) +} + +// Обработчик главной страницы +func (a *App) MainPageHandler() http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var err error + tmplName := "main" + a.Config.TemplatesExt + + // Страничка рендерится только если ее нет в кэше + pageData, ok := a.Cache.Get(tmplName) + if !ok { + context := map[string]any{ + "version": a.StartTime, + "renderingTimestamp": time.Now().Unix(), + } + pageData, err = a.RenderPage(tmplName, context) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + a.Cache.Set(tmplName, pageData) + } + + a.SendPage(w, pageData.([]byte)) + }) +} + +// Загрузка шаблонов +func (a *App) LoadTemplates(templatesPath string, ext string) error { + tmpls := template.New("") + + err := filepath.Walk(templatesPath, func(path string, f os.FileInfo, err error) error { + if err != nil { + return err + } + if !f.IsDir() && strings.HasSuffix(f.Name(), ext) { + _, err = tmpls.ParseFiles(path) + if err != nil { + return err + } + } + return nil + }) + + if err != nil { + return err + } + + a.Templates = tmpls + return nil +} + +// Рендерит шаблон в срез байт +func (a *App) RenderPage(tmplName string, context any) ([]byte, error) { + var pageData bytes.Buffer + + if err := a.Templates.ExecuteTemplate(&pageData, tmplName, context); err != nil { + return nil, err + } + + return pageData.Bytes(), nil +} + +// Отправляет страницу +func (a *App) SendPage(w http.ResponseWriter, data []byte) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(data) +} diff --git a/tools/inet.go b/tools/inet.go new file mode 100644 index 0000000..9832117 --- /dev/null +++ b/tools/inet.go @@ -0,0 +1,31 @@ +package tools + +import ( + "net" + "strings" +) + +// Проверяет есть ли айпи-адрес среди адресов сетевых интерфейсов +func IsIPInUse(targetIP string) (bool, error) { + // Получаем список всех сетевых интерфейсов + interfaces, err := net.Interfaces() + if err != nil { + return false, err + } + + for _, iface := range interfaces { + // Получаем адреса для каждого интерфейса + addrs, err := iface.Addrs() + if err != nil { + return false, err + } + + for _, addr := range addrs { + if strings.Split(addr.String(), "/")[0] == targetIP { + return true, nil + } + } + } + + return false, nil // Адрес не найден +}