Compare commits
30 commits
min-libcon
...
master
Author | SHA1 | Date | |
---|---|---|---|
Aravinth Manivannan | b0d94f91dc | ||
Aravinth Manivannan | d40e8642de | ||
Aravinth Manivannan | 5851b686b4 | ||
Aravinth Manivannan | db9115b90b | ||
Aravinth Manivannan | b15c72ef30 | ||
Aravinth Manivannan | cd0589fb2e | ||
Aravinth Manivannan | 58eef6b3fa | ||
Aravinth Manivannan | 158ec03aab | ||
Aravinth Manivannan | 0e388d4e1e | ||
Aravinth Manivannan | ef0175eca0 | ||
Aravinth Manivannan | 838cb9387a | ||
Aravinth Manivannan | 15a17a184d | ||
Aravinth Manivannan | 96c1b807a7 | ||
Aravinth Manivannan | 4db76a0705 | ||
Aravinth Manivannan | ccb9f0f046 | ||
Aravinth Manivannan | 3a2e6355da | ||
Aravinth Manivannan | a38411abaa | ||
Aravinth Manivannan | b8246f0fd8 | ||
Aravinth Manivannan | 1fa28ef9b7 | ||
Aravinth Manivannan | a4f4903120 | ||
Aravinth Manivannan | 1b40c44854 | ||
Aravinth Manivannan | 20aa88ca51 | ||
Aravinth Manivannan | 8d9bc95bf6 | ||
Aravinth Manivannan | 327ba35898 | ||
Aravinth Manivannan | f9d23cb3ef | ||
Aravinth Manivannan | 6145695980 | ||
Aravinth Manivannan | 82da016441 | ||
Aravinth Manivannan | 1fcded74c0 | ||
Aravinth Manivannan | 8b821b7bde | ||
Aravinth Manivannan | 6ff3bb7e5c |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
.env.local
|
||||
tarpaulin-report.html
|
|
@ -7,6 +7,17 @@ pipeline:
|
|||
# - make migrate
|
||||
- make
|
||||
- make test
|
||||
- make release
|
||||
|
||||
publish_bins:
|
||||
image: rust
|
||||
|
||||
commands:
|
||||
- apt update
|
||||
- apt-get -y --no-install-recommends install gpg tar curl wget
|
||||
- echo -n "$RELEASE_BOT_GPG_SIGNING_KEY" | gpg --batch --import --pinentry-mode loopback
|
||||
- ./scripts/bin-publish.sh publish master latest $DUMBSERVE_PASSWORD
|
||||
secrets: [ RELEASE_BOT_GPG_SIGNING_KEY, DUMBSERVE_PASSWORD, GPG_PASSWORD ]
|
||||
|
||||
publish:
|
||||
image: plugins/docker
|
||||
|
@ -14,7 +25,7 @@ pipeline:
|
|||
username: realaravinth
|
||||
password:
|
||||
from_secret: DOCKER_TOKEN
|
||||
repo: realaravinth/librepages-forms
|
||||
repo: realaravinth/librepages-conductor
|
||||
tags: latest
|
||||
|
||||
#services:
|
||||
|
|
705
Cargo.lock
generated
705
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -12,18 +12,23 @@ build = "build.rs"
|
|||
|
||||
[dependencies]
|
||||
actix-web = "4"
|
||||
actix-web-prom = "0.6.0"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.17"
|
||||
pretty_env_logger = "0.4.0"
|
||||
serde = { version = "1", features=["derive"]}
|
||||
actix-web-codegen-const-routes = { version = "0.1.0", tag = "0.1.0", git = "https://github.com/realaravinth/actix-web-codegen-const-routes" }
|
||||
libconfig = { version = "0.1.0", git = "https://git.batsense.net/librepages/libconfig" }
|
||||
derive_builder = "0.11.2"
|
||||
config = "0.11"
|
||||
config = "0.13"
|
||||
derive_more = "0.99.17"
|
||||
url = { version = "2.2.2", features = ["serde"]}
|
||||
serde_json = { version ="1", features = ["raw_value"]}
|
||||
clap = { vesrion = "3.2.20", features = ["derive"]}
|
||||
actix-web-httpauth = "0.8.0"
|
||||
mime_guess = "2.0.4"
|
||||
rust-embed = "6.4.2"
|
||||
|
||||
[dependencies.libconductor]
|
||||
path = "./env/libconductor"
|
||||
|
|
19
Makefile
19
Makefile
|
@ -1,3 +1,12 @@
|
|||
define lint
|
||||
cargo fmt -v --all -- --emit files
|
||||
cargo clippy --workspace --tests --all-features
|
||||
endef
|
||||
|
||||
define test
|
||||
cargo test --no-fail-fast --workspace --tests --all-features
|
||||
endef
|
||||
|
||||
default: ## Build app in debug mode
|
||||
cargo build
|
||||
|
||||
|
@ -25,8 +34,9 @@ env: ## Setup development environtment
|
|||
cargo fetch
|
||||
|
||||
lint: ## Lint codebase
|
||||
cargo fmt -v --all -- --emit files
|
||||
cargo clippy --workspace --tests --all-features
|
||||
$(call lint)
|
||||
cd env/dummy_conductor && $(call lint)
|
||||
cd env/libconductor && $(call lint)
|
||||
|
||||
#migrate: ## run migrations
|
||||
# unset DATABASE_URL && cargo build
|
||||
|
@ -36,7 +46,7 @@ release: ## Build app with release optimizations
|
|||
cargo build --release
|
||||
|
||||
run: ## Run app in debug mode
|
||||
cargo run
|
||||
cargo run -- serve
|
||||
|
||||
|
||||
#sqlx-offline-data: ## prepare sqlx offline data
|
||||
|
@ -45,7 +55,8 @@ run: ## Run app in debug mode
|
|||
# --all-features
|
||||
|
||||
test: ## Run all available tests
|
||||
cargo test --no-fail-fast --workspace
|
||||
$(call test)
|
||||
cd env/dummy_conductor && $(call test)
|
||||
|
||||
xml-test-coverage: ## Generate code coverage report in XML format
|
||||
cargo tarpaulin -t 1200 --out Xml
|
||||
|
|
|
@ -2,6 +2,9 @@ debug = true
|
|||
source_code = "https://git.batsense.net/librepages/conductor"
|
||||
conductor = "dummy"
|
||||
|
||||
[creds]
|
||||
token="longrandomlygeneratedpassword"
|
||||
|
||||
[server]
|
||||
# Please set a unique value, your mCaptcha instance's security depends on this being
|
||||
# unique
|
||||
|
@ -10,7 +13,7 @@ conductor = "dummy"
|
|||
port = 7000
|
||||
#IP address. Enter 0.0.0.0 to listen on all available addresses
|
||||
#ip= "0.0.0.0"
|
||||
ip= "192.168.0.104"
|
||||
ip= "127.0.0.1"
|
||||
# enter your hostname, eg: example.com
|
||||
domain = "localhost"
|
||||
# Set true if you have setup TLS with a reverse proxy like Nginx.
|
24
contrib/librepages-conductor.service
Normal file
24
contrib/librepages-conductor.service
Normal file
|
@ -0,0 +1,24 @@
|
|||
[Unit]
|
||||
Description=LibrePages Conductor: Easiest way to deploy websites. Conductor component
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/bin/conductor serve
|
||||
Restart=on-failure
|
||||
RestartSec=1
|
||||
SuccessExitStatus=3 4
|
||||
RestartForceExitStatus=3 4
|
||||
SystemCallArchitectures=native
|
||||
MemoryDenyWriteExecute=true
|
||||
NoNewPrivileges=true
|
||||
Environment="RUST_LOG=info"
|
||||
|
||||
[Unit]
|
||||
Wants=network-online.target
|
||||
Wants=network-online.target
|
||||
Requires=postgresql.service
|
||||
After=syslog.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
BIN
docs/openapi/favicon-16x16.png
Normal file
BIN
docs/openapi/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 665 B |
BIN
docs/openapi/favicon-32x32.png
Normal file
BIN
docs/openapi/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 628 B |
16
docs/openapi/index.css
Normal file
16
docs/openapi/index.css
Normal file
|
@ -0,0 +1,16 @@
|
|||
html {
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
}
|
19
docs/openapi/index.html
Normal file
19
docs/openapi/index.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>LibrePages Conductor | Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="/docs/openapi/swagger-ui.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/docs/openapi/index.css" />
|
||||
<link rel="icon" type="image/png" href="/docs/openapi/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="/docs/openapi/favicon-16x16.png" sizes="16x16" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="/docs/openapi/swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||
<script src="/docs/openapi/swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||
<script src="/docs/openapi/swagger-initializer.js" charset="UTF-8"> </script>
|
||||
</body>
|
||||
</html>
|
79
docs/openapi/oauth2-redirect.html
Normal file
79
docs/openapi/oauth2-redirect.html
Normal file
|
@ -0,0 +1,79 @@
|
|||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1).replace('?', '&');
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&");
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value);
|
||||
}
|
||||
) : {};
|
||||
|
||||
isValid = qp.state === sentState;
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg;
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
if (document.readyState !== 'loading') {
|
||||
run();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
run();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
178
docs/openapi/openapi.yml
Normal file
178
docs/openapi/openapi.yml
Normal file
|
@ -0,0 +1,178 @@
|
|||
openapi: 3.0.3
|
||||
info:
|
||||
title: LibrePages Conductor - OpenAPI 3.0
|
||||
description: |-
|
||||
|
||||
Conductor is the deployment manager used internally in LibrePages. It is
|
||||
responsible for creating, updating and deleting websites that are deployed
|
||||
with LibrePages
|
||||
|
||||
Some useful links:
|
||||
- [LibrePages Conductor repository](https://git.batsense.net/LibrePages/conductor)
|
||||
termsOfService: http://libreapages.org/terms/
|
||||
contact:
|
||||
email: contact@libreapages.org
|
||||
license:
|
||||
name: AGPLv3 or later version
|
||||
url: https://www.gnu.org/licenses/agpl.html
|
||||
version: 0.1.0
|
||||
externalDocs:
|
||||
description: LibrePages Conductor - internal service to update deployments
|
||||
url: http://git.batsense.net/LibrePages/conductor
|
||||
|
||||
tags:
|
||||
- name: meta
|
||||
description: Information about the system
|
||||
- name: site
|
||||
description: Information about customer site deployments
|
||||
paths:
|
||||
/api/v1/events/new:
|
||||
post:
|
||||
tags:
|
||||
- site
|
||||
summary: Post new event to Conductor
|
||||
description: Conductor schedules jobs based on events posted to it.
|
||||
operationId: eventsNew
|
||||
responses:
|
||||
"201":
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/eventsNewPayloadNewSite"
|
||||
- $ref: "#/components/schemas/eventsNewPayloadConfig"
|
||||
- $ref: "#/components/schemas/eventsNewPayloadDeleteSite"
|
||||
/api/v1/meta/build:
|
||||
get:
|
||||
tags:
|
||||
- meta
|
||||
summary: Get binary's build information
|
||||
description: Update an existing pet by Idinformation
|
||||
operationId: metaBuild
|
||||
responses:
|
||||
"200":
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BuildInformation"
|
||||
/api/v1/meta/health:
|
||||
get:
|
||||
tags:
|
||||
- meta
|
||||
summary: Get instance's health information
|
||||
description: Get instance's health information
|
||||
operationId: metaHealth
|
||||
responses:
|
||||
"200":
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HealthInformation"
|
||||
|
||||
|
||||
components:
|
||||
schemas:
|
||||
BuildInformation:
|
||||
required:
|
||||
- version
|
||||
- git_commit_hash
|
||||
- source_code
|
||||
type: object
|
||||
properties:
|
||||
version:
|
||||
type: string
|
||||
example: v0.1.0
|
||||
git_commit_hash:
|
||||
type: string
|
||||
example: 1fa28ef9b70bb04d6c76eee9e9bc5be77005b4b0
|
||||
source_code:
|
||||
type: string
|
||||
example: https://git.batsense.net/LibrePages
|
||||
HealthInformation:
|
||||
required:
|
||||
- conductor
|
||||
type: object
|
||||
properties:
|
||||
conductor:
|
||||
type: boolean
|
||||
example: true
|
||||
|
||||
eventsNewPayloadConfig:
|
||||
type: object
|
||||
required:
|
||||
- data
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/LibConfigConfig'
|
||||
LibConfigConfig:
|
||||
properties:
|
||||
source:
|
||||
$ref: '#/components/schemas/LibConfigSource'
|
||||
forms:
|
||||
$ref: '#/components/schemas/LibConfigForms'
|
||||
domains:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["example.com", "testing.example.org"]
|
||||
image_compression:
|
||||
$ref: '#/components/schemas/LibConfigImageCompression'
|
||||
redirects:
|
||||
$ref: '#/components/schemas/LibConfigRedirects'
|
||||
|
||||
LibConfigSource:
|
||||
properties:
|
||||
production_branch:
|
||||
type: string
|
||||
example: "librepages"
|
||||
staging_branch:
|
||||
type: string
|
||||
example: "librepages-staging"
|
||||
|
||||
LibConfigForms:
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
example: false
|
||||
|
||||
LibConfigImageCompression:
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
example: false
|
||||
|
||||
LibConfigRedirects:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
example: "/from"
|
||||
to:
|
||||
type: string
|
||||
example: "/to"
|
||||
|
||||
|
||||
eventsNewPayloadNewSite:
|
||||
properties:
|
||||
hostname:
|
||||
type: string
|
||||
example: "example.org"
|
||||
path:
|
||||
type: string
|
||||
example: "/tmp/example.org"
|
||||
branch:
|
||||
type: string
|
||||
example: "librepages"
|
||||
|
||||
eventsNewPayloadDeleteSite:
|
||||
properties:
|
||||
hostname:
|
||||
type: string
|
||||
example: "example.org"
|
||||
|
||||
securitySchemes:
|
||||
basicAuth:
|
||||
type: http
|
||||
scheme: basic
|
20
docs/openapi/swagger-initializer.js
Normal file
20
docs/openapi/swagger-initializer.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
window.onload = function() {
|
||||
//<editor-fold desc="Changeable Configuration Block">
|
||||
|
||||
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
|
||||
window.ui = SwaggerUIBundle({
|
||||
url: "/docs/openapi/openapi.yml",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
});
|
||||
|
||||
//</editor-fold>
|
||||
};
|
3
docs/openapi/swagger-ui-bundle.js
Normal file
3
docs/openapi/swagger-ui-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/openapi/swagger-ui-bundle.js.map
Normal file
1
docs/openapi/swagger-ui-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
3
docs/openapi/swagger-ui-es-bundle-core.js
Normal file
3
docs/openapi/swagger-ui-es-bundle-core.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/openapi/swagger-ui-es-bundle-core.js.map
Normal file
1
docs/openapi/swagger-ui-es-bundle-core.js.map
Normal file
File diff suppressed because one or more lines are too long
3
docs/openapi/swagger-ui-es-bundle.js
Normal file
3
docs/openapi/swagger-ui-es-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/openapi/swagger-ui-es-bundle.js.map
Normal file
1
docs/openapi/swagger-ui-es-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
3
docs/openapi/swagger-ui-standalone-preset.js
Normal file
3
docs/openapi/swagger-ui-standalone-preset.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/openapi/swagger-ui-standalone-preset.js.map
Normal file
1
docs/openapi/swagger-ui-standalone-preset.js.map
Normal file
File diff suppressed because one or more lines are too long
3
docs/openapi/swagger-ui.css
Normal file
3
docs/openapi/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
1
docs/openapi/swagger-ui.css.map
Normal file
1
docs/openapi/swagger-ui.css.map
Normal file
File diff suppressed because one or more lines are too long
2
docs/openapi/swagger-ui.js
Normal file
2
docs/openapi/swagger-ui.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/openapi/swagger-ui.js.map
Normal file
1
docs/openapi/swagger-ui.js.map
Normal file
File diff suppressed because one or more lines are too long
128
env/dummy_conductor/Cargo.lock
generated
vendored
128
env/dummy_conductor/Cargo.lock
generated
vendored
|
@ -13,6 +13,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "dummy_conductor"
|
||||
version = "0.1.0"
|
||||
|
@ -21,6 +27,16 @@ dependencies = [
|
|||
"libconductor",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -29,15 +45,46 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||
|
||||
[[package]]
|
||||
name = "libconductor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"libconfig",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libconfig"
|
||||
version = "0.1.0"
|
||||
source = "git+https://git.batsense.net/librepages/libconfig#f54290c4bae26b51a4945e0bf812e2b99856963b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.46"
|
||||
|
@ -104,8 +151,89 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"tokio-macros",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
|
|
3
env/dummy_conductor/Cargo.toml
vendored
3
env/dummy_conductor/Cargo.toml
vendored
|
@ -10,5 +10,8 @@ serde = { version = "1", features=["derive"]}
|
|||
serde_json = { version ="1", features = ["raw_value"]}
|
||||
async-trait = "0.1.57"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.23.0", features = ["rt-multi-thread", "macros", "rt"] }
|
||||
|
||||
[dependencies.libconductor]
|
||||
path = "../libconductor"
|
||||
|
|
4
env/dummy_conductor/src/lib.rs
vendored
4
env/dummy_conductor/src/lib.rs
vendored
|
@ -41,8 +41,8 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn all_good() {
|
||||
#[tokio::test]
|
||||
async fn all_good() {
|
||||
let c = DummyConductor {};
|
||||
assert_eq!(c.name(), DUMMY_CONDUCTOR_NAME);
|
||||
assert!(c.health().await);
|
||||
|
|
9
env/libconductor/Cargo.lock
generated
vendored
9
env/libconductor/Cargo.lock
generated
vendored
|
@ -24,10 +24,19 @@ name = "libconductor"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"libconfig",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libconfig"
|
||||
version = "0.1.0"
|
||||
source = "git+https://git.batsense.net/librepages/libconfig#f54290c4bae26b51a4945e0bf812e2b99856963b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.46"
|
||||
|
|
1
env/libconductor/Cargo.toml
vendored
1
env/libconductor/Cargo.toml
vendored
|
@ -9,6 +9,7 @@ edition = "2021"
|
|||
serde = { version = "1", features=["derive"]}
|
||||
serde_json = { version ="1", features = ["raw_value"]}
|
||||
async-trait = { version = "0.1.57", optional = true}
|
||||
libconfig = { version = "0.1.0", git = "https://git.batsense.net/librepages/libconfig" }
|
||||
|
||||
[features]
|
||||
default = [
|
||||
|
|
14
env/libconductor/src/event_types.rs
vendored
14
env/libconductor/src/event_types.rs
vendored
|
@ -14,10 +14,22 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use libconfig::Config as LibConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum EventType {
|
||||
NewHostname(String),
|
||||
NewSite {
|
||||
path: String,
|
||||
branch: String,
|
||||
hostname: String,
|
||||
},
|
||||
DeleteSite {
|
||||
hostname: String,
|
||||
},
|
||||
|
||||
Config {
|
||||
data: LibConfig,
|
||||
},
|
||||
}
|
||||
|
|
117
scripts/bin-publish.sh
Executable file
117
scripts/bin-publish.sh
Executable file
|
@ -0,0 +1,117 @@
|
|||
#!/bin/bash
|
||||
# Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# publish.sh: grab bin from docker container, pack, sign and upload
|
||||
# $2: binary version
|
||||
# $3: Docker img tag
|
||||
# $4: dumbserve password
|
||||
|
||||
set -xEeuo pipefail
|
||||
|
||||
DUMBSERVE_USERNAME=librepages
|
||||
DUMBSERVE_PASSWORD=$4
|
||||
DUMBSERVE_HOST="https://$DUMBSERVE_USERNAME:$DUMBSERVE_PASSWORD@dl.librepages.org"
|
||||
|
||||
NAME=conductor
|
||||
KEY=67880CA5F4BC99BF247330E2DA576B07BC323961
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
FILENAME="$NAME-$2-linux-amd64"
|
||||
TARBALL=$FILENAME.tar.gz
|
||||
TARGET_DIR="$TMP_DIR/$FILENAME/"
|
||||
mkdir -p $TARGET_DIR
|
||||
DOCKER_IMG="realaravinth/$NAME:$3"
|
||||
|
||||
|
||||
get_bin(){
|
||||
cp target/release/conductor $TARGET_DIR
|
||||
cp -r config/ $TARGET_DIR
|
||||
cp -r contrib/ $TARGET_DIR
|
||||
}
|
||||
|
||||
copy() {
|
||||
echo "[*] Copying dist assets"
|
||||
cp README.md $TARGET_DIR
|
||||
cp LICENSE.md $TARGET_DIR
|
||||
|
||||
mkdir $TARGET_DIR/docs
|
||||
# cp docs/CONFIGURATION.md $TARGET_DIR/docs
|
||||
# cp -r docs/installation/ $TARGET_DIR/docs
|
||||
|
||||
get_bin
|
||||
}
|
||||
|
||||
pack() {
|
||||
echo "[*] Creating dist tarball"
|
||||
pushd $TMP_DIR
|
||||
tar -cvzf $TARBALL $FILENAME
|
||||
popd
|
||||
}
|
||||
|
||||
checksum() {
|
||||
echo "[*] Generating dist tarball checksum"
|
||||
pushd $TMP_DIR
|
||||
sha256sum $TARBALL > $TARBALL.sha256
|
||||
popd
|
||||
}
|
||||
|
||||
sign() {
|
||||
echo "[*] Signing dist tarball checksum"
|
||||
pushd $TMP_DIR
|
||||
export GPG_TTY=$(tty)
|
||||
gpg --verbose \
|
||||
--pinentry-mode loopback \
|
||||
--batch --yes \
|
||||
--passphrase $GPG_PASSWORD \
|
||||
--local-user $KEY \
|
||||
--output $TARBALL.asc \
|
||||
--sign --detach \
|
||||
--armor $TARBALL
|
||||
popd
|
||||
}
|
||||
|
||||
delete_dir() {
|
||||
curl --location --request DELETE "$DUMBSERVE_HOST/api/v1/files/delete" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw "{
|
||||
\"path\": \"$1\"
|
||||
}"
|
||||
}
|
||||
|
||||
upload_dist() {
|
||||
upload_dist="conductor/$1"
|
||||
delete_dir $upload_dist
|
||||
|
||||
pushd $TMP_DIR
|
||||
for file in $TARBALL $TARBALL.asc $TARBALL.sha256
|
||||
do
|
||||
curl -v \
|
||||
-F upload=@$file \
|
||||
"$DUMBSERVE_HOST/api/v1/files/upload?path=$upload_dist/"
|
||||
done
|
||||
popd
|
||||
}
|
||||
|
||||
|
||||
publish() {
|
||||
copy
|
||||
pack
|
||||
checksum
|
||||
sign
|
||||
upload_dist $2
|
||||
}
|
||||
|
||||
$1 $@
|
|
@ -14,11 +14,32 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use actix_web::dev::ServiceRequest;
|
||||
use actix_web::web;
|
||||
use actix_web::Error;
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
|
||||
use crate::errors::*;
|
||||
use crate::AppCtx;
|
||||
use crate::SETTINGS;
|
||||
|
||||
pub mod meta;
|
||||
pub mod webhook;
|
||||
|
||||
pub async fn bearerauth(
|
||||
req: ServiceRequest,
|
||||
credentials: BearerAuth,
|
||||
) -> Result<ServiceRequest, (Error, ServiceRequest)> {
|
||||
let _ctx: &AppCtx = req.app_data().unwrap();
|
||||
let token = credentials.token();
|
||||
if SETTINGS.authenticate(token) {
|
||||
Ok(req)
|
||||
} else {
|
||||
let e = Error::from(ServiceError::Unauthorized);
|
||||
Err((e, req))
|
||||
}
|
||||
}
|
||||
|
||||
pub const API_V1_ROUTES: routes::Routes = routes::Routes::new();
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use libconductor::EventType;
|
||||
|
@ -23,6 +24,8 @@ use crate::errors::*;
|
|||
use crate::AppCtx;
|
||||
use crate::*;
|
||||
|
||||
use super::bearerauth;
|
||||
|
||||
pub mod routes {
|
||||
use super::*;
|
||||
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||
|
@ -42,7 +45,10 @@ pub fn services(cfg: &mut web::ServiceConfig) {
|
|||
cfg.service(post_event);
|
||||
}
|
||||
|
||||
#[actix_web_codegen_const_routes::post(path = "API_V1_ROUTES.webhook.post_event")]
|
||||
#[actix_web_codegen_const_routes::post(
|
||||
path = "API_V1_ROUTES.webhook.post_event",
|
||||
wrap = "HttpAuthentication::bearer(bearerauth)"
|
||||
)]
|
||||
async fn post_event(ctx: AppCtx, payload: web::Json<EventType>) -> ServiceResult<impl Responder> {
|
||||
ctx.conductor.process(payload.into_inner()).await;
|
||||
Ok(HttpResponse::Created())
|
||||
|
@ -50,9 +56,8 @@ async fn post_event(ctx: AppCtx, payload: web::Json<EventType>) -> ServiceResult
|
|||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use actix_web::{http::StatusCode, test, App};
|
||||
|
||||
use super::*;
|
||||
use actix_web::{http::StatusCode, test, App};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn submit_works() {
|
||||
|
@ -65,14 +70,22 @@ pub mod tests {
|
|||
)
|
||||
.await;
|
||||
|
||||
let new_hostname = EventType::NewHostname("demo.librepages.org".into());
|
||||
let creds = settings.creds.clone();
|
||||
let auth = format!("Bearer {}", creds.token,);
|
||||
|
||||
let msg = EventType::NewSite {
|
||||
hostname: "demo.librepages.org".into(),
|
||||
branch: "librepages".into(),
|
||||
path: "/tmp/librepages".into(),
|
||||
};
|
||||
|
||||
// upload json
|
||||
let upload_json = test::call_service(
|
||||
&app,
|
||||
test::TestRequest::post()
|
||||
.append_header((actix_web::http::header::AUTHORIZATION, auth.clone()))
|
||||
.uri(API_V1_ROUTES.webhook.post_event)
|
||||
.set_json(&new_hostname)
|
||||
.set_json(&msg)
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
|
|
10
src/ctx.rs
10
src/ctx.rs
|
@ -37,13 +37,9 @@ impl Ctx {
|
|||
pub async fn new(s: &Settings) -> ArcCtx {
|
||||
let source_code = {
|
||||
let mut url = s.source_code.clone();
|
||||
if !url.ends_with('/') {
|
||||
url.push('/');
|
||||
}
|
||||
let mut base = url::Url::parse(&url).unwrap();
|
||||
base = base.join("tree/").unwrap();
|
||||
base = base.join(crate::GIT_COMMIT_HASH).unwrap();
|
||||
base.into()
|
||||
url = url.join("tree/").unwrap();
|
||||
url = url.join(crate::GIT_COMMIT_HASH).unwrap();
|
||||
url.into()
|
||||
};
|
||||
|
||||
let conductor: Box<dyn Conductor> = match s.conductor {
|
||||
|
|
128
src/docs.rs
Normal file
128
src/docs.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use std::borrow::Cow;
|
||||
|
||||
use actix_web::body::BoxBody;
|
||||
use actix_web::{http::header, web, HttpResponse, Responder};
|
||||
use mime_guess::from_path;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
use crate::CACHE_AGE;
|
||||
|
||||
pub const DOCS: routes::Docs = routes::Docs::new();
|
||||
|
||||
pub mod routes {
|
||||
pub struct Docs {
|
||||
pub home: &'static str,
|
||||
pub spec: &'static str,
|
||||
pub assets: &'static str,
|
||||
}
|
||||
|
||||
impl Docs {
|
||||
pub const fn new() -> Self {
|
||||
Docs {
|
||||
home: "/docs/openapi",
|
||||
spec: "/docs/openapi/openapi.yml",
|
||||
assets: "/docs/openapi/{_:.*}",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(index).service(spec).service(dist);
|
||||
}
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "docs/openapi/"]
|
||||
struct Asset;
|
||||
|
||||
pub fn handle_embedded_file(path: &str) -> HttpResponse {
|
||||
match Asset::get(path) {
|
||||
Some(content) => {
|
||||
let body: BoxBody = match content.data {
|
||||
Cow::Borrowed(bytes) => BoxBody::new(bytes),
|
||||
Cow::Owned(bytes) => BoxBody::new(bytes),
|
||||
};
|
||||
|
||||
HttpResponse::Ok()
|
||||
.insert_header(header::CacheControl(vec![
|
||||
header::CacheDirective::Public,
|
||||
header::CacheDirective::Extension("immutable".into(), None),
|
||||
header::CacheDirective::MaxAge(CACHE_AGE),
|
||||
]))
|
||||
.content_type(from_path(path).first_or_octet_stream().as_ref())
|
||||
.body(body)
|
||||
}
|
||||
None => HttpResponse::NotFound().body("404 Not Found"),
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_web_codegen_const_routes::get(path = "DOCS.assets")]
|
||||
async fn dist(path: web::Path<String>) -> impl Responder {
|
||||
handle_embedded_file(&path)
|
||||
}
|
||||
const OPEN_API_SPEC: &str = include_str!("../docs/openapi/openapi.yml");
|
||||
|
||||
#[actix_web_codegen_const_routes::get(path = "DOCS.spec")]
|
||||
async fn spec() -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/yaml")
|
||||
.body(OPEN_API_SPEC)
|
||||
}
|
||||
|
||||
#[actix_web_codegen_const_routes::get(path = "DOCS.home")]
|
||||
async fn index() -> HttpResponse {
|
||||
handle_embedded_file("index.html")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::test;
|
||||
|
||||
use super::*;
|
||||
use crate::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn docs_works() {
|
||||
const FILE: &str = "openapi.yml";
|
||||
|
||||
let app = test::init_service(
|
||||
App::new()
|
||||
.wrap(actix_web::middleware::NormalizePath::new(
|
||||
actix_web::middleware::TrailingSlash::Trim,
|
||||
))
|
||||
.configure(services),
|
||||
)
|
||||
.await;
|
||||
|
||||
let resp =
|
||||
test::call_service(&app, test::TestRequest::get().uri(DOCS.home).to_request()).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let resp =
|
||||
test::call_service(&app, test::TestRequest::get().uri(DOCS.spec).to_request()).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let uri = format!("{}/{}", DOCS.home, "favicon-32x32.png");
|
||||
println!("{uri}");
|
||||
|
||||
let resp = test::call_service(&app, test::TestRequest::get().uri(&uri).to_request()).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ use std::env;
|
|||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::JsonConfig;
|
||||
use actix_web::{error::InternalError, middleware, App, HttpServer};
|
||||
use actix_web_prom::PrometheusMetricsBuilder;
|
||||
use clap::{Parser, Subcommand};
|
||||
use log::info;
|
||||
|
||||
|
@ -26,7 +27,7 @@ use lazy_static::lazy_static;
|
|||
|
||||
mod api;
|
||||
mod ctx;
|
||||
//mod docs;
|
||||
mod docs;
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
mod errors;
|
||||
//#[macro_use]
|
||||
|
@ -112,6 +113,11 @@ async fn serve(settings: Settings, ctx: AppCtx) -> std::io::Result<()> {
|
|||
let ip = settings.server.get_ip();
|
||||
println!("Starting server on: http://{ip}");
|
||||
|
||||
let prometheus = PrometheusMetricsBuilder::new("api")
|
||||
.endpoint("/metrics")
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(middleware::Logger::default())
|
||||
|
@ -124,6 +130,7 @@ async fn serve(settings: Settings, ctx: AppCtx) -> std::io::Result<()> {
|
|||
middleware::TrailingSlash::Trim,
|
||||
))
|
||||
.app_data(get_json_err())
|
||||
.wrap(prometheus.clone())
|
||||
.configure(routes::services)
|
||||
})
|
||||
.bind(ip)?
|
||||
|
|
|
@ -17,5 +17,6 @@
|
|||
use actix_web::web;
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
crate::docs::services(cfg);
|
||||
crate::api::v1::services(cfg);
|
||||
}
|
||||
|
|
103
src/settings.rs
103
src/settings.rs
|
@ -17,13 +17,17 @@
|
|||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
use config::{Config, ConfigError, Environment, File};
|
||||
use config::{builder::DefaultState, Config, ConfigBuilder, ConfigError, Environment, File};
|
||||
use derive_more::Display;
|
||||
use log::info;
|
||||
use log::warn;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use url::Url;
|
||||
|
||||
const PREFIX: &str = "LPCONDUCTOR";
|
||||
const SEPARATOR: &str = "_";
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Server {
|
||||
pub port: u32,
|
||||
|
@ -48,58 +52,115 @@ pub enum ConductorType {
|
|||
Dummy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Creds {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Settings {
|
||||
pub debug: bool,
|
||||
pub creds: Creds,
|
||||
pub server: Server,
|
||||
pub source_code: String,
|
||||
pub source_code: Url,
|
||||
pub conductor: ConductorType,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl Settings {
|
||||
pub fn new() -> Result<Self, ConfigError> {
|
||||
let mut s = Config::new();
|
||||
pub fn authenticate(&self, token: &str) -> bool {
|
||||
self.creds.token == token
|
||||
}
|
||||
|
||||
const CURRENT_DIR: &str = "./config/default.toml";
|
||||
const ETC: &str = "/etc/lpconductor/config.toml";
|
||||
pub fn new() -> Result<Self, ConfigError> {
|
||||
let mut s = Config::builder();
|
||||
|
||||
const CURRENT_DIR: &str = "./config/config.toml";
|
||||
const ETC: &str = "/etc/librepages/conductor/config.toml";
|
||||
|
||||
if let Ok(path) = env::var("LPCONDUCTOR_CONFIG") {
|
||||
s.merge(File::with_name(&path))?;
|
||||
s = s.add_source(File::with_name(&path));
|
||||
} else if Path::new(CURRENT_DIR).exists() {
|
||||
// merging default config from file
|
||||
s.merge(File::with_name(CURRENT_DIR))?;
|
||||
s = s.add_source(File::with_name(CURRENT_DIR));
|
||||
} else if Path::new(ETC).exists() {
|
||||
s.merge(File::with_name(ETC))?;
|
||||
s = s.add_source(File::with_name(ETC));
|
||||
} else {
|
||||
log::warn!("configuration file not found");
|
||||
warn!("configuration file not found");
|
||||
}
|
||||
|
||||
s.merge(Environment::with_prefix("LPCONDUCTOR").separator("_"))?;
|
||||
|
||||
check_url(&s);
|
||||
s = s.add_source(Environment::with_prefix(PREFIX).separator(SEPARATOR));
|
||||
s = set_separator_field(s);
|
||||
|
||||
match env::var("PORT") {
|
||||
Ok(val) => {
|
||||
s.set("server.port", val).unwrap();
|
||||
s = s.set_override("server.port", val).unwrap();
|
||||
}
|
||||
Err(e) => warn!("couldn't interpret PORT: {}", e),
|
||||
}
|
||||
|
||||
match s.try_into::<Self>() {
|
||||
let s = s.build()?;
|
||||
match s.try_deserialize::<Self>() {
|
||||
Ok(val) => {
|
||||
Ok(val)
|
||||
},
|
||||
Err(e) => Err(ConfigError::Message(format!("\n\nError: {}. If it says missing fields, then please refer to https://github.com/mCaptcha/mcaptcha#configuration to learn more about how mcaptcha reads configuration\n\n", e))),
|
||||
Err(e) => Err(ConfigError::Message(format!("\n\nError: {}. If it says missing fields, then please refer to https://git.batsense.net/LibrePages/conductor to learn more about how conductor reads configuration\n\n", e))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn check_url(s: &Config) {
|
||||
let url = s
|
||||
.get::<String>("source_code")
|
||||
.expect("Couldn't access source_code");
|
||||
fn set_separator_field(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
|
||||
// ref: https://github.com/mehcode/config-rs/issues/391
|
||||
|
||||
Url::parse(&url).expect("Please enter a URL for source_code in settings");
|
||||
fn from_env(
|
||||
s: ConfigBuilder<DefaultState>,
|
||||
env_name: &str,
|
||||
config_name: &str,
|
||||
) -> ConfigBuilder<DefaultState> {
|
||||
if let Ok(val) = env::var(env_name) {
|
||||
info!("Overriding {config_name} with data from env var {env_name}");
|
||||
s.set_override(config_name, val)
|
||||
.unwrap_or_else(|_| panic!("Couldn't set {config_name} from env var {env_name}"))
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
s = from_env(s, &format!("{PREFIX}{SEPARATOR}SOURCE_CODE"), "source_code");
|
||||
s = from_env(
|
||||
s,
|
||||
&format!("{PREFIX}{SEPARATOR}SERVER{SEPARATOR}URL_PREFIX"),
|
||||
"server.url_prefix",
|
||||
);
|
||||
s = from_env(
|
||||
s,
|
||||
&format!("{PREFIX}{SEPARATOR}SERVER{SEPARATOR}PROXY_HAS_TLS"),
|
||||
"server.proxy_has_tls",
|
||||
);
|
||||
|
||||
s = from_env(
|
||||
s,
|
||||
&format!("{PREFIX}{SEPARATOR}CREDS{SEPARATOR}TOKEN"),
|
||||
"creds.token",
|
||||
);
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn creds_works() {
|
||||
let settings = Settings::new().unwrap();
|
||||
let creds = settings.creds.clone();
|
||||
|
||||
assert!(settings.authenticate(&creds.token));
|
||||
|
||||
let mut creds = settings.creds.clone();
|
||||
|
||||
creds.token = "noexist".into();
|
||||
assert!(!settings.authenticate(&creds.token))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue