Allow server to make backups

This commit is contained in:
Tom Wiesing 2022-10-07 16:30:07 +02:00
parent aeceae11d5
commit b3a827e042
No known key found for this signature in database
27 changed files with 891 additions and 418 deletions

View file

@ -19,7 +19,7 @@ services:
# TODO: Mount docker socket properly!
- "/var/run/docker.sock:/var/run/docker.sock"
- "${CONFIG_PATH}:${CONFIG_PATH}:ro"
- "${DEPLOY_ROOT}:${DEPLOY_ROOT}:ro"
- "${DEPLOY_ROOT}:${DEPLOY_ROOT}:rw"
- "${GLOBAL_AUTHORIZED_KEYS_FILE}:${GLOBAL_AUTHORIZED_KEYS_FILE}:ro"
- "${SELF_OVERRIDES_FILE}:${SELF_OVERRIDES_FILE}:ro"
- "${SELF_RESOLVER_BLOCK_FILE}:${SELF_RESOLVER_BLOCK_FILE}:ro"

View file

@ -1,207 +0,0 @@
package control
import (
"context"
"html/template"
"net/http"
"strings"
"time"
_ "embed"
"github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
"github.com/tkw1536/goprogram/stream"
"golang.org/x/sync/errgroup"
)
type Info struct {
component.ComponentBase
Instances *instances.Instances
}
func (Info) Name() string { return "control-info" }
func (*Info) Routes() []string { return []string{"/dis/"} }
func (info *Info) Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error) {
mux := http.NewServeMux()
// handle everything under /dis/!
mux.HandleFunc("/dis/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/dis/" {
http.Redirect(w, r, "/dis/index", http.StatusTemporaryRedirect)
return
}
http.NotFound(w, r)
})
// render everything
mux.Handle("/dis/index", httpx.HTMLHandler[disIndex]{
Handler: info.disIndex,
Template: indexTemplate,
})
mux.Handle("/dis/instance/", httpx.HTMLHandler[disInstance]{
Handler: info.disInstance,
Template: instanceTemplate,
})
// api -- for future usage
mux.Handle("/dis/api/v1/instance/get/", httpx.JSON(info.getinstance))
mux.Handle("/dis/api/v1/instance/all", httpx.JSON(info.allinstances))
// ensure that everyone is logged in!
return httpx.BasicAuth(mux, "WissKI Distillery Admin", func(user, pass string) bool {
return user == info.Config.DisAdminUser && pass == info.Config.DisAdminPassword
}), nil
}
// disIndex is the context of the "/dis/index" page
type disIndex struct {
Time time.Time
Config *config.Config
Instances []instances.WissKIInfo
TotalCount int
RunningCount int
StoppedCount int
Backups []models.Snapshot
}
func (info *Info) disIndex(r *http.Request) (idx disIndex, err error) {
var group errgroup.Group
group.Go(func() error {
// load instances
idx.Instances, err = info.allinstances(r)
if err != nil {
return err
}
// count how many are running and how many are stopped
for _, i := range idx.Instances {
if i.Running {
idx.RunningCount++
} else {
idx.StoppedCount++
}
}
idx.TotalCount = len(idx.Instances)
return nil
})
// get the log entries
group.Go(func() (err error) {
idx.Backups, err = info.Instances.SnapshotLogFor("")
return
})
// get the static properties
idx.Config = info.Config
// current time
idx.Time = time.Now().UTC()
// wait for everything!
group.Wait()
return
}
// disInstance is the context of the "/dis/instance/*" page
type disInstance struct {
Time time.Time
Instance models.Instance
Info instances.WissKIInfo
}
func (info *Info) disInstance(r *http.Request) (is disInstance, err error) {
// find the slug as the last component of path!
slug := strings.TrimSuffix(r.URL.Path, "/")
slug = slug[strings.LastIndex(slug, "/")+1:]
// find the instance itself!
instance, err := info.Instances.WissKI(slug)
if err == instances.ErrWissKINotFound {
return is, httpx.ErrNotFound
}
if err != nil {
return is, err
}
is.Instance = instance.Instance
// get some more info about the wisski
is.Info, err = instance.Info(false)
if err != nil {
return is, err
}
// current time
is.Time = time.Now().UTC()
return
}
//go:embed "html/index.html"
var indexTemplateStr string
var indexTemplate = template.Must(template.New("index.html").Parse(indexTemplateStr))
//go:embed "html/instance.html"
var instanceTemplateString string
var instanceTemplate = template.Must(template.New("instance.html").Parse(instanceTemplateString))
func (info *Info) getinstance(r *http.Request) (iinfo instances.WissKIInfo, err error) {
// find the slug as the last component of path!
slug := strings.TrimSuffix(r.URL.Path, "/")
slug = slug[strings.LastIndex(slug, "/")+1:]
// load the wisski instance!
wisski, err := info.Instances.WissKI(strings.TrimSuffix(slug, "/"))
if err == instances.ErrWissKINotFound {
return iinfo, httpx.ErrNotFound
}
if err != nil {
return iinfo, err
}
// get info about it!
return wisski.Info(false)
}
func (info *Info) allinstances(*http.Request) (infos []instances.WissKIInfo, err error) {
var errgroup errgroup.Group
// list all the instances
all, err := info.Instances.All()
if err != nil {
return nil, err
}
// get all of their info!
infos = make([]instances.WissKIInfo, len(all))
for i, instance := range all {
{
i := i
instance := instance
errgroup.Go(func() (err error) {
infos[i], err = instance.Info(true)
return err
})
}
}
// wait for the results, and return
err = errgroup.Wait()
return
}

