Compare commits
125 Commits
Author | SHA1 | Date |
---|---|---|
Alexander Andreev | 70b45a628b | |
Alexander Andreev | 4c72141932 | |
Alexander Andreev | 12f53ba427 | |
Alexander Andreev | e1bc31eaa1 | |
Alexander Andreev | 9edacf1c8f | |
Alexander Andreev | 53097234e5 | |
Alexander Andreev | ffb9eaf356 | |
Alexander Andreev | eabe0cd1bd | |
Alexander Andreev | 244098253d | |
Alexander Andreev | 235de5f04c | |
Alexander Andreev | e37428d758 | |
Alexander Andreev | b32ad2613f | |
Alexander Andreev | 60efecc307 | |
Alexander Andreev | 0d854063d5 | |
Alexander Andreev | 9d1af110bd | |
Alexander Andreev | 4505cc03d4 | |
Alexander Andreev | 6871b68a13 | |
Alexander Andreev | 240540d75b | |
Alexander Andreev | ac5a4662f3 | |
Alexander Andreev | 6775b05d70 | |
Alexander Andreev | 6dc826b0ca | |
Alexander Andreev | b0b0e8a77a | |
Alexander Andreev | a2204947a0 | |
Alexander Andreev | 4634e76572 | |
Alexander Andreev | 6fd2a45569 | |
Alexander Andreev | 047f63d083 | |
Alexander Andreev | 8b46076669 | |
Alexander Andreev | d0a5bfc285 | |
Alexander Andreev | d4e07a2256 | |
Alexander Andreev | d4c414785a | |
Alexander Andreev | ac02190659 | |
Alexander Andreev | 34d469319d | |
Alexander Andreev | 86fdf05322 | |
Alexander Andreev | dbdf8ba62b | |
Alexander Andreev | 331781b711 | |
Alexander Andreev | 4ec84b5f1e | |
Alexander Andreev | 319471fab3 | |
Alexander Andreev | e5e07af896 | |
Alexander Andreev | a0c1272b3c | |
Alexander Andreev | 9962251279 | |
Alexander Andreev | 5539cd7357 | |
Alexander Andreev | e705aa80a5 | |
Alexander Andreev | b0b1730699 | |
Alexander Andreev | 000b5d1dce | |
Alexander Andreev | cbaa2f32b4 | |
Alexander Andreev | 24cf81a3ce | |
Alexander Andreev | 5bc4f13c38 | |
Alexander Andreev | 134ed23450 | |
Alexander Andreev | 33cd1dc3b8 | |
Alexander Andreev | a3525418f1 | |
Alexander Andreev | fb7459c021 | |
Alexander Andreev | 85a3a9692e | |
Alexander Andreev | 906f73c8a6 | |
Alexander Andreev | a464a11ea8 | |
Alexander Andreev | 03e7c478e0 | |
Alexander Andreev | 948715d8eb | |
Alexander Andreev | 9e33fa359b | |
Alexander Andreev | fe87114fe0 | |
Alexander Andreev | 48e7a86312 | |
Alexander Andreev | 72b09218f8 | |
Alexander Andreev | 7f17ebd2c5 | |
Alexander Andreev | 0695aa46b3 | |
Alexander Andreev | 1c3f0c7b2c | |
Alexander Andreev | 5b57592370 | |
Alexander Andreev | caf096c0f6 | |
Alexander Andreev | 20729899b0 | |
Alexander Andreev | 2b32422754 | |
Alexander Andreev | cb08e6e334 | |
Alexander Andreev | 6895c1c0a6 | |
Alexander Andreev | 98e948f982 | |
Alexander Andreev | a315679847 | |
Alexander Andreev | 90ce3c74f0 | |
Alexander Andreev | 38dadee5af | |
Alexander Andreev | f572f4b3e5 | |
Alexander Andreev | 1aaf2e8474 | |
Alexander Andreev | db34c5d249 | |
Alexander Andreev | ff70cd8ade | |
Alexander Andreev | fbb4389065 | |
Alexander Andreev | 69493f50aa | |
Alexander Andreev | 35ac352490 | |
Alexander Andreev | d373ad0f35 | |
Alexander Andreev | e992e6906a | |
Alexander Andreev | 9fd750a99b | |
Alexander Andreev | 3a3db1a673 | |
Alexander Andreev | 8caad6ca3e | |
Alexander Andreev | 7c17d7400b | |
Alexander Andreev | 24c5f59200 | |
Alexander Andreev | f93c732ada | |
Alexander Andreev | 27c336035b | |
Alexander Andreev | 798012b9cb | |
Alexander Andreev | 1628a26ed7 | |
Alexander Andreev | 35b48d29e6 | |
Alexander Andreev | 4523e5a622 | |
Alexander Andreev | ffbc027d64 | |
Alexander Andreev | 4a29a9754b | |
Alexander Andreev | 4fe528f141 | |
Alexander Andreev | c21d117120 | |
Alexander Andreev | 8a278c6052 | |
Alexander Andreev | 91d7d79109 | |
Alexander Andreev | b63d5e1ac0 | |
Alexander Andreev | 16ae8d4dca | |
Alexander Andreev | e1cf7187df | |
Alexander Andreev | a6bec0db75 | |
Alexander Andreev | 6fe6c856cd | |
Alexander Andreev | 87453e160d | |
Alexander Andreev | a5acbe4029 | |
Alexander Andreev | 78e5695f53 | |
Alexander Andreev | 7ee5939d39 | |
Alexander Andreev | 5802410fa4 | |
Alexander Andreev | 6567cef758 | |
Alexander Andreev | 7d7a1611ae | |
Alexander Andreev | 1cb73e088d | |
Alexander Andreev | 8a12af339c | |
Alexander Andreev | dc921ada8d | |
Alexander Andreev | c0ff9f4293 | |
Alexander Andreev | 7fc54658b0 | |
Alexander Andreev | d08231ac92 | |
Alexander Andreev | 2d3022aa27 | |
Alexander Andreev | a81375fc34 | |
Alexander Andreev | c3a6d57708 | |
Alexander Andreev | 053e22d131 | |
Alexander Andreev | ac1170b737 | |
Alexander Andreev | 7fa3918699 | |
Alexander Andreev | 3ea4900341 | |
Alexander Andreev | 023f2d1ac4 |
|
@ -0,0 +1,93 @@
|
|||
Copyright (c) 2012, Carrois Type Design, Ralph du Carrois (post@carrois.com www.carrois.com), with Reserved Font Name 'Share'
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
29
Makefile
29
Makefile
|
@ -2,33 +2,42 @@ TARGET=dwelling-home
|
|||
|
||||
SYSDDIR_=${shell pkg-config systemd --variable=systemdsystemunitdir}
|
||||
SYSDDIR=${SYSDDIR_:/%=%}
|
||||
DESTDIR=/
|
||||
DESTDIR:=
|
||||
PREFIX:=/usr/local
|
||||
|
||||
VERSION=23.22.0
|
||||
VERSION=24.16.1
|
||||
|
||||
FLAGS:=-buildmode=pie -modcacherw -mod=readonly -trimpath
|
||||
LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags osusergo,netgo
|
||||
|
||||
all: web/*.pug.go ${TARGET}
|
||||
.PHONY: run install uninstall clean
|
||||
|
||||
.PHONY: ${TARGET}
|
||||
|
||||
${TARGET}:
|
||||
go build -o bin/$@ ${LDFLAGS} cmd/$@/main.go
|
||||
${TARGET}: web/*.pug.go
|
||||
go build -o bin/$@ ${LDFLAGS} ${FLAGS} cmd/$@/main.go
|
||||
|
||||
web/*.pug.go: web/templates/*.pug
|
||||
ifeq (,$(wildcard $(shell go env GOPATH)/bin/jade))
|
||||
go install github.com/Joker/jade/cmd/jade@latest
|
||||
endif
|
||||
go generate web/web.go
|
||||
|
||||
run:
|
||||
bin/${TARGET} -l 127.0.0.1:18123 -db . -ce 10m -gbo Arav -gbp 10
|
||||
bin/${TARGET} -listen 127.0.0.1:18123 -database-path . -captcha-expiry 10m \
|
||||
-guestbook-page-size 10
|
||||
|
||||
install:
|
||||
install -Dm 0755 bin/${TARGET} ${DESTDIR}usr/bin/${TARGET}
|
||||
install -Dm 0755 bin/${TARGET} ${DESTDIR}${PREFIX}/bin/${TARGET}
|
||||
install -Dm 0644 init/systemd.service ${DESTDIR}${SYSDDIR}/${TARGET}.service
|
||||
# install -Dm 0644 LICENSE ${DESTDIR}/usr/share/licenses/${TARGET}/LICENSE
|
||||
# install -Dm 0644 LICENSE.RobotoCondensed.txt \
|
||||
# ${DESTDIR}/usr/share/licenses/${TARGET}/LICENSE.RobotoCondensed
|
||||
# install -Dm 0644 LICENSE.ShareTechMono.txt \
|
||||
# ${DESTDIR}/usr/share/licenses/${TARGET}/LICENSE.ShareTechMono
|
||||
|
||||
uninstall:
|
||||
rm ${DESTDIR}usr/bin/${TARGET}
|
||||
rm ${DESTDIR}${PREFIX}/bin/${TARGET}
|
||||
rm ${DESTDIR}${SYSDDIR}/${TARGET}.service
|
||||
# rm -r ${DESTDIR}/usr/share/licenses/${TARGET}
|
||||
|
||||
clean:
|
||||
rm -f bin/${TARGET}
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
|
||||
pkgname=dwelling-home
|
||||
pkgver=23.22.0
|
||||
pkgver=24.16.1
|
||||
pkgrel=1
|
||||
pkgdesc="Arav's dwelling / Home"
|
||||
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
|
||||
url="https://git.arav.su/Arav/dwelling-home"
|
||||
license=('MIT')
|
||||
depends=('sqlite')
|
||||
makedepends=('go')
|
||||
provides=('dwelling-home')
|
||||
conflicts=('dwelling-home')
|
||||
makedepends=('go>=1.17')
|
||||
source=("https://git.arav.su/Arav/dwelling-home/archive/v${pkgver}.tar.gz")
|
||||
md5sums=('SKIP')
|
||||
|
||||
build() {
|
||||
cd "$srcdir/$pkgname"
|
||||
export GOPATH="$srcdir"/gopath
|
||||
make VERSION=$pkgver DESTDIR="$pkgdir/"
|
||||
export CGO_CPPFLAGS="${CPPFLAGS}"
|
||||
export CGO_CFLAGS="${CFLAGS}"
|
||||
export CGO_CXXFLAGS="${CXXFLAGS}"
|
||||
export CGO_LDFLAGS="${LDFLAGS}"
|
||||
make VERSION=$pkgver DESTDIR="$pkgdir/" PREFIX="/usr"
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$srcdir/$pkgname"
|
||||
make DESTDIR="$pkgdir/" install
|
||||
make DESTDIR="$pkgdir/" PREFIX="/usr" install
|
||||
}
|
|
@ -5,11 +5,9 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
|
@ -22,12 +20,14 @@ import (
|
|||
|
||||
var version string
|
||||
|
||||
var showVersion *bool = flag.Bool("v", false, "show version")
|
||||
var listenAddress *string = flag.String("l", "/var/run/dwelling-home/sock", "listen address (ip:port|unix_path)")
|
||||
var captchaExpiry *time.Duration = flag.Duration("ce", 10*time.Minute, "CAPTCHA expiry (e.g. 5m, 60s)")
|
||||
var guestbookOwner *string = flag.String("gbo", "Admin", "name of a guestbook owner")
|
||||
var guestbookPageSize *int64 = flag.Int64("gbp", 60, "size of a guestbook page")
|
||||
var databasesPath *string = flag.String("db", "/var/lib/dwelling-home", "path to a directory where to store DB files")
|
||||
var (
|
||||
showVersion = flag.Bool("v", false, "show version")
|
||||
listenAddress = flag.String("listen", "/var/run/dwelling-home/sock", "listen address (ip:port|unix_path)")
|
||||
captchaExpiry = flag.Duration("captcha-expiry", 10*time.Minute, "CAPTCHA expiry (e.g. 5m, 60s)")
|
||||
guestbookOwner = "Arav"
|
||||
guestbookPageSize = flag.Int64("guestbook-page-size", 60, "size of a guestbook page")
|
||||
databasesPath = flag.String("database-path", "/var/lib/dwelling-home", "path to a directory where to store DB files")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
@ -39,25 +39,6 @@ func main() {
|
|||
|
||||
log.SetFlags(log.Llongfile)
|
||||
|
||||
var network string
|
||||
if !strings.ContainsRune(*listenAddress, ':') {
|
||||
network = "unix"
|
||||
defer os.Remove(*listenAddress)
|
||||
} else {
|
||||
ap, err := netip.ParseAddrPort(*listenAddress)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
} else if !ap.IsValid() {
|
||||
log.Fatalln(*listenAddress, "is not valid")
|
||||
}
|
||||
|
||||
if ap.Addr().Is4() {
|
||||
network = "tcp4"
|
||||
} else if ap.Addr().Is6() {
|
||||
network = "tcp6"
|
||||
}
|
||||
}
|
||||
|
||||
guestbookDB, err := gb.NewSQLiteDB(path.Join(*databasesPath, "guestbook.sqlite"))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
|
@ -70,12 +51,12 @@ func main() {
|
|||
}
|
||||
defer mindflowDB.Close()
|
||||
|
||||
hand := dwhttp.NewHandlers(*captchaExpiry, *guestbookOwner, *guestbookPageSize, guestbookDB, mindflowDB)
|
||||
hand := dwhttp.NewHandlers(*captchaExpiry, guestbookOwner, *guestbookPageSize, guestbookDB, mindflowDB)
|
||||
|
||||
r := httpr.New()
|
||||
|
||||
r.NotFoundHandler = func(w http.ResponseWriter, _ *http.Request) {
|
||||
web.ErrorXXX("/ Not Found", "", "", http.StatusNotFound, w)
|
||||
r.NotFoundHandler = func(w http.ResponseWriter, r *http.Request) {
|
||||
dwhttp.Error(w, http.StatusNotFound, "", "", r.Referer())
|
||||
}
|
||||
|
||||
r.ServeStatic("/assets/*filepath", web.Assets())
|
||||
|
@ -85,6 +66,7 @@ func main() {
|
|||
r.Handler(http.MethodGet, "/sitemap.xml", dwhttp.SitemapXml)
|
||||
|
||||
r.Handler(http.MethodGet, "/", hand.Index)
|
||||
r.Handler(http.MethodGet, "/privacy", hand.Privacy)
|
||||
r.Handler(http.MethodGet, "/stuff", hand.Stuff)
|
||||
r.Handler(http.MethodGet, "/stuff/article/*filepath", hand.Article)
|
||||
r.Handler(http.MethodGet, "/mindflow", hand.Mindflow)
|
||||
|
@ -95,32 +77,34 @@ func main() {
|
|||
|
||||
captchaApi := dwhttp.NewCaptchaApiHandlers(*captchaExpiry)
|
||||
|
||||
r.Handler(http.MethodPost, "/api/captcha", captchaApi.New)
|
||||
r.Handler(http.MethodPost, "/api/captcha/:id", captchaApi.Solve)
|
||||
r.Handler(http.MethodGet, "/api/captcha/:id/image", captchaApi.Image)
|
||||
s := r.Sub("/api/captcha")
|
||||
s.Handler(http.MethodPost, "/", captchaApi.New)
|
||||
s.Handler(http.MethodPost, "/:id", captchaApi.Solve)
|
||||
s.Handler(http.MethodGet, "/:id/image", captchaApi.Image)
|
||||
|
||||
guestbookApi := dwhttp.NewGuestbookApiHandlers(*guestbookOwner, *guestbookPageSize, guestbookDB)
|
||||
guestbookApi := dwhttp.NewGuestbookApiHandlers(guestbookOwner, *guestbookPageSize, guestbookDB)
|
||||
|
||||
r.Handler(http.MethodPost, "/api/guestbook", guestbookApi.New)
|
||||
r.Handler(http.MethodPatch, "/api/guestbook/:id", guestbookApi.Edit)
|
||||
r.Handler(http.MethodDelete, "/api/guestbook/:id", guestbookApi.Delete)
|
||||
r.Handler(http.MethodPost, "/api/guestbook/:id/reply", guestbookApi.Reply)
|
||||
r.Handler(http.MethodPatch, "/api/guestbook/:id/reply", guestbookApi.EditReply)
|
||||
r.Handler(http.MethodDelete, "/api/guestbook/:id/reply", guestbookApi.DeleteReply)
|
||||
s = r.Sub("/api/guestbook")
|
||||
s.Handler(http.MethodPost, "/", guestbookApi.New)
|
||||
s.Handler(http.MethodPatch, "/:id", guestbookApi.Edit)
|
||||
s.Handler(http.MethodDelete, "/:id", guestbookApi.Delete)
|
||||
s.Handler(http.MethodPost, "/:id/reply", guestbookApi.Reply)
|
||||
s.Handler(http.MethodPatch, "/:id/reply", guestbookApi.EditReply)
|
||||
s.Handler(http.MethodDelete, "/:id/reply", guestbookApi.DeleteReply)
|
||||
|
||||
mindflowApi := dwhttp.NewMindflowApiHandlers(mindflowDB)
|
||||
|
||||
r.Handler(http.MethodPost, "/api/mindflow", mindflowApi.NewPost)
|
||||
r.Handler(http.MethodPatch, "/api/mindflow/:id", mindflowApi.EditPost)
|
||||
r.Handler(http.MethodDelete, "/api/mindflow/:id", mindflowApi.DeletePost)
|
||||
|
||||
r.Handler(http.MethodPost, "/api/mindflow/category", mindflowApi.NewCategory)
|
||||
r.Handler(http.MethodPatch, "/api/mindflow/category/:id", mindflowApi.EditCategory)
|
||||
r.Handler(http.MethodDelete, "/api/mindflow/category/:id", mindflowApi.DeleteCategory)
|
||||
s = r.Sub("/api/mindflow")
|
||||
s.Handler(http.MethodPost, "/", mindflowApi.NewPost)
|
||||
s.Handler(http.MethodPatch, "/:id", mindflowApi.EditPost)
|
||||
s.Handler(http.MethodDelete, "/:id", mindflowApi.DeletePost)
|
||||
s.Handler(http.MethodPost, "/category", mindflowApi.NewCategory)
|
||||
s.Handler(http.MethodPatch, "/category/:id", mindflowApi.EditCategory)
|
||||
s.Handler(http.MethodDelete, "/category/:id", mindflowApi.DeleteCategory)
|
||||
|
||||
srv := dwhttp.NewHttpServer(r)
|
||||
|
||||
if err := srv.Start(network, *listenAddress); err != nil {
|
||||
if err := srv.Start(*listenAddress); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer func() {
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
server {
|
||||
listen 443 http2;
|
||||
listen 127.0.0.1:8110; # I2P
|
||||
# listen 8090; # Tor
|
||||
listen 8090; # Tor I2P
|
||||
listen [300:a98d:d6d0:8a08::f]:80; # Yggdrasil
|
||||
|
||||
server_name arav.su www.arav.su arav.i2p moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion;
|
||||
server_name arav.su www.arav.su arav.i2p t42fkp6zp5dfqywantq3zp427ig3q2onrmfv246tyaztpg4ckb5a.b32.i2p moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion;
|
||||
|
||||
access_log /var/log/nginx/dwelling/dwelling.log main if=$nolog;
|
||||
|
||||
|
@ -17,7 +16,7 @@ server {
|
|||
add_header X-Content-Type-Options "nosniff";
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
|
||||
# add_header Onion-Location "http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion$request_uri";
|
||||
add_header Onion-Location "http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion$request_uri";
|
||||
|
||||
|
||||
location / {
|
||||
|
|
10
go.mod
10
go.mod
|
@ -3,19 +3,19 @@ module git.arav.su/Arav/dwelling-home
|
|||
go 1.17
|
||||
|
||||
require (
|
||||
git.arav.su/Arav/justcaptcha v0.0.0-20230506005225-406fb66c17ca
|
||||
git.arav.su/Arav/justcaptcha/v2 v2.1.0
|
||||
git.arav.su/Arav/justguestbook v1.3.2
|
||||
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a
|
||||
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386
|
||||
github.com/pkg/errors v0.9.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fogleman/gg v1.3.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
golang.org/x/image v0.7.0 // indirect
|
||||
golang.org/x/image v0.14.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
git.arav.su/Arav/httpr v0.2.0
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
git.arav.su/Arav/httpr v0.3.2
|
||||
github.com/mattn/go-sqlite3 v1.14.18 // indirect
|
||||
)
|
||||
|
|
27
go.sum
27
go.sum
|
@ -1,26 +1,26 @@
|
|||
git.arav.su/Arav/httpr v0.2.0 h1:rtwUVl4ZDfvMf9DeLktxvups5GDY0ARVcUuUHR7Eb9E=
|
||||
git.arav.su/Arav/httpr v0.2.0/go.mod h1:z0SVYwe5dBReeVuFU9QH2PmBxICJwchxqY5OfZbeVzU=
|
||||
git.arav.su/Arav/justcaptcha v0.0.0-20230506005225-406fb66c17ca h1:keam4p+hYktoRNNparpacUS5LMjzFs4ERwHuV/IXqAw=
|
||||
git.arav.su/Arav/justcaptcha v0.0.0-20230506005225-406fb66c17ca/go.mod h1:eXjBHk26MLvfRZrf3MY29HCIN/YlICklXby0JVkG4oA=
|
||||
git.arav.su/Arav/httpr v0.3.2 h1:a+ifu+9+FnQe6p/Kd4kgTDKAFN6zBOJjBTMjbAuHxVk=
|
||||
git.arav.su/Arav/httpr v0.3.2/go.mod h1:z0SVYwe5dBReeVuFU9QH2PmBxICJwchxqY5OfZbeVzU=
|
||||
git.arav.su/Arav/justcaptcha/v2 v2.1.0 h1:EFerW2mP60rDczu1mPbJb7DiDeAQOcw83/KiWcxCOKs=
|
||||
git.arav.su/Arav/justcaptcha/v2 v2.1.0/go.mod h1:Ntab6TUAqCo/H9LSWOd4IQ9ANnp8G1PEygXtORcdTOY=
|
||||
git.arav.su/Arav/justguestbook v1.3.2 h1:B1AzS14dW3bVlp0Qj6pu1PrJlWaPK1mplmx3UNBcxo4=
|
||||
git.arav.su/Arav/justguestbook v1.3.2/go.mod h1:Umo/AzSOKu+OU+03V7/QQ6m6N0nXyeRAGOZT4P5ol98=
|
||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a h1:AWZzzFrqyjYlRloN6edwTLTUbKxf5flLXNuTBDm3Ews=
|
||||
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4=
|
||||
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
|
||||
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
|
||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -43,7 +43,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
|
|
@ -6,7 +6,9 @@ After=network-online.target
|
|||
Type=simple
|
||||
Restart=on-failure
|
||||
DynamicUser=yes
|
||||
ExecStart=/usr/bin/dwelling-home -l /var/run/dwelling-home/sock -db /var/lib/dwelling-home -ce 10m -gbo Arav -gbp 60
|
||||
ExecStart=/usr/bin/dwelling-home -listen /var/run/dwelling-home/sock \
|
||||
-database-path /var/lib/dwelling-home -captcha-expiry 10m \
|
||||
-guestbook-page-size 60
|
||||
|
||||
ReadOnlyPaths=/
|
||||
|
||||
|
@ -20,18 +22,33 @@ LockPersonality=true
|
|||
MemoryDenyWriteExecute=true
|
||||
NoNewPrivileges=true
|
||||
PrivateDevices=true
|
||||
PrivateTmp=true
|
||||
PrivateUsers=true
|
||||
ProcSubset=pid
|
||||
ProtectClock=true
|
||||
ProtectControlGroups=true
|
||||
ProtectHome=true
|
||||
ProtectHostname=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectProc=noaccess
|
||||
ProtectSystem=strict
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
RestrictNamespaces=true
|
||||
RestrictRealtime=true
|
||||
RestrictSUIDSGID=true
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=~@clock
|
||||
SystemCallFilter=~@cpu-emulation
|
||||
SystemCallFilter=~@debug
|
||||
SystemCallFilter=~@module
|
||||
SystemCallFilter=~@mount
|
||||
SystemCallFilter=~@obsolete
|
||||
SystemCallFilter=~@privileged
|
||||
SystemCallFilter=~@raw-io
|
||||
SystemCallFilter=~@reboot
|
||||
SystemCallFilter=~@swap
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -7,9 +7,9 @@ import (
|
|||
"time"
|
||||
|
||||
"git.arav.su/Arav/httpr"
|
||||
"git.arav.su/Arav/justcaptcha/pkg/captcha"
|
||||
"git.arav.su/Arav/justcaptcha/pkg/captcha/inmemdb"
|
||||
"git.arav.su/Arav/justcaptcha/pkg/dwcaptcha"
|
||||
captcha "git.arav.su/Arav/justcaptcha/v2"
|
||||
"git.arav.su/Arav/justcaptcha/v2/dwcaptcha"
|
||||
"git.arav.su/Arav/justcaptcha/v2/inmemdb"
|
||||
)
|
||||
|
||||
type CaptchaApiHandlers struct {
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"strings"
|
||||
|
||||
"git.arav.su/Arav/httpr"
|
||||
"git.arav.su/Arav/justcaptcha/pkg/captcha"
|
||||
"git.arav.su/Arav/justcaptcha/pkg/captcha/inmemdb"
|
||||
captcha "git.arav.su/Arav/justcaptcha/v2"
|
||||
"git.arav.su/Arav/justcaptcha/v2/inmemdb"
|
||||
"git.arav.su/Arav/justguestbook"
|
||||
)
|
||||
|
||||
|
@ -29,7 +29,8 @@ func (h *GuestbookApiHandlers) New(w http.ResponseWriter, r *http.Request) {
|
|||
r.ParseForm()
|
||||
|
||||
if !inmemdb.Solve(captcha.ID(r.FormValue("captcha_id")), captcha.Answer(r.FormValue("captcha_answer"))) {
|
||||
Error(w, http.StatusForbidden, "Wrong answer given.", "Here's your message:"+r.FormValue("message"))
|
||||
Error(w, http.StatusForbidden, "Wrong answer given.",
|
||||
"Here's your message:"+r.FormValue("message"), r.Referer())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -42,13 +43,15 @@ func (h *GuestbookApiHandlers) New(w http.ResponseWriter, r *http.Request) {
|
|||
entry, err = justguestbook.NewEntry(r.FormValue("name"), message,
|
||||
r.FormValue("website"), r.FormValue("hide_website") != "")
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, err.Error(), "Here's your message:"+r.FormValue("message"))
|
||||
Error(w, http.StatusInternalServerError, err.Error(),
|
||||
"Here's your message:"+r.FormValue("message"), r.Referer())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = h.db.NewEntry(entry); err != nil {
|
||||
Error(w, http.StatusInternalServerError, err.Error(), "Here's your message:"+r.FormValue("message"))
|
||||
Error(w, http.StatusInternalServerError, err.Error(),
|
||||
"Here's your message:"+r.FormValue("message"), r.Referer())
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ func (h *MindflowApiHandlers) NewPost(w http.ResponseWriter, r *http.Request) {
|
|||
category.ID, _ = strconv.ParseInt(r.FormValue("category"), 10, 64)
|
||||
}
|
||||
|
||||
post, err = mindflow.NewPost(*category, r.FormValue("title"), r.FormValue("body"))
|
||||
post, err = mindflow.NewPost(*category, r.FormValue("title"), r.FormValue("url"), r.FormValue("body"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -51,7 +51,7 @@ func (h *MindflowApiHandlers) NewPost(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
if err = h.db.NewPost(post); err != nil {
|
||||
msg := strings.Join([]string{"Title:", r.FormValue("title"), "| Body:", r.FormValue("body")}, " ")
|
||||
Error(w, http.StatusInternalServerError, err.Error(), msg)
|
||||
Error(w, http.StatusInternalServerError, err.Error(), msg, r.Referer())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -60,36 +60,22 @@ func (h *MindflowApiHandlers) NewPost(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func (h *MindflowApiHandlers) EditPost(w http.ResponseWriter, r *http.Request) {
|
||||
var post *mindflow.Post
|
||||
var category *mindflow.Category
|
||||
var category mindflow.Category
|
||||
var err error
|
||||
|
||||
if strings.Contains(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") {
|
||||
r.ParseForm()
|
||||
|
||||
body := cleanupNewlines(r.FormValue("body"))
|
||||
url := r.FormValue("url")
|
||||
|
||||
if r.FormValue("category") != "" {
|
||||
category, err = mindflow.NewCategory(r.FormValue("new-category"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
category.ID, _ = strconv.ParseInt(r.FormValue("category"), 10, 64)
|
||||
|
||||
if category.ID == 0 {
|
||||
category.ID, err = h.db.NewCategory(category)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
category = &mindflow.Category{}
|
||||
category.ID, _ = strconv.ParseInt(r.FormValue("old-category"), 10, 64)
|
||||
category.ID, err = strconv.ParseInt(r.FormValue("category"), 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
post, err = mindflow.NewPost(*category, r.FormValue("title"), body)
|
||||
post, err = mindflow.NewPost(category, r.FormValue("title"), url, body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -5,12 +5,15 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HttpServer struct {
|
||||
s http.Server
|
||||
s http.Server
|
||||
addr net.Addr
|
||||
}
|
||||
|
||||
func NewHttpServer(r http.Handler) *HttpServer {
|
||||
|
@ -20,7 +23,23 @@ func NewHttpServer(r http.Handler) *HttpServer {
|
|||
Handler: r}}
|
||||
}
|
||||
|
||||
func (s *HttpServer) Start(network, address string) error {
|
||||
func (s *HttpServer) Start(address string) error {
|
||||
var network string
|
||||
if !strings.ContainsRune(address, ':') {
|
||||
network = "unix"
|
||||
} else {
|
||||
ap, err := netip.ParseAddrPort(address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ap.Addr().Is4() {
|
||||
network = "tcp4"
|
||||
} else if ap.Addr().Is6() {
|
||||
network = "tcp6"
|
||||
}
|
||||
}
|
||||
|
||||
listener, err := net.Listen(network, address)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -30,6 +49,8 @@ func (s *HttpServer) Start(network, address string) error {
|
|||
os.Chmod(address, 0777)
|
||||
}
|
||||
|
||||
s.addr = listener.Addr()
|
||||
|
||||
go func() {
|
||||
if err = s.s.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalln(err)
|
||||
|
@ -43,6 +64,10 @@ func (s *HttpServer) Stop() error {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if s.addr.Network() == "unix" {
|
||||
defer os.Remove(s.addr.String())
|
||||
}
|
||||
|
||||
if err := s.s.Shutdown(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
"git.arav.su/Arav/dwelling-home/pkg/servicestat"
|
||||
"git.arav.su/Arav/dwelling-home/pkg/util"
|
||||
"git.arav.su/Arav/dwelling-home/web"
|
||||
"git.arav.su/Arav/justcaptcha/pkg/captcha/inmemdb"
|
||||
"git.arav.su/Arav/justcaptcha/pkg/dwcaptcha"
|
||||
"git.arav.su/Arav/justcaptcha/v2/dwcaptcha"
|
||||
"git.arav.su/Arav/justcaptcha/v2/inmemdb"
|
||||
"git.arav.su/Arav/justguestbook"
|
||||
)
|
||||
|
||||
|
@ -39,9 +39,17 @@ func (h *Handlers) Index(w http.ResponseWriter, r *http.Request) {
|
|||
web.Index("", w)
|
||||
}
|
||||
|
||||
func (h *Handlers) Privacy(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
web.Privacy("/ Privacy statements",
|
||||
util.GetServiceByHost(r.Host, "radio"),
|
||||
util.GetServiceByHost(r.Host, "files"),
|
||||
util.GetServiceByHost(r.Host, "upload"), w)
|
||||
}
|
||||
|
||||
func (h *Handlers) Stuff(w http.ResponseWriter, r *http.Request) {
|
||||
web.Stuff("/ Stuff", util.GetServiceByHost(r.Host, util.ServiceGit),
|
||||
util.GetServiceByHost(r.Host, util.ServiceFiles), web.GetArticlesMetadata(), w)
|
||||
util.GetServiceByHost(r.Host, util.ServiceFiles), web.Metadata, w)
|
||||
}
|
||||
|
||||
func (h *Handlers) Mindflow(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -62,13 +70,13 @@ func (h *Handlers) Mindflow(w http.ResponseWriter, r *http.Request) {
|
|||
func (h *Handlers) MindflowAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
posts, err := h.mindflowDB.Posts()
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, err.Error(), "failed to load posts")
|
||||
Error(w, http.StatusInternalServerError, err.Error(), "failed to load posts", r.Referer())
|
||||
return
|
||||
}
|
||||
|
||||
categories, err := h.mindflowDB.Categories()
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, err.Error(), "failed to load categories")
|
||||
Error(w, http.StatusInternalServerError, err.Error(), "failed to load categories", r.Referer())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -95,9 +103,9 @@ func (h *Handlers) About(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func (h *Handlers) Article(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
|
||||
artcl, err := web.GetArticle(name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
artcl := web.GetArticle(name)
|
||||
if artcl == nil {
|
||||
http.Error(w, "an article doesn't exist", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -145,7 +153,7 @@ func (h *Handlers) GuestbookAdmin(w http.ResponseWriter, r *http.Request) {
|
|||
entriesCount, _ := h.guestbookDB.Count()
|
||||
entries, err := h.guestbookDB.Entries(1, entriesCount)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, err.Error(), "cannot load gb")
|
||||
Error(w, http.StatusInternalServerError, err.Error(), "cannot load gb", r.Referer())
|
||||
return
|
||||
}
|
||||
for _, entry := range entries {
|
||||
|
@ -160,8 +168,14 @@ func (h *Handlers) RSS(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
var scheme string
|
||||
if r.Header.Get("Scheme") != "" {
|
||||
scheme = r.Header.Get("Scheme")
|
||||
} else {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
web.RSS(r.URL.Scheme+"://"+r.Host, "Arav", posts, r, w)
|
||||
web.RSS(scheme+"://"+r.Host, "Arav", posts, r, w)
|
||||
}
|
||||
|
||||
func FaviconIco(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -179,8 +193,9 @@ func SitemapXml(w http.ResponseWriter, r *http.Request) {
|
|||
w.Write(data)
|
||||
}
|
||||
|
||||
func Error(w http.ResponseWriter, code int, reason, message string) {
|
||||
web.ErrorXXX("/ "+http.StatusText(code), reason, message, code, w)
|
||||
func Error(w http.ResponseWriter, code int, reason, message, referer string) {
|
||||
w.WriteHeader(code)
|
||||
web.ErrorXXX("/ "+http.StatusText(code), reason, message, referer, code, w)
|
||||
}
|
||||
|
||||
func cleanupNewlines(text string) (out string) {
|
||||
|
|
|
@ -9,12 +9,18 @@ import (
|
|||
|
||||
type Article struct {
|
||||
ID int64
|
||||
Title string
|
||||
Date time.Time
|
||||
Title string
|
||||
Description string
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func New(body, meta []byte) (article *Article, err error) {
|
||||
article = &Article{Body: body}
|
||||
err = article.ParseMetadata(meta)
|
||||
return
|
||||
}
|
||||
|
||||
func (artcl *Article) ParseMetadata(data []byte) error {
|
||||
lines := bytes.Split(data, []byte{'\n'})
|
||||
if len(lines) != 4 {
|
||||
|
|
|
@ -122,7 +122,8 @@ func (s *SQLiteMindflow) NewPost(post *mindflow.Post) error {
|
|||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
r, err := tx.Stmt(stmtPostNew).Exec(post.Category.ID, post.Date.UTC().Unix(), post.Title, post.Body)
|
||||
r, err := tx.Stmt(stmtPostNew).Exec(post.Category.ID, post.Date.UTC().Unix(),
|
||||
post.Title, post.URL, post.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -144,7 +145,8 @@ func (s *SQLiteMindflow) EditPost(post *mindflow.Post) error {
|
|||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
_, err = tx.Stmt(stmtPostEdit).Exec(post.Category.ID, post.Title, post.Body, post.ID)
|
||||
_, err = tx.Stmt(stmtPostEdit).Exec(post.Category.ID, post.Title, post.URL,
|
||||
post.Body, post.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -187,7 +189,8 @@ func (s *SQLiteMindflow) Posts() (posts []mindflow.Post, err error) {
|
|||
var post mindflow.Post
|
||||
var date_unix int64
|
||||
|
||||
if err = rows.Scan(&post.ID, &post.Category.ID, &post.Category.Name, &date_unix, &post.Title, &post.Body); err != nil {
|
||||
if err = rows.Scan(&post.ID, &post.Category.ID, &post.Category.Name,
|
||||
&date_unix, &post.Title, &post.URL, &post.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -2,5 +2,6 @@ UPDATE OR REPLACE `post`
|
|||
SET
|
||||
`category_id` = ?,
|
||||
`title` = ?,
|
||||
`url` = ?,
|
||||
`body` = ?
|
||||
WHERE `post_id` = ?;
|
|
@ -4,6 +4,7 @@ SELECT
|
|||
`category`.`name` AS `category`,
|
||||
`post`.`date`,
|
||||
`post`.`title`,
|
||||
`post`.`url`,
|
||||
`post`.`body`
|
||||
FROM `post`
|
||||
LEFT JOIN `category`
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
INSERT INTO `post`
|
||||
(`category_id`, `date`, `title`, `body`)
|
||||
(`category_id`, `date`, `title`, `url`, `body`)
|
||||
VALUES
|
||||
(?, ?, ?, ?);
|
||||
(?, ?, ?, ?, ?);
|
|
@ -15,6 +15,7 @@ CREATE TABLE IF NOT EXISTS `post` (
|
|||
`category_id` INTEGER NOT NULL,
|
||||
`date` INTEGER NOT NULL,
|
||||
`title` TEXT NOT NULL,
|
||||
`url` TEXT NOT NULL DEFAULT "",
|
||||
`body` TEXT NOT NULL,
|
||||
PRIMARY KEY (`post_id`),
|
||||
FOREIGN KEY (`category_id`)
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.arav.su/Arav/dwelling-home/pkg/util"
|
||||
)
|
||||
|
||||
type Category struct {
|
||||
|
@ -24,10 +26,11 @@ type Post struct {
|
|||
Category Category
|
||||
Date time.Time
|
||||
Title string
|
||||
URL string
|
||||
Body string
|
||||
}
|
||||
|
||||
func NewPost(category Category, title, body string) (*Post, error) {
|
||||
func NewPost(category Category, title, url, body string) (*Post, error) {
|
||||
if title == "" || body == "" {
|
||||
return nil, errors.New("empty title or body is not allowed")
|
||||
}
|
||||
|
@ -35,6 +38,7 @@ func NewPost(category Category, title, body string) (*Post, error) {
|
|||
Category: category,
|
||||
Date: time.Now().UTC(),
|
||||
Title: title,
|
||||
URL: url,
|
||||
Body: body}, nil
|
||||
}
|
||||
|
||||
|
@ -44,6 +48,22 @@ func (p *Post) PostID() string {
|
|||
return fmt.Sprint(name, "-", p.Date.UTC().Format("20060102-150405"))
|
||||
}
|
||||
|
||||
func (p *Post) PostURL(host string, rss bool) string {
|
||||
if p.URL != "" {
|
||||
if p.URL[0] == ':' {
|
||||
lastColon := strings.IndexByte(p.URL[1:], ':')
|
||||
service := p.URL[1 : lastColon+1]
|
||||
return strings.Replace(p.URL, p.URL[:lastColon+2],
|
||||
util.GetServiceByHost(host, service), 1)
|
||||
} else if rss && p.URL[0] == '/' {
|
||||
return util.GetServiceByHost(host, "") + p.URL
|
||||
}
|
||||
} else if p.URL == "" && rss {
|
||||
return util.GetServiceByHost(host, "") + "/mindflow#" + p.PostID()
|
||||
}
|
||||
return p.URL
|
||||
}
|
||||
|
||||
type Mindflow interface {
|
||||
NewPost(post *Post) error
|
||||
EditPost(post *Post) error
|
||||
|
|
|
@ -3,6 +3,7 @@ package servicestat
|
|||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ServiceList map[string]bool
|
||||
|
@ -11,7 +12,7 @@ func GatherStatus(url string) (lst ServiceList, err error) {
|
|||
rq, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
rq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
cl := &http.Client{}
|
||||
cl := &http.Client{Timeout: 1 * time.Second}
|
||||
r, err := cl.Do(rq)
|
||||
if err != nil {
|
||||
return lst, err
|
||||
|
|
|
@ -43,6 +43,19 @@ func GetServiceByHost(host, service string) string {
|
|||
default:
|
||||
return "http://[300:a98d:d6d0:8a08::f]"
|
||||
}
|
||||
} else if strings.HasSuffix(host, "onion") {
|
||||
switch service {
|
||||
case "radio":
|
||||
return "http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion"
|
||||
case "files":
|
||||
return "http://qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion"
|
||||
case "upload":
|
||||
return "http://4usftbmjpfexkr2x5xbp5ukmygpmg4fgrnx2wbifsexqctooz5hmviyd.onion"
|
||||
case "git":
|
||||
return "http://qqitm7qlsbbubwmjos4cqzmvkqidg34rfnbyhuydhalep33fbvh22xyd.onion"
|
||||
default:
|
||||
return "http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion"
|
||||
}
|
||||
} else {
|
||||
switch service {
|
||||
case "radio":
|
||||
|
|
|
@ -2,6 +2,7 @@ package web
|
|||
|
||||
import (
|
||||
"embed"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -17,7 +18,7 @@ const (
|
|||
|
||||
//go:embed articles
|
||||
var articlesFS embed.FS
|
||||
var articles map[string]article.Article = make(map[string]article.Article)
|
||||
var articles map[string]*article.Article = make(map[string]*article.Article)
|
||||
|
||||
// ArticleMetadata holds date, title and URL of an article used in articles list
|
||||
// on the Stuff page.
|
||||
|
@ -28,11 +29,46 @@ type ArticleMetadata struct {
|
|||
URL string
|
||||
}
|
||||
|
||||
// GetArticlesMetadata returns a slice of metadata that is sorted by date
|
||||
func GetArticlesMetadata() (meta []ArticleMetadata) {
|
||||
for urlid, article := range articles {
|
||||
var Metadata []ArticleMetadata
|
||||
|
||||
meta = append(meta,
|
||||
func GetArticle(name string) *article.Article {
|
||||
if artcl, ok := articles[name]; ok {
|
||||
return artcl
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
//// Load articles
|
||||
|
||||
entries, _ := articlesFS.ReadDir("articles")
|
||||
|
||||
for _, entry := range entries {
|
||||
if strings.HasSuffix(entry.Name(), articleFileExtension) {
|
||||
name := strings.TrimSuffix(entry.Name(), articleFileExtension)
|
||||
|
||||
meta, err := articlesFS.ReadFile("articles/" + name + articleMetaExtension)
|
||||
if err != nil {
|
||||
log.Fatalln("an article \"", name, "\" cannot be read:", err)
|
||||
}
|
||||
|
||||
md, err := articlesFS.ReadFile("articles/" + entry.Name())
|
||||
if err != nil {
|
||||
log.Fatalln("a metadata for an article \"", name, "\" cannot be read:", err)
|
||||
}
|
||||
|
||||
articles[name], err = article.New(markdown.ToHTML(md, nil, nil), meta)
|
||||
if err != nil {
|
||||
log.Fatalln("an article \"", name, "\" cannot be parsed:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//// Fill and sort metadata slice
|
||||
|
||||
for urlid, article := range articles {
|
||||
Metadata = append(Metadata,
|
||||
ArticleMetadata{
|
||||
ID: article.ID,
|
||||
Date: article.Date,
|
||||
|
@ -40,44 +76,9 @@ func GetArticlesMetadata() (meta []ArticleMetadata) {
|
|||
URL: "stuff/article/" + urlid})
|
||||
}
|
||||
|
||||
sort.Slice(meta, func(i, j int) bool {
|
||||
return meta[i].ID > meta[j].ID
|
||||
sort.Slice(Metadata, func(i, j int) bool {
|
||||
return Metadata[i].ID > Metadata[j].ID
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GetArticle(name string) (*article.Article, error) {
|
||||
if artcl, ok := articles[name]; ok {
|
||||
return &artcl, nil
|
||||
} else {
|
||||
meta, err := articlesFS.ReadFile("articles/" + name + articleMetaExtension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
md, err := articlesFS.ReadFile("articles/" + name + articleFileExtension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
artcl := article.Article{
|
||||
Body: markdown.ToHTML(md, nil, nil)}
|
||||
|
||||
artcl.ParseMetadata(meta)
|
||||
|
||||
articles[name] = artcl
|
||||
|
||||
return &artcl, nil
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
entries, _ := articlesFS.ReadDir("articles")
|
||||
|
||||
for _, entry := range entries {
|
||||
if strings.HasSuffix(entry.Name(), articleFileExtension) {
|
||||
GetArticle(strings.TrimSuffix(entry.Name(), articleFileExtension))
|
||||
}
|
||||
}
|
||||
articlesFS = embed.FS{}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#error {
|
||||
font-size: 3.5rem;
|
||||
line-height: 5rem;
|
||||
text-align: center;
|
||||
margin: 6rem 0; }
|
||||
|
||||
#error h1 { font-size: 8rem; }
|
|
@ -1,6 +1,6 @@
|
|||
body {
|
||||
height: 75vh;
|
||||
margin-top: 25vh; }
|
||||
margin-top: 25vh;
|
||||
min-height: 75vh; }
|
||||
|
||||
header { position: relative; }
|
||||
|
||||
|
|
|
@ -85,6 +85,8 @@ tr :is(th, td) {
|
|||
|
||||
tr :is(th, td):not(:last-child) { padding-right: .5rem; }
|
||||
|
||||
tr a:not(:last-child) { margin-right: .25rem; }
|
||||
|
||||
.highlighted { color: var(--primary-color); }
|
||||
|
||||
html {
|
||||
|
@ -98,6 +100,7 @@ body {
|
|||
font-size: 1.1rem;
|
||||
margin: 0 auto;
|
||||
max-width: 960px;
|
||||
min-height: 100vh;
|
||||
width: 98%; }
|
||||
|
||||
@media screen and (min-width: 1280px) {
|
||||
|
|
|
@ -4,6 +4,9 @@ button:not(:last-child) { padding-right: 1rem; }
|
|||
|
||||
article header a { color: var(--text-color); }
|
||||
|
||||
article p.quote { font-style: italic; }
|
||||
|
||||
article footer {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0; }
|
|
@ -17,18 +17,16 @@ form#add {
|
|||
display: grid;
|
||||
gap: .5rem;
|
||||
grid-template-areas:
|
||||
"c n"
|
||||
"t t"
|
||||
"b b"
|
||||
"a a";
|
||||
grid-template-columns: 1fr 1fr;
|
||||
"b b b b"
|
||||
"c t u a";
|
||||
grid-template-columns: repeat(1fr, 3) .25fr;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
width: 100%; }
|
||||
|
||||
form#add select { grid-area: c; }
|
||||
form#add input[name="new-category"] { grid-area: n; }
|
||||
form#add input[name="title"] { grid-area: t; }
|
||||
form#add input[name="url"] { grid-area: u; }
|
||||
form#add textarea[name="body"] { grid-area: b; }
|
||||
form#add button[type="submit"] { grid-area: a; }
|
||||
|
||||
|
@ -36,13 +34,16 @@ form.edit {
|
|||
display: grid;
|
||||
gap: .5rem;
|
||||
grid-template-areas:
|
||||
"b b"
|
||||
"e d";
|
||||
grid-template-columns: 1fr 1fr;
|
||||
"b b b b b"
|
||||
"c t u e d";
|
||||
grid-template-columns: 1fr 1fr 1fr .25fr .25fr;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
width: 100%; }
|
||||
|
||||
form.edit textarea { grid-area: b; }
|
||||
form.edit select[name="category"] { grid-area: c; }
|
||||
form.edit input[name="title"] { grid-area: t; }
|
||||
form.edit input[name="url"] { grid-area: u; }
|
||||
form.edit input[name="edit"] { grid-area: e; }
|
||||
form.edit input[name="delete"] { grid-area: d; }
|
|
@ -6,9 +6,8 @@ function edit_post(e) {
|
|||
e.preventDefault();
|
||||
let data = new URLSearchParams();
|
||||
data.append("category", get_field(e.target, "category"))
|
||||
data.append("new-category", get_field(e.target, "new-category"))
|
||||
data.append("old-category", get_field(e.target, "old-category"))
|
||||
data.append("title", get_field(e.target, "title"))
|
||||
data.append("url", get_field(e.target, "url"))
|
||||
data.append("body", get_field(e.target, "body"))
|
||||
fetch(`/api/mindflow/${get_field(e.target, "post-id")}`, {method: "PATCH", body: data})
|
||||
.catch(e => console.log(e))
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://arav.su/</loc>
|
||||
<lastmod>2023-03-02</lastmod>
|
||||
<lastmod>2024-03-04</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://arav.su/stuff</loc>
|
||||
<lastmod>2023-07-04</lastmod>
|
||||
<lastmod>2024-03-04</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://arav.su/stuff/article/rpi_root_on_external_drive</loc>
|
||||
|
@ -34,10 +34,14 @@
|
|||
</url>
|
||||
<url>
|
||||
<loc>https://arav.su/about</loc>
|
||||
<lastmod>2023-07-04</lastmod>
|
||||
<lastmod>2024-04-16</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://arav.su/guestbook</loc>
|
||||
<changefreq>always</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://arav.su/privacy</loc>
|
||||
<lastmod>2023-07-12</lastmod>
|
||||
</url>
|
||||
</urlset>
|
|
@ -15,7 +15,7 @@ block nav
|
|||
h1 About
|
||||
|
||||
mixin isServiceUp(header, service)
|
||||
if (services[service])
|
||||
if v, ok := services[service]; ok && v
|
||||
h3.service-up= header
|
||||
else
|
||||
h3.service-down= header
|
||||
|
@ -24,12 +24,11 @@ block content
|
|||
:go:func About(title, files_site string, services servicestat.ServiceList)
|
||||
section#about-me
|
||||
h2 Me
|
||||
p #[span.highlighted Who am I?] My name is Alexander Andreev. I'm a russian guy of age 30 who likes tinkering with computers.
|
||||
p #[span.highlighted Why am I doing all of this?] Machines are up 24/7 anyway, so let they do some useful work.
|
||||
p #[span.highlighted Why am I almost “doxxing” myself?] Yes, my real first and last names, and age are here, and since I self-host you can get my city as well. And I simply don't see this as a problem. :|
|
||||
p #[span.highlighted Who am I?] My name is Alexander Andreev. I'm a russian guy of age 31 who likes tinkering with computers.
|
||||
p #[span.highlighted Why am I doing all of this?] Machines are up 24/7 anyway, so why not?
|
||||
p #[span.highlighted Why am I almost “doxxing” myself?] Yes, my real first and last names and age are here, and since I self-host you can get my city as well. And I simply don't see this as a problem. :|
|
||||
p #[span.highlighted Why particularly that slogan?] I just wanted sort of a slogan that would fit under the logo and this was the first thing came on mind. I actually like it. Don't you think of your websites as of very special place of your own? Of course you do. :) And who we are if not the wanderers who are looking for something in the vastness of Intenet? xD And yes, it's a pain in the ass to make it fit under logo across different browsers. That was the moment when I found out that different browsers render text differently.
|
||||
p #[span.highlighted Why English only?] I want this place to be accessible for a wide audience, and English happen to be todays lingua franca. Second, to practice English. And third, I'm lazy to maintain two versions of the website, neither see a good reason for that.
|
||||
p Contact information and banners are #[a(href='#contacts') at the bottom of this page]. Yes, I totally agree with you not wanting to add my banner since I don't have a link page myself, neither participate in any webring. :) Well, it's way easier to make a banner than a links page. :)
|
||||
p #[span.highlighted Why English only?] I want this place to be accessible for a wide audience and English happen to be todays lingua franca, and to practice the language of course.
|
||||
p Now a little break for...
|
||||
section#my-cat
|
||||
h2 My kot Boris :3
|
||||
|
@ -54,39 +53,36 @@ block content
|
|||
|
||||
h3 What I know
|
||||
h4 Human languages
|
||||
p #[span.highlighted English] is my main foreign language and I think that I know it quite well, but every time I reread what I wrote here I constantly catch cringe vibes. xD I have a nice pronunciation (my teacher once said so :)) and tend to British non-rhotic variant. Of course I end up with a mix of all the flavours of different dialects. What I struggle from is a lack of speaking practice and vocabulary is also still a problem.
|
||||
p #[span.highlighted Ukrainian (Українська)] is a special case. :) Russian and ukrainian are related languages, so mutually understandable (must be), yet many russians are struggling to understand it, and I don't know why. It is just a matter of putting some little effort to negatiate it. I never learnt it, aside from reading it here and there, and looking up some words in a dictionary, and especially throughout past #[s 8]9 years I saw enough material to learn it to a decent level of understanding and some speaking.
|
||||
p Агась, нiколи спецiйно не вчив українську, користував перекладач тiльки як словник, за цi #[s вiсiм] вже дев'ять рокiв було досить матерiалiв. :) P.S.: Слава Українi у складi Росiї! :)
|
||||
p #[span.highlighted English] is my main foreign language and I think that I know it quite well, but every time I reread what I wrote here I constantly catch cringe vibes. xD I have a nice pronunciation (my teacher once said so :)) and tend to British non-rhotic variant. Of course I end up with a mix of all the flavours of different dialects. What I struggle from is a lack of speaking practice and vocabulary is still a problem as well.
|
||||
p #[span.highlighted Ukrainian (Українська)] is a special case. :) Russian and ukrainian are related languages, so are mutually understandable (at least must be), yet many russians are struggling to understand it, and I genuinely don't get why. It is just a matter of putting some little effort to negatiate it. I never learnt it, aside from reading it here and there, and looking up some words in a dictionary, and especially throughout past #[s 8] #[s 9] 10 years I saw enough material to learn it to a decent level of understanding and some speaking.
|
||||
p Агась, нiколи спецiйно не вчив українську, користував перекладач тiльки як словник, за цi #[s вiсiм ] #[s вже дев'ять] десять рокiв було досить матерiалiв. :) P.S.: Слава Українi у складi Росiї! :)
|
||||
p I like learning languages, but me being a lazy fuck won't let me learn something up to a reasonable level. I ended up with being able to +- read some languages, like norwegian, german, polish, french, japanese (hiragana + some yet not forgotten kanji). So, now I main only English.
|
||||
h4 Computer languages
|
||||
p #[span.highlighted For programming and scripting:] Python, Go, JavaScript, and Bash for now are most used by me. Later I used or tasted: Pascal, Delphi, C#, C, C++, Java, PHP. And wrote hello worlds in some others like LISP and Haskell.
|
||||
p #[span.highlighted Markup and declarative:] SQL (SQLite3, MariaDB), HTML5, CSS3, LaTeX, Markdown, XML, and JSON.
|
||||
p And I don't know what else to put here. :^)
|
||||
|
||||
h3 What I like
|
||||
h3 What I liked
|
||||
h4 Anime
|
||||
p Ghost in the Shell, Shaman King, Hellsing, Steins;Gate, Cowboy Beebop, Ergo Proxy, Jin-Rou, Black Lagoon, Jojo's Bizzare Adventures, Spice and Wolf, Konosuba, Demon Slayer: Kimetsu no Yaiba
|
||||
h4 Films
|
||||
p Boss Nigger (The Black Bounty Killer) (1974), Movies with Jackie Chan, Blade Runner (1982), WarGames (1983), Robocop (1987), Talk Radio (1988), Stargate (1994), Hackers (1995), Johnny Mnemonic (1995), Contact (1997), Matrix (1999, 2003), Snatch (2000), Oldboy (2003), The Day After Tomorrow (2004), The Gingerdead Man (2005), Lucky Number Slevin (2006), I Am Legend (2007), Valhalla Rising (2009), Filth (2013), Mandariinid (2013), Gingerdead Man vs. Evil Bong (2013), Who am I (2014), Arrival (2016), Contratiempo (2016), Wandering Earth (2019), Everything Everywhere All at Once (2022), Dungeons & Dragons: Honor Among Thieves (2023), Wandering Earth 2 (2019)
|
||||
p Paths of Glory (1957), Boss Nigger (The Black Bounty Killer) (1974), Movies with Jackie Chan, Blade Runner (1982), WarGames (1983), Robocop (1987), Talk Radio (1988), Stargate (1994), Hackers (1995), Johnny Mnemonic (1995), Contact (1997), Matrix (1999, 2003), Snatch (2000), Oldboy (2003), The Day After Tomorrow (2004), Alpha Dog (2005), Constantine (2005), The Gingerdead Man (2005), Lucky Number Slevin (2006), 99 francs (2007), I Am Legend (2007), Inglourious Basterds (2009), Valhalla Rising (2009), The Guard (2011), Filth (2013), Mandariinid (2013), Gingerdead Man vs. Evil Bong (2013), Gone Girl (2014), Who am I (2014), Arrival (2016), Contratiempo (2016), Wandering Earth (2019), Wandering Earth 2 (2019), Greyhound (2020), I Care A Lot (2020), The Greatest Beer Run Ever (2022), Everything Everywhere All at Once (2022), Dungeons & Dragons: Honor Among Thieves (2023)
|
||||
h4 TV shows
|
||||
p X-Files (1993—2002), Lost (2004—2010), Stargate: SG-1 (1997—2007), Stargate: Atlantis (2004—2009), The Shivering Truth (2018—2020), Два холма (Two Hills) (2022), Narcos (2015—2017)
|
||||
p These lists aren't complete, will extend when remember or find something.
|
||||
p I actually don't watch much, and if I watch I go for a marathon, and if only I take a break in the middle of a show, then that break could last for years. xD And if I download some film it could sit for years, as it was with Valhalla Rising, it was waiting for me to watch for 3 fucking years. xD Oh, what made me to watch it? An one and a half hour long power outage. xD
|
||||
p I found my way of watching films and anime. Watching it with my favorite streamers! xD
|
||||
h4 Games for SEGA Mega Drive
|
||||
p Not a lot of games I have played.
|
||||
p Bubba'n'Stix, Battletoads, Granada, Demolition Man, Road Rash, Doom Troopers - The Mutant Chronicles, The Lost Vikings, Ghostbusters, Mig-29 Fighter Pilot.
|
||||
h4 Games for PC
|
||||
p Grand Theft Auto: Vice City, Half-Life (all), StarCraft, Diablo 2, Far Cry (2004), Grand Theft Auto: San Andreas, Boiling Point: Road to Hell, Portal, Freelancer, F.E.A.R., S.T.A.L.K.E.R., Grand Theft Auto 4, Lineage 2, The Elder Scrolls V: Skyrim, The Walking Dead, The Wolf Among Us, Payday 2, The Witcher (1st, 2nd, 3rd didn't finished yet), Minecraft, Terraria, Starbound, Euro Truck Simulator 2, Mount & Blade: Warband, Papers, Please, Insurgency, Elite: Dangerous, theHunter: Call of the Wild, American Truck Simulator, Rocket League, Sea Of Thieves.
|
||||
p Grand Theft Auto: Vice City, Half-Life (all), StarCraft, Diablo 2, Far Cry (2004), Battlefield: Vietnam, Delta Force: Black Hawk Down, Silent Hunter 3, Grand Theft Auto: San Andreas, Boiling Point: Road to Hell, Portal, Freelancer, F.E.A.R., S.T.A.L.K.E.R., Grand Theft Auto 4, Lineage 2, The Elder Scrolls V: Skyrim, Battlefield 3, The Walking Dead, The Wolf Among Us, Payday 2, The Witcher (1st, 2nd, 3rd didn't finished yet), Minecraft, Terraria, Starbound, Euro Truck Simulator 2, Mount & Blade: Warband, Papers, Please, Insurgency, Elite: Dangerous, theHunter: Call of the Wild, American Truck Simulator, Rocket League, Sea Of Thieves.
|
||||
p I'm CMDR Arav in Elite: Dangerous. My <a href="https://www.edsm.net/en/user/profile/id/22541/cmdr/Arav" rel="noopener noreferrer">EDSM profile</a>.
|
||||
p I play TruckersMP mod for Euro Truck Simulator 2 and American Truck Simulator. Nickname is Arav with a tag [RU64].
|
||||
h4 Music
|
||||
p My favorites that I like almost fully are Falkenbach, Korpiklaani, Sólstafir, Lacrimosa, Enigma, Röyksopp, Nagrobki, Burzum, maybe I forgot to mention something. You can see what I like on #[a(href=files_site+"/music") file share]. I have a tendency to listen to the same music for a long time, so only way for me to know about other music are online-radios and WEBM threads on IBs. :)
|
||||
p You can see what I like in #[a(href=files_site+"/music") a file share].
|
||||
section#servers-summary
|
||||
h2 My servers
|
||||
p I have two of them, the first one is a #[a(href='https://www.raspberrypi.org/products/raspberry-pi-3-model-b/' rel='nofollow noreferrer') Raspberry Pi 3 rev. B] single board computer and the second one is a laptop. Yeah, not quite impressive, but they do their work just fine.
|
||||
p Laptop is Acer Packard Bell TE69CX that has a 2 core Intel Pentium 2117U 1.8GHz and 10GB RAM (2GB + 8GB), the system disk is a 120GB SSD and the other one is 2TB HDD where all the shit I have is stored. I've replaced my good old 500GB disk with 2TB one, since it became a little too tight, now it is a system disk for Raspberry Pi. Everything that have just a bit of importantance to me is backed up, encrypted and stored in the clouds. :)
|
||||
p #[span.highlighted TL;DR about services.] Laptop runs a network file share, a public file upload service, a seedbox, a HTTP public file share, TeamSpeak 3, Mumble, #[s Matrix], I2P, an internet-radio, git server, ClamAV for email server, a web-server, and a database. Raspberry Pi 3 runs #[s OpenVPN], BIND9 via DNSCrypt-proxy, #[s a Tor relay], an email server, and a XMPP server.
|
||||
p Also now I have a helper VPS with a 1 core 2.2GHz CPU and 512M RAM, and a 10GB disk I use as a slave DNS server for my domain and a postfix relay to send a mail.
|
||||
section#servers-photos
|
||||
div.columns.figs
|
||||
figure
|
||||
|
@ -102,45 +98,44 @@ block content
|
|||
p.center That are available for everyone.
|
||||
div.columns
|
||||
div
|
||||
+isServiceUp("Internet-radio", "liquidsoap")
|
||||
+isServiceUp("Internet-radio", "radio")
|
||||
p
|
||||
a(href='https://radio.arav.su') radio.arav.su
|
||||
| .
|
||||
a(href='http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion') #[s onion]
|
||||
a(href='http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion') onion
|
||||
| .
|
||||
a(href='http://radio.arav.i2p') i2p
|
||||
| .
|
||||
a(href='http://[300:a98d:d6d0:8a08::e]') ygg
|
||||
p Runs using Liquidsoap through Icecast, which goes through NGiNX. I broadcast almost everything I have. Sorry, but I'm too lazy to make some kind of broadcasting program to stream so I just randomise the playlist and throw new music there from time to time.
|
||||
p This project grew from MPD I used to stream music over LAN for myself. Then I let it out and placed a player on my Neocities website. Then Icecast was added to see if there are listeners. I wasn't happy on how MPD was nearly overloading CPU, and later, after moving the radio off to a laptop, MPD was replaced by Liquidsoap.
|
||||
p Runs using Ezstream through Icecast which goes through NGiNX. Ezstream fetches a playlist from a self-written solution. I broadcast almost everything I have. I'm too lazy to make some kind of broadcasting program to stream so I just randomise the playlist and throw new music there from time to time.
|
||||
div
|
||||
+isServiceUp("File share", "dwelling-files")
|
||||
p
|
||||
a(href='https://files.arav.su') files.arav.su
|
||||
| .
|
||||
a(href='http://qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion') #[s onion]
|
||||
a(href='http://qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion') onion
|
||||
| .
|
||||
a(href='http://files.arav.i2p') i2p
|
||||
| .
|
||||
a(href='http://[300:a98d:d6d0:8a08::d]') ygg
|
||||
p There you'll find music, videos, films, animes, TV shows, books, games, programs, drivers (a very few), OS images (Winblows :)). Go check it out!
|
||||
p There you'll find music, videos, films, animes, TV shows, books, games, programs, drivers (a very few), OS images (Winblows mainly). Go check it out!
|
||||
div
|
||||
+isServiceUp("File upload", "dwelling-upload")
|
||||
p
|
||||
a(href='https://upload.arav.su') upload.arav.su
|
||||
| .
|
||||
a(href='http://.onion') #[s onion]
|
||||
a(href='http://4usftbmjpfexkr2x5xbp5ukmygpmg4fgrnx2wbifsexqctooz5hmviyd.onion') onion
|
||||
| .
|
||||
a(href='http://upload.arav.i2p') i2p
|
||||
| .
|
||||
a(href='http://[300:a98d:d6d0:8a08::c]') ygg
|
||||
p I've made a file upload service for ya with a limit of 128MiB per file, keeping it for 36 hours, and overall storage I dedicated for it is 100GiB.
|
||||
div
|
||||
+isServiceUp("Git", "gitea")
|
||||
+isServiceUp("Git", "git")
|
||||
p
|
||||
a(href='https://git.arav.su') git.arav.su
|
||||
| .
|
||||
a(href='http://qqitm7qlsbbubwmjos4cqzmvkqidg34rfnbyhuydhalep33fbvh22xyd.onion') #[s onion]
|
||||
a(href='http://qqitm7qlsbbubwmjos4cqzmvkqidg34rfnbyhuydhalep33fbvh22xyd.onion') onion
|
||||
| .
|
||||
a(href='http://git.arav.i2p') i2p
|
||||
| .
|
||||
|
@ -149,7 +144,6 @@ block content
|
|||
div
|
||||
h3 Tor relay
|
||||
p #[a(href='https://metrics.torproject.org/rs.html#details/CEF2FD0E1973EA04D1444DDAEFF1B0BC3C0C39B1' rel='nofollow noreferrer') metrics.torproject.org]
|
||||
p It is set to use 8 MBits of my Internet connection. It cannot became a guard node because of dynamic IP-address.
|
||||
p #[b Since 1st December 2021 Tor is blocked in Russia, so the relay is down.]
|
||||
div
|
||||
h3 I2P router
|
||||
|
@ -159,50 +153,39 @@ block content
|
|||
p.center That are available for a narrow circle of people.
|
||||
div.columns
|
||||
div
|
||||
+isServiceUp("E-Mail server", "postfix")
|
||||
p Postfix, Dovecot (w/Sieve), Spamassassin, ClamAV, OpenDMARC and OpenDKIM.
|
||||
p Alas, I don't have a static IP. And there is not much sense in it because my ISP doesn't offer reverse DNS for regular clients (it'd be bloody awesome).
|
||||
p It results in my messages ends up in a spam box at best. Well, Yandex, Google, Microsoft Live, and Rambler lets me in an Inbox. :) But if service use Spamhaus' blocklists then I'm being rejected.
|
||||
p #[b TLDR; I may not be able to send a reply].
|
||||
+isServiceUp("E-Mail server", "mail")
|
||||
p Postfix, Dovecot (w/Sieve), rspamd, ClamAV.
|
||||
div
|
||||
+isServiceUp("XMPP/Jabber", "prosody")
|
||||
p Prosody.
|
||||
div
|
||||
+isServiceUp("Mumble", "murmurd")
|
||||
+isServiceUp("Mumble", "mumble")
|
||||
p #[a(href="mumble://arav.su") mumble://arav.su]
|
||||
p It's private, but you still can join an entrance room where you can only write messages. A certificate is necessary.
|
||||
p Certificate is necessary. Only an entrance room is open for non-members.
|
||||
div
|
||||
+isServiceUp("TeamSpeak 3", "ts3server")
|
||||
+isServiceUp("TeamSpeak 3", "teamspeak3")
|
||||
p #[a(href="ts3server://arav.su") ts3server://arav.su]
|
||||
p Same as with Mumble applies to TS. It's security level is set to 29, so is yours identifier's level should be at least that high.
|
||||
div
|
||||
h3 Game servers
|
||||
p All game servers are private, if there's exception I'll mention it in its description. They are listed in a section below.
|
||||
p Some servers are listed in an in-game server list and theirs names looks like “Arav's dwelling / <Game name>”.
|
||||
p And, of course, I don't run them simultaneously and start them only when needed.
|
||||
p Expect poor performance because the server is a low-end laptop.
|
||||
p Same as for Mumble. Your identifier should have a security level greater or equal to 29.
|
||||
section#servers-games
|
||||
h2 Game servers
|
||||
div.columns
|
||||
div
|
||||
+isServiceUp("Minecraft", "forge-1.16.5")
|
||||
+isServiceUp("Minecraft", "game-minecraft")
|
||||
p.highlighted arav.su:25565
|
||||
p Version is 1.16.5 currently. I homebrew clients for it. Here is #[a(href=files_site+"/games/minecraft/aravsdwelling-minecraft-1.16.5.exe") an installer for Windows] (412MiB) I make with NSIS. And a #[a(href=files_site+"/games/minecraft/aravsdwelling-minecraft-1.16.5.tar.gz") TAR.GZ archive] (424 MiB) that is for both Windows and Linux. You'll need to set a PLAYER and MC_DIR variables in scripts. And change JVM_PARAMS to your preferences (-Xmx usually needs to be adjusted).
|
||||
p #[a(href='/minemap') Web map].
|
||||
p Now run a version 1.20.4 with fabric. Here's a #[a(href='https://files.arav.su/file/games/minecraft/1.20.4-fabric-modpack.zip') modpack].
|
||||
div
|
||||
+isServiceUp("Starbound", "starbound_server")
|
||||
p.highlighted arav.su:21065
|
||||
p Server is using a Frackin' Universe mod.
|
||||
p Don't forget to set “Allow assets mismatch” option that you can find on the first page of the “Options” menu.
|
||||
p Access is restricted using accounts. Contact me if you want to play here, you'll need to give me a nickname and a password.
|
||||
div
|
||||
+isServiceUp("Avorion", "AvorionServer")
|
||||
+isServiceUp("Avorion", "game-avorion")
|
||||
p Server is listed in a server list as “Arav's dwelling / Avorion”. Whitelist is enabled. Bring your SteamID64 if you want to play here.
|
||||
div
|
||||
+isServiceUp("Project Zomboid", "ProjectZomboid")
|
||||
+isServiceUp("Starbound", "game-starbound")
|
||||
p.highlighted arav.su:21065
|
||||
p Server is using a Frackin' Universe mod.
|
||||
p Don't forget to set “Allow assets mismatch” option that can be found on the first page of the “Options” menu.
|
||||
p Access is restricted using accounts. Contact me if you want to play here, you would need to give me a nickname and a password.
|
||||
div
|
||||
+isServiceUp("Project Zomboid", "game-pzomboid")
|
||||
p.highlighted arav.su:16261
|
||||
p Server is listed as “Arav's dwelling / Project Zomboid”. Whitelist is enabled.
|
||||
div
|
||||
+isServiceUp("Don't Starve Together", "dontstarve_dedicated_server_nullrenderer_x64")
|
||||
+isServiceUp("Don't Starve Together", "game-dontstarve")
|
||||
p.highlighted arav.su:10899
|
||||
p Server is listed as “Arav's dwelling / Don't Starve Together”.
|
||||
section#servers-inner-services
|
||||
|
@ -211,46 +194,43 @@ block content
|
|||
div.columns
|
||||
div
|
||||
h3 Web-server
|
||||
p NGiNX #[s + PHP-FPM (just for phpMyAdmin)] #[s + NodeJS] + Go.
|
||||
p NGiNX FTW.
|
||||
div
|
||||
h3 Database server
|
||||
p #[s MariaDB managed with phpMyAdmin] Replaced by SQLite3 on my services.
|
||||
h3 Database
|
||||
p #[s MariaDB managed with phpMyAdmin] was replaced by SQLite3 on my services.
|
||||
div
|
||||
h3 VPN
|
||||
p #[s OpenVPN] Wireguard.
|
||||
div
|
||||
h3 Network file share
|
||||
p Samba.
|
||||
p Samba and NFSv4.
|
||||
div
|
||||
h3 Torrent seedbox
|
||||
h3 Torrents
|
||||
p Transmission-cli gets shit done.
|
||||
p The only annoying thing is that it sometimes creates .part files for one of unchecked files.
|
||||
div
|
||||
h3 Print server
|
||||
h3 Printing
|
||||
p CUPS with CCP (Canon CAPT printer).
|
||||
p Holy shit, it finally works! #[s Wow, even after kernel update it doesn't require a reboot anymore!] Actually, requires. :)
|
||||
p Holy shit, it finally works! #[s Wow, even after kernel update it doesn't require a reboot anymore!] #[s Actually, requires. :)] Sometimes do, sometimes not. xD
|
||||
div
|
||||
h3 DNS server
|
||||
h3 DNS
|
||||
p BIND9 via DNSCrypt-proxy.
|
||||
p For LAN I use “home.arpa” special-use domain introduced by RFC 8375. And for uniqueness “arav.home.arpa” specifically.
|
||||
p Now I serve my domain using a VPS as a slave.
|
||||
section#contacts
|
||||
h2 Contacts
|
||||
span E-Mail: #[a(href="mailto:me@arav.su" title='May not be able to reply due to being blacklisted because of residental dynamic IP.') me@arav.su]
|
||||
span Jabber: #[a(href="xmpp://arav@arav.su") arav@arav.su]
|
||||
span E-Mail: #[a(href="mailto:me@arav.su") me@arav.su]
|
||||
br
|
||||
span
|
||||
| PGP key: #[a(href="~arav/2E873A7831FF0BB640ACEDA5D22A817D95815393.asc") 2E87 3A78 31FF 0BB6 40AC EDA5 D22A 817D 9581 5393] (available through #[a(href="https://wiki.gnupg.org/WKD" rel="nofollow noreferrer") Web Key Directory])
|
||||
br
|
||||
span
|
||||
| Jabber OMEMO fingerprint: 5F705E2C 9CE0F56D BD28F52D 6744FDD3 4477E9C7 CB856590 BDEF5EE1 2F6D566C
|
||||
| PGP key: #[a(href="/~arav/739850CD5051DE554368709225969B23DCB5CA34.asc") 7398 50CD 5051 DE55 4368 7092 2596 9B23 DCB5 CA34] (available through #[a(href="https://wiki.gnupg.org/WKD" rel="nofollow noreferrer") Web Key Directory])
|
||||
section#donation
|
||||
h2 Donation
|
||||
a(href="monero:48namnfX17TX1kEGCpkXaRWhtw8p92cQjd5uQg7ivybgUuW4BTVaX8egxQhEi75JwuUGn3MDLKHYGNhu4eCfM6dRAAL2QAq") monero:
|
||||
| 48namnfX17TX1kEGCpkXaRWhtw8p92cQjd5uQg7ivybgUuW4BTVaX8egxQhEi75JwuUGn3MDLKHYGNhu4eCfM6dRAAL2QAq
|
||||
br
|
||||
| #[a(href="https://www.donationalerts.com/r/arav") DonationAlerts]
|
||||
| #[a(href="https://qiwi.com/n/UPPON082") QIWI]
|
||||
| #[a(href="https://www.tinkoff.ru/rm/andreev.aleksandr1164/cksD590894/") Tinkoff]
|
||||
| #[a(href="https://www.tinkoff.ru/rm/andreev.aleksandr1164/5nVjK98501") Tinkoff]
|
||||
| #[a(href="https://www.tinkoff.ru/baf/5zRsqEQic6r") Tinkoff рефералка]
|
||||
section#banners
|
||||
h2 Banners
|
||||
p In case you found my site anyhow useful and would like to add me to your links page here are the banners for you.
|
||||
|
@ -264,7 +244,3 @@ block content
|
|||
img(src="/assets/img/banners/24060l.gif" alt="Light banner 240x60")
|
||||
figure
|
||||
img(src="/assets/img/banners/24060d.gif" alt="Dark banner 240x60")
|
||||
section#privacy-statements
|
||||
h2 Privacy statements
|
||||
p Logs are collected and include a date and time of access, your IP-address, User-Agent, referer URL, and a request.
|
||||
p JavaScript is used at guestbook page to refresh CAPTCHA, and at mindflow page to filter posts by categories.
|
|
@ -5,7 +5,7 @@ block meta_description
|
|||
|
||||
block append head
|
||||
link(href='/assets/css/articles.css' rel='stylesheet')
|
||||
link(rel='canonical' href='#{host}/stuff/article/#{canonical}')
|
||||
link(rel='canonical' href='/stuff/article/'+canonical)
|
||||
|
||||
block nav
|
||||
a(href='/') Home
|
||||
|
@ -22,5 +22,6 @@ block content
|
|||
h2= title
|
||||
div.menu
|
||||
a(href='/stuff#articles') Go back to articles list
|
||||
time(datetime=util.ToClientTimezone(date, r).Format("2006-01-02")) #{util.ToClientTimezone(date, r).Format("02 January 2006")}
|
||||
- dctz := util.ToClientTimezone(date, r)
|
||||
time(datetime=dctz.Format("2006-01-02")) #{dctz.Format("02 January 2006")}
|
||||
| !{body}
|
|
@ -9,7 +9,7 @@ html(lang='en')
|
|||
meta(name='theme-color' content='#cd2682')
|
||||
block meta_description
|
||||
link(rel='icon' href='/assets/img/favicon.svg' sizes='any' type='image/svg+xml')
|
||||
link(rel='alternate' href='rss.xml' type='application/rss+xml' title="Arav's dwelling")
|
||||
link(rel='alternate' href='/rss.xml' type='application/rss+xml' title="Arav's dwelling")
|
||||
link(href='/assets/css/main.css' rel='stylesheet')
|
||||
body
|
||||
header
|
||||
|
@ -22,4 +22,4 @@ html(lang='en')
|
|||
footer
|
||||
a(href='/rss.xml' title="Stay up to date on what's going on.") RSS feed
|
||||
br
|
||||
| 2017—2023 Arav <#[a(href='mailto:me@arav.su') me@arav.su]>
|
||||
| © 2017—2024 Alexander "Arav" Andreev <#[a(href='mailto:me@arav.su') me@arav.su]> #[a(href='/privacy') Privacy statements]
|
||||
|
|
|
@ -4,14 +4,7 @@ block meta_description
|
|||
meta(name='description' content=http.StatusText(code))
|
||||
|
||||
block append head
|
||||
style(type="text/css").
|
||||
#error {
|
||||
font-size: 3.5rem;
|
||||
line-height: 5rem;
|
||||
text-align: center;
|
||||
margin: 6rem 0; }
|
||||
|
||||
#error h1 { font-size: 8rem; }
|
||||
link(href='/assets/css/error.css' rel='stylesheet')
|
||||
|
||||
block nav
|
||||
a(href='/') Home
|
||||
|
@ -22,11 +15,14 @@ block nav
|
|||
h1 #{http.StatusText(code)}
|
||||
|
||||
block content
|
||||
:go:func ErrorXXX(title, reason, message string, code int)
|
||||
section#error
|
||||
h1 #{code}
|
||||
| #{http.StatusText(code)}
|
||||
:go:func ErrorXXX(title, reason, message, referer string, code int)
|
||||
section#error
|
||||
h1 #{code}
|
||||
| #{http.StatusText(code)}
|
||||
if reason != ""
|
||||
p #{reason}
|
||||
if message != ""
|
||||
p #{message}
|
||||
if referer != ""
|
||||
section
|
||||
h2 #[a(href=referer) Go back]
|
||||
|
|
|
@ -31,6 +31,8 @@ block content
|
|||
span.checkboxes
|
||||
input(type='checkbox' id='hide-website' name='hide_website' checked='')
|
||||
label(for='hide-website') Hide website #[small (only I can see if set)]
|
||||
br
|
||||
small Use > to make a quote.
|
||||
span.captcha
|
||||
input(type='hidden' value=captcha_id name='captcha_id')
|
||||
img(src='/api/captcha/'+captcha_id+'/image', alt="CAPTCHA" width='160' height='40')
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
extends base.pug
|
||||
|
||||
block meta_description
|
||||
meta(name='description' content="A homepage of a russian guy Alexander aka Arav. Not just homepage, but file share, radio and upload services as well.")
|
||||
meta(name='description' content="A homepage of a russian guy Alexander aka Arav. Not just homepage, but something more...")
|
||||
|
||||
block append head
|
||||
link(href='assets/css/index.css' rel='stylesheet')
|
||||
|
@ -19,8 +19,8 @@ block content
|
|||
span
|
||||
a(href='https://arav.su') arav.su
|
||||
| .
|
||||
a(href='http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion') #[s onion]
|
||||
| .
|
||||
a(href='http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion') onion
|
||||
| .
|
||||
a(href='http://[300:a98d:d6d0:8a08::f]') ygg
|
||||
| .
|
||||
a(href='http://arav.i2p') i2p
|
||||
|
@ -30,8 +30,8 @@ block content
|
|||
span
|
||||
a(href='https://radio.arav.su') radio
|
||||
| .
|
||||
a(href='http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion') #[s onion]
|
||||
| .
|
||||
a(href='http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion') onion
|
||||
| .
|
||||
a(href='http://[300:a98d:d6d0:8a08::e]') ygg
|
||||
| .
|
||||
a(href='http://radio.arav.i2p') i2p
|
||||
|
@ -39,8 +39,8 @@ block content
|
|||
span
|
||||
a(href='https://files.arav.su') files
|
||||
| .
|
||||
a(href='http://qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion') #[s onion]
|
||||
| .
|
||||
a(href='http://qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion') onion
|
||||
| .
|
||||
a(href='http://[300:a98d:d6d0:8a08::d]') ygg
|
||||
| .
|
||||
a(href='http://files.arav.i2p') i2p
|
||||
|
@ -48,8 +48,8 @@ block content
|
|||
span
|
||||
a(href='https://upload.arav.su') upload
|
||||
| .
|
||||
a(href='#') #[s onion]
|
||||
| .
|
||||
a(href='http://4usftbmjpfexkr2x5xbp5ukmygpmg4fgrnx2wbifsexqctooz5hmviyd.onion') onion
|
||||
| .
|
||||
a(href='http://[300:a98d:d6d0:8a08::c]') ygg
|
||||
| .
|
||||
a(href='http://upload.arav.i2p') i2p
|
||||
|
@ -57,11 +57,11 @@ block content
|
|||
span
|
||||
a(href='https://git.arav.su') git
|
||||
| .
|
||||
a(href='http://qqitm7qlsbbubwmjos4cqzmvkqidg34rfnbyhuydhalep33fbvh22xyd.onion') #[s onion]
|
||||
| .
|
||||
a(href='http://qqitm7qlsbbubwmjos4cqzmvkqidg34rfnbyhuydhalep33fbvh22xyd.onion') onion
|
||||
| .
|
||||
a(href='http://[300:a98d:d6d0:8a08::b]') ygg
|
||||
| .
|
||||
a(href='http://git.arav.i2p') i2p
|
||||
sup #[a(href="http://git.arav.i2p/?i2paddresshelper=eFIfcBUv3lHFSnglHfncs5XXtYwm9gCpmAYuio~9CeENBAXKRggPiY1tQC-otCon2hCSpr56WlVBeZk1txKuUnbjHTN7GBFaKW5wJEO2WmKEWPKdcjUDOYZN0D3TwXaYfiBuELD3200lBfDmPEJ01iC2o7B5yvpOqtEKDcaqkIp4vafDuPPumJ~XiCGdUAe~vr52w3Tbuz5x7wbltk-gUELY0-ZAQBos4jOJ6QT1W1lhycHPhAK8qslgwfk94opyIl2pkRyuJhU-2VHc6Fsd621VXC86YAMT1SIfTZlFpoGVCFXDM~BXaLvygFaKf62qardAe0T48Ax6GxosAKXe-yLCVRaiD3KErULfwZXl23kQzRfxM4odG4DWeXawtuvypOmTjHT1skQHU0h52ujye5nT~2bOy14HkCoCnxJ7gSj3MjkmWLd1JhBsPH4ymRmI7jFJR1GYl8Wp5IigMBBzWfJUEEjS7QDHaRo5TCZJ9SXz6sgkGdfh74~r8FWL559gBQAEAAcAAA==" title="address helper") ah]
|
||||
section
|
||||
p Welcome, Anon. I'm Arav, I self-host some services for myself, friends and you. Not much I have to offer, but maybe you'll find something useful for yourself. :)
|
||||
p Welcome, Anon. I'm Alexander aka Arav, I self-host some services for myself, friends and you. Not much I have to offer, but maybe you'll find something useful for yourself. :)
|
|
@ -17,7 +17,7 @@ block nav
|
|||
|
||||
block content
|
||||
:go:func Mindflow(title string, posts []mindflow.Post, categories []mindflow.Category, r *http.Request)
|
||||
p.center Here I post updates on websites and infrastructure, my very important opinions and thoughts no one asked for. If you'd like to subscribe to this bullshittery then #[a(href='rss.xml') RSS feed] at your service. :)
|
||||
p.center Here I post updates on websites and infrastructure, my very important opinions and thoughts no one asked for. If you'd like to subscribe to this bullshittery then #[a(href='/rss.xml') RSS feed] at your service. :)
|
||||
|
||||
section#filter.hidden
|
||||
p.center
|
||||
|
@ -32,8 +32,15 @@ block content
|
|||
a(href=`#${post.PostID()}`)
|
||||
h3= post.Category.Name + ": " + post.Title
|
||||
each line in strings.Split(post.Body, "\n")
|
||||
p!= line
|
||||
if (len(line) > 0 && line[0] == '>')
|
||||
p.quote!= line
|
||||
else
|
||||
p!= line
|
||||
footer
|
||||
if (post.URL != "")
|
||||
a(href=post.PostURL(r.Host, false)) #{post.PostURL(r.Host, false)}
|
||||
else
|
||||
span
|
||||
time(datetime=util.ToClientTimezone(post.Date, r))= util.ToClientTimezone(post.Date, r).Format(time.RFC1123)
|
||||
else
|
||||
p.center Nothing? There must be some... Looks like database went down.
|
||||
|
|
|
@ -25,7 +25,7 @@ block content
|
|||
each category in categories
|
||||
option(value=category.ID) #{category.Name}
|
||||
option(value='0') -- New category --
|
||||
input(type='text', placeholder='Category name' name='name')
|
||||
input(type='text', placeholder='New category name' name='name')
|
||||
button(type="submit" name="add") Add
|
||||
button(type="submit" name="edit") Edit
|
||||
button(type="submit" name="delete") Delete
|
||||
|
@ -35,13 +35,9 @@ block content
|
|||
form(id='add' action='/api/mindflow', method='POST')
|
||||
select(name='category' required='')
|
||||
each category in categories
|
||||
if (category.ID == 1)
|
||||
option(value=category.ID selected='') #{category.Name}
|
||||
else
|
||||
option(value=category.ID) #{category.Name}
|
||||
option(value='0') -- New category --
|
||||
input(type='text', placeholder='New category name' name='new-category')
|
||||
option(value=category.ID) #{category.Name}
|
||||
input(type='text', placeholder='Title' name='title' required='')
|
||||
input(type='text', placeholder='URL' name='url')
|
||||
textarea(placeholder='Body post' name='body' required='')
|
||||
button(type="submit") Add
|
||||
section
|
||||
|
@ -58,11 +54,9 @@ block content
|
|||
option(value=category.ID selected='') #{category.Name}
|
||||
else
|
||||
option(value=category.ID) #{category.Name}
|
||||
option(value='0') -- New category --
|
||||
input(type='text', placeholder='New category name' name='new-category')
|
||||
input(type='hidden', name='post-id' value=post.ID)
|
||||
input(type='hidden', name='old-category' value=post.Category.ID)
|
||||
input(type='text', placeholder='Title' name='title' value=post.Title required='')
|
||||
input(type='text', placeholder='URL' name='url' value=post.URL)
|
||||
textarea(placeholder='Body post' name='body' required='')!= post.Body
|
||||
button(name='edit-post') Edit
|
||||
button(name='delete-post') Delete
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
extends base.pug
|
||||
|
||||
block meta_description
|
||||
meta(name='description' content="Privacy statements for all of my services.")
|
||||
|
||||
block append head
|
||||
link(rel='canonical' href='/privacy')
|
||||
|
||||
block nav
|
||||
a(href='/') Home
|
||||
a(href='/stuff') Stuff
|
||||
a(href='/mindflow') Mindflow
|
||||
a(href='/about') About
|
||||
a(href='/guestbook') Guestbook
|
||||
|
||||
block content
|
||||
:go:func Privacy(title, radioSrvc, filesSrvc, uploadSrvc string)
|
||||
section#privacy
|
||||
h2 Privacy statements
|
||||
h3 General data
|
||||
p Across all of my Web-services following data is being collected: date of access, IP-address, User-Agent, referer URL, request URL.
|
||||
h3 Use of JavaScript
|
||||
p JS is used on a #[a(href='/') main website] at a guestbook page to refresh CAPTCHA; at mindflow page to filter posts by categories.
|
||||
p #[a(href=radioSrvc) Radio service] uses JS to update Last N songs list section, and to get current radio statistics.
|
||||
p #[a(href=filesSrvc) Files service] uses JS to add functionality such as an overlay to view files without the need to leave a site, and implements some keyboard control for convenience.
|
||||
h3 Upload service specific
|
||||
p Every action (upload, download, and delete) is being logged and includes this additional data:
|
||||
ol
|
||||
li File's name it was uploaded/downloaded with;
|
||||
li Unsalted SHA-256 hash of a file;
|
||||
li A salted hash encoded as base64 in raw URL variant that is used to download/delete a file;
|
||||
li File's size.
|
||||
p And I will cooperate with law enforcements and provide them with all information (logs and a file itself if it is still present).
|
||||
p As already stated at #[a(href=uploadSrvc) Upload service]'s page, file's content must comply with law of Russian Federation. Anything like extremist materials, CP, and so on is forbidden.
|
|
@ -6,15 +6,15 @@ rss(version='2.0')
|
|||
title Arav's dwelling
|
||||
description Updates on my websites and infrastructure.
|
||||
language en-gb
|
||||
link= host
|
||||
| <link>#{host}</link>
|
||||
|
||||
each post in posts
|
||||
item
|
||||
title= post.Title
|
||||
category= post.Category.Name
|
||||
guid!= `${post.PostID()}`
|
||||
pubDate= util.ToClientTimezone(post.Date, r).Format("20060102-150405")
|
||||
link!= `${host}/mindflow#${post.PostID()}`
|
||||
pubDate= util.ToClientTimezone(post.Date, r).Format(time.RFC1123Z)
|
||||
| <link>#{post.PostURL(r.Host, true)}</link>
|
||||
author!= author
|
||||
description
|
||||
| <![CDATA[
|
||||
|
|
|
@ -18,8 +18,10 @@ block content
|
|||
p.center Here lies everything I've made that I'm willing to share.
|
||||
section#articles
|
||||
h2 Articles
|
||||
p These articles are more like the sysadmin's notes. I describe those parts here that I did myself. The date here represents when article was updated last time.
|
||||
table
|
||||
tr
|
||||
th Last Update
|
||||
th Article
|
||||
each entry in metadata
|
||||
tr
|
||||
td
|
||||
|
@ -28,11 +30,21 @@ block content
|
|||
a(href=entry.URL) #{entry.Title}
|
||||
section#programs-scripts
|
||||
h2 Programs and scripts
|
||||
p.center Simple, yet useful (at least for me) programs and scripts I made.
|
||||
table
|
||||
tr
|
||||
td mccl
|
||||
td 0.1.2 (14 December 2023)
|
||||
td Go
|
||||
td GPLv3
|
||||
td
|
||||
a(href=git_site+'/Arav/mccl') source
|
||||
a(href=git_site+'/Arav/mccl/releases') releases
|
||||
tr
|
||||
td(colspan='5')
|
||||
p A console Minecraft launcher. Take a look at README.md for explanation.
|
||||
tr
|
||||
td httpr
|
||||
td 0.2.0 (27 May 2023)
|
||||
td 0.3.2 (20 September 2023)
|
||||
td Go
|
||||
td MIT
|
||||
td
|
||||
|
@ -40,7 +52,7 @@ block content
|
|||
a(href=git_site+'/Arav/httpr/releases') releases
|
||||
tr
|
||||
td(colspan='5')
|
||||
p A simple HTTP router that supports having both regular and parametrised path's parts at the same level, like #[code /assets/*filepath] and #[code /:a/:b] in the same HTTP method.
|
||||
p A simple HTTP router that supports having both regular and parametrised path's parts at the same level, like #[code /assets/*filepath] and #[code /:a/:b] with the same HTTP method.
|
||||
p Yeah, not as efficient as httprouter is, but in my case no performance loose was noticed (on a small amount of paths). Yet I gained prettiness because it allowed me, for example, to ditch #[code /f/] part and leave just #[code /:hash/:name] instead of #[code /f/:hash/:name] in a dwelling-upload service.
|
||||
tr
|
||||
td justguestbook
|
||||
|
@ -48,23 +60,23 @@ block content
|
|||
td Go
|
||||
td MIT
|
||||
td
|
||||
a(href=git_site+'/Arav/justcaptcha') source
|
||||
a(href=git_site+'/Arav/justguestbook') source
|
||||
tr
|
||||
td(colspan='5')
|
||||
p A simple guestbook with owner's replies implementation made into a library.
|
||||
tr
|
||||
td justcaptcha
|
||||
td 2.0.2 (6 May 2023)
|
||||
td 2.1.0 (12 August 2023)
|
||||
td Go
|
||||
td MIT
|
||||
td
|
||||
a(href=git_site+'/Arav/justcaptcha') source
|
||||
tr
|
||||
td(colspan='5')
|
||||
p A simple CAPTCHA implementation that can work as a standalone service or as a library.
|
||||
p A simple CAPTCHA implementation.
|
||||
tr
|
||||
td kwh-cost
|
||||
td 1.0.0 (24 December 2021)
|
||||
td 1.1.1 (23 September 2023)
|
||||
td C
|
||||
td MIT+NIGGER
|
||||
td
|
||||
|
@ -74,7 +86,7 @@ block content
|
|||
p KWh cost calculator in C.
|
||||
tr
|
||||
td httpprocprobed
|
||||
td 2.0.1 (4 February 2023)
|
||||
td 3.1.0 (16 December 2023)
|
||||
td Go
|
||||
td MIT+NIGGER
|
||||
td
|
||||
|
@ -115,7 +127,7 @@ block content
|
|||
a(href=git_site+'/Arav/PiggyBank/releases') releases
|
||||
tr
|
||||
td(colspan='5')
|
||||
p A program to help you to keep track of your piggy bank.
|
||||
p A program to help you to keep track of your piggy bank. A simple script I once wrote that I rewrote and made into a package just to learn how to do it.
|
||||
section#music
|
||||
h2 Music
|
||||
p There was a period in my life when I was playing with audio sequencers. I lost all project files and only 3 tracks survived in mp3 that #[a(href=files_site+"/music/My%20tracks,%20that%20survived/") you can get here].
|
|
@ -6,12 +6,10 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
// To install a Jade compiler: go install github.com/Joker/jade/cmd/jade@latest
|
||||
//
|
||||
//go:generate $GOPATH/bin/jade -basedir ./templates -pkg=web -writer index.pug stuff.pug mindflow.pug
|
||||
//go:generate $GOPATH/bin/jade -basedir ./templates -pkg=web -writer about.pug guestbook.pug rss.pug
|
||||
//go:generate $GOPATH/bin/jade -basedir ./templates -pkg=web -writer guestbook_admin.pug mindflow_admin.pug
|
||||
//go:generate $GOPATH/bin/jade -basedir ./templates -pkg=web -writer article.pug
|
||||
//go:generate $GOPATH/bin/jade -basedir ./templates -pkg=web -writer article.pug privacy.pug
|
||||
//go:generate $GOPATH/bin/jade -basedir ./templates -pkg=web -writer errorXXX.pug
|
||||
|
||||
//go:embed assets
|
||||
|
|
Loading…
Reference in New Issue