initial server setup
This commit is contained in:
commit
af16863256
10 changed files with 314 additions and 0 deletions
0
files/blog-posts/technology/test.md
Normal file
0
files/blog-posts/technology/test.md
Normal file
40
files/blog-posts/the-death-of-the-wild-web.md
Normal file
40
files/blog-posts/the-death-of-the-wild-web.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# the death of the wild web
|
||||
|
||||
## from chaos to corporatism
|
||||
|
||||
the internet was once a chaotic, anarchic landscape. anyone with an idea and a little technical skill could carve out a space, build something unique, and contribute to the weird, messy digital ecosystem. websites were personal, forums were thriving, and search engines indexed a wild frontier of human creativity.
|
||||
|
||||
now? everything is pipes leading into five or six giant silos. if you’re not posting on twitter (or its dozen corporate clones), youtube, tiktok, or facebook, you effectively don’t exist. gone are the days when the best search result was a forgotten blog post with more insight than a thousand corporate articles combined. now, everything is sanitized, optimized, and locked behind engagement-maximizing algorithms designed to keep you scrolling, not learning.
|
||||
|
||||
## the enclosure of the digital commons
|
||||
|
||||
it didn’t happen overnight, but the internet has been enclosed just like the commons of old. instead of digital land grabs by settlers, we got consolidation by corporations. google, meta, amazon, and a handful of others have reduced the web to a handful of walled gardens where they dictate the rules. the decentralized, federated, and diverse nature of the early internet has been all but erased.
|
||||
|
||||
- search engines prioritize ad-driven, corporate-approved results
|
||||
- social media limits organic reach unless you pay for visibility
|
||||
- independent forums and communities die off in favor of platform-optimized "engagement hubs"
|
||||
- content is ephemeral, existing at the whims of moderation policies and algorithmic whims
|
||||
|
||||
if a website dies today, it’s not just a loss—it’s an extinction. archives break, links rot, and the collective memory of the internet gets shorter by the year. what survives is what’s profitable, not what’s valuable.
|
||||
|
||||
## digital landlords and algorithmic serfdom
|
||||
|
||||
users have become tenants, not owners. platforms own your data, your reach, your ability to communicate. build an audience on instagram? meta decides if you can reach them. post a video on youtube? it lives or dies by an algorithm you’ll never fully understand. even email—once the great decentralized communication method—is being throttled by ai-driven spam filters that quietly kill independent newsletters in favor of corporate mailers.
|
||||
|
||||
meanwhile, everything is monetized in the worst possible way. ads track your every move. paywalls fragment information. and the walled gardens demand not just your content, but your time, your engagement, your obedience to ever-shifting rules.
|
||||
|
||||
## the path forward: rebuilding the indie web
|
||||
|
||||
it’s not all doom. people are waking up. self-hosting is making a return. rss is experiencing a quiet renaissance. blogs, newsletters, and independent forums are proving that not everyone wants to live inside corporate-owned cages.
|
||||
|
||||
the solution?
|
||||
|
||||
- **host your own site** – own your content, your data, your voice
|
||||
- **use decentralized networks** – mastodon, matrix, and other federated platforms are growing
|
||||
- **revive old tools** – rss, webrings, and independent search engines offer alternatives
|
||||
- **support independent creators** – subscribe to newsletters, fund open-source projects, share content outside of corporate silos
|
||||
|
||||
most of all: resist the idea that the internet must be owned by corporations. the original spirit of the web—a decentralized, user-driven, chaotic mess—was its greatest strength. the more we cede to platforms, the harder it becomes to reclaim.
|
||||
|
||||
it's time to break the fences and take back the web.
|
||||
|
||||
BIN
files/download-lists/xi.gif
Normal file
BIN
files/download-lists/xi.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
176
main.go
Normal file
176
main.go
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
FILES_DIR = "files"
|
||||
TEMPLATES_DIR = "templates"
|
||||
TIME_FORMAT = "Jan 2 2006 15:04:05"
|
||||
)
|
||||
|
||||
const (
|
||||
POST_ICON = "/static/post.gif"
|
||||
FILE_ICON = "/static/file.gif"
|
||||
)
|
||||
|
||||
var (
|
||||
headerPattern = regexp.MustCompile(`(?m)^# (.+)$`)
|
||||
subHeaderPattern = regexp.MustCompile(`(?m)^## (.+)$`)
|
||||
newlinePattern = regexp.MustCompile(`(?m)^([^#<].+)`)
|
||||
boldPattern = regexp.MustCompile(`\*\*(.*?)\*\*|__(.*?)__`)
|
||||
italicPattern = regexp.MustCompile(`\*(.*?)\*|_(.*?)_`)
|
||||
inlineCodePattern = regexp.MustCompile("`([^`]+)`")
|
||||
codeBlockPattern = regexp.MustCompile("(?s)```(.*?)```")
|
||||
blockquotePattern = regexp.MustCompile(`(?m)^> (.+)$`)
|
||||
ulPattern = regexp.MustCompile(`(?m)^(?:-|\*) (.+)$`)
|
||||
olPattern = regexp.MustCompile(`(?m)^\d+\. (.+)$`)
|
||||
linkPattern = regexp.MustCompile(`\[(.*?)\]\((.*?)\)`)
|
||||
)
|
||||
|
||||
type FileEntry struct {
|
||||
Name string
|
||||
Timestamp string
|
||||
ParentPath string
|
||||
Icon string
|
||||
}
|
||||
|
||||
type Page struct {
|
||||
Title string
|
||||
Content template.HTML
|
||||
Files []FileEntry
|
||||
Folders []string
|
||||
BasePath string
|
||||
ParentPath string
|
||||
TimeStamp string
|
||||
}
|
||||
|
||||
func listEntries(basePath string) ([]FileEntry, []string) {
|
||||
entries, err := os.ReadDir(filepath.Join(FILES_DIR, strings.TrimPrefix(basePath, "/")))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var files []FileEntry
|
||||
var folders []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
folders = append(folders, entry.Name())
|
||||
} else if strings.HasSuffix(entry.Name(), ".md") {
|
||||
info, _ := entry.Info()
|
||||
files = append(files, FileEntry{Name: entry.Name(), Timestamp: info.ModTime().Format(TIME_FORMAT), ParentPath: basePath, Icon: POST_ICON})
|
||||
}else
|
||||
{
|
||||
info, _ := entry.Info()
|
||||
files = append(files, FileEntry{Name: entry.Name(), Timestamp: info.ModTime().Format(TIME_FORMAT), ParentPath: basePath, Icon: FILE_ICON})
|
||||
}
|
||||
}
|
||||
sort.Slice(files, func(i, j int) bool { return files[i].Name < files[j].Name })
|
||||
sort.Strings(folders)
|
||||
return files, folders
|
||||
}
|
||||
|
||||
func readMarkdown(filename string) string {
|
||||
content, err := os.ReadFile(filepath.Join(FILES_DIR, filename))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return string(content)
|
||||
}
|
||||
|
||||
|
||||
func renderMarkdown(content string) template.HTML {
|
||||
content = headerPattern.ReplaceAllString(content, "<h1>$1</h1>")
|
||||
content = subHeaderPattern.ReplaceAllString(content, "<h2>$1</h2>")
|
||||
content = boldPattern.ReplaceAllString(content, "<b>$1$2</b>")
|
||||
content = italicPattern.ReplaceAllString(content, "<i>$1$2</i>")
|
||||
content = inlineCodePattern.ReplaceAllString(content, "<code>$1</code>")
|
||||
content = codeBlockPattern.ReplaceAllString(content, "<pre><code>$1</code></pre>")
|
||||
content = blockquotePattern.ReplaceAllString(content, "<blockquote>$1</blockquote>")
|
||||
content = ulPattern.ReplaceAllString(content, "<li>$1</li>")
|
||||
content = olPattern.ReplaceAllString(content, "<li>$1</li>")
|
||||
content = linkPattern.ReplaceAllString(content, `<a href="$2">$1</a>`)
|
||||
|
||||
// wrap list items in <ul> or <ol>
|
||||
content = regexp.MustCompile(`(<li>.+?</li>)`).ReplaceAllString(content, "<ul>$1</ul>")
|
||||
|
||||
// convert newlines to paragraphs
|
||||
content = newlinePattern.ReplaceAllString(content, "<p>$1</p>")
|
||||
|
||||
return template.HTML(content)
|
||||
}
|
||||
|
||||
|
||||
func loadTemplate(name string) *template.Template {
|
||||
tmplPath := filepath.Join(TEMPLATES_DIR, name)
|
||||
tmpl, err := template.ParseFiles(tmplPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return tmpl
|
||||
}
|
||||
|
||||
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
basePath := strings.TrimPrefix(r.URL.Path, "/")
|
||||
files, folders := listEntries(r.URL.Path)
|
||||
page := Page{Files: files, Folders: folders, BasePath: basePath}
|
||||
tmpl := loadTemplate("index.html")
|
||||
tmpl.Execute(w, page)
|
||||
}
|
||||
|
||||
func postHandler(w http.ResponseWriter, r *http.Request) {
|
||||
filename := strings.TrimPrefix(r.URL.Path, "/")
|
||||
|
||||
parentPath := filepath.ToSlash(filepath.Dir(filename) + "/")
|
||||
if parentPath == "." {
|
||||
parentPath = ""
|
||||
}
|
||||
|
||||
info, err := os.Stat(filepath.Join(FILES_DIR, filename))
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
timeStamp := info.ModTime().Format(TIME_FORMAT)
|
||||
|
||||
content := readMarkdown(filename)
|
||||
page := Page{Title: filename, Content: renderMarkdown(content), ParentPath: parentPath, TimeStamp: timeStamp}
|
||||
tmpl := loadTemplate("post.html")
|
||||
tmpl.Execute(w, page)
|
||||
|
||||
println("handling request " + r.URL.Path + " with parentdir " + parentPath)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if _, err := os.Stat(FILES_DIR); os.IsNotExist(err) {
|
||||
os.Mkdir(FILES_DIR, os.ModePerm)
|
||||
}
|
||||
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
path := strings.TrimPrefix(r.URL.Path, "/")
|
||||
if strings.HasSuffix(path, ".md") {
|
||||
postHandler(w, r)
|
||||
} else if strings.Contains(path, ".") { // detect non-md files
|
||||
http.ServeFile(w, r, filepath.Join(FILES_DIR, path)) // serve raw file
|
||||
} else {
|
||||
indexHandler(w, r)
|
||||
}
|
||||
})
|
||||
|
||||
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "static/post.gif") // or just `w.WriteHeader(http.StatusNoContent)`
|
||||
})
|
||||
|
||||
|
||||
log.Println("Serving on :8080")
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
BIN
static/file.gif
Normal file
BIN
static/file.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 244 B |
BIN
static/folder.gif
Normal file
BIN
static/folder.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 327 B |
BIN
static/post.gif
Normal file
BIN
static/post.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 253 B |
BIN
static/scroll.gif
Normal file
BIN
static/scroll.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 B |
59
templates/index.html
Normal file
59
templates/index.html
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Codex.os31.xyz/{{ .BasePath }}</title>
|
||||
<link rel="icon" type="image/x-icon" href="/static/post.gif">
|
||||
<style>
|
||||
table {
|
||||
width: auto;
|
||||
border-collapse: collapse;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 25px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
a {
|
||||
padding-left: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Codex.os31.xyz/{{ .BasePath }}</h1>
|
||||
<hr>
|
||||
<table>
|
||||
{{if .BasePath}}
|
||||
<tr>
|
||||
<td colspan="2"><img src="/static/folder.gif" alt="Folder"><a href="../">../</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{range .Folders}}
|
||||
<tr>
|
||||
<td><img src="/static/folder.gif" alt="Folder"><a href="{{ . }}/">{{ . }}/</a>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{range .Files}}
|
||||
<tr>
|
||||
<td> <img src="{{ .Icon }}" alt="File"><a href="{{ .ParentPath }}{{ .Name }}">{{ .Name }}</a>
|
||||
</td>
|
||||
<td>{{ .Timestamp }}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
<hr>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
39
templates/post.html
Normal file
39
templates/post.html
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{ .Title }}</title>
|
||||
<link rel="icon" type="image/x-icon" href="/static/post.gif">
|
||||
<style>
|
||||
body {
|
||||
margin: 25px;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 100ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Codex.os31.xyz{{ .BasePath }}/{{ .Title }}</h1>
|
||||
<p>Last modified on {{ .TimeStamp }}</p>
|
||||
<hr>
|
||||
<div class="content-container">
|
||||
<div class="content">{{ .Content }}</div>
|
||||
</div>
|
||||
<hr>
|
||||
<img src="/static/folder.gif" alt="Back"> <a href="/{{ .ParentPath }}">Back</a>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Loading…
Reference in a new issue