Skip to content
Snippets Groups Projects
Unverified Commit cd0a4b47 authored by Kevin Lyda's avatar Kevin Lyda
Browse files

Implement install command

parent 2fb36814
No related branches found
No related tags found
No related merge requests found
...@@ -4,7 +4,20 @@ getting a line of text, getting a choice from a liat and other things. ...@@ -4,7 +4,20 @@ getting a line of text, getting a choice from a liat and other things.
*/ */
package ask 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. // GetLine gets a line.
func GetLine(prompt string) (string, error) { func GetLine(prompt string) (string, error) {
......
...@@ -2,11 +2,19 @@ ...@@ -2,11 +2,19 @@
package batch package batch
import ( import (
"context"
_ "embed" _ "embed"
"errors"
"fmt" "fmt"
"os" "os"
"path"
"strings" "strings"
"text/template" "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 //go:embed crontab
...@@ -26,6 +34,44 @@ func Expire() int { ...@@ -26,6 +34,44 @@ func Expire() int {
// Install is an interactive command used to install the crontab. // Install is an interactive command used to install the crontab.
func Install() int { 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() bulletin, err := os.Executable()
if err != nil { if err != nil {
panic(err) // TODO: cleanup error handling. panic(err) // TODO: cleanup error handling.
...@@ -34,9 +80,16 @@ func Install() int { ...@@ -34,9 +80,16 @@ func Install() int {
template.Must(template.New("crontab").Parse(crontabTemplate)). template.Must(template.New("crontab").Parse(crontabTemplate)).
Execute(crontab, map[string]string{"Bulletin": bulletin}) Execute(crontab, map[string]string{"Bulletin": bulletin})
fmt.Printf("Add this to crontab:\n\n%s\n", crontab.String()) 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 return 0
} }
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
}
// 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
}
package storage package storage
import ( import (
"embed"
"errors" "errors"
"os"
"github.com/jmoiron/sqlx" "path"
// Included to connect to sqlite. // Included to connect to sqlite.
_ "github.com/golang-migrate/migrate/v4/database/sqlite" _ "github.com/golang-migrate/migrate/v4/database/sqlite"
_ "modernc.org/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. // 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)") db, err := sqlx.Connect("sqlite", "file://"+dbfile+"?_pragma=foreign_keys(1)")
if err != nil { if err != nil {
return nil, errors.New("bulletin database problem") return nil, errors.New("bulletin database problem")
......
package storage
import (
"embed"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/source/iofs"
// Included to connect to sqlite.
_ "github.com/golang-migrate/migrate/v4/database/sqlite"
_ "modernc.org/sqlite"
)
//go:embed migrations/*.sql
var migrationsFS embed.FS
// Migrate creates and updates the database.
func Migrate(dbfile string) error {
// Run db migrations if needed.
migrations, err := iofs.New(migrationsFS, "migrations")
if err != nil {
return err
}
m, err := migrate.NewWithSourceInstance("iofs", migrations,
"sqlite://"+dbfile+"?_pragma=foreign_keys(1)")
if err != nil {
return err
}
defer m.Close()
err = m.Up()
if err != nil && err != migrate.ErrNoChange {
return err
}
return nil
}
...@@ -13,22 +13,16 @@ import ( ...@@ -13,22 +13,16 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"os"
"path"
"git.lyda.ie/kevin/bulletin/ask" "git.lyda.ie/kevin/bulletin/ask"
"git.lyda.ie/kevin/bulletin/storage" "git.lyda.ie/kevin/bulletin/storage"
"git.lyda.ie/kevin/bulletin/users" "git.lyda.ie/kevin/bulletin/users"
"github.com/adrg/xdg"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
// User is the user for this session. // User is the user for this session.
var User storage.User var User storage.User
// DBFile is the path for the storage.
var DBFile string
// Store is the store for this session. // Store is the store for this session.
var Store *sqlx.DB var Store *sqlx.DB
...@@ -49,16 +43,8 @@ func StartThis(login, name string) error { ...@@ -49,16 +43,8 @@ func StartThis(login, name string) error {
return err return err
} }
// Run migrations. // Connect to the DB.
bulldir := path.Join(xdg.DataHome, "BULLETIN") Store, err = storage.Open()
err = os.MkdirAll(bulldir, 0700)
if err != nil {
return errors.New("bulletin directory problem")
}
DBFile := path.Join(bulldir, "bulletin.db")
storage.Migrate(DBFile)
Store, err = storage.Open(DBFile)
if err != nil { if err != nil {
return err return err
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment