Compare commits
No commits in common. "main" and "main" have entirely different histories.
13 changed files with 116 additions and 155 deletions
19
Makefile
Normal file
19
Makefile
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
BIN=guichet
|
||||||
|
SRC=main.go ssha.go profile.go admin.go invite.go directory.go picture.go
|
||||||
|
DOCKER=lxpz/guichet_amd64
|
||||||
|
|
||||||
|
all: $(BIN)
|
||||||
|
|
||||||
|
$(BIN): $(SRC)
|
||||||
|
go get -d -v
|
||||||
|
go build -v -o $(BIN)
|
||||||
|
|
||||||
|
$(BIN).static: $(SRC)
|
||||||
|
go get -d -v
|
||||||
|
CGO_ENABLED=0 GOOS=linux go build -a -v -o $(BIN).static
|
||||||
|
|
||||||
|
docker: $(BIN).static
|
||||||
|
docker build -t $(DOCKER):$(TAG) .
|
||||||
|
docker push $(DOCKER):$(TAG)
|
||||||
|
docker tag $(DOCKER):$(TAG) $(DOCKER):latest
|
||||||
|
docker push $(DOCKER):latest
|
81
README.md
81
README.md
|
@ -8,10 +8,10 @@ Guichet is a simple LDAP web interface for the following tasks:
|
||||||
- administration of the LDAP directory
|
- administration of the LDAP directory
|
||||||
- inviting new users to create accounts
|
- inviting new users to create accounts
|
||||||
|
|
||||||
Guichet works well with the [Bottin](https://git.deuxfleurs.fr/deuxfleurs/bottin) LDAP server.
|
Guichet works well with the [Bottin](https://bottin.eu) LDAP server.
|
||||||
Currently, Guichet's templates are only in French as it has been created for
|
Currently, Guichet's templates are only in French as it has been created for
|
||||||
the [Deuxfleurs](https://deuxfleurs.fr) collective.
|
the [Deuxfleurs](https://deuxfleurs.fr) collective.
|
||||||
We would gladly merge a pull request with an English translation !
|
We would gladly merge a pull request with an English transaltion !
|
||||||
|
|
||||||
A Docker image is provided on the [Docker hub](https://hub.docker.com/r/lxpz/guichet_amd64).
|
A Docker image is provided on the [Docker hub](https://hub.docker.com/r/lxpz/guichet_amd64).
|
||||||
An example for running Guichet on a Nomad cluster can be found in `guichet.hcl.example`.
|
An example for running Guichet on a Nomad cluster can be found in `guichet.hcl.example`.
|
||||||
|
@ -27,23 +27,9 @@ Guichet is licensed under the terms of the GPLv3.
|
||||||
|
|
||||||
Guichet requires go 1.13 or later.
|
Guichet requires go 1.13 or later.
|
||||||
|
|
||||||
Development build:
|
To build Guichet, clone this repository outside of your `$GOPATH`.
|
||||||
|
Then, run `make` in the root of the repo.
|
||||||
|
|
||||||
```
|
|
||||||
go build
|
|
||||||
```
|
|
||||||
|
|
||||||
Production build:
|
|
||||||
|
|
||||||
```
|
|
||||||
nix build
|
|
||||||
```
|
|
||||||
|
|
||||||
Production container:
|
|
||||||
|
|
||||||
```
|
|
||||||
docker run .#docker
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration of Guichet
|
## Configuration of Guichet
|
||||||
|
|
||||||
|
@ -143,62 +129,3 @@ Here is an example of Bottin ACLs that may be used to support Guichet invitation
|
||||||
|
|
||||||
Consult [this directory](https://git.deuxfleurs.fr/Deuxfleurs/infrastructure/src/branch/main/app/directory/config)
|
Consult [this directory](https://git.deuxfleurs.fr/Deuxfleurs/infrastructure/src/branch/main/app/directory/config)
|
||||||
to view the full configuration in use on Deuxfleurs.
|
to view the full configuration in use on Deuxfleurs.
|
||||||
|
|
||||||
## Contribute & local development
|
|
||||||
|
|
||||||
Guichet needs a few components to work :
|
|
||||||
- A Bottin server
|
|
||||||
- that needs a consul server
|
|
||||||
- And a Garage cluster (of at least one node)
|
|
||||||
A basic consul / bottin stack is available through the docker compose file you can find in `integration` subdirectory:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd integration
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
You can then run Guichet locally :
|
|
||||||
```sh
|
|
||||||
# First, copy a sample config file
|
|
||||||
copy config.json.example config.json
|
|
||||||
|
|
||||||
# Run the go development server
|
|
||||||
go run .
|
|
||||||
```
|
|
||||||
|
|
||||||
It will be available on http://localhost:9991.
|
|
||||||
|
|
||||||
### First run
|
|
||||||
|
|
||||||
#### How to get my admin password
|
|
||||||
|
|
||||||
On first Bottin's run, it is displayed in the logs.
|
|
||||||
You can easily find it by reading the container logs :
|
|
||||||
```sh
|
|
||||||
docker compose logs bottin | grep password:
|
|
||||||
```
|
|
||||||
|
|
||||||
- The **username** is provided in the log, and should look like this: `cn=admin,dc=bottin,dc=eu`.
|
|
||||||
- The **password** is right after in the same log line.
|
|
||||||
|
|
||||||
|
|
||||||
#### Garage
|
|
||||||
⚠️ Be aware at this stage that your local Guichet installation is not 100% working, especially the websites features.
|
|
||||||
You need to initialise Garage. It can be done in a few commands, coming from [the official Garage's documentation](https://garagehq.deuxfleurs.fr/documentation/quick-start/):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Find your Garage node ID
|
|
||||||
docker compose exec garage /garage
|
|
||||||
|
|
||||||
# Your id is eb820c8da5605f78 in the output below
|
|
||||||
ID Hostname Address Tags Zone Capacity DataAvail
|
|
||||||
eb820c8da5605f78 9bd710b31be0 127.0.0.1:3901 NO ROLE ASSIGNED
|
|
||||||
|
|
||||||
# Then create a cluster layout with this id
|
|
||||||
docker compose exec garage /garage layout assign -z dc1 -c 1G eb820c8da5605f78
|
|
||||||
|
|
||||||
# Finally, apply the layout
|
|
||||||
docker compose exec garage /garage layout apply
|
|
||||||
```
|
|
||||||
|
|
||||||
🎉 You now can go to http://localhost:9991/website without getting 503 errors.
|
|
||||||
|
|
11
flake.nix
11
flake.nix
|
@ -47,20 +47,11 @@
|
||||||
Entrypoint = "/bin/guichet";
|
Entrypoint = "/bin/guichet";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
docker = pkgs.writeScriptBin "guichet-docker" ''
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euxo pipefail
|
|
||||||
docker load <$(nix build --print-out-paths .#container)
|
|
||||||
'';
|
|
||||||
|
|
||||||
in {
|
in {
|
||||||
packages.x86_64-linux.guichet = guichet;
|
packages.x86_64-linux.guichet = guichet;
|
||||||
packages.x86_64-linux.container = container;
|
packages.x86_64-linux.container = container;
|
||||||
packages.x86_64-linux.docker = docker;
|
|
||||||
packages.x86_64-linux.default = guichet;
|
packages.x86_64-linux.default = guichet;
|
||||||
|
|
||||||
devShell.x86_64-linux = pkgs.mkShell { nativeBuildInputs = [ pkgs.go pkgs.gomod2nix ]; };
|
devShell.x86_64-linux = pkgs.mkShell { nativeBuildInputs = [ pkgs.go pkgs.gomod2nix ]; };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -31,6 +31,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20230131081355-c965fe7f7dc9 h1:ERg8KCpIKym98EOKa8Gq0NSBxsasD3sqb/R0gg1wOzU=
|
||||||
|
git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20230131081355-c965fe7f7dc9/go.mod h1:TlSL6QVxozmdRaSgP6Akspi0HCJv4HAkkq3Dldru4GM=
|
||||||
git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20231128153612-8b81fae65e5e h1:h89CAh0qmUcGJykss/utXIw+yRGa3Gr6VyrZ5ZWN0kY=
|
git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20231128153612-8b81fae65e5e h1:h89CAh0qmUcGJykss/utXIw+yRGa3Gr6VyrZ5ZWN0kY=
|
||||||
git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20231128153612-8b81fae65e5e/go.mod h1:TlSL6QVxozmdRaSgP6Akspi0HCJv4HAkkq3Dldru4GM=
|
git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20231128153612-8b81fae65e5e/go.mod h1:TlSL6QVxozmdRaSgP6Akspi0HCJv4HAkkq3Dldru4GM=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
|
|
@ -21,7 +21,7 @@ services:
|
||||||
garage:
|
garage:
|
||||||
# sync with deuxfleurs/nixcfg/cluster/prod/app/garage/deploy/garage.hcl
|
# sync with deuxfleurs/nixcfg/cluster/prod/app/garage/deploy/garage.hcl
|
||||||
# to ensure compatibility with prod
|
# to ensure compatibility with prod
|
||||||
image: dxflrs/garage:v1.99.1-internal
|
image: superboum/garage:v1.0.0-rc1-hotfix-red-ftr-wquorum
|
||||||
ports:
|
ports:
|
||||||
- "3900:3900"
|
- "3900:3900"
|
||||||
- "3902:3902"
|
- "3902:3902"
|
||||||
|
|
56
invite.go
56
invite.go
|
@ -1,15 +1,19 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/emersion/go-sasl"
|
||||||
|
"github.com/emersion/go-smtp"
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
|
@ -247,11 +251,21 @@ func handleInviteSendCode(w http.ResponseWriter, r *http.Request) {
|
||||||
WebBaseAddress: config.WebAddress,
|
WebBaseAddress: config.WebAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
tryGenerateInvitation(user, data)
|
if r.Method == "POST" {
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
choice := strings.Join(r.Form["choice"], "")
|
||||||
|
sendto := strings.Join(r.Form["sendto"], "")
|
||||||
|
|
||||||
|
if choice == "display" || choice == "send" {
|
||||||
|
trySendCode(user, choice, sendto, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
templateInviteSendCode.Execute(w, data)
|
templateInviteSendCode.Execute(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryGenerateInvitation(user *LoggedUser, data *SendCodeData) {
|
func trySendCode(user *LoggedUser, choice string, sendto string, data *SendCodeData) {
|
||||||
// Generate code
|
// Generate code
|
||||||
code, code_id, code_pw := genCode()
|
code, code_id, code_pw := genCode()
|
||||||
|
|
||||||
|
@ -272,9 +286,43 @@ func tryGenerateInvitation(user *LoggedUser, data *SendCodeData) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we want to display it, do so
|
||||||
|
if choice == "display" {
|
||||||
|
data.Success = true
|
||||||
|
data.CodeDisplay = code
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we are sending a mail
|
||||||
|
if !EMAIL_REGEXP.MatchString(sendto) {
|
||||||
|
data.ErrorInvalidEmail = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
templateMail := template.Must(template.ParseFiles(templatePath + "/invite_mail.txt"))
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
templateMail.Execute(buf, &CodeMailFields{
|
||||||
|
To: sendto,
|
||||||
|
From: config.MailFrom,
|
||||||
|
InviteFrom: user.WelcomeName(),
|
||||||
|
Code: code,
|
||||||
|
WebBaseAddress: config.WebAddress,
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("Sending mail to: %s", sendto)
|
||||||
|
var auth sasl.Client = nil
|
||||||
|
if config.SMTPUsername != "" {
|
||||||
|
auth = sasl.NewPlainClient("", config.SMTPUsername, config.SMTPPassword)
|
||||||
|
}
|
||||||
|
err = smtp.SendMail(config.SMTPServer, auth, config.MailFrom, []string{sendto}, buf)
|
||||||
|
if err != nil {
|
||||||
|
data.ErrorMessage = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Mail sent.")
|
||||||
|
|
||||||
data.Success = true
|
data.Success = true
|
||||||
data.CodeDisplay = code
|
data.CodeSentTo = sendto
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func genCode() (code string, code_id string, code_pw string) {
|
func genCode() (code string, code_id string, code_pw string) {
|
||||||
|
|
9
main.go
9
main.go
|
@ -237,6 +237,8 @@ func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
// --- Login Controller ---
|
// --- Login Controller ---
|
||||||
type LoginFormData struct {
|
type LoginFormData struct {
|
||||||
Username string
|
Username string
|
||||||
|
WrongUser bool
|
||||||
|
WrongPass bool
|
||||||
ErrorMessage string
|
ErrorMessage string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,9 +266,10 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
data := &LoginFormData{
|
data := &LoginFormData{
|
||||||
Username: username,
|
Username: username,
|
||||||
}
|
}
|
||||||
if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) ||
|
if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) {
|
||||||
ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
|
data.WrongPass = true
|
||||||
data.ErrorMessage = "Le mot de passe et identifiant ne correspondent pas."
|
} else if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
|
||||||
|
data.WrongUser = true
|
||||||
} else {
|
} else {
|
||||||
data.ErrorMessage = err.Error()
|
data.ErrorMessage = err.Error()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,21 +15,7 @@ paths:
|
||||||
operationId: "ListWebsites"
|
operationId: "ListWebsites"
|
||||||
summary: "List all websites"
|
summary: "List all websites"
|
||||||
description: |
|
description: |
|
||||||
List all the user's websites.
|
List all the user's websites
|
||||||
|
|
||||||
Also includes information about global quotas for the user, and for each website, bucket names and website URL.
|
|
||||||
|
|
||||||
- `username`: the name of the user making the request.
|
|
||||||
- `quota_website_count` indicates the quota of the number of websites that the user is allowed to create.
|
|
||||||
|
|
||||||
- Website size is set at 100MB by default, and users can increase this quota autonomously up to a certain value defined by `burst_bucket_quota_size`, by default it's set to 200MB but it can be overriden on a per-user basis.
|
|
||||||
|
|
||||||
- Each element of the `vhosts` array describes a website. A website is described by:
|
|
||||||
+ `name`: current main name of the website, which determines the URL it can be accessed from (specified in `domain`). This is also a name of the S3 bucket storing the website.
|
|
||||||
+ `alt_name`: an array of aliases for the website. These can also be used to access the S3 bucket storing the website (a bucket can have several aliased names), but cannot be used in a URL to access the website.
|
|
||||||
+ `expanded`: whether the bucket name already corresponds to a full URL for the website
|
|
||||||
+ `domain`: the URL at which the website can be reached
|
|
||||||
|
|
||||||
responses:
|
responses:
|
||||||
'500':
|
'500':
|
||||||
description: |
|
description: |
|
||||||
|
@ -37,6 +23,7 @@ paths:
|
||||||
'200':
|
'200':
|
||||||
description: |
|
description: |
|
||||||
Returns information about all the user's websites, and user information related to websites.
|
Returns information about all the user's websites, and user information related to websites.
|
||||||
|
The website quota (`quota_website_count`) indicates a quota in number of websites that the user is allowed to create. Website size is set at 100MB by default, and users can increase this quota autonomously up to a certain value defined by `burst_bucket_quota_size`, by default it's set to 200MB but it can be overriden on a per-user basis.
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
@ -71,13 +58,10 @@ paths:
|
||||||
summary: "Details on a website"
|
summary: "Details on a website"
|
||||||
description: |
|
description: |
|
||||||
Gets the configuration and status for the website `vhost`.
|
Gets the configuration and status for the website `vhost`.
|
||||||
This includes information about bucket names, domains, bucket access keys, and quotas.
|
This includes information about domains, bucket access keys, and quotas.
|
||||||
|
|
||||||
- `vhost` contains informations about bucket names and the website URL. See the documentation of the response of "List all websites" for more information
|
`quota_size` is given in bytes.
|
||||||
- `access_key_id`: the S3 access key id for the bucket
|
`quota_files` indicates a number of files.
|
||||||
- `secret_access_key`: the S3 secret access key for the bucket
|
|
||||||
- `quota_size`: the quota for the size of the website, in bytes.
|
|
||||||
- `quota_files`: the quota for the number of files.
|
|
||||||
responses:
|
responses:
|
||||||
'500':
|
'500':
|
||||||
description: |
|
description: |
|
||||||
|
@ -113,7 +97,7 @@ paths:
|
||||||
Request forbidden, you have reached your quota of maximum number of websites allowed.
|
Request forbidden, you have reached your quota of maximum number of websites allowed.
|
||||||
'200':
|
'200':
|
||||||
description: |
|
description: |
|
||||||
Returns information about the website that has been created. (See the documentation of "Details on a website" for more info.)
|
Returns information about the website that has been created.
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
@ -160,7 +144,7 @@ paths:
|
||||||
Website not found
|
Website not found
|
||||||
'200':
|
'200':
|
||||||
description: |
|
description: |
|
||||||
Returns updated information about the website. (See the documentation of "Details on a website" for more info.)
|
Returns updated information about the website
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
|
|
@ -57,9 +57,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>Vous devez éditer votre zone DNS, souvent gérée par votre bureau d'enregistrement, comme Gandi, pour la faire pointer vers Deuxfleurs. Si vous utilisez un sous domaine (eg. <code>site.example.com</code>), une entrée <code>CNAME</code> est appropriée :</p>
|
<p>Vous devez éditer votre zone DNS, souvent gérée par votre bureau d'enregistrement, comme Gandi, pour la faire pointer vers Deuxfleurs. Si vous utilisez un sous domaine (eg. <code>site.example.com</code>), une entrée <code>CNAME</code> est appropriée :</p>
|
||||||
<pre>site CNAME 3600 global.site.deuxfleurs.fr.</pre>
|
<pre>site CNAME 3600 garage.deuxfleurs.fr.</pre>
|
||||||
<p>Si vous utilisez la racine de votre nom de domaine (eg. <code>example.com</code>, aussi appelée APEX), la solution dépend de votre fournisseur DNS, il vous faudra au choix une entrée <code>ALIAS</code> ou <code>CNAME</code> en fonction de ce que votre fournisseur supporte :</p>
|
<p>Si vous utilisez la racine de votre nom de domaine (eg. <code>example.com</code>, aussi appelée APEX), la solution dépend de votre fournisseur DNS, il vous faudra au choix une entrée <code>ALIAS</code> ou <code>CNAME</code> en fonction de ce que votre fournisseur supporte :</p>
|
||||||
<pre>@ ALIAS 3600 global.site.deuxfleurs.fr.</pre>
|
<pre>@ ALIAS 3600 garage.deuxfleurs.fr.</pre>
|
||||||
<p>La première fois que vous chargerez votre site web, une erreur de certificat sera renvoyée. C'est normal, il faudra patienter quelques minutes le temps que le certificat se génère.</p>
|
<p>La première fois que vous chargerez votre site web, une erreur de certificat sera renvoyée. C'est normal, il faudra patienter quelques minutes le temps que le certificat se génère.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
|
|
|
@ -42,12 +42,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<h2>{{ .View.Name.Url }}</h2>
|
||||||
<h2>{{ .View.Name.Url }}</h2>
|
|
||||||
<div>
|
|
||||||
<a href="https://{{ .View.Name.Url }}" target="_blank" rel="noreferrer external" class="btn btn-dark">Visiter</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- QUOTAS -->
|
<!-- QUOTAS -->
|
||||||
|
|
||||||
|
@ -81,10 +76,7 @@
|
||||||
<h5 class="mt-3">Informations de connexion</h5>
|
<h5 class="mt-3">Informations de connexion</h5>
|
||||||
<ul class="nav nav-tabs" id="proto" role="tablist">
|
<ul class="nav nav-tabs" id="proto" role="tablist">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" id="dxfl-tab" data-toggle="tab" href="#dxfl" role="tab" aria-controls="dxfl" aria-selected="true">dxfl (recommandé)</a>
|
<a class="nav-link active" id="s3-tab" data-toggle="tab" href="#s3" role="tab" aria-controls="s3" aria-selected="true">S3 (recommandé)</a>
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" id="s3-tab" data-toggle="tab" href="#s3" role="tab" aria-controls="s3" aria-selected="false">S3</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" id="sftp-tab" data-toggle="tab" href="#sftp" role="tab" aria-controls="sftp" aria-selected="false">SFTP</a>
|
<a class="nav-link" id="sftp-tab" data-toggle="tab" href="#sftp" role="tab" aria-controls="sftp" aria-selected="false">SFTP</a>
|
||||||
|
@ -94,20 +86,7 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content" id="protocols">
|
<div class="tab-content" id="protocols">
|
||||||
<div class="tab-pane fade show active" id="dxfl" role="tabpanel" aria-labelledby="dxfl-tab">
|
<div class="tab-pane fade show active" id="s3" role="tabpanel" aria-labelledby="s3-tab">
|
||||||
<p>Première configuration :</p>
|
|
||||||
|
|
||||||
<pre>
|
|
||||||
sudo npm install -g dxfl
|
|
||||||
dxfl login {{ .Describe.Username }}
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
<p>Pour déployer votre site contenu dans le dossier <code>public</code> :</p>
|
|
||||||
<pre>
|
|
||||||
dxfl deploy {{ .View.Name.Pretty }} ./public
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane fade show" id="s3" role="tabpanel" aria-labelledby="s3-tab">
|
|
||||||
<table class="table mt-4">
|
<table class="table mt-4">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -117,7 +96,8 @@ dxfl deploy {{ .View.Name.Pretty }} ./public
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Clé secrète</th>
|
<th scope="row">Clé secrète</th>
|
||||||
<td>
|
<td>
|
||||||
<a href="#secret_key" onclick="document.getElementById('secret_key').style.display='inline'; this.style.display='none'">[Afficher la clé secrète]</a><span id="secret_key" style="display: none">{{ .View.SecretAccessKey }}</span>
|
<a href="#" onclick="document.getElementById('secret_key').style.display='inline'; this.style.display='none'">[Afficher la clé secrète]</a>
|
||||||
|
<span id="secret_key" style="display: none">{{ .View.SecretAccessKey }}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -158,7 +138,7 @@ dxfl deploy {{ .View.Name.Pretty }} ./public
|
||||||
<p>Entrez les informations suivantes quand elles vous sont demandées :</p>
|
<p>Entrez les informations suivantes quand elles vous sont demandées :</p>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>AWS Access Key ID [None]:</dt><dd>{{ .View.AccessKeyId }}</dd>
|
<dt>AWS Access Key ID [None]:</dt><dd>{{ .View.AccessKeyId }}</dd>
|
||||||
<dt>AWS Secret Access Key [None]:</dt><dd><a href="#aws_secret_key" onclick="document.getElementById('aws_secret_key').style.display='inline'; this.style.display='none'">[Afficher la clé secrète]</a><span id="aws_secret_key" style="display: none">{{ .View.SecretAccessKey }}</span></dd>
|
<dt>AWS Secret Access Key [None]:</dt><dd><a href="#" onclick="document.getElementById('aws_secret_key').style.display='inline'; this.style.display='none'">[Afficher la clé secrète]</a><span id="aws_secret_key" style="display: none">{{ .View.SecretAccessKey }}</span></dd>
|
||||||
<dt>Default region name [None]:</dt> <dd>garage</dd>
|
<dt>Default region name [None]:</dt> <dd>garage</dd>
|
||||||
<dt>Default output format [None]:</dt> <dd>(laissez vide et appuyez sur entrée)</dd>
|
<dt>Default output format [None]:</dt> <dd>(laissez vide et appuyez sur entrée)</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
@ -192,7 +172,7 @@ mc alias set \
|
||||||
{{ .View.Name.Pretty }} \
|
{{ .View.Name.Pretty }} \
|
||||||
https://garage.deuxfleurs.fr \
|
https://garage.deuxfleurs.fr \
|
||||||
{{ .View.AccessKeyId }} \
|
{{ .View.AccessKeyId }} \
|
||||||
<a href="#minio_secret_key" onclick="document.getElementById('minio_secret_key').style.display='inline'; this.style.display='none'">[Afficher la clé secrète]</a><span id="minio_secret_key" style="display: none">{{ .View.SecretAccessKey }}</span> \
|
<a href="#" onclick="document.getElementById('minio_secret_key').style.display='inline'; this.style.display='none'">[Afficher la clé secrète]</a><span id="minio_secret_key" style="display: none">{{ .View.SecretAccessKey }}</span> \
|
||||||
--api S3v4
|
--api S3v4
|
||||||
</pre>
|
</pre>
|
||||||
<p>Et ensuite copiez votre site web avec la sous-commande mirror de Minio CLI :</p>
|
<p>Et ensuite copiez votre site web avec la sous-commande mirror de Minio CLI :</p>
|
||||||
|
@ -216,12 +196,12 @@ mc mirror --overwrite ./public/ {{ .View.Name.Pretty }}/
|
||||||
<p>Créez un fichier nommé <code>.deployment.secrets</code> (ne commitez pas ce fichier dans votre dépôt !) :</p>
|
<p>Créez un fichier nommé <code>.deployment.secrets</code> (ne commitez pas ce fichier dans votre dépôt !) :</p>
|
||||||
<pre>
|
<pre>
|
||||||
export AWS_ACCESS_KEY_ID={{ .View.AccessKeyId }}
|
export AWS_ACCESS_KEY_ID={{ .View.AccessKeyId }}
|
||||||
export AWS_SECRET_ACCESS_KEY=<a href="#hugo_secret_key" onclick="document.getElementById('hugo_secret_key').style.display='inline'; this.style.display='none'">[Afficher la clé secrète]</a><span id="hugo_secret_key" style="display: none">{{ .View.SecretAccessKey }}</span>
|
export AWS_SECRET_ACCESS_KEY=<a href="#" onclick="document.getElementById('ugo_secret_key').style.display='inline'; this.style.display='none'">[Afficher la clé secrète]</a><span id="hugo_secret_key" style="display: none">{{ .View.SecretAccessKey }}</span>
|
||||||
</pre>
|
</pre>
|
||||||
<p>Dans votre fichier de configuration Hugo <code>config.toml</code> (que vous pouvez commiter), rajoutez :</p>
|
<p>Dans votre fichier de configuration Hugo <code>config.toml</code> (que vous pouvez commiter), rajoutez :</p>
|
||||||
<pre>
|
<pre>
|
||||||
[[deployment.targets]]
|
[[deployment.targets]]
|
||||||
URL = "s3://{{ .View.Name.Pretty }}?endpoint=https://garage.deuxfleurs.fr&awssdk=v2&use_path_style=true&region=garage&disable_https=false
|
URL = "s3://bucket?endpoint=garage.deuxfleurs.fr&s3ForcePathStyle=true&region=garage"
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>Pour déployer, sourcez le fichier de configuration et laissez hugo faire : </p>
|
<p>Pour déployer, sourcez le fichier de configuration et laissez hugo faire : </p>
|
||||||
|
@ -365,7 +345,7 @@ scp -oHostKeyAlgorithms=+ssh-rsa -P2222 -r ./public/ {{ .Describe.Username }}@sf
|
||||||
|
|
||||||
{{ if .View.Name.Expanded }}
|
{{ if .View.Name.Expanded }}
|
||||||
<h5 class="mt-5">Vous ne savez pas comment configurer votre nom de domaine ?</h5>
|
<h5 class="mt-5">Vous ne savez pas comment configurer votre nom de domaine ?</h5>
|
||||||
<p> Le nom de domaine {{ .View.Name.Url }} n'est pas géré par Deuxfleurs, il vous revient donc de configurer la zone DNS. Vous devez ajouter une entrée <code>CNAME global.site.deuxfleurs.fr</code> ou <code>ALIAS global.site.deuxfleurs.fr</code> auprès de votre hébergeur DNS, qui est souvent aussi le bureau d'enregistrement (eg. Gandi, GoDaddy, BookMyName, etc.).</p>
|
<p> Le nom de domaine {{ .View.Name.Url }} n'est pas géré par Deuxfleurs, il vous revient donc de configurer la zone DNS. Vous devez ajouter une entrée <code>CNAME garage.deuxfleurs.fr</code> ou <code>ALIAS garage.deuxfleurs.fr</code> auprès de votre hébergeur DNS, qui est souvent aussi le bureau d'enregistrement (eg. Gandi, GoDaddy, BookMyName, etc.).</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -57,9 +57,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>Vous devez éditer votre zone DNS, souvent gérée par votre bureau d'enregistrement, comme Gandi, pour la faire pointer vers Deuxfleurs. Si vous utilisez un sous domaine (eg. <code>site.example.com</code>), une entrée <code>CNAME</code> est appropriée :</p>
|
<p>Vous devez éditer votre zone DNS, souvent gérée par votre bureau d'enregistrement, comme Gandi, pour la faire pointer vers Deuxfleurs. Si vous utilisez un sous domaine (eg. <code>site.example.com</code>), une entrée <code>CNAME</code> est appropriée :</p>
|
||||||
<pre>site CNAME 3600 global.site.deuxfleurs.fr.</pre>
|
<pre>site CNAME 3600 garage.deuxfleurs.fr.</pre>
|
||||||
<p>Si vous utilisez la racine de votre nom de domaine (eg. <code>example.com</code>, aussi appelée APEX), la solution dépend de votre fournisseur DNS, il vous faudra au choix une entrée <code>ALIAS</code> ou <code>CNAME</code> en fonction de ce que votre fournisseur supporte :</p>
|
<p>Si vous utilisez la racine de votre nom de domaine (eg. <code>example.com</code>, aussi appelée APEX), la solution dépend de votre fournisseur DNS, il vous faudra au choix une entrée <code>ALIAS</code> ou <code>CNAME</code> en fonction de ce que votre fournisseur supporte :</p>
|
||||||
<pre>@ ALIAS 3600 global.site.deuxfleurs.fr.</pre>
|
<pre>@ ALIAS 3600 garage.deuxfleurs.fr.</pre>
|
||||||
<p>La première fois que vous chargerez votre site web, une erreur de certificat sera renvoyée. C'est normal, il faudra patienter quelques minutes le temps que le certificat se génère.</p>
|
<p>La première fois que vous chargerez votre site web, une erreur de certificat sera renvoyée. C'est normal, il faudra patienter quelques minutes le temps que le certificat se génère.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
|
|
|
@ -38,7 +38,8 @@
|
||||||
Inviter des gens sur Deuxfleurs
|
Inviter des gens sur Deuxfleurs
|
||||||
</div>
|
</div>
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
<a class="list-group-item list-group-item-action" href="/invite/send_code">Générer un code d'invitation</a>
|
<a class="list-group-item list-group-item-action" href="/invite/send_code">Envoyer un code d'invitation</a>
|
||||||
|
<a class="list-group-item list-group-item-action" href="/invite/new_account">Créer un nouveau compte directement</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -4,9 +4,15 @@
|
||||||
<h4>S'identifier</h4>
|
<h4>S'identifier</h4>
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
{{ with .ErrorMessage}}
|
{{if .WrongUser}}
|
||||||
|
<div class="alert alert-danger">Identifiant invalide.</div>
|
||||||
|
{{end}}
|
||||||
|
{{if .WrongPass}}
|
||||||
|
<div class="alert alert-danger">Mot de passe invalide.</div>
|
||||||
|
{{end}}
|
||||||
|
{{if .ErrorMessage}}
|
||||||
<div class="alert alert-danger">Impossible de se connecter.
|
<div class="alert alert-danger">Impossible de se connecter.
|
||||||
<div style="font-size: 0.8em">{{ . }}</div>
|
<div style="font-size: 0.8em">{{ .ErrorMessage }}</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
Loading…
Add table
Reference in a new issue