Unverified Commit cd0a4b47 authored by Kevin Lyda's avatar Kevin Lyda
Browse files

Implement install command

parent 2fb36814
Loading
Loading
Loading
Loading
+14 −1
Original line number Diff line number Diff line
@@ -4,7 +4,20 @@ getting a line of text, getting a choice from a liat and other things.
*/
package ask

import "github.com/chzyer/readline"
import (
	"fmt"
	"os"

	"github.com/chzyer/readline"
)

// CheckErr prints an error message and exits.
func CheckErr(err error) {
	if err != nil {
		fmt.Printf("ERROR: %s\n", err)
		os.Exit(1)
	}
}

// GetLine gets a line.
func GetLine(prompt string) (string, error) {
+56 −3
Original line number Diff line number Diff line
@@ -2,11 +2,19 @@
package batch

import (
	"context"
	_ "embed"
	"errors"
	"fmt"
	"os"
	"path"
	"strings"
	"text/template"

	"git.lyda.ie/kevin/bulletin/ask"
	"git.lyda.ie/kevin/bulletin/key"
	"git.lyda.ie/kevin/bulletin/storage"
	"github.com/adrg/xdg"
)

//go:embed crontab
@@ -26,6 +34,44 @@ func Expire() int {

// Install is an interactive command used to install the crontab.
func Install() int {
	// Check if install has run before.
	touchfile := path.Join(xdg.Home, ".bulletin-installed")
	if _, err := os.Stat(touchfile); err == nil {
		ask.CheckErr(errors.New("~/.bulletin-installed exists; BULLETIN install has run"))
	} else {
		if !errors.Is(err, os.ErrNotExist) {
			fmt.Println("ERROR: Unknown error checking in ~/.bulletin-installed exists.")
			fmt.Println("ERROR: Can't see if BULLETIN has been installed.")
			ask.CheckErr(err)
		}
	}

	// Connect to the database.
	store, err := storage.Open()
	ask.CheckErr(err)
	q := storage.New(store.DB)

	// Create the initial users.
	login, err := ask.GetLine("Enter login of initial user: ")
	ask.CheckErr(err)
	name, err := ask.GetLine("Enter name of initial user: ")
	ask.CheckErr(err)
	sshkey, err := ask.GetLine("Enter ssh public key of initial user: ")
	ask.CheckErr(err)

	// Seed data.
	ctx := context.TODO()
	ask.CheckErr(q.SeedUserSystem(ctx))
	ask.CheckErr(q.SeedFolderGeneral(ctx))
	ask.CheckErr(q.SeedGeneralOwner(ctx))
	_, err = q.AddUser(ctx, storage.AddUserParams{
		Login: login,
		Name:  name,
	})
	ask.CheckErr(q.SeedGeneralOwner(ctx))
	key.Add(login, sshkey)

	// Install crontab.
	bulletin, err := os.Executable()
	if err != nil {
		panic(err) // TODO: cleanup error handling.
@@ -34,9 +80,16 @@ func Install() int {
	template.Must(template.New("crontab").Parse(crontabTemplate)).
		Execute(crontab, map[string]string{"Bulletin": bulletin})
	fmt.Printf("Add this to crontab:\n\n%s\n", crontab.String())
	err = installCrontab(crontab.String())
	if err != nil {
		panic(err) // TODO: cleanup error handling.
	}

	// Mark that install has happened.
	err = touch(touchfile)
	if err != nil {
		panic(err) // TODO: cleanup error handling.
	}

	fmt.Println("TODO: Finish installing bulletin.")
	// TODO: Offer to add ssh key for system user.
	// TODO: Offer to add ssh key for other user(s).
	return 0
}

batch/file.go

0 → 100644
+55 −0
Original line number Diff line number Diff line
package batch

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
)

func touch(name string) error {
	f, err := os.OpenFile(name, os.O_RDONLY|os.O_CREATE, 0666)
	if err != nil {
		return err
	}
	f.Close()
	return nil
}

func installCrontab(crontab string) error {
	// Make a temp file.
	f, err := os.CreateTemp("", "crontab")
	if err != nil {
		return err
	}
	defer func() {
		f.Close()
		os.Remove(f.Name())
	}()

	// Put the crontab in the temp file.
	n, err := f.WriteString(crontab)
	if err != nil {
		return err
	}
	if n != len(crontab) {
		return fmt.Errorf("Failed to write crontab fully %d of %d chars written",
			n, len(crontab))
	}

	// Have the crontab command read in the file.
	cmd := exec.Command("crontab", f.Name())
	cmd.Stdin = strings.NewReader("")
	var stdout strings.Builder
	cmd.Stdout = &stdout
	var stderr strings.Builder
	cmd.Stderr = &stdout
	err = cmd.Run()
	if err != nil {
		fmt.Printf("ERROR: stdout: [%s]\n", stdout.String())
		fmt.Printf("ERROR: stderr: [%s]\n", stderr.String())
		return err
	}

	return nil
}

key/key.go

0 → 100644
+40 −0
Original line number Diff line number Diff line
// Package key manages the authorized keys file.
package key

import (
	"fmt"
	"os"
	"path"

	"github.com/adrg/xdg"
)

var keytemplate = `command="%s -u %s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s\n`

// Add adds an ssh key to the `authorized_keys` file.
func Add(login, public string) error {
	bulletin, err := os.Executable()
	if err != nil {
		return err
	}

	keyline := fmt.Sprintf(keytemplate, bulletin, login, public)

	keyfile := path.Join(xdg.Home, ".ssh", "authorized_keys")

	f, err := os.OpenFile(keyfile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		return err
	}
	defer f.Close()
	n, err := f.WriteString(keyline)
	if err != nil {
		return err
	}
	if n != len(keyline) {
		return fmt.Errorf("Failed to write authorized_key fully %d of %d chars written",
			n, len(keyline))
	}

	return nil
}
+37 −3
Original line number Diff line number Diff line
package storage

import (
	"embed"
	"errors"

	"github.com/jmoiron/sqlx"
	"os"
	"path"

	// Included to connect to sqlite.
	_ "github.com/golang-migrate/migrate/v4/database/sqlite"
	_ "modernc.org/sqlite"

	"github.com/adrg/xdg"
	"github.com/golang-migrate/migrate/v4"
	"github.com/golang-migrate/migrate/v4/source/iofs"
	"github.com/jmoiron/sqlx"
)

//go:embed migrations/*.sql
var migrationsFS embed.FS

// Open opens the bulletin database.
func Open(dbfile string) (*sqlx.DB, error) {
func Open() (*sqlx.DB, error) {
	// Determine path names and create components.
	bulldir := path.Join(xdg.DataHome, "BULLETIN")
	err := os.MkdirAll(bulldir, 0700)
	if err != nil {
		return nil, errors.New("bulletin directory problem")
	}
	dbfile := path.Join(bulldir, "bulletin.db")

	// Run db migrations if needed.
	migrations, err := iofs.New(migrationsFS, "migrations")
	if err != nil {
		return nil, err
	}
	m, err := migrate.NewWithSourceInstance("iofs", migrations,
		"sqlite://"+dbfile+"?_pragma=foreign_keys(1)")
	if err != nil {
		return nil, err
	}
	defer m.Close()
	err = m.Up()
	if err != nil && err != migrate.ErrNoChange {
		return nil, err
	}

	// Connect to the db.
	db, err := sqlx.Connect("sqlite", "file://"+dbfile+"?_pragma=foreign_keys(1)")
	if err != nil {
		return nil, errors.New("bulletin database problem")
Loading