town/cmd/welcome/main.go

225 lines
5.2 KiB
Go
Raw Normal View History

package main
2023-02-26 08:57:46 +00:00
import (
2023-03-04 00:15:12 +00:00
"bytes"
2023-03-06 19:33:36 +00:00
"encoding/json"
2023-02-28 19:30:07 +00:00
"errors"
2023-02-26 08:57:46 +00:00
"fmt"
"os"
2023-03-03 21:39:36 +00:00
"os/exec"
2023-02-26 08:57:46 +00:00
2023-02-28 19:30:07 +00:00
"git.tilde.town/tildetown/town/invites"
2023-02-28 23:18:11 +00:00
"git.tilde.town/tildetown/town/stats"
2023-03-06 19:33:36 +00:00
"git.tilde.town/tildetown/town/towndb"
2023-02-28 05:50:55 +00:00
"github.com/charmbracelet/lipgloss"
2023-02-26 08:57:46 +00:00
_ "embed"
)
2023-03-07 01:06:46 +00:00
// TODO mark on user table what signup id led to the account for forensics
2023-03-06 19:33:36 +00:00
// TODO add logging like the signup tool has
// TODO consider merging adduser, usermod, and createkeyfile into single createuser helper to limit sudoers list
2023-03-04 00:51:40 +00:00
// TODO move magic key machine to static page
2023-03-06 19:33:36 +00:00
// TODO add alerts for new users (mailing list post, irc post, etc(?))
2023-03-04 00:51:40 +00:00
2023-02-26 08:57:46 +00:00
//go:embed welcome.txt
var welcomeArt string
type newUserData struct {
Username string
DisplayName string
Email string
PubKey string
}
2023-03-04 00:51:40 +00:00
func defaultStyle() lipgloss.Style {
return lipgloss.NewStyle().
Foreground(lipgloss.AdaptiveColor{
Light: "#7D19BD",
Dark: "#E0B0FF",
})
2023-03-01 05:22:09 +00:00
}
2023-02-28 05:50:55 +00:00
func _main() error {
2023-02-28 19:30:07 +00:00
inviteDB, err := invites.ConnectDB()
if err != nil {
return err
}
2023-03-04 00:51:40 +00:00
s := defaultStyle().SetString(welcomeArt)
2023-02-28 05:50:55 +00:00
fmt.Println(s)
2023-02-26 08:57:46 +00:00
2023-02-28 19:30:07 +00:00
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()
2023-02-28 05:50:55 +00:00
if err != nil {
return err
}
data := &newUserData{
Email: invite.Email,
}
2023-02-26 08:57:46 +00:00
a := &asker{
UserData: data,
Invite: *invite,
TownData: townData,
2023-03-04 00:51:40 +00:00
Style: defaultStyle(),
}
if err = a.Ask(); err != nil {
2023-03-01 00:10:45 +00:00
return err
}
conf, err := confirmContinue()
2023-03-01 05:22:09 +00:00
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
}
}
}
2023-03-06 21:00:59 +00:00
s = s.SetString("town users all agree to our code of conduct: https://tilde.town/wiki/conduct.html")
fmt.Println(s)
coc, err := confirmCoC()
if err != nil {
return err
}
if !coc {
s = s.SetString("bummer. have a good one.")
fmt.Println(s)
return nil
}
s = s.SetString("cool, awesome, thank you, going to make your account now...")
fmt.Println(s)
2023-03-01 05:22:09 +00:00
2023-03-03 21:29:13 +00:00
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
2023-03-04 00:51:40 +00:00
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()))
2023-03-03 21:29:13 +00:00
fmt.Println(s)
return nil
}
2023-03-04 00:51:40 +00:00
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)
2023-03-06 20:44:19 +00:00
if err = invite.MarkUsed(inviteDB); err != nil {
return fmt.Errorf("could not mark invite as used: %w", err)
}
2023-03-06 19:33:36 +00:00
// TODO burn invite
2023-02-26 08:57:46 +00:00
2023-02-28 05:50:55 +00:00
return nil
2023-02-26 08:57:46 +00:00
}
2023-03-03 21:39:36 +00:00
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)
}
2023-03-04 01:02:52 +00:00
cmd = exec.Command("sudo", "--user", data.Username, "/town/bin/createkeyfile", data.Username)
2023-03-04 00:15:12 +00:00
cmd.Stdin = bytes.NewBufferString(keyfileText(data))
2023-03-06 20:34:29 +00:00
stdoutBuff := bytes.NewBuffer([]byte{})
cmd.Stdout = stdoutBuff
2023-03-04 00:15:12 +00:00
if err = cmd.Run(); err != nil {
2023-03-06 20:34:29 +00:00
return fmt.Errorf("createkeyfile failed with '%s': %w", string(stdoutBuff.Bytes()), err)
2023-03-04 00:15:12 +00:00
}
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.
}
2023-03-06 19:33:36 +00:00
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)
}
2023-03-06 20:34:29 +00:00
cmd = exec.Command("sudo", "/town/bin/registeruser")
stderrBuff := bytes.NewBuffer([]byte{})
cmd.Stderr = stderrBuff
2023-03-06 19:33:36 +00:00
cmd.Stdin = bytes.NewBuffer(out)
if err = cmd.Run(); err != nil {
2023-03-06 20:34:29 +00:00
return fmt.Errorf("register user failed with '%s': %w", string(stderrBuff.Bytes()), err)
2023-03-06 19:33:36 +00:00
}
2023-03-04 00:20:18 +00:00
return nil
2023-03-03 21:29:13 +00:00
}
2023-03-04 00:15:12 +00:00
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() {
2023-02-28 19:30:07 +00:00
// TODO friendlier error handling
2023-02-26 08:57:46 +00:00
err := _main()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}