View file

@ -1,79 +0,0 @@
<!DOCTYPE html>
<link rel="stylesheet" href="/static/control/index.css">
<title>Distillery Status Page</title>
<h1 id="top">Distillery Status Page</h1>
<h2 id="overview">Overview</h2>
<p>
<b>Domain:</b> <code>{{.Config.DefaultDomain}}</code> <br />
<b>Legacy Domain(s):</b> <code>{{.Config.SelfExtraDomains}}</code><br />
<b>HTTPS Email:</b> <code>{{.Config.CertbotEmail}}</code><br />
<hr />
<b>Homepage Redirect:</b><a href="{{.Config.SelfRedirect}}" target="_blank" rel="noopener noreferrer">{{.Config.SelfRedirect}}</a><br />
<hr />
<b>Backup Age:</b> <code>{{.Config.MaxBackupAge}}</code> Day(s)<br />
<hr />
<b>Base Directory:</b> <code>{{.Config.DeployRoot}}</code><br />
<b>Configuration File:</b> <code>{{.Config.ConfigPath}}</code><br />
<b>Authorized_Keys File:</b> <code>{{.Config.GlobalAuthorizedKeysFile}}</code><br />
<hr />
<b>MySQL User Prefix:</b> <code>{{.Config.MysqlUserPrefix}}</code><br />
<b>MySQL Database Prefix:</b> <code>{{.Config.MysqlDatabasePrefix}}</code><br />
<b>GraphDB User Prefix:</b> <code>{{.Config.GraphDBUserPrefix}}</code><br />
<b>GraphDB Database Prefix:</b> <code>{{.Config.GraphDBRepoPrefix}}</code><br />
<hr />
<b>Bookkeeping Database:</b> <code>{{.Config.DistilleryDatabase}}</code><br />
<hr />
<b>Backups:</b>
<table>
<thead>
<tr>
<th>Path</th>
<th>Created</th>
<th>Packed</th>
</tr>
</thead>
<tbody>
{{ range .Backups }}
<tr>
<td>
<code class="path">{{ .Path }}</code>
</td>
<td>
<code class="date">{{ .Created.Format "2006-01-02T15:04:05Z07:00" }}</code>
</td>
<td>
{{ .Packed }}
</td>
</tr>
{{ end}}
</tbody>
</table>
</p>
<h2 id="instances">Instances</h2>
<p>
<code>{{ .TotalCount }}</code> instance(s) = <code>{{ .RunningCount }}</code> running + <code>{{ .StoppedCount }}</code> stopped<br />
</p>
{{range .Instances}}
<div class="wisski {{ if .Running }}running{{ else }}stopped{{ end }}">
<h3 id="instance-{{.Slug}}">{{.Slug}}{{ if not .Running }}&nbsp;<small>not running</small>{{ end }}</h3>
<p>
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a><br />
<small>
<a href="/dis/instance/{{.Slug}}">More Details</a>
</small>
</p>
</div>
{{end}}
<footer>
Generated at <code>{{ .Time }}</code>
</footer>
<script src="/static/control/index.js"></script>

