Compare commits
No commits in common. "cf998071264f1be53b2c9c260435876b9290a8ed" and "b1f7e9842d33c31630c5e02884b76862008e2914" have entirely different histories.
cf99807126
...
b1f7e9842d
|
@ -111,8 +111,6 @@ func submit(opts *contribOpts) error {
|
||||||
"social",
|
"social",
|
||||||
"game",
|
"game",
|
||||||
"utility",
|
"utility",
|
||||||
"programming",
|
|
||||||
"community",
|
|
||||||
"misc",
|
"misc",
|
||||||
}
|
}
|
||||||
var choice int
|
var choice int
|
||||||
|
@ -161,7 +159,7 @@ func submit(opts *contribOpts) error {
|
||||||
return fmt.Errorf("failed to serialize contrib: %w", err)
|
return fmt.Errorf("failed to serialize contrib: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fname := fmt.Sprintf("%d.yml", time.Now().Unix())
|
fname := fmt.Sprintf("%d", rand.Intn(10000))
|
||||||
f, err := os.Create(path.Join(contribRequestPath, fname))
|
f, err := os.Create(path.Join(contribRequestPath, fname))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open contrib file for writing: %w", err)
|
return fmt.Errorf("failed to open contrib file for writing: %w", err)
|
||||||
|
@ -246,8 +244,7 @@ func rootCmd() *cobra.Command {
|
||||||
|
|
||||||
rc.Flags().StringVarP(&updateName, "update", "u", "", "Name of command to update")
|
rc.Flags().StringVarP(&updateName, "update", "u", "", "Name of command to update")
|
||||||
rc.Flags().StringVarP(&delName, "delete", "d", "", "Name of command to delete")
|
rc.Flags().StringVarP(&delName, "delete", "d", "", "Name of command to delete")
|
||||||
// TODO hiding this until i decide i want it or not
|
rc.Flags().BoolVarP(&force, "force", "f", false, "skip request, just install the command")
|
||||||
//rc.Flags().BoolVarP(&force, "force", "f", false, "skip request, just install the command")
|
|
||||||
|
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,11 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
townuser "git.tilde.town/tildetown/town/user"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
@ -46,7 +48,19 @@ const binroot = "/town/commands"
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "town",
|
Use: "town",
|
||||||
Short: "Run commands unique to tilde.town",
|
Short: "Run commands unique to tilde.town",
|
||||||
// TODO Long example showing how to contribute a command
|
}
|
||||||
|
|
||||||
|
var adminCmd = &cobra.Command{
|
||||||
|
Use: "admin",
|
||||||
|
Short: "Run administrative commands",
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAdmin() (bool, error) {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to get information about current user: %w", err)
|
||||||
|
}
|
||||||
|
return townuser.IsAdmin(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCommands(targetCmd *cobra.Command, path string) error {
|
func parseCommands(targetCmd *cobra.Command, path string) error {
|
||||||
|
@ -65,43 +79,61 @@ func parseCommands(targetCmd *cobra.Command, path string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type cmdDoc struct {
|
type commandDoc struct {
|
||||||
CmdName string
|
ShortDesc string `yaml:"shortDesc"`
|
||||||
ExecPath string
|
LongDesc string `yaml:"longDesc"`
|
||||||
|
Examples string
|
||||||
Maintainer string
|
Maintainer string
|
||||||
Category string
|
|
||||||
ShortDesc string
|
|
||||||
LongDesc string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCommand(targetCmd *cobra.Command, yamlPath string) {
|
func parseCommand(targetCmd *cobra.Command, yamlPath string) {
|
||||||
|
executablePath := strings.TrimSuffix(yamlPath, ".yml")
|
||||||
|
|
||||||
|
// TODO handle when files lack executable bit
|
||||||
|
_, err := os.Stat(executablePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "could not find matching executable for %s; skipping...\n", yamlPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
yamlBytes, err := ioutil.ReadFile(yamlPath)
|
yamlBytes, err := ioutil.ReadFile(yamlPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "could not read %s; skipping...\n", yamlPath)
|
fmt.Fprintf(os.Stderr, "could not read %s; skipping...\n", yamlPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var doc cmdDoc
|
doc := commandDoc{}
|
||||||
err = yaml.Unmarshal(yamlBytes, &doc)
|
err = yaml.Unmarshal(yamlBytes, &doc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "could not parse %s; skipping...\n", yamlPath)
|
fmt.Fprintf(os.Stderr, "could not parse %s; skipping...\n", yamlPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if doc.Maintainer == "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s is missing maintainer field; skipping...\n", yamlPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
parsedCmd := &cobra.Command{
|
parsedCmd := &cobra.Command{
|
||||||
Use: doc.CmdName,
|
Use: filepath.Base(executablePath),
|
||||||
RunE: execWrapper(doc.ExecPath),
|
RunE: execWrapper(executablePath),
|
||||||
DisableFlagParsing: true,
|
DisableFlagParsing: true,
|
||||||
}
|
}
|
||||||
if doc.ShortDesc != "" {
|
if doc.ShortDesc != "" {
|
||||||
parsedCmd.Short = doc.ShortDesc
|
parsedCmd.Short = doc.ShortDesc
|
||||||
}
|
}
|
||||||
|
|
||||||
if doc.LongDesc != "" {
|
if doc.LongDesc != "" {
|
||||||
parsedCmd.Long = doc.LongDesc
|
parsedCmd.Long = doc.LongDesc
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedCmd.Long += fmt.Sprintf("\nMaintained by %s; reach out to them via mail or chat with questions", doc.Maintainer)
|
parsedCmd.Long += fmt.Sprintf("\nMaintained by %s; reach out to them via mail or chat with questions", doc.Maintainer)
|
||||||
|
|
||||||
|
if doc.Examples != "" {
|
||||||
|
parsedCmd.Example = doc.Examples
|
||||||
|
}
|
||||||
|
|
||||||
targetCmd.AddCommand(parsedCmd)
|
targetCmd.AddCommand(parsedCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,14 +148,30 @@ func execWrapper(executablePath string) func(*cobra.Command, []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func cli() int {
|
func cli() int {
|
||||||
files, err := ioutil.ReadDir(binroot)
|
err := parseCommands(rootCmd, "core")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "failed to list directory %s: %s", binroot, err)
|
fmt.Fprintf(os.Stderr, "failed to parse core commands: %s", err)
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
err = parseCommands(rootCmd, "contrib")
|
||||||
if strings.HasSuffix(file.Name(), "yml") {
|
if err != nil {
|
||||||
parseCommand(rootCmd, filepath.Join(binroot, file.Name()))
|
fmt.Fprintf(os.Stderr, "failed to parse contrib commands: %s", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
admin, err := isAdmin()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to check admin status: %s", err)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if admin {
|
||||||
|
rootCmd.AddCommand(adminCmd)
|
||||||
|
err = parseCommands(adminCmd, "admin")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to parse admin commands: %s", err)
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
The point of this project is to enable signing up for tilde.town via an ssh connection.
|
|
||||||
|
|
||||||
It ought to work something like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
|
|
||||||
ssh signup@tilde.town
|
|
||||||
|
|
||||||
<ascii art>
|
|
||||||
|
|
||||||
tilde.town
|
|
||||||
|
|
||||||
|
|
||||||
a creature stands before you. what does it look like?
|
|
||||||
|
|
||||||
> a floating gray cube
|
|
||||||
a calico cat with softly glowing eyes
|
|
||||||
a squid
|
|
||||||
something else
|
|
||||||
|
|
||||||
cube: you'd like to sign up for tilde.town, yes?
|
|
||||||
|
|
||||||
> yeah
|
|
||||||
nah
|
|
||||||
why should I?
|
|
||||||
|
|
||||||
you say, "yeah"
|
|
||||||
|
|
||||||
cube: by what name should i call you?
|
|
||||||
|
|
||||||
~cowcow________
|
|
||||||
|
|
||||||
cube: unfortunately, i already know someone by that name. what else can i call you?
|
|
||||||
|
|
||||||
~shelf_________
|
|
||||||
|
|
||||||
cube: excellent, hello ~shelf. have you been invited here by anyone i know?
|
|
||||||
|
|
||||||
> yes, i have an invite code
|
|
||||||
no
|
|
||||||
|
|
||||||
you say "yes, i have an invite code"
|
|
||||||
|
|
||||||
cube: great. please paste it and press enter:
|
|
||||||
|
|
||||||
_____________
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
...and so on.
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
- [ ] make signup user
|
|
||||||
- [ ] get dummy program to run as ssh handler
|
|
||||||
- [ ] tcell bootstrapping some kind of interactivity
|
|
||||||
- [ ] existing username check
|
|
||||||
- [ ] email validation
|
|
||||||
- [ ] collect responses
|
|
||||||
- [ ] create the signup request (on disk? db?)
|
|
||||||
- [ ] invite system support
|
|
|
@ -1,366 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/rivo/tview"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Assumes:
|
|
||||||
|
|
||||||
Match User join
|
|
||||||
ForceCommand /town/src/town/cmd/signup/signup
|
|
||||||
PubkeyAuthentication no
|
|
||||||
KbdInteractiveAuthentication no
|
|
||||||
PasswordAuthentication yes
|
|
||||||
PermitEmptyPasswords yes
|
|
||||||
DisableForwarding yes
|
|
||||||
|
|
||||||
in sshd_config, and:
|
|
||||||
|
|
||||||
auth [success=done default=ignore] pam_succeed_if.so user ingroup join
|
|
||||||
|
|
||||||
in /etc/pam.d/sshd
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
is there a way :qa
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
type streams struct {
|
|
||||||
In io.Reader
|
|
||||||
Out io.Writer
|
|
||||||
Err io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
type Prompter struct {
|
|
||||||
in io.Reader
|
|
||||||
out io.Writer
|
|
||||||
width int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Prompter) Say(m string) {
|
|
||||||
for _, line := range wrap(m, p.width) {
|
|
||||||
fmt.Fprintln(p.out, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Prompter) CharSay(c, m string) {
|
|
||||||
sayPrefix := fmt.Sprintf("%s says: ", c)
|
|
||||||
prefixWidth := utf8.RuneCountInString(sayPrefix)
|
|
||||||
width := p.width - prefixWidth
|
|
||||||
indent := ""
|
|
||||||
for x := 0; x < utf8.RuneCountInString(sayPrefix); x++ {
|
|
||||||
indent += " "
|
|
||||||
}
|
|
||||||
for i, line := range wrap(m, width) {
|
|
||||||
if i == 0 {
|
|
||||||
fmt.Fprintln(p.out, sayPrefix+line)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(p.out, indent+line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type InputAnswer struct {
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
func runeLen(s string) int {
|
|
||||||
return utf8.RuneCountInString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrap(s string, width int) []string {
|
|
||||||
fielded := strings.Fields(s)
|
|
||||||
out := []string{}
|
|
||||||
line := ""
|
|
||||||
for i, field := range fielded {
|
|
||||||
if runeLen(field)+runeLen(line)+1 < width {
|
|
||||||
line += field + " "
|
|
||||||
} else {
|
|
||||||
out = append(out, line)
|
|
||||||
line = field + " "
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if i == len(fielded)-1 {
|
|
||||||
out = append(out, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Prompter) Confirm(m string) (bool, error) {
|
|
||||||
result := InputAnswer{}
|
|
||||||
|
|
||||||
for {
|
|
||||||
err := p.Ask(m, &result)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
val := strings.TrimSpace(result.Value)
|
|
||||||
|
|
||||||
switch val {
|
|
||||||
case "yes", "y":
|
|
||||||
return true, nil
|
|
||||||
case "no", "n":
|
|
||||||
return false, nil
|
|
||||||
default:
|
|
||||||
p.Say("sorry, please say y or n")
|
|
||||||
p.Pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Prompter) Ask(m string, result *InputAnswer) error {
|
|
||||||
fmt.Fprintf(p.out, "%s ", m)
|
|
||||||
var val string
|
|
||||||
var err error
|
|
||||||
|
|
||||||
reader := bufio.NewReader(p.in)
|
|
||||||
for val == "" {
|
|
||||||
val, err = reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if val == "" {
|
|
||||||
p.Say("hmm, what was that?")
|
|
||||||
p.Pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Value = val
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Prompter) AskLong(m string, result *InputAnswer) error {
|
|
||||||
fmt.Fprintf(p.out, "%s\n", m)
|
|
||||||
var val []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
reader := bufio.NewReader(p.in)
|
|
||||||
for len(val) == 0 {
|
|
||||||
val, err = ioutil.ReadAll(reader)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(val) == 0 {
|
|
||||||
fmt.Fprintf(p.out, "%s\n", m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Value = string(val)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Prompter) Pause() {
|
|
||||||
fmt.Fprintln(p.out)
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrompter(width int, s *streams) Prompter {
|
|
||||||
return Prompter{
|
|
||||||
in: s.In,
|
|
||||||
out: s.Out,
|
|
||||||
width: width,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type answers struct {
|
|
||||||
username string
|
|
||||||
email string
|
|
||||||
applied time.Time
|
|
||||||
|
|
||||||
howDay string
|
|
||||||
howHeard string
|
|
||||||
reasons string
|
|
||||||
plans string
|
|
||||||
socials string
|
|
||||||
sshKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
func _main(args []string, s *streams) error {
|
|
||||||
a := answers{}
|
|
||||||
|
|
||||||
p := NewPrompter(80, s)
|
|
||||||
|
|
||||||
// disable input buffering
|
|
||||||
exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
|
|
||||||
|
|
||||||
// LOL i don't think this will work
|
|
||||||
reader := bufio.NewReader(p.in)
|
|
||||||
s, _ := reader.ReadString(4)
|
|
||||||
fmt.Printf("DBG %#v\n", s)
|
|
||||||
//var b []byte = make([]byte, 100)
|
|
||||||
//for {
|
|
||||||
// os.Stdin.Read(b)
|
|
||||||
// fmt.Println("I got the byte", b, "("+string(b)+")")
|
|
||||||
//}
|
|
||||||
|
|
||||||
var ia InputAnswer
|
|
||||||
if err := p.AskLong("lol give me stuff hit ctrl+d", &ia); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("DBG %#v\n", ia.Value)
|
|
||||||
|
|
||||||
p.Say("you are standing in a field.")
|
|
||||||
p.Pause()
|
|
||||||
p.Say("there are flowers around you. you are standing in a slight depression and before you is grass touching a purple sky.")
|
|
||||||
p.Pause()
|
|
||||||
p.Say("you are not sure how long it's been when a cube about a meter high appears at the top of the hill before you.")
|
|
||||||
p.Say("the cube's surface is murky but iridescently reflective like an oil slick.")
|
|
||||||
p.Pause()
|
|
||||||
p.CharSay("cube", "hello. how is your day going?")
|
|
||||||
p.Say("(you can type a response and hit enter to talk to the cube)")
|
|
||||||
p.Pause()
|
|
||||||
|
|
||||||
howDay := InputAnswer{}
|
|
||||||
err := p.Ask("you say:", &howDay)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.howDay = howDay.Value
|
|
||||||
|
|
||||||
p.Say("the cube inclines towards you gently as if nodding.")
|
|
||||||
p.CharSay("cube", "i see.")
|
|
||||||
p.Pause()
|
|
||||||
|
|
||||||
p.CharSay("cube", "i am guessing that if you are here, you want to be there.")
|
|
||||||
p.Pause()
|
|
||||||
|
|
||||||
p.Say("you blink and are somewhere else.")
|
|
||||||
p.Pause()
|
|
||||||
|
|
||||||
p.Say("the field of flowers is behind you and now you are up on the hill. the cube is next to you.")
|
|
||||||
p.Pause()
|
|
||||||
p.Say("you can see clear across a wide open plain. structures large and small dot the landscape. you catch a whiff of a savory smell and can just barely hear voices on the wind.")
|
|
||||||
p.Pause()
|
|
||||||
|
|
||||||
p.CharSay("cube", "the tilde town lies before us. if you want to continue, i'll ask you some questions about acquiring a home down in the town. you'll be free to edit your responses before i carry them off.")
|
|
||||||
p.Pause()
|
|
||||||
|
|
||||||
p.CharSay("cube", "first, i'm curious how you found out about the town?")
|
|
||||||
p.Pause()
|
|
||||||
|
|
||||||
answer := InputAnswer{}
|
|
||||||
|
|
||||||
if err := p.Ask("you say:", &answer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.howHeard = answer.Value
|
|
||||||
|
|
||||||
p.CharSay("cube", "interesting, thanks.")
|
|
||||||
p.CharSay("cube", "what sort of stuff do you want to get up to on the town?")
|
|
||||||
|
|
||||||
if err := p.Ask("you say:", &answer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.plans = answer.Value
|
|
||||||
|
|
||||||
p.CharSay("cube", "thanks.")
|
|
||||||
p.CharSay("cube", "what do you like about the town?")
|
|
||||||
|
|
||||||
if err := p.Ask("you say:", &answer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.reasons = answer.Value
|
|
||||||
|
|
||||||
p.CharSay("cube", "i appreciate it.")
|
|
||||||
p.CharSay("cube", "can you paste some links to other places you are active online? maybe a homepage or social media profile? if you aren't comfortable sharing or there are none, just say so.")
|
|
||||||
|
|
||||||
if err := p.Ask("you say:", &answer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.socials = answer.Value
|
|
||||||
|
|
||||||
p.CharSay("cube", "ok, last thing. in order to break ground in the town, you'll need an SSH key. If you don't know what that is, you can check out this link: https://tilde.town/ssh.html .")
|
|
||||||
p.Pause()
|
|
||||||
|
|
||||||
if err := p.AskLong("paste SSH public key; press control+d to submit:", &answer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.sshKey = answer.Value
|
|
||||||
|
|
||||||
p.CharSay("cube", "i know that was a lot so i appreciate it. i've got everything written down here. before i carry it off, do you want to review and edit what you wrote?")
|
|
||||||
|
|
||||||
confirmed, err := p.Confirm("type y or n: ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if confirmed {
|
|
||||||
err := edit(s, &a)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("DBG %#v\n", confirmed)
|
|
||||||
|
|
||||||
fmt.Printf("DBG %#v\n", a)
|
|
||||||
|
|
||||||
// TODO allow for editing
|
|
||||||
// TODO write answers to disk
|
|
||||||
// TODO add a log
|
|
||||||
// TODO pretty colors
|
|
||||||
// TODO ascii art
|
|
||||||
// TODO IP throttling?
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func edit(s *streams, a *answers) error {
|
|
||||||
// TODO make more real
|
|
||||||
/*
|
|
||||||
username string
|
|
||||||
email string
|
|
||||||
applied time.Time
|
|
||||||
|
|
||||||
howDay string
|
|
||||||
howHeard string
|
|
||||||
reasons string
|
|
||||||
plans string
|
|
||||||
socials string
|
|
||||||
sshKey string
|
|
||||||
*/
|
|
||||||
// TODO add note about tabbing around
|
|
||||||
|
|
||||||
app := tview.NewApplication()
|
|
||||||
form := tview.NewForm().
|
|
||||||
AddInputField("how did you hear about the town?", a.howHeard, 0, nil, nil).
|
|
||||||
AddButton("cool i'm good", nil).
|
|
||||||
AddButton("cancel and discard all this please", func() { app.Stop() })
|
|
||||||
form.SetBorder(true).SetTitle("edit your stuff").SetTitleAlign(tview.AlignCenter)
|
|
||||||
|
|
||||||
return app.SetRoot(form, true).EnableMouse(true).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
retcode := 0
|
|
||||||
s := &streams{
|
|
||||||
In: os.Stdin,
|
|
||||||
Out: os.Stdout,
|
|
||||||
Err: os.Stderr,
|
|
||||||
}
|
|
||||||
err := _main(os.Args, s)
|
|
||||||
if err != nil {
|
|
||||||
retcode = 1
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
}
|
|
||||||
os.Exit(retcode)
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
# New commands for signups
|
|
||||||
|
|
||||||
## town-signup
|
|
||||||
|
|
||||||
this binary is run by OpenSSH when a user anywhere in the world runs `ssh join@tilde.town`. It should:
|
|
||||||
|
|
||||||
- collect information about an application from a user
|
|
||||||
- allow them to edit their responses before submitting
|
|
||||||
- write a yaml file of their responses to disk
|
|
||||||
|
|
||||||
## review-signups
|
|
||||||
|
|
||||||
this binary is run by town admins in order to review, approve, notate, and reject applications. It should:
|
|
||||||
|
|
||||||
- iterate over signups
|
|
||||||
- allow fuzzy finding a particular signup
|
|
||||||
- allow approval using create-user
|
|
||||||
- allow rejection
|
|
||||||
- just move signup to archived rejected signup directory
|
|
||||||
- allow notating an application, ie:
|
|
||||||
- lock the yaml file for writing
|
|
||||||
- add notes to the yaml file that can be seen by other admins
|
|
||||||
- print info about historical signups
|
|
||||||
|
|
||||||
## create-user
|
|
||||||
|
|
||||||
this binary is called by `review-signups` to take a yaml application and create a user on disk. It should:
|
|
||||||
|
|
||||||
- create the user idempotently
|
|
||||||
- `adduser`
|
|
||||||
- `usermod` to set group
|
|
||||||
- calling `add-key` for user
|
|
||||||
- move the yaml file to an archive directory of approved signups
|
|
||||||
|
|
||||||
## user-key
|
|
||||||
|
|
||||||
this binary helps manage keys for users; basically automating the listing, adding, and removing of public keys for a user.
|
|
||||||
|
|
||||||
- `user-key add <username> <keyfile`
|
|
||||||
- `user-key list <username>`
|
|
||||||
- `user-key remove <username>`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
7
go.mod
7
go.mod
|
@ -5,7 +5,6 @@ go 1.18
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.5
|
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||||
github.com/charmbracelet/glamour v0.5.0
|
github.com/charmbracelet/glamour v0.5.0
|
||||||
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
|
||||||
)
|
)
|
||||||
|
@ -14,8 +13,6 @@ require (
|
||||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
github.com/gdamore/encoding v1.0.0 // indirect
|
|
||||||
github.com/gdamore/tcell/v2 v2.5.3 // 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
|
||||||
|
@ -28,12 +25,12 @@ require (
|
||||||
github.com/muesli/reflow v0.3.0 // indirect
|
github.com/muesli/reflow v0.3.0 // indirect
|
||||||
github.com/muesli/termenv v0.9.0 // indirect
|
github.com/muesli/termenv v0.9.0 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/rivo/uniseg v0.4.2 // indirect
|
github.com/rivo/uniseg v0.2.0 // 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/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // 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.3.7 // indirect
|
golang.org/x/text v0.3.6 // indirect
|
||||||
)
|
)
|
||||||
|
|
15
go.sum
15
go.sum
|
@ -16,10 +16,6 @@ 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/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
|
||||||
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/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
|
||||||
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=
|
||||||
|
@ -51,12 +47,9 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
|
||||||
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=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c h1:zIYU4PjQJ4BnYryMmpyizt1Un13V0ToCMXvC05DK8xc=
|
|
||||||
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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
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.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
|
||||||
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=
|
||||||
|
@ -77,18 +70,14 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||||
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-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 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/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-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||||
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 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
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/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=
|
||||||
|
|
Loading…
Reference in New Issue