Compare commits
3 Commits
b1f7e9842d
...
cf99807126
Author | SHA1 | Date |
---|---|---|
vilmibm | cf99807126 | |
vilmibm | 65228979e9 | |
vilmibm | 75d64bf7b6 |
|
@ -111,6 +111,8 @@ func submit(opts *contribOpts) error {
|
||||||
"social",
|
"social",
|
||||||
"game",
|
"game",
|
||||||
"utility",
|
"utility",
|
||||||
|
"programming",
|
||||||
|
"community",
|
||||||
"misc",
|
"misc",
|
||||||
}
|
}
|
||||||
var choice int
|
var choice int
|
||||||
|
@ -159,7 +161,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", rand.Intn(10000))
|
fname := fmt.Sprintf("%d.yml", time.Now().Unix())
|
||||||
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)
|
||||||
|
@ -244,7 +246,8 @@ 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")
|
||||||
rc.Flags().BoolVarP(&force, "force", "f", false, "skip request, just install the command")
|
// TODO hiding this until i decide i want it or not
|
||||||
|
//rc.Flags().BoolVarP(&force, "force", "f", false, "skip request, just install the command")
|
||||||
|
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,9 @@ 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"
|
||||||
)
|
)
|
||||||
|
@ -48,19 +46,7 @@ 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 {
|
||||||
|
@ -79,61 +65,43 @@ func parseCommands(targetCmd *cobra.Command, path string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type commandDoc struct {
|
type cmdDoc struct {
|
||||||
ShortDesc string `yaml:"shortDesc"`
|
CmdName string
|
||||||
LongDesc string `yaml:"longDesc"`
|
ExecPath string
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
doc := commandDoc{}
|
var doc cmdDoc
|
||||||
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: filepath.Base(executablePath),
|
Use: doc.CmdName,
|
||||||
RunE: execWrapper(executablePath),
|
RunE: execWrapper(doc.ExecPath),
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,30 +116,14 @@ func execWrapper(executablePath string) func(*cobra.Command, []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func cli() int {
|
func cli() int {
|
||||||
err := parseCommands(rootCmd, "core")
|
files, err := ioutil.ReadDir(binroot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "failed to parse core commands: %s", err)
|
fmt.Fprintf(os.Stderr, "failed to list directory %s: %s", binroot, err)
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = parseCommands(rootCmd, "contrib")
|
for _, file := range files {
|
||||||
if err != nil {
|
if strings.HasSuffix(file.Name(), "yml") {
|
||||||
fmt.Fprintf(os.Stderr, "failed to parse contrib commands: %s", err)
|
parseCommand(rootCmd, filepath.Join(binroot, file.Name()))
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
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
|
|
@ -0,0 +1,366 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
# 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,6 +5,7 @@ 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
|
||||||
)
|
)
|
||||||
|
@ -13,6 +14,8 @@ 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
|
||||||
|
@ -25,12 +28,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.2.0 // 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/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.6 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
)
|
)
|
||||||
|
|
15
go.sum
15
go.sum
|
@ -16,6 +16,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/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=
|
||||||
|
@ -47,9 +51,12 @@ 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=
|
||||||
|
@ -70,14 +77,18 @@ 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