View file

@ -1,72 +0,0 @@
<!DOCTYPE html>
<link rel="stylesheet" href="/static/control/index.css">
<title>Distillery Status Page - {{ .Info.Slug }}</title>
<h1 id="top">Distillery Status Page - {{ .Info.Slug }}</h1>
<p>
<a href="/dis/index">Back to index</a>
</p>
<p>
<b>Slug:</b> <code>{{ .Info.Slug }}</code> <br />
<b>URL:</b> <a href="{{ .Info.URL }}" target="_blank" rel="noopener noreferrer">{{ .Info.URL }}</a> <br />
<hr />
<b>URI Prefixes: </b>
<ul>
{{ range .Info.Prefixes }}
<li><code>{{ . }}</code></li>
{{ end}}
</ul>
<b>Excluded from Resolver:</b> <code>{{ .Info.NoPrefixes }}</code><br />
<hr />
<b>Running:</b> <code>{{ .Info.Running }}</code> <br />
<!-- <b>OwnerEmail:</b> <code>{{ .Instance.OwnerEmail }}</code> <br /> -->
<hr />
<b>Created:</b> <code class="date">{{ .Instance.Created.Format "2006-01-02T15:04:05Z07:00" }}</code> <br />
<b>Last Rebuild:</b> <code class="date">{{ .Info.LastRebuild.Format "2006-01-02T15:04:05Z07:00" }}</code> <br />
<hr />
<b>FilesystemBase:</b> <code>{{ .Instance.FilesystemBase }}</code> <br />
<b>AutoBlindUpdateEnabled:</b> <code>{{ .Instance.AutoBlindUpdateEnabled }}</code> <br />
<hr />
<b>Pathbuilders:</b> <code class="pathbuilders">{{ .Info.Pathbuilders }}</code><br />
<script>window.pathbuilders={{ .Info.Pathbuilders }};</script>
<hr />
<b>SqlDatabase:</b> <code>{{ .Instance.SqlDatabase }}</code> <br />
<b>SqlUsername:</b> <code>{{ .Instance.SqlUsername }}</code> <br />
<hr />
<b>GraphDBRepository:</b> <code>{{ .Instance.GraphDBRepository }}</code> <br />
<b>GraphDBUsername:</b> <code>{{ .Instance.GraphDBUsername }}</code> <br />
<hr />
<b>Snapshots:</b>
<table>
<thead>
<tr>
<th>Path</th>
<th>Created</th>
<th>Packed</th>
</tr>
</thead>
<tbody>
{{ range .Info.Snapshots }}
<tr>
<td>
<code class="path">{{ .Path }}</code>
</td>
<td>
<code class="date">{{ .Created.Format "2006-01-02T15:04:05Z07:00" }}</code>
</td>
<td>
{{ .Packed }}
</td>
</tr>
{{ end }}
</tbody>
</table>
<hr />
</p>
<footer>
Generated at <code>{{ .Time }}</code>
</footer>
<script src="/static/control/index.js"></script>