Allow server to make backups
This commit is contained in:
parent
aeceae11d5
commit
b3a827e042
27 changed files with 891 additions and 418 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 }} <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>
|
||||
|
|
@ -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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue