summaryrefslogtreecommitdiff
path: root/website.go
diff options
context:
space:
mode:
authorKyren223 <contact@kyren.codes>2026-10-10 04:43:44 +0300
committerKyren223 <contact@kyren.codes>2026-10-10 04:43:44 +0300
commit332051138391a86325e4e780895aa4148f81c5d5 (patch)
treee80c35a1f76c383d8dc1ffbd13921e326638877b /website.go
parent03403186339abb58c5d4a720a5da23ddde66014b (diff)
Website stuff, mostly done, needs finishing touches that I am too lazy
to do
Diffstat (limited to 'website.go')
-rw-r--r--website.go126
1 files changed, 120 insertions, 6 deletions
diff --git a/website.go b/website.go
index e64e7f8..b7cfe62 100644
--- a/website.go
+++ b/website.go
@@ -11,10 +11,12 @@ import (
"html/template"
"io/fs"
"log"
+ "maps"
"net/http"
"os"
"regexp"
"slices"
+ "sort"
"strings"
"sync"
"time"
@@ -401,12 +403,101 @@ func (externalLinks) Transform(doc *ast.Document, reader text.Reader, pc parser.
})
}
+type ShellResult struct {
+ Output templ.Component // nil renders nothing
+ Clear bool
+ Vars map[string]string
+ Cookies []*http.Cookie
+}
+
+type shellCommand func(r *http.Request, args []string) ShellResult
+
+var shellCommands map[string]shellCommand
+
+func init() {
+ shellCommands = map[string]shellCommand{
+ "fastfetch": func(*http.Request, []string) ShellResult { return ShellResult{Output: Fastfetch(fetchRows)} },
+ "clear": func(*http.Request, []string) ShellResult { return ShellResult{Clear: true} },
+ "help": func(*http.Request, []string) ShellResult { return ShellResult{Output: ShellHelp(shellNames())} },
+ "export": func(r *http.Request, args []string) ShellResult {
+ if len(args) == 0 {
+ return ShellResult{Output: ShellText("usage: export KEY=value")}
+ }
+ res := ShellResult{Vars: map[string]string{}}
+ var lines []string
+ for _, arg := range args {
+ key, value, ok := strings.Cut(arg, "=")
+ if !ok {
+ lines = append(lines, "export: expected KEY=value, got "+arg)
+ continue
+ }
+ value = strings.Trim(value, `"'`)
+ vars, cookie, err := setEnvVar(key, value)
+ if err != nil {
+ lines = append(lines, "export: "+err.Error())
+ continue
+ }
+ maps.Copy(res.Vars, vars)
+ res.Cookies = append(res.Cookies, cookie)
+ lines = append(lines, key+"="+value)
+ }
+ res.Output = ShellLines(lines)
+ return res
+ },
+ }
+}
+
+func setEnvVar(key, value string) (map[string]string, *http.Cookie, error) {
+ switch key {
+ case "THEME":
+ theme, ok := GetTheme(value)
+ if !ok {
+ return nil, nil, fmt.Errorf("unknown theme: %s", value)
+ }
+ return theme.Vars(), envCookie("theme", value), nil
+ case "FONT_PACK":
+ pack, ok := GetFontPack(value)
+ if !ok {
+ return nil, nil, fmt.Errorf("unknown font pack: %s", value)
+ }
+ return pack.Vars(), envCookie("font_pack", value), nil
+ default:
+ return nil, nil, fmt.Errorf("unknown variable: %s", key)
+ }
+}
+
+func envCookie(name, value string) *http.Cookie {
+ return &http.Cookie{Name: name, Value: value, Path: "/", MaxAge: 31536000, SameSite: http.SameSiteLaxMode}
+}
+
+func shellNames() []string {
+ names := make([]string, 0, len(shellCommands))
+ for name := range shellCommands {
+ names = append(names, name)
+ }
+ sort.Strings(names)
+ return names
+}
+
+func runShell(r *http.Request, line string) ShellResult {
+ fields := strings.Fields(line)
+ if len(fields) == 0 {
+ return ShellResult{}
+ }
+ run, ok := shellCommands[fields[0]]
+ if !ok {
+ return ShellResult{Output: ShellText(fields[0] + ": command not found")}
+ }
+
+ return run(r, fields[1:])
+}
+
var projects = []Project{
{
Slug: "krypton",
Title: "Krypton",
Category: "Programming Language Compiler",
- Desc: "",
+ Desc: "Krypton is a simple, high-performance, general-purpose language with C-like control designed for good programmers.",
// Icon: IconCpu("text-[#F5A670] self-center"),
Icon: IconKrypton(),
Tags: []ProjectTag{
@@ -556,7 +647,7 @@ func GetMyUptime() string {
defer myUptime.mu.RUnlock()
// If no ping received in 3 minutes, treat as offline
- if time.Since(myUptime.LastUpdate) > 1*time.Minute {
+ if time.Since(myUptime.LastUpdate) > 3*time.Minute {
return "Asleep"
}
@@ -691,10 +782,6 @@ func main() {
HomePage(currentStyle(r), latestBlog(getBlogs())).Render(r.Context(), w)
})
- mux.HandleFunc("GET /info", func(w http.ResponseWriter, r *http.Request) {
- InfoPage(currentStyle(r)).Render(r.Context(), w)
- })
-
mux.HandleFunc("GET /projects", func(w http.ResponseWriter, r *http.Request) {
ProjectsPage(currentStyle(r), projects, 2).Render(r.Context(), w)
})
@@ -712,6 +799,33 @@ func main() {
BlogPage(currentStyle(r), *blog, prev, next).Render(r.Context(), w)
})
+ mux.HandleFunc("GET /shell", func(w http.ResponseWriter, r *http.Request) {
+ ShellPage(currentStyle(r)).Render(r.Context(), w)
+ })
+
+ mux.HandleFunc("POST /shell/run", func(w http.ResponseWriter, r *http.Request) {
+ r.Body = http.MaxBytesReader(w, r.Body, 1<<10)
+ line := strings.TrimSpace(r.FormValue("cmd"))
+ if len(line) > 200 {
+ line = line[:200]
+ }
+ res := runShell(r, line)
+ for _, c := range res.Cookies {
+ http.SetCookie(w, c)
+ }
+
+ var buf bytes.Buffer
+ if res.Output != nil {
+ if err := ShellEntry(line, res.Output).Render(r.Context(), &buf); err != nil {
+ http.Error(w, "render failed", http.StatusInternalServerError)
+ return
+ }
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.Header().Set("Cache-Control", "no-store")
+ json.NewEncoder(w).Encode(map[string]any{"clear": res.Clear, "html": buf.String(), "vars": res.Vars})
+ })
+
mux.HandleFunc("GET /theme/{name}", styleHandler("theme", func(n string) (map[string]string, bool) {
theme, ok := GetTheme(n)
return theme.Vars(), ok