Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
equa | 01b214b29f | |
equa | 955aeed8b5 |
|
@ -1,15 +1,10 @@
|
||||||
*.swp
|
*.swp
|
||||||
bin/
|
|
||||||
cmd/launcher/launcher
|
cmd/launcher/launcher
|
||||||
cmd/request/request
|
cmd/request/request
|
||||||
cmd/contrib/contrib
|
cmd/contrib/contrib
|
||||||
cmd/visit/visit
|
cmd/visit/visit
|
||||||
|
cmd/signup/signup
|
||||||
cmd/review/review
|
cmd/review/review
|
||||||
cmd/stats/stats
|
cmd/welcome/welcome
|
||||||
external/cmd/signup/signup
|
cmd/createkeyfile/createkeyfile
|
||||||
external/cmd/welcome/welcome
|
cmd/registeruser/registeruser
|
||||||
external/cmd/help/help
|
|
||||||
external/cmd/helpers/emailtouser/emailtouser
|
|
||||||
external/cmd/helpers/createkeyfile/createkeyfile
|
|
||||||
external/cmd/helpers/registeruser/registeruser
|
|
||||||
external/cmd/helpers/appendkeyfile/appendkeyfile
|
|
||||||
|
|
71
Makefile
71
Makefile
|
@ -1,71 +0,0 @@
|
||||||
all: cmds external
|
|
||||||
|
|
||||||
install: all
|
|
||||||
cp bin/tma /town/bin/
|
|
||||||
cp bin/launcher /usr/local/bin/town
|
|
||||||
cp bin/stats /town/bin/
|
|
||||||
cp bin/contrib /town/bin/
|
|
||||||
cp bin/con /town/bin/
|
|
||||||
cp bin/request /town/bin/
|
|
||||||
cp bin/appendkeyfile /town/bin/
|
|
||||||
cp bin/createkeyfile /town/bin/
|
|
||||||
cp bin/emailtouser /town/bin/
|
|
||||||
cp bin/registeruser /town/bin/
|
|
||||||
cp bin/help /town/bin/external/
|
|
||||||
cp bin/welcome /town/bin/external/
|
|
||||||
cp bin/signup /town/bin/external/
|
|
||||||
cp bin/review /town/bin/
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf bin
|
|
||||||
|
|
||||||
external: bin/help bin/welcome bin/signup exthelpers
|
|
||||||
|
|
||||||
bin/help: external/cmd/help/main.go bin
|
|
||||||
go build -o bin/help ./external/cmd/help
|
|
||||||
|
|
||||||
bin/welcome: external/cmd/welcome/main.go bin
|
|
||||||
go build -o bin/welcome ./external/cmd/welcome
|
|
||||||
|
|
||||||
bin/signup: external/cmd/signup/main.go bin
|
|
||||||
go build -o bin/signup ./external/cmd/signup
|
|
||||||
|
|
||||||
exthelpers: bin/appendkeyfile bin/createkeyfile bin/emailtouser bin/registeruser
|
|
||||||
|
|
||||||
bin/appendkeyfile: external/cmd/helpers/appendkeyfile/main.go bin
|
|
||||||
go build -o bin/appendkeyfile ./external/cmd/helpers/appendkeyfile
|
|
||||||
|
|
||||||
bin/createkeyfile: external/cmd/helpers/createkeyfile/main.go bin
|
|
||||||
go build -o bin/createkeyfile ./external/cmd/helpers/createkeyfile
|
|
||||||
|
|
||||||
bin/emailtouser: external/cmd/helpers/emailtouser/main.go bin
|
|
||||||
go build -o bin/emailtouser ./external/cmd/helpers/emailtouser
|
|
||||||
|
|
||||||
bin/registeruser: external/cmd/helpers/registeruser/main.go bin
|
|
||||||
go build -o bin/registeruser ./external/cmd/helpers/registeruser
|
|
||||||
|
|
||||||
cmds: bin/launcher bin/stats bin/con bin/contrib bin/request bin/review bin/tma
|
|
||||||
|
|
||||||
bin/launcher: cmd/launcher/main.go bin
|
|
||||||
go build -o bin/launcher ./cmd/launcher
|
|
||||||
|
|
||||||
bin/stats: cmd/stats/main.go bin
|
|
||||||
go build -o bin/stats ./cmd/stats
|
|
||||||
|
|
||||||
bin/contrib: cmd/contrib/main.go bin
|
|
||||||
go build -o bin/contrib ./cmd/contrib
|
|
||||||
|
|
||||||
bin/con: cmd/towncon/main.go bin
|
|
||||||
go build -o bin/con ./cmd/towncon
|
|
||||||
|
|
||||||
bin/request: cmd/request/main.go bin
|
|
||||||
go build -o bin/request ./cmd/request
|
|
||||||
|
|
||||||
bin/review: cmd/review/main.go bin
|
|
||||||
go build -o bin/review ./cmd/review
|
|
||||||
|
|
||||||
bin/tma: cmd/tma/main.go bin
|
|
||||||
go build -o bin/tma ./cmd/tma
|
|
||||||
|
|
||||||
bin:
|
|
||||||
mkdir -p bin
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
|
@ -70,6 +71,8 @@ func validExec(execPath string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func submit(opts *contribOpts) error {
|
func submit(opts *contribOpts) error {
|
||||||
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
|
|
||||||
var cmdName string
|
var cmdName string
|
||||||
var category string
|
var category string
|
||||||
var shortDesc string
|
var shortDesc string
|
||||||
|
|
|
@ -76,13 +76,12 @@ func main() {
|
||||||
quit(fmt.Sprintf("file contents look wrong: %s", string(stdin)), 8)
|
quit(fmt.Sprintf("file contents look wrong: %s", string(stdin)), 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = f.Write(stdin)
|
n, err = f.Write(stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
quit(err.Error(), 9)
|
quit(err.Error(), 9)
|
||||||
|
} else if n == 0 {
|
||||||
|
quit("wrote nothing to keyfile", 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = f.WriteString("\n")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
|
@ -0,0 +1,7 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("TODO")
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
|
||||||
|
"git.tilde.town/tildetown/town/towndb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func confirmRename() {
|
||||||
|
fmt.Println("this will kill all of the user's current processes. if you're doing this live you should give them a good contact if things break")
|
||||||
|
|
||||||
|
var text string
|
||||||
|
|
||||||
|
for text != "y\n" {
|
||||||
|
fmt.Print("enter 'y' to continue: ")
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
text, _ = reader.ReadString('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func killUser(name string) (err error) {
|
||||||
|
cmd := exec.Command("pkill", "-SIGKILL", "-u", name)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err = cmd.Run(); err != nil {
|
||||||
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
|
if exiterr.ExitCode() == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func usermod(old_name string, new_name string) (err error) {
|
||||||
|
cmd := exec.Command(
|
||||||
|
"usermod",
|
||||||
|
"-l", new_name,
|
||||||
|
"-m",
|
||||||
|
"-d", path.Join("/home", new_name),
|
||||||
|
old_name,
|
||||||
|
)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err = cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = exec.Command("groupmod", "-n", new_name, old_name)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err = cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func renameDb(old_name string, new_name string) (err error) {
|
||||||
|
db, err := towndb.ConnectDB()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = towndb.RenameUser(db, old_name, new_name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(text string) {
|
||||||
|
fmt.Println(text)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 3 {
|
||||||
|
quit("usage: rename username new_username")
|
||||||
|
}
|
||||||
|
|
||||||
|
old_name := os.Args[1]
|
||||||
|
new_name := os.Args[2]
|
||||||
|
|
||||||
|
user, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
quit(fmt.Sprintf("couldn't get user info: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Uid != "0" {
|
||||||
|
quit("must be run as root")
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmRename()
|
||||||
|
|
||||||
|
if err := killUser(old_name); err != nil {
|
||||||
|
quit(fmt.Sprintf("pkill failed: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("killed old processes")
|
||||||
|
|
||||||
|
if err := usermod(old_name, new_name); err != nil {
|
||||||
|
quit(fmt.Sprintf("unix user rename failed: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("renamed unix user")
|
||||||
|
|
||||||
|
if err := renameDb(old_name, new_name); err != nil {
|
||||||
|
fmt.Println(fmt.Sprintf("couldn't rename user in /town/var/town.db: %v", err))
|
||||||
|
fmt.Println("something might be amiss with town.db")
|
||||||
|
} else {
|
||||||
|
fmt.Println("renamed user in /town/var/town.db")
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/email"
|
"git.tilde.town/tildetown/town/email"
|
||||||
"git.tilde.town/tildetown/town/invites"
|
"git.tilde.town/tildetown/town/invites"
|
||||||
|
@ -32,20 +31,20 @@ See you on the server,
|
||||||
func loadPassword() (string, error) {
|
func loadPassword() (string, error) {
|
||||||
f, err := os.Open("/town/docs/smtp.pw")
|
f, err := os.Open("/town/docs/smtp.pw")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("could not open smtp password file: %w", err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
pw := make([]byte, 100)
|
pw := make([]byte, 100)
|
||||||
|
|
||||||
n, err := f.Read(pw)
|
n, err := f.Read(pw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("could not read smtp password file: %w", err)
|
return "", err
|
||||||
}
|
}
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return "", errors.New("smtp password file was empty")
|
return "", errors.New("read nothing")
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSpace(string(pw[0:n])), nil
|
return string(pw[0:n]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendInviteEmail(invite invites.Invite) error {
|
func sendInviteEmail(invite invites.Invite) error {
|
||||||
|
|
|
@ -153,6 +153,8 @@ func _main() error {
|
||||||
|
|
||||||
r := newReviewer(signupDB, u.Username)
|
r := newReviewer(signupDB, u.Username)
|
||||||
|
|
||||||
|
rand.Seed(time.Now().Unix())
|
||||||
|
|
||||||
su := models.TownSignup{}
|
su := models.TownSignup{}
|
||||||
|
|
||||||
signups, err := su.All(signupDB)
|
signups, err := su.All(signupDB)
|
||||||
|
@ -435,8 +437,7 @@ func _main() error {
|
||||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
// no match or interrupt. who cares
|
// no match or interrupt. who cares
|
||||||
switch exiterr.ExitCode() {
|
switch exiterr.ExitCode() {
|
||||||
case 1:
|
case 1: case 130:
|
||||||
case 130:
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func _main(l *log.Logger, db *sql.DB) error {
|
func _main(l *log.Logger, db *sql.DB) error {
|
||||||
|
l.Println("starting a session")
|
||||||
pages := tview.NewPages()
|
pages := tview.NewPages()
|
||||||
mainFlex := tview.NewFlex()
|
mainFlex := tview.NewFlex()
|
||||||
input := tview.NewTextArea()
|
input := tview.NewTextArea()
|
||||||
|
@ -304,6 +305,7 @@ func _main(l *log.Logger, db *sql.DB) error {
|
||||||
/nod
|
/nod
|
||||||
and pressing enter will cause you to nod. some other verbs: /quit /look`))
|
and pressing enter will cause you to nod. some other verbs: /quit /look`))
|
||||||
case "quit":
|
case "quit":
|
||||||
|
l.Println("got /quit")
|
||||||
app.Stop()
|
app.Stop()
|
||||||
case "look":
|
case "look":
|
||||||
fmt.Fprintln(msgScroll, "")
|
fmt.Fprintln(msgScroll, "")
|
||||||
|
@ -312,6 +314,8 @@ func _main(l *log.Logger, db *sql.DB) error {
|
||||||
if !sm.Advance() {
|
if !sm.Advance() {
|
||||||
fmt.Fprintln(msgScroll, "you nod, but nothing happens.")
|
fmt.Fprintln(msgScroll, "you nod, but nothing happens.")
|
||||||
fmt.Fprintln(msgScroll)
|
fmt.Fprintln(msgScroll)
|
||||||
|
} else {
|
||||||
|
l.Println("advancing scene")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -330,7 +334,10 @@ func _main(l *log.Logger, db *sql.DB) error {
|
||||||
msgScroll.ScrollToEnd()
|
msgScroll.ScrollToEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
defer db.Close()
|
defer func() {
|
||||||
|
l.Println("exiting")
|
||||||
|
db.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||||
switch event.Key() {
|
switch event.Key() {
|
101
cmd/tma/main.go
101
cmd/tma/main.go
|
@ -1,101 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/towndb"
|
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dbs struct {
|
|
||||||
Invites *sql.DB
|
|
||||||
Signups *sql.DB
|
|
||||||
Users *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func connect() (*dbs, error) {
|
|
||||||
users, err := sql.Open("sqlite3", "/town/var/town.db?mode=r")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
signups, err := sql.Open("sqlite3", "/town/var/signups/signups.db?mode=r")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
invites, err := sql.Open("sqlite3", "/town/var/invites/invites.db?mode=r")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &dbs{
|
|
||||||
Invites: invites,
|
|
||||||
Signups: signups,
|
|
||||||
Users: users,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func _main(argv []string) error {
|
|
||||||
if len(argv) < 3 {
|
|
||||||
return errors.New("want two args (user|email) <lookup>")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch argv[1] {
|
|
||||||
case "user":
|
|
||||||
return userLookup(argv[2])
|
|
||||||
case "email":
|
|
||||||
return emailLookup(argv[2])
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("idk %s", argv[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func userLookup(username string) error {
|
|
||||||
dbs, err := connect()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := dbs.Users.Prepare("SELECT id,created,username,state,admin FROM users WHERE username = ?")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("user select prepare failed: %w", err)
|
|
||||||
}
|
|
||||||
var created string
|
|
||||||
tu := towndb.TownUser{}
|
|
||||||
err = stmt.QueryRow(username).Scan(&tu.ID, &created, &tu.Username, &tu.State, &tu.IsAdmin)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to select user: %w", err)
|
|
||||||
}
|
|
||||||
i, err := strconv.ParseInt(created, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("time what? %w", err)
|
|
||||||
}
|
|
||||||
tu.Created = time.Unix(i, 0)
|
|
||||||
|
|
||||||
fmt.Println("id,created,username,state,admin")
|
|
||||||
fmt.Printf("%d,%s,%s,%s,%v\n", tu.ID, tu.Created, tu.Username, tu.State, tu.IsAdmin)
|
|
||||||
|
|
||||||
// TODO rest
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func emailLookup(address string) error {
|
|
||||||
// TODO
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := _main(os.Args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
[0;1;35;95m.-[0;1;31;91m''[0;1;33;93m'-[0;1;32;92m.[0m
|
|
||||||
[0;1;35;95m'[0m [0;1;33;93m_[0m [0;1;34;94m\[0m
|
|
||||||
[0;1;35;95m/[0m [0;1;33;93m/[0;1;32;92m`[0m [0;1;36;96m'.[0m [0;1;35;95m\[0m [0;1;32;92m_[0;1;36;96m..[0;1;34;94m._[0m
|
|
||||||
[0;1;31;91m.[0m [0;1;32;92m|[0m [0;1;35;95m\[0m [0;1;31;91m'[0m [0;1;34;94m_[0m [0;1;33;93m_[0m [0;1;32;92m.[0;1;36;96m'[0m [0;1;31;91m'.[0m
|
|
||||||
[0;1;35;95m.[0;1;31;91m|[0m [0;1;33;93m|[0m [0;1;36;96m'[0m [0;1;31;91m|[0m [0;1;32;92m'/[0;1;36;96m\[0m [0;1;35;95m\[0;1;31;91m\[0m [0;1;32;92m//[0;1;36;96m.[0m [0;1;35;95m.-[0;1;31;91m.[0m [0;1;32;92m.[0m
|
|
||||||
[0;1;35;95m.[0;1;31;91m'[0m [0;1;33;93m|_[0;1;32;92m\[0m [0;1;34;94m\[0m [0;1;33;93m/[0m [0;1;32;92m/[0m [0;1;36;96m`[0;1;34;94m\\[0m [0;1;31;91m//[0;1;33;93m\\[0m [0;1;32;92m/[0;1;36;96m/[0m [0;1;34;94m|[0m [0;1;35;95m'[0m [0;1;33;93m'[0m [0;1;36;96m|[0m
|
|
||||||
[0;1;35;95m.[0;1;31;91m'[0m [0;1;36;96m|`[0;1;34;94m.[0m [0;1;31;91m`[0m [0;1;33;93m..[0;1;32;92m'[0m [0;1;36;96m/[0m [0;1;35;95m\[0;1;31;91m`/[0;1;33;93m/[0m [0;1;32;92m\[0;1;36;96m'/[0m [0;1;35;95m|[0m [0;1;31;91m|[0m [0;1;32;92m|[0m [0;1;34;94m|[0m
|
|
||||||
[0;1;31;91m'-[0;1;33;93m-.[0m [0;1;36;96m.-[0;1;34;94m'[0m [0;1;31;91m'-[0;1;33;93m..[0;1;32;92m.-[0;1;36;96m'`[0m [0;1;33;93m\|[0m [0;1;36;96m|[0;1;34;94m/[0m [0;1;31;91m|[0m [0;1;33;93m|[0m [0;1;36;96m|[0m [0;1;35;95m|[0m
|
|
||||||
[0;1;32;92m|[0m [0;1;34;94m|[0m [0;1;32;92m'[0m [0;1;33;93m|[0m [0;1;32;92m|[0m [0;1;34;94m|[0m [0;1;31;91m|[0m
|
|
||||||
[0;1;36;96m|[0m [0;1;35;95m|[0m [0;1;32;92m|[0m [0;1;36;96m|[0m [0;1;35;95m|[0m [0;1;33;93m|[0m
|
|
||||||
[0;1;34;94m|[0m [0;1;31;91m'.[0;1;33;93m'[0m [0;1;36;96m|[0m [0;1;34;94m|[0m [0;1;31;91m|[0m [0;1;32;92m|[0m
|
|
||||||
[0;1;35;95m|[0m [0;1;33;93m/[0m [0;1;34;94m|[0m [0;1;35;95m|[0m [0;1;33;93m|[0m [0;1;36;96m|[0m
|
|
||||||
[0;1;31;91m`[0;1;33;93m'-[0;1;32;92m'[0m [0;1;35;95m'-[0;1;31;91m-'[0m [0;1;32;92m'[0;1;36;96m--[0;1;34;94m'[0m
|
|
||||||
[0;1;36;96m_[0;1;34;94m..[0;1;35;95m._[0m [0;1;36;96m.[0;1;34;94m-'[0;1;35;95m''[0;1;31;91m-.[0m
|
|
||||||
[0;1;36;96m.-[0;1;34;94m'_[0;1;35;95m..[0;1;31;91m._[0;1;33;93m''[0;1;32;92m.[0m [0;1;34;94m'[0m [0;1;31;91m_[0m [0;1;32;92m\[0m
|
|
||||||
[0;1;36;96m.'[0m [0;1;34;94m.[0;1;35;95m'[0m [0;1;32;92m'[0;1;36;96m.\[0;1;34;94m/[0m [0;1;31;91m/`[0m [0;1;33;93m'[0;1;32;92m.[0m [0;1;34;94m\[0m [0;1;31;91m_[0;1;33;93m..[0;1;32;92m._[0m
|
|
||||||
[0;1;36;96m/[0m [0;1;34;94m.[0;1;35;95m'[0m [0;1;34;94m.[0m [0;1;31;91m|[0m [0;1;36;96m\[0m [0;1;35;95m'[0m [0;1;31;91m.[0;1;33;93m'[0m [0;1;34;94m'.[0m
|
|
||||||
[0;1;34;94m.[0m [0;1;35;95m'[0m [0;1;35;95m|[0m [0;1;33;93m'[0m [0;1;35;95m|[0m [0;1;31;91m'[0;1;33;93m.[0m [0;1;36;96m.-[0;1;34;94m.[0m [0;1;31;91m.[0m
|
|
||||||
[0;1;35;95m|[0m [0;1;31;91m|[0m [0;1;31;91m\[0m [0;1;36;96m\[0m [0;1;31;91m/[0m [0;1;33;93m/[0m [0;1;32;92m|[0m [0;1;36;96m'[0m [0;1;35;95m'[0m [0;1;33;93m|[0m
|
|
||||||
[0;1;31;91m|[0m [0;1;33;93m|[0m [0;1;32;92m`.[0m [0;1;34;94m`[0m [0;1;35;95m.[0;1;31;91m.'[0m [0;1;33;93m/[0m [0;1;36;96m|[0m [0;1;34;94m|[0m [0;1;31;91m|[0m [0;1;32;92m|[0m
|
|
||||||
[0;1;33;93m.[0m [0;1;32;92m'[0m [0;1;34;94m'[0;1;35;95m-.[0;1;31;91m..[0;1;33;93m-'[0;1;32;92m`[0m [0;1;34;94m|[0m [0;1;35;95m|[0m [0;1;33;93m|[0m [0;1;36;96m|[0m
|
|
||||||
[0;1;32;92m\[0m [0;1;36;96m'[0;1;34;94m.[0m [0;1;36;96m.[0m [0;1;35;95m|[0m [0;1;31;91m|[0m [0;1;32;92m|[0m [0;1;34;94m|[0m
|
|
||||||
[0;1;34;94m'.[0m [0;1;35;95m`[0;1;31;91m._[0;1;33;93m__[0;1;32;92m__[0;1;36;96m.-[0;1;34;94m'/[0m [0;1;31;91m|[0m [0;1;33;93m|[0m [0;1;36;96m|[0m [0;1;35;95m|[0m
|
|
||||||
[0;1;31;91m`-[0;1;33;93m._[0;1;32;92m__[0;1;36;96m__[0;1;34;94m_[0m [0;1;35;95m/[0m [0;1;33;93m|[0m [0;1;32;92m|[0m [0;1;34;94m|[0m [0;1;31;91m|[0m
|
|
||||||
[0;1;35;95m`[0m [0;1;32;92m|[0m [0;1;36;96m|[0m [0;1;35;95m|[0m [0;1;33;93m|[0m
|
|
||||||
[0;1;36;96m'-[0;1;34;94m-'[0m [0;1;31;91m'[0;1;33;93m--[0;1;32;92m'[0m
|
|
|
@ -1,32 +0,0 @@
|
||||||
[0;1;36;96m,-[0;1;34;94m--[0;1;35;95m-,[0m
|
|
||||||
[0;1;36;96m,/[0m [0;1;35;95m.[0;1;31;91m`|[0m [0;1;32;92m,-[0;1;36;96m--[0;1;34;94m-.[0;1;35;95m.[0m [0;1;33;93m,[0;1;32;92m--[0;1;36;96m.[0m
|
|
||||||
[0;1;36;96m,`[0m [0;1;35;95m.[0;1;31;91m'[0m [0;1;33;93m:[0m [0;1;32;92m/[0m [0;1;34;94m/[0m [0;1;31;91m\[0m [0;1;33;93m.[0;1;32;92m--[0;1;36;96m-.[0m [0;1;33;93m,[0;1;32;92m--[0;1;36;96m.'[0;1;34;94m|[0m
|
|
||||||
[0;1;36;96m;[0m [0;1;35;95m;[0m [0;1;32;92m/[0;1;36;96m/[0m [0;1;35;95m.[0m [0;1;32;92m:[0m [0;1;32;92m/.[0m [0;1;36;96m.[0;1;34;94m/|[0m [0;1;31;91m,[0;1;33;93m--[0;1;32;92m,:[0m [0;1;34;94m:[0m [0;1;35;95m|[0m
|
|
||||||
[0;1;36;96m.'[0;1;34;94m__[0;1;35;95m_,[0;1;31;91m/[0m [0;1;32;92m,[0;1;36;96m'.[0m [0;1;35;95m/[0m [0;1;33;93m;[0;1;32;92m.[0m [0;1;36;96m\[0m [0;1;33;93m.-[0;1;32;92m-'[0;1;36;96m.[0m [0;1;34;94m'[0m [0;1;35;95m;[0;1;31;91m,`[0;1;33;93m--[0;1;32;92m.'[0;1;36;96m`|[0m [0;1;35;95m'[0m [0;1;31;91m:[0m
|
|
||||||
[0;1;34;94m|[0m [0;1;31;91m:[0m [0;1;36;96m|[0;1;34;94m.[0m [0;1;31;91m;[0m [0;1;32;92m/[0m [0;1;36;96m`[0m [0;1;34;94m;[0m [0;1;33;93m/[0;1;32;92m__[0;1;36;96m./[0m [0;1;34;94m\[0m [0;1;35;95m:[0m [0;1;31;91m|[0;1;33;93m|[0m [0;1;36;96m:[0m [0;1;34;94m:[0m [0;1;31;91m|[0m [0;1;33;93m|[0m
|
|
||||||
[0;1;35;95m;[0m [0;1;33;93m|[0;1;32;92m.'[0;1;36;96m;[0m [0;1;34;94m;[0;1;35;95m;[0m [0;1;33;93m|[0m [0;1;32;92m;[0m [0;1;36;96m\[0m [0;1;34;94m;[0m [0;1;35;95m|[0m [0;1;31;91m.[0;1;33;93m--[0;1;32;92m'.[0m [0;1;34;94m'[0m [0;1;31;91m\'[0m [0;1;33;93m.[0;1;32;92m:[0m [0;1;34;94m|[0m [0;1;31;91m\[0m [0;1;33;93m|[0m [0;1;32;92m:[0m
|
|
||||||
[0;1;31;91m`-[0;1;33;93m--[0;1;32;92m-'[0m [0;1;34;94m|[0m [0;1;35;95m|[0;1;31;91m|[0m [0;1;32;92m:[0m [0;1;36;96m|[0m [0;1;34;94m;[0m [0;1;35;95m|[0m [0;1;31;91m'[0;1;33;93m/_[0;1;32;92m__[0;1;36;96m/[0m [0;1;34;94m\[0m [0;1;35;95m|[0m [0;1;33;93m'[0m [0;1;32;92m'[0;1;36;96m|[0m [0;1;35;95m:[0m [0;1;31;91m'[0m [0;1;33;93m'[0;1;32;92m;[0m [0;1;36;96m|[0m
|
|
||||||
[0;1;36;96m'[0m [0;1;35;95m:[0m [0;1;31;91m;[0;1;33;93m.[0m [0;1;36;96m|[0m [0;1;34;94m'[0m [0;1;35;95m'[0m [0;1;31;91m'[0m [0;1;33;93m:[0;1;32;92m;[0m [0;1;34;94m\[0m [0;1;35;95m\[0;1;31;91m;[0m [0;1;36;96m:[0;1;34;94m'[0m [0;1;31;91m'[0m [0;1;33;93m;.[0m [0;1;34;94m;[0m
|
|
||||||
[0;1;34;94m|[0m [0;1;31;91m|[0m [0;1;33;93m'[0;1;32;92m'[0m [0;1;34;94m;[0m [0;1;35;95m\[0;1;31;91m;[0m [0;1;33;93m/[0m [0;1;32;92m|[0m [0;1;36;96m\[0m [0;1;35;95m;[0m [0;1;33;93m`[0m [0;1;34;94m|[0;1;35;95m|[0m [0;1;33;93m|[0m [0;1;32;92m|[0m [0;1;36;96m\[0m [0;1;35;95m|[0m
|
|
||||||
[0;1;35;95m'[0m [0;1;33;93m:[0m [0;1;32;92m|[0m [0;1;36;96m\[0m [0;1;35;95m\[0m [0;1;33;93m',[0m [0;1;36;96m/[0m [0;1;35;95m.[0m [0;1;33;93m\[0m [0;1;36;96m.[0;1;34;94m\[0m [0;1;35;95m;[0;1;31;91m'[0m [0;1;32;92m:[0m [0;1;36;96m|[0m [0;1;34;94m;[0m [0;1;35;95m.[0;1;31;91m'[0m
|
|
||||||
[0;1;31;91m;[0m [0;1;32;92m|.[0;1;36;96m'[0m [0;1;35;95m;[0m [0;1;33;93m:[0m [0;1;36;96m/[0m [0;1;31;91m\[0m [0;1;32;92m\[0m [0;1;34;94m'[0m [0;1;35;95m\[0m [0;1;31;91m|[0;1;33;93m|[0m [0;1;36;96m|[0m [0;1;34;94m'`[0;1;35;95m--[0;1;31;91m'[0m
|
|
||||||
[0;1;33;93m'-[0;1;32;92m--[0;1;36;96m'[0m [0;1;31;91m\[0m [0;1;32;92m\[0m [0;1;36;96m.[0;1;34;94m'[0m [0;1;32;92m:[0m [0;1;34;94m'[0m [0;1;35;95m|[0;1;31;91m--[0;1;33;93m"[0m [0;1;32;92m'[0m [0;1;34;94m:[0m [0;1;35;95m|[0m
|
|
||||||
[0;1;32;92m`-[0;1;36;96m--[0;1;34;94m`[0m [0;1;36;96m\[0m [0;1;35;95m\[0m [0;1;31;91m;[0m [0;1;36;96m;[0m [0;1;35;95m|.[0;1;31;91m'[0m
|
|
||||||
[0;1;35;95m'-[0;1;31;91m--[0;1;33;93m"[0m [0;1;34;94m'-[0;1;35;95m--[0;1;31;91m'[0m
|
|
||||||
|
|
||||||
|
|
||||||
[0;1;35;95m,[0;1;31;91m--[0;1;33;93m--[0;1;32;92m..[0m [0;1;36;96m,-[0;1;34;94m-.[0m
|
|
||||||
[0;1;31;91m,-[0;1;33;93m--[0;1;32;92m-.[0;1;36;96m.[0m [0;1;31;91m/[0m [0;1;32;92m/[0m [0;1;34;94m\[0m [0;1;36;96m,-[0;1;34;94m-.[0;1;35;95m'|[0m
|
|
||||||
[0;1;31;91m/[0m [0;1;32;92m/[0m [0;1;34;94m\[0m [0;1;31;91m/[0m [0;1;32;92m.[0m [0;1;35;95m:[0m [0;1;32;92m,-[0;1;36;96m-,[0;1;34;94m:[0m [0;1;35;95m:[0m [0;1;31;91m|[0m
|
|
||||||
[0;1;33;93m|[0m [0;1;36;96m:[0m [0;1;31;91m:[0m [0;1;33;93m.[0m [0;1;36;96m/[0m [0;1;35;95m;.[0m [0;1;33;93m\,[0;1;32;92m`-[0;1;36;96m-.[0;1;34;94m'`[0;1;35;95m|[0m [0;1;31;91m'[0m [0;1;33;93m:[0m
|
|
||||||
[0;1;32;92m.[0m [0;1;34;94m|[0m [0;1;35;95m;[0;1;31;91m.[0m [0;1;33;93m/.[0m [0;1;36;96m;[0m [0;1;35;95m/[0m [0;1;33;93m`[0m [0;1;32;92m;|[0m [0;1;34;94m:[0m [0;1;31;91m:[0m [0;1;33;93m|[0m [0;1;32;92m|[0m
|
|
||||||
[0;1;36;96m.[0m [0;1;35;95m;[0m [0;1;31;91m/-[0;1;33;93m-`[0m [0;1;32;92m;[0m [0;1;34;94m|[0m [0;1;31;91m;[0m [0;1;33;93m\[0m [0;1;32;92m;[0m [0;1;36;96m|:[0m [0;1;35;95m|[0m [0;1;33;93m\[0m [0;1;32;92m|[0m [0;1;36;96m:[0m
|
|
||||||
[0;1;34;94m;[0m [0;1;31;91m|[0m [0;1;33;93m;[0m [0;1;36;96m|[0m [0;1;35;95m:[0m [0;1;33;93m|[0m [0;1;32;92m;[0m [0;1;36;96m|[0m [0;1;34;94m'|[0m [0;1;31;91m:[0m [0;1;33;93m'[0m [0;1;36;96m';[0m [0;1;34;94m|[0m
|
|
||||||
[0;1;35;95m|[0m [0;1;33;93m:[0m [0;1;32;92m|[0m [0;1;34;94m.[0m [0;1;31;91m|[0m [0;1;32;92m'[0m [0;1;36;96m'[0m [0;1;34;94m'[0m [0;1;35;95m:'[0m [0;1;33;93m'[0m [0;1;32;92m;[0;1;36;96m.[0m [0;1;35;95m;[0m
|
|
||||||
[0;1;31;91m.[0m [0;1;32;92m|[0m [0;1;36;96m'_[0;1;34;94m__[0m [0;1;35;95m'[0m [0;1;33;93m;[0m [0;1;36;96m\;[0m [0;1;34;94m/[0m [0;1;31;91m||[0m [0;1;32;92m|[0m [0;1;36;96m|[0m [0;1;34;94m\[0m [0;1;31;91m|[0m
|
|
||||||
[0;1;33;93m'[0m [0;1;36;96m;[0m [0;1;34;94m:[0m [0;1;35;95m.'[0;1;31;91m|[0m [0;1;33;93m\[0m [0;1;36;96m\[0m [0;1;34;94m'[0;1;35;95m,[0m [0;1;31;91m/[0m [0;1;33;93m'[0m [0;1;36;96m:[0m [0;1;34;94m|[0m [0;1;31;91m;[0m [0;1;33;93m.'[0m
|
|
||||||
[0;1;32;92m'[0m [0;1;34;94m|[0m [0;1;35;95m'/[0m [0;1;33;93m:[0m [0;1;32;92m;[0m [0;1;34;94m:[0m [0;1;33;93m/[0m [0;1;32;92m|[0m [0;1;34;94m|[0m [0;1;35;95m'[0;1;31;91m`-[0;1;33;93m-'[0m
|
|
||||||
[0;1;36;96m|[0m [0;1;35;95m:[0m [0;1;33;93m/[0m [0;1;34;94m\[0m [0;1;31;91m\[0m [0;1;33;93m.'[0m [0;1;36;96m'[0m [0;1;35;95m:[0m [0;1;31;91m|[0m
|
|
||||||
[0;1;34;94m\[0m [0;1;31;91m\[0m [0;1;33;93m.[0;1;32;92m'[0m [0;1;35;95m`[0;1;31;91m--[0;1;33;93m-`[0m [0;1;34;94m;[0m [0;1;31;91m|[0;1;33;93m.'[0m
|
|
||||||
[0;1;31;91m`-[0;1;33;93m--[0;1;32;92m`[0m [0;1;35;95m'[0;1;31;91m--[0;1;33;93m-'[0m
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
[0;1;35;95m__[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m\\[0;1;36;96m\\[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_/[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_/[0;1;31;91m\\[0;1;33;93m\_[0m
|
|
||||||
[0;1;31;91m_[0;1;33;93m\/[0;1;32;92m//[0;1;36;96m//[0;1;34;94m//[0;1;35;95m\\[0;1;31;91m\/[0;1;33;93m//[0;1;32;92m//[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m//[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m\\[0;1;31;91m\_[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m_\[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m_[0m
|
|
||||||
[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_\[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m_/[0;1;33;93m\\[0;1;32;92m\/[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m//[0;1;31;91m\\[0;1;33;93m\_[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_\[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m\\[0;1;34;94m\_[0m
|
|
||||||
[0;1;36;96m_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_/[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m_\[0;1;33;93m//[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m__[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m\\[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m_\[0;1;34;94m/\[0;1;35;95m\\[0;1;31;91m_[0m
|
|
||||||
[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m/\[0;1;35;95m\\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m/\[0;1;36;96m\\[0;1;34;94m__[0;1;35;95m_\[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m\/[0;1;31;91m\\[0;1;33;93m\_[0m
|
|
||||||
[0;1;31;91m_[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m\\[0;1;31;91m\_[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m/\[0;1;35;95m\\[0;1;31;91m__[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m_[0m
|
|
||||||
[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_\[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m//[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m_/[0;1;35;95m\\[0;1;31;91m\_[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m\\[0;1;34;94m\_[0m
|
|
||||||
[0;1;36;96m_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m//[0;1;34;94m/\[0;1;35;95m\\[0;1;31;91m\\[0;1;33;93m/_[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m\\[0;1;31;91m\_[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m_[0m
|
|
||||||
[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m//[0;1;34;94m/_[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m\/[0;1;31;91m//[0;1;33;93m//[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m//[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m//[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_\[0;1;31;91m//[0;1;33;93m/_[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m//[0;1;31;91m//[0;1;33;93m__[0m
|
|
||||||
[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m\\[0;1;36;96m\\[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_/[0;1;32;92m\\[0;1;36;96m\\[0;1;34;94m\_[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m/\[0;1;36;96m\\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_/[0;1;32;92m\\[0;1;36;96m\_[0m
|
|
||||||
[0;1;36;96m_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m//[0;1;32;92m//[0;1;36;96m//[0;1;34;94m//[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m/\[0;1;36;96m\\[0;1;34;94m//[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m__[0;1;36;96m\/[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m_[0m
|
|
||||||
[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_/[0;1;31;91m\\[0;1;33;93m\/[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_/[0;1;36;96m\\[0;1;34;94m\/[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m//[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m_\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m\\[0;1;31;91m\_[0m
|
|
||||||
[0;1;31;91m_[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m_/[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m_/[0;1;34;94m\\[0;1;35;95m\_[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m//[0;1;34;94m\\[0;1;35;95m\_[0;1;31;91m\/[0;1;33;93m\\[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m_\[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m_[0m
|
|
||||||
[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_\[0;1;32;92m/\[0;1;36;96m\\[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m_\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m_\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m_\[0;1;32;92m/\[0;1;36;96m\\[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\_[0m
|
|
||||||
[0;1;36;96m_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m\/[0;1;34;94m/\[0;1;35;95m\\[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m__[0;1;36;96m\/[0;1;34;94m\\[0;1;35;95m\_[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m_[0m
|
|
||||||
[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m\/[0;1;31;91m//[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m//[0;1;34;94m\\[0;1;35;95m\_[0;1;31;91m_/[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m_\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m\_[0m
|
|
||||||
[0;1;31;91m_[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_\[0;1;32;92m//[0;1;36;96m//[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m_\[0;1;35;95m//[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m\\[0;1;36;96m/_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m_[0m
|
|
||||||
[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m_\[0;1;35;95m//[0;1;31;91m//[0;1;33;93m//[0;1;32;92m//[0;1;36;96m/_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m\/[0;1;32;92m//[0;1;36;96m//[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_\[0;1;32;92m//[0;1;36;96m/_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m//[0;1;32;92m//[0;1;36;96m__[0m
|
|
|
@ -1,10 +0,0 @@
|
||||||
|
|
||||||
[0;1;33;93md[0;1;32;92m8P[0m
|
|
||||||
[0;1;33;93md8[0;1;32;92m88[0;1;36;96m88[0;1;34;94m8P[0m
|
|
||||||
[0;1;36;96m?8[0;1;34;94m8'[0m [0;1;31;91md[0;1;33;93m88[0;1;32;92m88[0;1;36;96mb[0m [0;1;34;94m?[0;1;35;95m88[0m [0;1;33;93md[0;1;32;92m8P[0m [0;1;34;94md8[0;1;35;95mP[0m [0;1;31;91m8[0;1;33;93m8b[0;1;32;92md8[0;1;36;96m8b[0m [0;1;33;93md8[0;1;32;92m88[0;1;36;96m8b[0m [0;1;34;94md[0;1;35;95m88[0;1;31;91m88[0;1;33;93mb[0m [0;1;36;96m88[0;1;34;94mbd[0;1;35;95m88[0;1;31;91mb[0m
|
|
||||||
[0;1;34;94m88[0;1;35;95mP[0m [0;1;33;93md8[0;1;32;92mP'[0m [0;1;36;96m?[0;1;34;94m88[0m [0;1;35;95md[0;1;31;91m88[0m [0;1;32;92md8[0;1;36;96mP'[0m [0;1;34;94md[0;1;35;95m8P[0;1;31;91m'[0m [0;1;33;93m8[0;1;32;92m8P[0;1;36;96m'[0m [0;1;34;94m?8[0;1;35;95mb[0m [0;1;33;93md[0;1;32;92m8P[0;1;36;96m'[0m [0;1;34;94m`P[0;1;35;95md8[0;1;31;91mP'[0m [0;1;33;93m?[0;1;32;92m88[0m [0;1;34;94m88[0;1;35;95mP'[0m [0;1;31;91m?[0;1;33;93m8b[0m
|
|
||||||
[0;1;35;95m88[0;1;31;91mb[0m [0;1;32;92m88[0;1;36;96mb[0m [0;1;34;94md[0;1;35;95m88[0m [0;1;31;91m?[0;1;33;93m8b[0m [0;1;32;92m,[0;1;36;96m88[0;1;34;94mb[0m [0;1;35;95m,8[0;1;31;91m8'[0m [0;1;32;92md8[0;1;36;96m8[0m [0;1;35;95m88[0;1;31;91mP[0m [0;1;32;92m8[0;1;36;96m8b[0m [0;1;31;91m88[0;1;33;93mb[0m [0;1;32;92md[0;1;36;96m88[0m [0;1;34;94md[0;1;35;95m88[0m [0;1;33;93m8[0;1;32;92m8P[0m
|
|
||||||
[0;1;31;91m`?[0;1;33;93m8b[0m [0;1;36;96m`?[0;1;34;94m88[0;1;35;95m88[0;1;31;91mP'[0m [0;1;33;93m`[0;1;32;92m?8[0;1;36;96m88[0;1;34;94mP'[0;1;35;95m88[0;1;31;91m8P[0;1;33;93m'[0m [0;1;32;92md[0;1;36;96m88[0;1;34;94m'[0m [0;1;31;91m88[0;1;33;93mb[0m [0;1;36;96m`[0;1;34;94m?8[0;1;35;95m88[0;1;31;91mP'[0;1;33;93m`?[0;1;32;92m88[0;1;36;96m88[0;1;34;94mP'[0;1;35;95md8[0;1;31;91m8'[0m [0;1;32;92m8[0;1;36;96m8b[0m
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,216 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/user"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/email"
|
|
||||||
"github.com/charmbracelet/glamour"
|
|
||||||
"github.com/charmbracelet/huh"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed banners/*
|
|
||||||
var banners embed.FS
|
|
||||||
|
|
||||||
//go:embed md/*
|
|
||||||
var md embed.FS
|
|
||||||
|
|
||||||
func banner() (string, error) {
|
|
||||||
dirs, err := banners.ReadDir("banners")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
mx := len(dirs)
|
|
||||||
ix := rand.Intn(mx)
|
|
||||||
content, err := banners.ReadFile(path.Join("banners", dirs[ix].Name()))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(content), nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func info() error {
|
|
||||||
infoContent, err := md.ReadFile("md/info.md")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
out, err := glamour.Render(string(infoContent), "dracula")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd := exec.Command("/usr/bin/bat")
|
|
||||||
cmd.Stdin = strings.NewReader(out)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
type physicalData struct {
|
|
||||||
Arriving string
|
|
||||||
Departing string
|
|
||||||
Transportation bool
|
|
||||||
Lodging bool
|
|
||||||
Allergies string
|
|
||||||
Couch bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type rsvpData struct {
|
|
||||||
Being string
|
|
||||||
PhysicalData *physicalData `yaml:"PhysicalData,omitempty"`
|
|
||||||
Freeform string
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendRSVPEmail(rd rsvpData) error {
|
|
||||||
bs, err := yaml.Marshal(rd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return email.SendLocalEmail("vilmibm", "RSVP TOWNCON24", string(bs))
|
|
||||||
}
|
|
||||||
|
|
||||||
func rsvp(o opts) error {
|
|
||||||
var being string
|
|
||||||
var arriving string
|
|
||||||
var departing string
|
|
||||||
var transportation bool
|
|
||||||
lodging := true
|
|
||||||
var allergies string
|
|
||||||
couch := true
|
|
||||||
var freeform string
|
|
||||||
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewSelect[string]().Title("How will you be attending?").
|
|
||||||
Options(
|
|
||||||
huh.NewOption("Digitally", "digital"),
|
|
||||||
huh.NewOption("Phyiscally", "physical"),
|
|
||||||
).Value(&being)),
|
|
||||||
|
|
||||||
// Physical attendee form
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().Title("When (day/time) are you arriving?").
|
|
||||||
Value(&arriving),
|
|
||||||
huh.NewInput().Title("When (day/time) are you departing?").
|
|
||||||
Value(&departing),
|
|
||||||
).WithHideFunc(func() bool {
|
|
||||||
return being != "physical"
|
|
||||||
}),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("Will you be staying overnight at the venue?").
|
|
||||||
Value(&lodging),
|
|
||||||
).WithHideFunc(func() bool {
|
|
||||||
return being != "physical"
|
|
||||||
}),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("If the need arises are you ok sleeping on a couch?").
|
|
||||||
Value(&couch),
|
|
||||||
).WithHideFunc(func() bool {
|
|
||||||
return being != "physical" && !lodging
|
|
||||||
}),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().Title("Do you have any food allergies I should be aware of?").
|
|
||||||
Value(&allergies),
|
|
||||||
huh.NewConfirm().Title("Will you need any help getting to the venue?").
|
|
||||||
Description("I have a car and have some ability to help getting people to the venue").
|
|
||||||
Value(&transportation),
|
|
||||||
).WithHideFunc(func() bool {
|
|
||||||
return being != "physical"
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Catch all freeform
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewText().
|
|
||||||
Title("Anything you want me to know? Any questions?").
|
|
||||||
Value(&freeform)))
|
|
||||||
|
|
||||||
err := form.Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var pd *physicalData
|
|
||||||
|
|
||||||
if being == "physical" {
|
|
||||||
pd = &physicalData{
|
|
||||||
Arriving: arriving,
|
|
||||||
Departing: departing,
|
|
||||||
Transportation: transportation,
|
|
||||||
Lodging: lodging,
|
|
||||||
Allergies: allergies,
|
|
||||||
Couch: couch,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rd := rsvpData{
|
|
||||||
Being: being,
|
|
||||||
Freeform: freeform,
|
|
||||||
PhysicalData: pd}
|
|
||||||
|
|
||||||
return sendRSVPEmail(rd)
|
|
||||||
}
|
|
||||||
|
|
||||||
type opts struct {
|
|
||||||
Username string
|
|
||||||
}
|
|
||||||
|
|
||||||
func _main() error {
|
|
||||||
b, err := banner()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(b)
|
|
||||||
|
|
||||||
u, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
o := opts{
|
|
||||||
Username: u.Username,
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("\t\t^_^_^_^_^_^_^ hi ~%s ^_^_^_^_^_^_^\n", o.Username)
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
var mode string
|
|
||||||
|
|
||||||
huh.NewSelect[string]().Title("whuddyu wanna doo?").
|
|
||||||
Options(
|
|
||||||
huh.NewOption("Get an info dump", "info"),
|
|
||||||
huh.NewOption("RSVP", "rsvp"),
|
|
||||||
huh.NewOption("Submit a talk proposal or finished work", "submit"),
|
|
||||||
huh.NewOption("Propose a creative jam", "jam"),
|
|
||||||
huh.NewOption("Quit", "quit"),
|
|
||||||
).Value(&mode).Run()
|
|
||||||
|
|
||||||
switch mode {
|
|
||||||
case "info":
|
|
||||||
return info()
|
|
||||||
case "rsvp":
|
|
||||||
return rsvp(o)
|
|
||||||
case "submit":
|
|
||||||
case "jam":
|
|
||||||
case "quit":
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("wtf '%s'", mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := _main(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, ":( failed: %s\n", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
# TOWN CON INFORMATION
|
|
||||||
|
|
||||||
TOWN CON is a celebration of tilde.town's 10 year anniversary featuring 24 hours of talks, art, and music by townies.
|
|
||||||
|
|
||||||
TOWN CON begins at 0:00 UTC on October 11, 2024 and ends 23:59 UTC on October 12, 2024.
|
|
||||||
|
|
||||||
There is an additional day of TOWN CON on October 12th for townies attending TOWN CON MEAT EDITION in Chicago, Illinois, USA.
|
|
||||||
|
|
||||||
## DIGITAL EVENT
|
|
||||||
|
|
||||||
TOWN CON's primary locus of reality will be here on the server in a text-mode environment. This environment will be supplemented with audio/video feeds as needed.
|
|
||||||
|
|
||||||
Works submitted by townies will all be slotted into a viewing schedule to run throughout the 24 hours of the event. The text-mode environment will either show the submitted work or have a pointer off to where it can be consumed in a browser.
|
|
||||||
|
|
||||||
The text mode environment will either be a HERMETICUM instance (if I finish the project in time) or just IRC.
|
|
||||||
|
|
||||||
### JAMS
|
|
||||||
|
|
||||||
Throughout the event townies are encouraged to cluster and collaborate on various creative **JAMS**. Re-run this tool and select _Propose a creative jam_ if you want to suggest one. Suggested jams:
|
|
||||||
|
|
||||||
- 88x31 badge jam
|
|
||||||
- forum software jam
|
|
||||||
- game jam
|
|
||||||
- dos jam
|
|
||||||
|
|
||||||
## PHYSICAL EVENT
|
|
||||||
|
|
||||||
TOWN CON MEAT EDITION takes place October 11th and 12th in Chicago, Illinois, USA.
|
|
||||||
|
|
||||||
On the 11th we'll be plugged into the digital event together and on the 12th we'll do something in meatspace.
|
|
||||||
|
|
||||||
### live music jam
|
|
||||||
|
|
||||||
Meat edition guests are encouraged to bring small sound or music emitting devices. We'll set them up with recording system and do some jamming. Some synthesizers and a sampler will be provided.
|
|
||||||
|
|
||||||
### VENUE and LODGING
|
|
||||||
|
|
||||||
The venue is a converted warehouse loft located at:
|
|
||||||
|
|
||||||
```
|
|
||||||
Loft 606
|
|
||||||
2605 W Armitage Ave,
|
|
||||||
Chicago, IL 60647
|
|
||||||
```
|
|
||||||
|
|
||||||
The venue has enough sleeping space to lodge everyone who expressed interest in coming in person, however some people will need to volunteer to sleep on couches. I have the venue reserved for:
|
|
||||||
|
|
||||||
- the evening of October 10th (feel free to arrive this day!)
|
|
||||||
- all day October 11th
|
|
||||||
- all day October 12th
|
|
||||||
- the morning of October 13th (feel free to leave this day!)
|
|
||||||
|
|
||||||
The total cost to book this venue was **$8,637**. I've paid for it out of pocket and would love if townies could help chip in for the cost. As of right now (`2024-08-31`) I have received **$0** of chip in and will update this as I get donations.
|
|
||||||
|
|
||||||
To learn more about the venue you can visit their website: https://www.loft606.com/
|
|
||||||
|
|
||||||
### FOOD
|
|
||||||
|
|
||||||
I'll arrange food throughout the event.
|
|
||||||
|
|
||||||
- 10th
|
|
||||||
- **dinner**: local restaurant food for dinner
|
|
||||||
- 11th
|
|
||||||
- breakfast: bulk order from Colectivo
|
|
||||||
- lunch: sandwich fixins
|
|
||||||
- dinner: vegan deep dish pizza from Kitchen 17
|
|
||||||
- 12th
|
|
||||||
- breakfast: bulk order from Colectivo
|
|
||||||
- lunch: sandwich fixins
|
|
||||||
- dinner: vegan mexican food from Quesadilla Del Reina Del Sur
|
|
||||||
- 13th
|
|
||||||
- breakfast: bulk order from Colectivo
|
|
||||||
|
|
||||||
There are also plenty of restaurants around the venue if people want to get their own food.
|
|
||||||
|
|
||||||
### TRANSPORTATION
|
|
||||||
|
|
||||||
The venue is accessible from rail, bus, and cab/uber/lyft. I have a car and can make myself available to pick people up and get them to the venue once they have made it to Chicago if they need. Depending on timing I might not be able to help, however. Contact via my cell phone (`~vilmibm/phone.txt`) to ask about my availability for a ride during the event.
|
|
||||||
|
|
||||||
If you are driving parking might be tough in the area--I can offer a few spots by my place in Forest Park if you want to put a car there and then get a ride over to the venue.
|
|
||||||
|
|
||||||
### ACTIVITIES
|
|
||||||
|
|
||||||
I'd like to do a field trip on the 12th...Some potential ideas:
|
|
||||||
|
|
||||||
- go to the nearby thrift store
|
|
||||||
- go out to minigolf
|
|
||||||
- go to a museum
|
|
||||||
- go into the woods
|
|
||||||
- just hang out and play synths (field trip of the mind)
|
|
||||||
|
|
||||||
!!! TBD !!!
|
|
||||||
|
|
||||||
## COMMS
|
|
||||||
|
|
||||||
For any kind of emergency during the event please contact me via my cell phone. My number can be found at `~vilmibm/phone.txt`.
|
|
|
@ -3,11 +3,13 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/charmbracelet/glamour"
|
"github.com/charmbracelet/glamour"
|
||||||
|
@ -37,6 +39,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func visitRandomUser() error {
|
func visitRandomUser() error {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
usernames, err := getUsernames()
|
usernames, err := getUsernames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -75,7 +78,7 @@ func visitPrompt() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func visitUser(username string) error {
|
func visitUser(username string) error {
|
||||||
files, err := os.ReadDir(filepath.Join("/home", username))
|
files, err := ioutil.ReadDir(filepath.Join("/home", username))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("user is not accepting visitors (could not read user's home directory: %w)", err)
|
return fmt.Errorf("user is not accepting visitors (could not read user's home directory: %w)", err)
|
||||||
}
|
}
|
||||||
|
@ -87,7 +90,7 @@ func visitUser(username string) error {
|
||||||
|
|
||||||
path := filepath.Join("/home", username, file.Name())
|
path := filepath.Join("/home", username, file.Name())
|
||||||
|
|
||||||
data, err := os.ReadFile(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/invites"
|
"git.tilde.town/tildetown/town/invites"
|
||||||
|
@ -143,7 +142,7 @@ This program is going to exit and you are now free to ssh to town as yourself:
|
||||||
|
|
||||||
if your public key isn't found by ssh, you'll need to explain to ssh how to find it with:
|
if your public key isn't found by ssh, you'll need to explain to ssh how to find it with:
|
||||||
|
|
||||||
ssh -i "replace with path to private key file" %[1]s@tilde.town
|
ssh -i "replace with path to public key file" %[1]s@tilde.town
|
||||||
|
|
||||||
for help with ssh, see: https://tilde.town/wiki/getting-started/ssh.html
|
for help with ssh, see: https://tilde.town/wiki/getting-started/ssh.html
|
||||||
|
|
||||||
|
@ -206,16 +205,12 @@ func createUser(data newUserData) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyfileText(data newUserData) string {
|
func keyfileText(data newUserData) string {
|
||||||
pkey := data.PubKey
|
|
||||||
if !strings.HasSuffix(pkey, "\n") {
|
|
||||||
pkey += "\n"
|
|
||||||
}
|
|
||||||
header := `########## GREETINGS! ##########
|
header := `########## GREETINGS! ##########
|
||||||
# This file was automatically generated by tilde.town when
|
# Hi! This file was automatically generated by tilde.town when
|
||||||
# your account was created. You can edit it if you want, but we
|
# your account was created. You can edit it if you want, but we
|
||||||
# recommend adding stuff to ~/.ssh/authorized_keys instead.`
|
# recommend adding stuff to ~/.ssh/authorized_keys instead.`
|
||||||
|
|
||||||
return fmt.Sprintf("%s\n%s", header, pkey)
|
return fmt.Sprintf("%s\n%s", header, data.PubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
|
@ -1,47 +0,0 @@
|
||||||
package codes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const codeLen = 32
|
|
||||||
|
|
||||||
func NewCode(email string) string {
|
|
||||||
charset := "abcdefghijklmnopqrztuvwxyz"
|
|
||||||
charset += strings.ToUpper(charset)
|
|
||||||
charset += "0123456789"
|
|
||||||
charset += "`~!@#$%^&*()-=_+[]{}|;:,./<>?"
|
|
||||||
|
|
||||||
code := []byte{}
|
|
||||||
|
|
||||||
max := big.NewInt(int64(len(charset)))
|
|
||||||
for len(code) < codeLen {
|
|
||||||
ix, err := rand.Int(rand.Reader, max)
|
|
||||||
if err != nil {
|
|
||||||
// TODO this is bad but I'm just kind of hoping it doesn't happen...often
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
code = append(code, charset[ix.Int64()])
|
|
||||||
}
|
|
||||||
|
|
||||||
code = append(code, ' ')
|
|
||||||
|
|
||||||
eb := []byte(email)
|
|
||||||
for x := 0; x < len(eb); x++ {
|
|
||||||
code = append(code, eb[x])
|
|
||||||
}
|
|
||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Decode(code string) ([]string, error) {
|
|
||||||
decoded, err := base64.StdEncoding.DecodeString(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Split(string(decoded), " "), nil
|
|
||||||
}
|
|
|
@ -4,15 +4,13 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"net"
|
|
||||||
"net/mail"
|
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SMTPHost = "smtp.migadu.com"
|
from = "root@tilde.town"
|
||||||
|
SMTPHost = "smtp.zoho.com"
|
||||||
SMTPPort = 465
|
SMTPPort = 465
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,79 +38,61 @@ func NewExternalMailer(pw string) *ExternalMailer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ExternalMailer) Send(address, subj, body string) error {
|
func (m *ExternalMailer) Send(address, subject, body string) error {
|
||||||
from := mail.Address{Name: "Tilde Town Admins", Address: "root@tilde.town"}
|
headers := map[string]string{
|
||||||
to := mail.Address{Name: "", Address: address}
|
"From": from,
|
||||||
|
"To": address,
|
||||||
|
"Subject": subject,
|
||||||
|
}
|
||||||
|
|
||||||
// Setup headers
|
|
||||||
headers := make(map[string]string)
|
|
||||||
headers["From"] = from.String()
|
|
||||||
headers["To"] = to.String()
|
|
||||||
headers["Subject"] = subj
|
|
||||||
|
|
||||||
// Setup message
|
|
||||||
message := ""
|
message := ""
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
message += fmt.Sprintf("%s: %s\r\n", k, v)
|
message += fmt.Sprintf("%s: %s\r\n", k, v)
|
||||||
}
|
}
|
||||||
message += "\r\n" + body
|
message += "\r\n" + body
|
||||||
|
|
||||||
// Connect to the SMTP Server
|
auth := smtp.PlainAuth("", from, m.Password, SMTPHost)
|
||||||
servername := fmt.Sprintf("%s:%d", SMTPHost, SMTPPort)
|
|
||||||
|
|
||||||
host, _, _ := net.SplitHostPort(servername)
|
server := fmt.Sprintf("%s:%d", SMTPHost, SMTPPort)
|
||||||
|
|
||||||
auth := smtp.PlainAuth("", "root@tilde.town", m.Password, host)
|
tlsconf := &tls.Config{
|
||||||
|
|
||||||
// TLS config
|
|
||||||
tlsconfig := &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
ServerName: host,
|
ServerName: server,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here is the key, you need to call tls.Dial instead of smtp.Dial
|
conn, err := tls.Dial("tcp", server, tlsconf)
|
||||||
// for smtp servers running on 465 that require an ssl connection
|
|
||||||
// from the very beginning (no starttls)
|
|
||||||
conn, err := tls.Dial("tcp", servername, tlsconfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed dial: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := smtp.NewClient(conn, host)
|
c, err := smtp.NewClient(conn, SMTPHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to make smtp client: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth
|
|
||||||
if err = c.Auth(auth); err != nil {
|
if err = c.Auth(auth); err != nil {
|
||||||
return fmt.Errorf("failed to make auth: %w", err)
|
return fmt.Errorf("auth failed for smtp: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// To && From
|
if err = c.Mail(from); err != nil {
|
||||||
if err = c.Mail(from.Address); err != nil {
|
return err
|
||||||
return fmt.Errorf("failed to create mail: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = c.Rcpt(to.Address); err != nil {
|
if err = c.Rcpt(address); err != nil {
|
||||||
return fmt.Errorf("failed to add rcpt: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data
|
|
||||||
w, err := c.Data()
|
w, err := c.Data()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send data: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = w.Write([]byte(message))
|
_, err = w.Write([]byte(message))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write: %w", err)
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
err = w.Close()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to close: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Close()
|
||||||
c.Quit()
|
c.Quit()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
this folder contains the things that external people can access via ssh: join@tilde.town, welcome@tilde.town, and help@tilde.town
|
|
|
@ -1,61 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/email"
|
|
||||||
)
|
|
||||||
|
|
||||||
const emailText = `hello!
|
|
||||||
|
|
||||||
You (hopefully) requested to add a new public key to your tilde.town account.
|
|
||||||
|
|
||||||
If you didn't, feel free to ignore this email (or report it to an admin).
|
|
||||||
|
|
||||||
If you did, here is your auth code: %s
|
|
||||||
|
|
||||||
To use this code, please open a terminal and run:
|
|
||||||
|
|
||||||
ssh help@tilde.town
|
|
||||||
|
|
||||||
Follow the instructions there to add your new key and restore access to your account.
|
|
||||||
|
|
||||||
best,
|
|
||||||
~vilmibm`
|
|
||||||
|
|
||||||
func loadPassword() (string, error) {
|
|
||||||
f, err := os.Open("/town/docs/smtp_help.pw")
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("could not open smtp password file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pw := make([]byte, 100)
|
|
||||||
|
|
||||||
n, err := f.Read(pw)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("could not read smtp password file: %w", err)
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
return "", errors.New("smtp password file was empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.TrimSpace(string(pw[0:n])), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendAuthCodeEmail(ac AuthCode) error {
|
|
||||||
pw, err := loadPassword()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
body := fmt.Sprintf(emailText, ac.Code)
|
|
||||||
|
|
||||||
mailer := email.NewExternalMailer(pw)
|
|
||||||
return mailer.Send(
|
|
||||||
ac.Email,
|
|
||||||
"Adding a new tilde.town public key",
|
|
||||||
body)
|
|
||||||
}
|
|
|
@ -1,404 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/codes"
|
|
||||||
"git.tilde.town/tildetown/town/external/lockingwriter"
|
|
||||||
"git.tilde.town/tildetown/town/sshkey"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"github.com/mattn/go-tty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO consider local-only help command for renaming, email mgmt, deleting account
|
|
||||||
|
|
||||||
// TODO put colorscheme, prompting stuff into own packages for use in the other commands. would be good to get off of survey.
|
|
||||||
|
|
||||||
// TODO use new lockingwriter for logging in the other external commands
|
|
||||||
|
|
||||||
func connectDB() (*sql.DB, error) {
|
|
||||||
db, err := sql.Open("sqlite3", "/town/var/codes/codes.db?mode=rw")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return db, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type colorScheme struct {
|
|
||||||
Header func(...string) string
|
|
||||||
Subtitle func(...string) string
|
|
||||||
Prompt func(...string) string
|
|
||||||
Email func(...string) string
|
|
||||||
Option func(...string) string
|
|
||||||
Error func(...string) string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newColorScheme() colorScheme {
|
|
||||||
s2r := func(s lipgloss.Style) func(...string) string {
|
|
||||||
return s.Render
|
|
||||||
}
|
|
||||||
c := func(s string) lipgloss.Color {
|
|
||||||
return lipgloss.Color(s)
|
|
||||||
}
|
|
||||||
s := lipgloss.NewStyle
|
|
||||||
return colorScheme{
|
|
||||||
Header: s2r(s().Bold(true).Foreground(c("#E0B0FF"))),
|
|
||||||
Subtitle: s2r(s().Italic(true).Foreground(c("gray"))),
|
|
||||||
Email: s2r(s().Bold(true).Underline(true)),
|
|
||||||
Prompt: s2r(s().Bold(true).Foreground(c("#00752d"))),
|
|
||||||
Option: s2r(s().Bold(true).Foreground(c("#38747a"))),
|
|
||||||
Error: s2r(s().Bold(true).Foreground(c("#f43124"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Prompter struct {
|
|
||||||
cs colorScheme
|
|
||||||
tty *tty.TTY
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrompter(tty *tty.TTY, cs colorScheme) *Prompter {
|
|
||||||
return &Prompter{
|
|
||||||
cs: cs,
|
|
||||||
tty: tty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Prompter) String(prompt string) (string, error) {
|
|
||||||
// TODO assumes blank is no bueno
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var answer string
|
|
||||||
for answer == "" {
|
|
||||||
fmt.Println("")
|
|
||||||
fmt.Println(p.cs.Prompt(prompt))
|
|
||||||
fmt.Println(p.cs.Subtitle("(press enter to submit)"))
|
|
||||||
answer, err = p.tty.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("couldn't collect input: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return answer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Prompter) Select(prompt string, opts []string) (int, error) {
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(p.cs.Prompt(prompt))
|
|
||||||
fmt.Println(p.cs.Subtitle("(pick an option using the corresponding number)"))
|
|
||||||
|
|
||||||
chosen := -1
|
|
||||||
for chosen < 0 {
|
|
||||||
fmt.Println()
|
|
||||||
for ix, o := range opts {
|
|
||||||
fmt.Printf("%s: %s\n", p.cs.Option(fmt.Sprintf("%d", ix+1)), o)
|
|
||||||
}
|
|
||||||
r, err := p.tty.ReadRune()
|
|
||||||
if err != nil {
|
|
||||||
return -1, fmt.Errorf("could not collect answer for '%s': %w", prompt, err)
|
|
||||||
}
|
|
||||||
c, err := strconv.Atoi(string(r))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Printf("I could not understand '%s'. Try again, please.\n", string(r))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c > len(opts) || c == 0 {
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Printf("%s is not an option. Try again, please.\n", string(r))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
chosen = c - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("")
|
|
||||||
|
|
||||||
return chosen, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func _main(cs colorScheme) error {
|
|
||||||
lw := lockingwriter.New()
|
|
||||||
l := log.New(lw, "help: ", log.Ldate|log.Ltime|log.LUTC|log.Lshortfile|log.Lmsgprefix)
|
|
||||||
|
|
||||||
db, err := connectDB()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not connect to database. please let root@tilde.town know about this.")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(cs.Header("Hi, you have reached the tilde town help desk."))
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Please check out the options below.")
|
|
||||||
fmt.Printf("If none of them apply to your case, you can email %s. \n", cs.Email("root@tilde.town"))
|
|
||||||
tty, err := tty.Open()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not open tty: %w", err)
|
|
||||||
}
|
|
||||||
defer tty.Close()
|
|
||||||
|
|
||||||
p := NewPrompter(tty, cs)
|
|
||||||
|
|
||||||
options := []string{
|
|
||||||
"I need to request that a new SSH key be added to my account.",
|
|
||||||
"I have a code from my e-mail to redeem for a new SSH key",
|
|
||||||
"I just want out of here",
|
|
||||||
}
|
|
||||||
c, err := p.Select("What do you need help with?", options)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
}()
|
|
||||||
|
|
||||||
switch c {
|
|
||||||
case 0:
|
|
||||||
return collectEmail(l, db, cs, p)
|
|
||||||
case 1:
|
|
||||||
return redeemCode(l, db, cs, p)
|
|
||||||
case 2:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func emailToUsername(email string) (string, error) {
|
|
||||||
cmd := exec.Command("sudo", "/town/bin/emailtouser", email)
|
|
||||||
stderrBuff := bytes.NewBuffer([]byte{})
|
|
||||||
stdoutBuff := bytes.NewBuffer([]byte{})
|
|
||||||
cmd.Stderr = stderrBuff
|
|
||||||
cmd.Stdout = stdoutBuff
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return "", fmt.Errorf("emailtouser failed with '%s': %w", stderrBuff.String(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.TrimSpace(stdoutBuff.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectEmail(l *log.Logger, db *sql.DB, cs colorScheme, p *Prompter) error {
|
|
||||||
fmt.Println(cs.Header("We can send a authorization code to an email associated with your town account."))
|
|
||||||
email, err := p.String("email to send reset code to?")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(cs.Header("thanks!"))
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Printf("If %s is associated with a town account we'll email an authorization code.\n", cs.Email(email))
|
|
||||||
|
|
||||||
mustHave := []string{"@", "."}
|
|
||||||
found := 0
|
|
||||||
for _, s := range mustHave {
|
|
||||||
if strings.Contains(email, s) {
|
|
||||||
found++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if found != len(mustHave) {
|
|
||||||
l.Printf("corrupt email '%s'", email)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = emailToUsername(email); err != nil {
|
|
||||||
l.Printf("no user for '%s'", email)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
code := codes.NewCode(email)
|
|
||||||
|
|
||||||
ac := &AuthCode{
|
|
||||||
Code: code,
|
|
||||||
Email: email,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = ac.Insert(db); err != nil {
|
|
||||||
l.Printf("database error: %s", err.Error())
|
|
||||||
return errors.New("the database was sad")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = sendAuthCodeEmail(*ac); err != nil {
|
|
||||||
l.Printf("mail send error: %s", err.Error())
|
|
||||||
return errors.New("email sending failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func redeemCode(l *log.Logger, db *sql.DB, cs colorScheme, p *Prompter) error {
|
|
||||||
fmt.Println(cs.Header("redeem an auth code and add a new public key"))
|
|
||||||
c, err := p.String("paste your auth code:")
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("failed to prompt: %s", err.Error())
|
|
||||||
fmt.Println(cs.Error("sorry, I couldn't read that."))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parts, err := codes.Decode(c)
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("failed to decode auth code: %s", err.Error())
|
|
||||||
fmt.Println(cs.Error("sorry, that doesn't look like an auth code..."))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
code := &AuthCode{
|
|
||||||
Code: c,
|
|
||||||
Email: parts[1],
|
|
||||||
}
|
|
||||||
|
|
||||||
err = code.Hydrate(db)
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("hydrate failed: %s", err.Error())
|
|
||||||
return errors.New("the database is sad")
|
|
||||||
}
|
|
||||||
|
|
||||||
if code.Used {
|
|
||||||
fmt.Println(cs.Error("That code has already been redeemed. You'll have to request a new one."))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
username, err := emailToUsername(code.Email)
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("could not find user: %s", err.Error())
|
|
||||||
fmt.Println(cs.Error("That code doesn't seem to match an account."))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Printf("hi, ~%s", username)
|
|
||||||
|
|
||||||
key, err := p.String("paste your new public key:")
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("failed to prompt: %s", err.Error())
|
|
||||||
fmt.Println(cs.Error("sorry, I couldn't read that."))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
valid, err := sshkey.ValidKey(key)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to validate key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
errMsg := fmt.Sprintf("that key is invalid: %s", err.Error())
|
|
||||||
fmt.Println(cs.Error(errMsg))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command("sudo", "--user", username, "/town/bin/appendkeyfile")
|
|
||||||
cmd.Stdin = bytes.NewBufferString(key + "\n")
|
|
||||||
stdoutBuff := bytes.NewBuffer([]byte{})
|
|
||||||
cmd.Stdout = stdoutBuff
|
|
||||||
stderrBuff := bytes.NewBuffer([]byte{})
|
|
||||||
cmd.Stderr = stderrBuff
|
|
||||||
if err = cmd.Run(); err != nil {
|
|
||||||
l.Printf("appendkeyfile failed with '%s', '%s': %s", stderrBuff.String(), stdoutBuff.String(), err.Error())
|
|
||||||
return errors.New("adding to keys file failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = code.MarkUsed(db)
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("failed to mark used: %s", err.Error())
|
|
||||||
return errors.New("database was sad")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("new key added! you should be able to use it to log in.")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cs := newColorScheme()
|
|
||||||
err := _main(cs)
|
|
||||||
defer func() {
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(cs.Header("bye~"))
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(
|
|
||||||
cs.Error(fmt.Sprintf("sorry, something went wrong: %s", err.Error())))
|
|
||||||
fmt.Println("Please let an admin know by emailing a copy of this error to root@tilde.town")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthCode struct {
|
|
||||||
ID int64
|
|
||||||
Code string
|
|
||||||
Email string
|
|
||||||
Used bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AuthCode) Insert(db *sql.DB) error {
|
|
||||||
stmt, err := db.Prepare(`
|
|
||||||
INSERT INTO auth_codes (code, email)
|
|
||||||
VALUES (?, ?)`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
result, err := stmt.Exec(c.Code, c.Email)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
liid, err := result.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.ID = liid
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AuthCode) Hydrate(db *sql.DB) error {
|
|
||||||
stmt, err := db.Prepare(`
|
|
||||||
SELECT id, used
|
|
||||||
FROM auth_codes
|
|
||||||
WHERE code = ? AND email = ?`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
return stmt.QueryRow(c.Code, c.Email).Scan(&c.ID, &c.Used)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AuthCode) MarkUsed(db *sql.DB) error {
|
|
||||||
if c.ID == 0 {
|
|
||||||
return errors.New("not hydrated")
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := db.Prepare(`
|
|
||||||
UPDATE auth_codes SET used = 1 WHERE id = ?`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
result, err := stmt.Exec(c.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var rowsAffected int64
|
|
||||||
|
|
||||||
if rowsAffected, err = result.RowsAffected(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowsAffected == 0 {
|
|
||||||
return errors.New("no rows affected")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
The purpose of this command is to be run via sudo as an arbitrary user by the "help" user. It is invoked as part of the "i need to add a new public key" flow from "ssh help@tilde.town".
|
|
||||||
|
|
||||||
It's based on the createkeyfile helper and heavily copy pasta'd. They should probably share code or be a single command but I wanted to keep things simple for now.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
const keyfileName = "authorized_keys2"
|
|
||||||
|
|
||||||
func quit(msg string, code int) {
|
|
||||||
// TODO print to stderr
|
|
||||||
fmt.Println(msg)
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
u, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
quit(err.Error(), 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
sshPath := path.Join("/home", u.Username, ".ssh")
|
|
||||||
keyfilePath := path.Join(sshPath, keyfileName)
|
|
||||||
|
|
||||||
f, err := os.OpenFile(keyfilePath, os.O_APPEND|os.O_WRONLY, 0600)
|
|
||||||
if err != nil {
|
|
||||||
quit(fmt.Sprintf("failed to open %s: %s", keyfilePath, err.Error()), 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
stdin := make([]byte, 90000) // arbitrary limit
|
|
||||||
|
|
||||||
n, err := os.Stdin.Read(stdin)
|
|
||||||
if err != nil {
|
|
||||||
quit(err.Error(), 6)
|
|
||||||
} else if n == 0 {
|
|
||||||
quit("nothing passed on STDIN", 7)
|
|
||||||
}
|
|
||||||
|
|
||||||
stdin = stdin[0:n]
|
|
||||||
|
|
||||||
n, err = f.Write(stdin)
|
|
||||||
if err != nil {
|
|
||||||
quit(err.Error(), 9)
|
|
||||||
} else if n == 0 {
|
|
||||||
quit("wrote nothing to keyfile", 10)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/towndb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func _main(args []string) error {
|
|
||||||
if len(args) < 2 {
|
|
||||||
return errors.New("need email")
|
|
||||||
}
|
|
||||||
email := args[1]
|
|
||||||
|
|
||||||
db, err := towndb.ConnectDB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := towndb.UserForEmail(db, email)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if user == nil {
|
|
||||||
return errors.New("email does not correspond to user")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(user.Username)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := _main(os.Args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
package lockingwriter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gofrs/flock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// for now this package defines a writer for use with log.New(). It it intended to be used from the external ssh applications. This logger uses a file lock to allow all the various ssh applications to log to the same file.
|
|
||||||
// the correct way to do this is to send log events to a daemon
|
|
||||||
// but i'm trying to cut a corner
|
|
||||||
|
|
||||||
type LockingWriter struct {
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
fp = "/town/var/log/external.log"
|
|
||||||
lp = "/town/var/log/log.lock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func New() *LockingWriter {
|
|
||||||
f, err := os.OpenFile(fp, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0660)
|
|
||||||
if err != nil && !os.IsExist(err) {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
return &LockingWriter{
|
|
||||||
path: fp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LockingWriter) Write(p []byte) (n int, err error) {
|
|
||||||
fl := flock.New(lp)
|
|
||||||
|
|
||||||
var locked bool
|
|
||||||
for !locked {
|
|
||||||
locked, err = fl.TryLock()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
var f *os.File
|
|
||||||
f, err = os.OpenFile(l.path, os.O_APPEND|os.O_WRONLY, 0660)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer f.Close()
|
|
||||||
defer fl.Unlock()
|
|
||||||
|
|
||||||
n, err = f.Write(p)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
//return f.Write(p)
|
|
||||||
}
|
|
34
go.mod
34
go.mod
|
@ -6,12 +6,9 @@ require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.5
|
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||||
github.com/MakeNowJust/heredoc/v2 v2.0.1
|
github.com/MakeNowJust/heredoc/v2 v2.0.1
|
||||||
github.com/charmbracelet/glamour v0.5.0
|
github.com/charmbracelet/glamour v0.5.0
|
||||||
github.com/charmbracelet/huh v0.5.3
|
github.com/charmbracelet/lipgloss v0.6.0
|
||||||
github.com/charmbracelet/lipgloss v0.13.0
|
|
||||||
github.com/gdamore/tcell/v2 v2.5.3
|
github.com/gdamore/tcell/v2 v2.5.3
|
||||||
github.com/gofrs/flock v0.8.1
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16
|
github.com/mattn/go-sqlite3 v1.14.16
|
||||||
github.com/mattn/go-tty v0.0.5
|
|
||||||
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c
|
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c
|
||||||
github.com/spf13/cobra v1.5.0
|
github.com/spf13/cobra v1.5.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
@ -19,42 +16,27 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/catppuccin/go v0.2.0 // indirect
|
|
||||||
github.com/charmbracelet/bubbles v0.19.0 // indirect
|
|
||||||
github.com/charmbracelet/bubbletea v0.27.0 // indirect
|
|
||||||
github.com/charmbracelet/x/ansi v0.2.2 // indirect
|
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
|
||||||
github.com/charmbracelet/x/term v0.2.0 // indirect
|
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
|
||||||
github.com/gdamore/encoding v1.0.0 // indirect
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
github.com/gorilla/css v1.0.0 // indirect
|
github.com/gorilla/css v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
github.com/microcosm-cc/bluemonday v1.0.17 // indirect
|
github.com/microcosm-cc/bluemonday v1.0.17 // indirect
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
||||||
github.com/muesli/reflow v0.3.0 // indirect
|
github.com/muesli/reflow v0.3.0 // indirect
|
||||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
|
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.2 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/yuin/goldmark v1.4.4 // indirect
|
github.com/yuin/goldmark v1.4.4 // indirect
|
||||||
github.com/yuin/goldmark-emoji v1.0.1 // indirect
|
github.com/yuin/goldmark-emoji v1.0.1 // indirect
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
||||||
golang.org/x/sys v0.24.0 // indirect
|
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
)
|
)
|
||||||
|
|
74
go.sum
74
go.sum
|
@ -1,36 +1,17 @@
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ=
|
github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ=
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
|
||||||
github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A=
|
github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A=
|
||||||
github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=
|
github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
|
|
||||||
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
|
||||||
github.com/charmbracelet/bubbles v0.19.0 h1:gKZkKXPP6GlDk6EcfujDK19PCQqRjaJZQ7QRERx1UF0=
|
|
||||||
github.com/charmbracelet/bubbles v0.19.0/go.mod h1:WILteEqZ+krG5c3ntGEMeG99nCupcuIk7V0/zOP0tOA=
|
|
||||||
github.com/charmbracelet/bubbletea v0.27.0 h1:Mznj+vvYuYagD9Pn2mY7fuelGvP0HAXtZYGgRBCbHvU=
|
|
||||||
github.com/charmbracelet/bubbletea v0.27.0/go.mod h1:5MdP9XH6MbQkgGhnlxUqCNmBXf9I74KRQ8HIidRxV1Y=
|
|
||||||
github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g=
|
github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g=
|
||||||
github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc=
|
github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc=
|
||||||
github.com/charmbracelet/huh v0.5.3 h1:3KLP4a/K1/S4dq4xFMTNMt3XWhgMl/yx8NYtygQ0bmg=
|
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
|
||||||
github.com/charmbracelet/huh v0.5.3/go.mod h1:OZC3lshuF+/y8laj//DoZdFSHxC51OrtXLJI8xWVouQ=
|
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
|
||||||
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
|
||||||
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
|
||||||
github.com/charmbracelet/x/ansi v0.2.2 h1:BC7xzaVpfWIYZRNE8NhO9zo8KA4eGUL6L/JWXDh3GF0=
|
|
||||||
github.com/charmbracelet/x/ansi v0.2.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
|
||||||
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
|
|
||||||
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
|
@ -39,16 +20,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
|
||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
|
github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
|
||||||
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
|
||||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
|
||||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||||
|
@ -59,41 +34,29 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
|
||||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/mattn/go-tty v0.0.5 h1:s09uXI7yDbXzzTTfw3zonKFzwGkyYlgU3OMjqA0ddz4=
|
|
||||||
github.com/mattn/go-tty v0.0.5/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y=
|
github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
||||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
|
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY=
|
||||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
|
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
@ -102,8 +65,8 @@ github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c h1:zIYU4PjQJ4BnYryMmpyi
|
||||||
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c/go.mod h1:lBUy/T5kyMudFzWUH/C2moN+NlU5qF505vzOyINXuUQ=
|
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c/go.mod h1:lBUy/T5kyMudFzWUH/C2moN+NlU5qF505vzOyINXuUQ=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||||
|
@ -120,20 +83,14 @@ github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18W
|
||||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
|
||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
|
||||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
||||||
|
@ -141,9 +98,8 @@ golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
package invites
|
package invites
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/codes"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dsn = "/town/var/invites/invites.db?mode=rw"
|
dsn = "/town/var/invites/invites.db?mode=rw"
|
||||||
|
codeLen = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
type Invite struct {
|
type Invite struct {
|
||||||
|
@ -29,7 +33,7 @@ func (i *Invite) Insert(db *sql.DB) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
i.Code = codes.NewCode(i.Email)
|
i.Code = generateCode(i.Email)
|
||||||
|
|
||||||
_, err = stmt.Exec(i.Code, i.Email)
|
_, err = stmt.Exec(i.Code, i.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -49,6 +53,44 @@ func ConnectDB() (*sql.DB, error) {
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateCode(email string) string {
|
||||||
|
|
||||||
|
charset := "abcdefghijklmnopqrztuvwxyz"
|
||||||
|
charset += strings.ToUpper(charset)
|
||||||
|
charset += "0123456789"
|
||||||
|
charset += "`~!@#$%^&*()-=_+[]{}|;:,./<>?"
|
||||||
|
|
||||||
|
code := []byte{}
|
||||||
|
|
||||||
|
max := big.NewInt(int64(len(charset)))
|
||||||
|
for len(code) < codeLen {
|
||||||
|
ix, err := rand.Int(rand.Reader, max)
|
||||||
|
if err != nil {
|
||||||
|
// TODO this is bad but I'm just kind of hoping it doesn't happen...often
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
code = append(code, charset[ix.Int64()])
|
||||||
|
}
|
||||||
|
|
||||||
|
code = append(code, ' ')
|
||||||
|
|
||||||
|
eb := []byte(email)
|
||||||
|
for x := 0; x < len(eb); x++ {
|
||||||
|
code = append(code, eb[x])
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decode(code string) ([]string, error) {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Split(string(decoded), " "), nil
|
||||||
|
}
|
||||||
|
|
||||||
func Get(db *sql.DB, code string) (*Invite, error) {
|
func Get(db *sql.DB, code string) (*Invite, error) {
|
||||||
inv := &Invite{
|
inv := &Invite{
|
||||||
Code: code,
|
Code: code,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
email "git.tilde.town/tildetown/town/email"
|
email "git.tilde.town/tildetown/town/email"
|
||||||
)
|
)
|
||||||
|
@ -177,6 +178,7 @@ if you did _not_ request this, please let an admin know.
|
||||||
}
|
}
|
||||||
|
|
||||||
func genGiteaPassword() string {
|
func genGiteaPassword() string {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
b := make([]byte, 20)
|
b := make([]byte, 20)
|
||||||
for i := range b {
|
for i := range b {
|
||||||
b[i] = pwLetters[rand.Intn(len(pwLetters))]
|
b[i] = pwLetters[rand.Intn(len(pwLetters))]
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
CREATE TABLE IF NOT EXISTS auth_codes (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
created TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M', 'now', 'localtime')),
|
|
||||||
code TEXT,
|
|
||||||
email TEXT,
|
|
||||||
used INTEGER DEFAULT 0
|
|
||||||
);
|
|
|
@ -2,7 +2,7 @@ package towndb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
@ -143,27 +143,29 @@ func (u *TownUser) Insert(db *sql.DB) (err error) {
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserForEmail returns the user associated with an email or nil if no matching user is found
|
// TODO: really we should have like GetUser or something. but i don't want
|
||||||
func UserForEmail(db *sql.DB, address string) (*TownUser, error) {
|
// to have to populate the struct to do this operation for now ~equa
|
||||||
stmt, err := db.Prepare(`
|
func RenameUser(db *sql.DB, old_name string, new_name string) (err error) {
|
||||||
SELECT u.id, u.username FROM users u
|
stmt, err := db.Prepare(`UPDATE users SET username = ? WHERE username = ?`)
|
||||||
JOIN emails e ON e.userid = u.id
|
|
||||||
WHERE e.address = ?
|
|
||||||
`)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
row := stmt.QueryRow(address)
|
|
||||||
u := &TownUser{}
|
|
||||||
if err = row.Scan(&u.ID, &u.Username); err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, nil
|
result, err := stmt.Exec(new_name, old_name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows == 0 {
|
||||||
|
return fmt.Errorf("couldn't find user")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectDB() (*sql.DB, error) {
|
func ConnectDB() (*sql.DB, error) {
|
||||||
|
|
Loading…
Reference in New Issue