master
serr 2025-04-05 18:26:56 +03:00
commit daf390bf6c
11 changed files with 450 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
config.json

57
assets/css/styles.css Normal file
View File

@ -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%;
}
}

BIN
assets/pic/favicon.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

BIN
assets/pic/footer.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
assets/pic/header.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>hikan.ru</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/assets/pic/favicon.webp?v={{ .version }}" type="image/x-icon">
<link rel="stylesheet" href="/assets/css/styles.css?v={{ .version }}" type="text/css">
</head>
<body>
<header>
<div>
<img src="/assets/pic/header.webp?v={{ .version }}" width="100%" height="100%">
</div>
<div>
<h1>
contacts
</h1>
<p>
you can message me on <a href="https://t.me/semaphoreslover" target="_blank">telegram</a> or <a href="https://mastodon.ml/@serr" target="_blank">mastodon</a>
</p>
</div>
<div>
<h1>
some system information
</h1>
<ul>
<li>unix timestamp of page rendering - <strong>{{ .renderingTimestamp }}</strong></li>
</ul>
</div>
</header>
<main>
<div>
<h1>
$whoami
</h1>
<p>
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
</p>
<p>
i was born in 2003, i'm currently a cybersecurity major at university
</p>
<p>
<code>pronouns: he/him</code>
</p>
</div>
<div>
<h1>
what do i do?
</h1>
<p>
programming is my everything - my job, my hobby, my lifelong obsession
</p>
<p>
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!
</p>
<p>
i like the idea of <a href="https://en.wikipedia.org/wiki/Symbolic_execution" target="_blank">symbolic</a>/concolic execution and virtual code execution in general
</p>
</div>
<div>
<h1>
things i love
</h1>
<ul>
<li><strong>coffee</strong>. i REALLY love coffee. almost any. and a lot of</li>
<li><strong>movies and TV series</strong> (especially TV series). i watch something almost every day</li>
<li><strong>true crime</strong>. i'm obsessed with serial killer cases, mysterious disappearances, unsolved murders - all that dark stuff</li>
<li><strong>russian underground rap</strong> like Slava KPSS, Zamay, MB Packet, Ovsyankin etc.</li>
<li><strong>simple and extensible code</strong>. i think if your code is overly complex, it means you are doing something wrong. most things are simpler than they seem</li>
</ul>
</div>
<div>
<h1>
projects
</h1>
<ul>
<li><a href="https://git.hikan.ru/serr" target="_blank">git.hikan.ru/serr</a> - check my repos</li>
<li><del>telegram bot with schedule for SPBPU - <a href="https://t.me/polysched_bot" target="_blank">polysched_bot</a></del> (transferred to a more proactive owner)</li>
<li><del>telegram bot with schedule for SPMI - <a href="https://t.me/gornischedule_bot" target="_blank">gornischedule_bot</a></del> (closed)</li>
</ul>
</div>
<div>
<h1>
nice links
</h1>
<ul>
<li><a href="https://mo.rijndael.cc/" target="_blank">Mo</a>, thx for design idea!</li>
<li>huge collection of Xakep issues - <a href="https://図書館.きく.コム/" target="_blank">図書館.きく.コム</a></li>
<li>i love this website highlighting the Small Web - <a href="https://smallweb.cc/" target="_blank">smallweb</a></li>
<li>very atmospheric forum about black metal - <a href="https://www.lycanthropia.net/" target="_blank">lycanthropia</a></li>
</ul>
</div>
</main>
<footer>
<div>
<img src="/assets/pic/footer.webp?v={{ .version }}" width="100%" height="100%">
</div>
<div>
<p>
and also you can subscribe to my Telegram channel with pictures!
</p>
<p>
<a href="https://t.me/lolistack" target="_blank">digital countryside</a>
</p>
</div>
<div>
<p>
this site is written in Go without using frameworks in few hours, hosting is <a href="https://htk.ge" target="_blank">hostetski</a>, domain bought for the price of a can of beer
</p>
<p>
<code>2024 - now</code>
</p>
</div>
</footer>
</body>
</html>

25
cache/cache.go vendored Normal file
View File

@ -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()
}

40
config/config.go Normal file
View File

@ -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
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module main
go 1.23.2

176
main.go Normal file
View File

@ -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)
}

31
tools/inet.go Normal file
View File

@ -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 // Адрес не найден
}