forked from tildetown/town
210 lines
4.9 KiB
Go
210 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
|
|
"git.tilde.town/tildetown/town/invites"
|
|
"git.tilde.town/tildetown/town/stats"
|
|
"git.tilde.town/tildetown/town/towndb"
|
|
"github.com/charmbracelet/lipgloss"
|
|
|
|
_ "embed"
|
|
)
|
|
|
|
// TODO add logging like the signup tool has
|
|
// TODO consider merging adduser, usermod, and createkeyfile into single createuser helper to limit sudoers list
|
|
// TODO move magic key machine to static page
|
|
// TODO link to code of conduct as part of the form and ask if they agree
|
|
// TODO add alerts for new users (mailing list post, irc post, etc(?))
|
|
|
|
//go:embed welcome.txt
|
|
var welcomeArt string
|
|
|
|
type newUserData struct {
|
|
Username string
|
|
DisplayName string
|
|
Email string
|
|
PubKey string
|
|
}
|
|
|
|
func defaultStyle() lipgloss.Style {
|
|
return lipgloss.NewStyle().
|
|
Foreground(lipgloss.AdaptiveColor{
|
|
Light: "#7D19BD",
|
|
Dark: "#E0B0FF",
|
|
})
|
|
}
|
|
|
|
func _main() error {
|
|
inviteDB, err := invites.ConnectDB()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s := defaultStyle().SetString(welcomeArt)
|
|
fmt.Println(s)
|
|
|
|
code, err := promptCode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
invite, err := invites.Get(inviteDB, code)
|
|
if err != nil {
|
|
return fmt.Errorf("could not look up invite code: %w", err)
|
|
}
|
|
|
|
if invite.Used {
|
|
return errors.New("that invite code has already been used.")
|
|
}
|
|
|
|
s = s.SetString("thanks!! just gotta collect some information now and then your account will be ready.")
|
|
|
|
fmt.Println(s)
|
|
|
|
townData, err := stats.Stats()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data := &newUserData{
|
|
Email: invite.Email,
|
|
}
|
|
|
|
a := &asker{
|
|
UserData: data,
|
|
Invite: *invite,
|
|
TownData: townData,
|
|
Style: defaultStyle(),
|
|
}
|
|
|
|
if err = a.Ask(); err != nil {
|
|
return err
|
|
}
|
|
|
|
conf, err := confirmContinue()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !conf {
|
|
for !conf {
|
|
if err = a.Ask(); err != nil {
|
|
return err
|
|
}
|
|
if conf, err = confirmContinue(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
s = s.SetString("cool, awesome, going to make your account now...")
|
|
fmt.Println(s)
|
|
|
|
if err = createUser(*data); err != nil {
|
|
s = s.SetString(fmt.Sprintf(`augh, I'm sorry. the account creation failed.
|
|
Please email root@tilde.town and paste the following error:
|
|
|
|
%s
|
|
|
|
Your invite code has not been marked as used and you're welcome to try again,
|
|
though if there is a system issue you might need to wait for word from an admin.`, err.Error()))
|
|
fmt.Println(s)
|
|
return nil
|
|
}
|
|
|
|
s = s.SetString(fmt.Sprintf(`OK! your user account has been created.
|
|
|
|
welcome, ~%[1]s <3
|
|
|
|
This program is going to exit and you are now free to ssh to town as yourself:
|
|
|
|
ssh %[1]s@tilde.town
|
|
|
|
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 public key file" %[1]s@tilde.town
|
|
|
|
for help with ssh, see: https://tilde.town/wiki/getting-started/ssh.html
|
|
|
|
if you end up very stuck, you can email root@tilde.town .`, data.Username))
|
|
|
|
fmt.Println(s)
|
|
|
|
if err = invite.MarkUsed(inviteDB); err != nil {
|
|
return fmt.Errorf("could not mark invite as used: %w", err)
|
|
}
|
|
|
|
// TODO burn invite
|
|
|
|
return nil
|
|
}
|
|
|
|
func createUser(data newUserData) (err error) {
|
|
cmd := exec.Command("sudo", "/usr/sbin/adduser", "--quiet", "--disabled-password", data.Username)
|
|
if err = cmd.Run(); err != nil {
|
|
return fmt.Errorf("adduser failed: %w", err)
|
|
}
|
|
|
|
cmd = exec.Command("sudo", "/usr/sbin/usermod", "-a", "-G", "town", data.Username)
|
|
if err = cmd.Run(); err != nil {
|
|
return fmt.Errorf("usermod failed: %w", err)
|
|
}
|
|
|
|
cmd = exec.Command("sudo", "--user", data.Username, "/town/bin/createkeyfile", data.Username)
|
|
cmd.Stdin = bytes.NewBufferString(keyfileText(data))
|
|
stdoutBuff := bytes.NewBuffer([]byte{})
|
|
cmd.Stdout = stdoutBuff
|
|
if err = cmd.Run(); err != nil {
|
|
return fmt.Errorf("createkeyfile failed with '%s': %w", string(stdoutBuff.Bytes()), err)
|
|
}
|
|
|
|
cmd = exec.Command("sudo", "/town/bin/generate_welcome_present.sh", data.Username)
|
|
if err = cmd.Run(); err != nil {
|
|
// TODO log this. no reason to bail out.
|
|
}
|
|
|
|
tu := towndb.TownUser{
|
|
Username: data.Username,
|
|
Emails: []string{
|
|
data.Email,
|
|
},
|
|
State: towndb.StateActive,
|
|
}
|
|
var out []byte
|
|
if out, err = json.Marshal(tu); err != nil {
|
|
return fmt.Errorf("could not serialize user data: %w", err)
|
|
}
|
|
cmd = exec.Command("sudo", "/town/bin/registeruser")
|
|
stderrBuff := bytes.NewBuffer([]byte{})
|
|
cmd.Stderr = stderrBuff
|
|
cmd.Stdin = bytes.NewBuffer(out)
|
|
if err = cmd.Run(); err != nil {
|
|
return fmt.Errorf("register user failed with '%s': %w", string(stderrBuff.Bytes()), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func keyfileText(data newUserData) string {
|
|
header := `########## GREETINGS! ##########
|
|
# Hi! This file was automatically generated by tilde.town when
|
|
# your account was created. You can edit it if you want, but we
|
|
# recommend adding stuff to ~/.ssh/authorized_keys instead.`
|
|
|
|
return fmt.Sprintf("%s\n%s", header, data.PubKey)
|
|
}
|
|
|
|
func main() {
|
|
// TODO friendlier error handling
|
|
err := _main()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|