···44tmp/
55test/
66uploads/
77-docker-compose-test.yml
77+docker-compose*.yml
88+!docker-compose.example.yml
99+config*.toml
1010+>>>>>>> dev
+22-11
README.md
···4455---
6677-built up from the initial [static](https://git.arimelody.me/ari/arimelody.me-static) branch, this powerful, server-side rendered version comes complete with live updates, powered by a new database and super handy admin panel!
77+built up from the initial [static](https://git.arimelody.me/ari/arimelody.me-static)
88+branch, this powerful, server-side rendered version comes complete with live
99+updates, powered by a new database and handy admin panel!
81099-the admin panel currently facilitates live updating of my music discography, though i plan to expand it towards art portfolio and blog posts in the future. if all goes well, i'd like to later separate these components into their own library for others to use in their own sites. exciting stuff!
1111+the admin panel currently facilitates live updating of my music discography,
1212+though i plan to expand it towards art portfolio and blog posts in the future.
1313+if all goes well, i'd like to later separate these components into their own
1414+library for others to use in their own sites. exciting stuff!
10151116## build
12171313-easy! just `git clone` this repo and `go build` from the root. `arimelody-web(.exe)` should be generated.
1818+- `git clone` this repo, and `cd` into it.
1919+- `go build -o arimelody-web .`
14201521## running
16221717-the webserver depends on some environment variables (don't worry about forgetting some; it'll be sure to bug you about them):
2323+the server should be run once to generate a default `config.toml` file.
2424+configure as needed. note that a valid DB connection is required, and the admin
2525+panel will be disabled without valid discord app credentials (this can however
2626+be bypassed by running the server with `-adminBypass`).
18271919-- `HTTP_DOMAIN`: the domain the webserver will use for generating oauth redirect URIs (default `https://arimelody.me`)
2020-- `DISCORD_ADMIN`[^1]: the user ID of your discord account (discord auth is intended to be temporary, and will be replaced with its own auth system later)
2121-- `DISCORD_CLIENT`[^1]: the client ID of your discord OAuth application.
2222-- `DISCORD_SECRET`[^1]: the client secret of your discord OAuth application.
2828+the configuration may be overridden using environment variables in the format
2929+`ARIMELODY_<SECTION_NAME>_<KEY_NAME>`. for example, `db.host` in the config may
3030+be overridden with `ARIMELODY_DB_HOST`.
23312424-[^1]: not required, but the admin panel will be **disabled** if these are not provided.
3232+the location of the configuration file can also be overridden with
3333+`ARIMELODY_CONFIG`.
25342626-the webserver requires a database to run. in this case, postgres.
3535+## database
27362828-the [docker compose script](docker-compose.yml) contains the basic requirements to get you up and running, though it does not currently initialise the schema on first run. you'll need to `docker compose exec -it arimelody.me-db-1` to access the database container while it's running, run `psql -U arimelody` to get a postgres shell, and copy/paste the contents of [schema.sql](schema.sql) to initialise the database. i'll build an automated initialisation script later ;p
3737+the server requires a postgres database to run. you can use the
3838+[schema.sql](schema.sql) provided in this repo to generate the required tables.
3939+automatic schema building/migration may come in a future update.
···11+package global
22+33+import (
44+ "errors"
55+ "fmt"
66+ "os"
77+ "strconv"
88+ "strings"
99+1010+ "github.com/jmoiron/sqlx"
1111+ "github.com/pelletier/go-toml/v2"
1212+)
1313+1414+type (
1515+ dbConfig struct {
1616+ Host string `toml:"host"`
1717+ Name string `toml:"name"`
1818+ User string `toml:"user"`
1919+ Pass string `toml:"pass"`
2020+ }
2121+2222+ discordConfig struct {
2323+ AdminID string `toml:"admin_id" comment:"NOTE: admin_id to be deprecated in favour of local accounts and SSO."`
2424+ ClientID string `toml:"client_id"`
2525+ Secret string `toml:"secret"`
2626+ }
2727+2828+ config struct {
2929+ BaseUrl string `toml:"base_url" comment:"Used for OAuth redirects."`
3030+ Port int64 `toml:"port"`
3131+ DataDirectory string `toml:"data_dir"`
3232+ DB dbConfig `toml:"db"`
3333+ Discord discordConfig `toml:"discord"`
3434+ }
3535+)
3636+3737+var Config = func() config {
3838+ configFile := os.Getenv("ARIMELODY_CONFIG")
3939+ if configFile == "" {
4040+ configFile = "config.toml"
4141+ }
4242+4343+ config := config{
4444+ BaseUrl: "https://arimelody.me",
4545+ Port: 8080,
4646+ }
4747+4848+ data, err := os.ReadFile(configFile)
4949+ if err != nil {
5050+ configOut, _ := toml.Marshal(&config)
5151+ os.WriteFile(configFile, configOut, os.ModePerm)
5252+ fmt.Printf(
5353+ "A default config.toml has been created. " +
5454+ "Please configure before running again!\n")
5555+ os.Exit(0)
5656+ }
5757+5858+ err = toml.Unmarshal([]byte(data), &config)
5959+ if err != nil {
6060+ fmt.Fprintf(os.Stderr, "FATAL: Failed to parse configuration file: %s\n", err.Error())
6161+ os.Exit(1)
6262+ }
6363+6464+ err = handleConfigOverrides(&config)
6565+ if err != nil {
6666+ fmt.Fprintf(os.Stderr, "FATAL: Failed to parse environment variable %s\n", err.Error())
6767+ os.Exit(1)
6868+ }
6969+7070+ return config
7171+}()
7272+7373+func handleConfigOverrides(config *config) error {
7474+ var err error
7575+7676+ if env, has := os.LookupEnv("ARIMELODY_BASE_URL"); has { config.BaseUrl = env }
7777+ if env, has := os.LookupEnv("ARIMELODY_PORT"); has {
7878+ config.Port, err = strconv.ParseInt(env, 10, 0)
7979+ if err != nil { return errors.New("ARIMELODY_PORT: " + err.Error()) }
8080+ }
8181+ if env, has := os.LookupEnv("ARIMELODY_DATA_DIR"); has { config.DataDirectory = env }
8282+8383+ if env, has := os.LookupEnv("ARIMELODY_DB_HOST"); has { config.DB.Host = env }
8484+ if env, has := os.LookupEnv("ARIMELODY_DB_NAME"); has { config.DB.Name = env }
8585+ if env, has := os.LookupEnv("ARIMELODY_DB_USER"); has { config.DB.User = env }
8686+ if env, has := os.LookupEnv("ARIMELODY_DB_PASS"); has { config.DB.Pass = env }
8787+8888+ if env, has := os.LookupEnv("ARIMELODY_DISCORD_ADMIN_ID"); has { config.Discord.AdminID = env }
8989+ if env, has := os.LookupEnv("ARIMELODY_DISCORD_CLIENT_ID"); has { config.Discord.ClientID = env }
9090+ if env, has := os.LookupEnv("ARIMELODY_DISCORD_SECRET"); has { config.Discord.Secret = env }
9191+9292+ return nil
9393+}
9494+9595+var Args = func() map[string]string {
9696+ args := map[string]string{}
9797+9898+ index := 0
9999+ for index < len(os.Args[1:]) {
100100+ arg := os.Args[index + 1]
101101+ if !strings.HasPrefix(arg, "-") {
102102+ fmt.Printf("FATAL: Parameters must follow an argument (%s).\n", arg)
103103+ os.Exit(1)
104104+ }
105105+106106+ if index + 3 > len(os.Args) || strings.HasPrefix(os.Args[index + 2], "-") {
107107+ args[arg[1:]] = "true"
108108+ index += 1
109109+ continue
110110+ }
111111+112112+ val := os.Args[index + 2]
113113+ args[arg[1:]] = val
114114+ // fmt.Printf("%s: %s\n", arg[1:], val)
115115+ index += 2
116116+ }
117117+118118+ return args
119119+}()
120120+121121+var DB *sqlx.DB
+3
global/const.go
···11+package global
22+33+const COOKIE_TOKEN string = "AM_TOKEN"
-45
global/data.go
···11-package global
22-33-import (
44- "fmt"
55- "os"
66- "strings"
77-88- "github.com/jmoiron/sqlx"
99-)
1010-1111-var Args = func() map[string]string {
1212- args := map[string]string{}
1313-1414- index := 0
1515- for index < len(os.Args[1:]) {
1616- arg := os.Args[index + 1]
1717- if !strings.HasPrefix(arg, "-") {
1818- fmt.Printf("FATAL: Parameters must follow an argument (%s).\n", arg)
1919- os.Exit(1)
2020- }
2121-2222- if index + 3 > len(os.Args) || strings.HasPrefix(os.Args[index + 2], "-") {
2323- args[arg[1:]] = "true"
2424- index += 1
2525- continue
2626- }
2727-2828- val := os.Args[index + 2]
2929- args[arg[1:]] = val
3030- // fmt.Printf("%s: %s\n", arg[1:], val)
3131- index += 2
3232- }
3333-3434- return args
3535-}()
3636-3737-var HTTP_DOMAIN = func() string {
3838- domain := os.Getenv("HTTP_DOMAIN")
3939- if domain == "" {
4040- return "https://arimelody.me"
4141- }
4242- return domain
4343-}()
4444-4545-var DB *sqlx.DB
+45-7
global/funcs.go
···11package global
2233import (
44- "fmt"
55- "net/http"
66- "strconv"
77- "time"
44+ "fmt"
55+ "math/rand"
66+ "net/http"
77+ "strconv"
88+ "time"
8999- "arimelody-web/colour"
1010+ "arimelody-web/colour"
1011)
11121313+var PoweredByStrings = []string{
1414+ "nerd rage",
1515+ "estrogen",
1616+ "your mother",
1717+ "awesome powers beyond comprehension",
1818+ "jared",
1919+ "the weight of my sins",
2020+ "the arc reactor",
2121+ "AA batteries",
2222+ "15 euro solar panel from ebay",
2323+ "magnets, how do they work",
2424+ "a fax machine",
2525+ "dell optiplex",
2626+ "a trans girl's nintendo wii",
2727+ "BASS",
2828+ "electricity, duh",
2929+ "seven hamsters in a big wheel",
3030+ "girls",
3131+ "mzungu hosting",
3232+ "golang",
3333+ "the state of the world right now",
3434+ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)",
3535+ "the good folks at aperture science",
3636+ "free2play CDs",
3737+ "aridoodle",
3838+ "the love of creating",
3939+ "not for the sake of art; not for the sake of money; we like painting naked people",
4040+ "30 billion dollars in VC funding",
4141+}
4242+1243func DefaultHeaders(next http.Handler) http.Handler {
1344 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1445 w.Header().Add("Server", "arimelody.me")
1515- w.Header().Add("Cache-Control", "max-age=2592000")
4646+ w.Header().Add("Do-Not-Stab", "1")
4747+ w.Header().Add("X-Clacks-Overhead", "GNU Terry Pratchett")
4848+ w.Header().Add("X-Hacker", "spare me please")
4949+ w.Header().Add("X-Robots-TXT", "'; DROP TABLE pages;")
5050+ w.Header().Add("X-Thinking-With", "Portals")
5151+ w.Header().Add(
5252+ "X-Powered-By",
5353+ PoweredByStrings[rand.Intn(len(PoweredByStrings))],
5454+ )
1655 next.ServeHTTP(w, r)
1756 })
1857}
···6099 r.Header["User-Agent"][0])
61100 })
62101}
6363-