Add scripts for installing firefox sync server
This commit is contained in:
parent
9857514544
commit
56fc7da02c
10 changed files with 339 additions and 0 deletions
|
@ -27,6 +27,7 @@ in
|
||||||
services = {
|
services = {
|
||||||
anki-sync.enable = mkEnableOption "Anki Sync server";
|
anki-sync.enable = mkEnableOption "Anki Sync server";
|
||||||
drone.enable = mkEnableOption "drone server";
|
drone.enable = mkEnableOption "drone server";
|
||||||
|
firefox-sync.enable = mkEnableOption "Firefox Sync server";
|
||||||
forgejo.enable = mkEnableOption "Forgejo server";
|
forgejo.enable = mkEnableOption "Forgejo server";
|
||||||
gotify.enable = mkEnableOption "Gotify server";
|
gotify.enable = mkEnableOption "Gotify server";
|
||||||
jellyfin.enable = mkEnableOption "Jellyfin media server";
|
jellyfin.enable = mkEnableOption "Jellyfin media server";
|
||||||
|
|
|
@ -108,6 +108,7 @@ in {
|
||||||
services = {
|
services = {
|
||||||
anki-sync.enable = true;
|
anki-sync.enable = true;
|
||||||
drone.enable = true;
|
drone.enable = true;
|
||||||
|
firefox-sync.enable = true;
|
||||||
forgejo.enable = true;
|
forgejo.enable = true;
|
||||||
gotify.enable = true;
|
gotify.enable = true;
|
||||||
jellyfin.enable = true;
|
jellyfin.enable = true;
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!*.patch
|
|
@ -0,0 +1,28 @@
|
||||||
|
FROM rust AS build
|
||||||
|
ARG SYNC_STORAGE_VERSION=0.18.2
|
||||||
|
RUN git clone https://github.com/mozilla-services/syncstorage-rs -b ${SYNC_STORAGE_VERSION} /app
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ./public-url.patch .
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
apt-get update \
|
||||||
|
&& apt-get install -y libpython3-dev \
|
||||||
|
&& git apply public-url.patch \
|
||||||
|
&& cargo install --path ./syncserver --features mysql --locked \
|
||||||
|
&& cargo install diesel_cli --no-default-features --features mysql --locked \
|
||||||
|
&& cargo clean \
|
||||||
|
&& apt-get remove -y libpython3-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists \
|
||||||
|
&& bash -O extglob -c 'rm -rf /usr/local/cargo/!(bin)' \
|
||||||
|
&& bash -O extglob -c 'rm -rf /usr/local/cargo/bin/!(diesel|syncserver)'
|
||||||
|
|
||||||
|
FROM python:3.11 AS sync
|
||||||
|
COPY --from=build /usr/local/cargo/bin/syncserver /usr/local/bin
|
||||||
|
COPY --from=build /app/requirements.txt .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
CMD [ "/usr/local/bin/syncserver" ]
|
||||||
|
|
||||||
|
FROM mariadb AS db
|
||||||
|
RUN mkdir -p /app/tokenserver-db
|
||||||
|
COPY --from=build /app/tokenserver-db/migrations /app/tokenserver-db/migrations
|
||||||
|
COPY --from=build /usr/local/cargo/bin/diesel /usr/local/bin
|
|
@ -0,0 +1,37 @@
|
||||||
|
services:
|
||||||
|
sync-server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: sync
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
RUST_LOG: warn
|
||||||
|
SYNC_HUMAN_LOGS: 1
|
||||||
|
SYNC_HOST: "0.0.0.0"
|
||||||
|
SYNC_PORT: 80
|
||||||
|
SYNC_SYNCSTORAGE__ENABLED: "true"
|
||||||
|
SYNC_SYNCSTORAGE__ENABLE_QUOTA: 0
|
||||||
|
SYNC_TOKENSERVER__ENABLED: "true"
|
||||||
|
SYNC_TOKENSERVER__RUN_MIGRATIONS: "true"
|
||||||
|
SYNC_TOKENSERVER__FXA_EMAIL_DOMAIN: api.accounts.firefox.com
|
||||||
|
SYNC_TOKENSERVER__FXA_OAUTH_SERVER_URL: https://oauth.accounts.firefox.com
|
||||||
|
SYNC_TOKENSERVER__FXA_BROWSERID_AUDIENCE: https://token.services.mozilla.com
|
||||||
|
SYNC_TOKENSERVER__FXA_BROWSERID_ISSUER: https://api.accounts.firefox.com
|
||||||
|
SYNC_TOKENSERVER__FXA_BROWSERID_SERVER_URL: https://verifier.accounts.firefox.com/v2
|
||||||
|
sync-db:
|
||||||
|
image: mariadb
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MARIADB_RANDOM_ROOT_PASSWORD: "yes"
|
||||||
|
volumes:
|
||||||
|
- ./data/sync:/var/lib/mysql
|
||||||
|
token-db:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MARIADB_RANDOM_ROOT_PASSWORD: "yes"
|
||||||
|
volumes:
|
||||||
|
- ./data/token:/var/lib/mysql
|
||||||
|
- ./init:/docker-entrypoint-initdb.d
|
|
@ -0,0 +1,21 @@
|
||||||
|
services:
|
||||||
|
sync-server:
|
||||||
|
environment:
|
||||||
|
SYNC_PUBLIC_URL: https://example.com
|
||||||
|
SYNC_MASTER_SECRET: secret
|
||||||
|
SYNC_SYNCSTORAGE__DATABASE_URL: mysql://sync:password@sync-db/SyncStorage
|
||||||
|
SYNC_TOKENSERVER__DATABASE_URL: mysql://token:password@token-db/TokenServer
|
||||||
|
SYNC_TOKENSERVER__FXA_METRICS_HASH_SECRET: secret
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:1337:80
|
||||||
|
sync-db:
|
||||||
|
environment:
|
||||||
|
MARIADB_USER: sync
|
||||||
|
MARIADB_PASSWORD: password
|
||||||
|
MARIADB_DATABASE: SyncStorage
|
||||||
|
token-db:
|
||||||
|
environment:
|
||||||
|
SYNC_PUBLIC_URL: https://example.com
|
||||||
|
MARIADB_USER: token
|
||||||
|
MARIADB_PASSWORD: password
|
||||||
|
MARIADB_DATABASE: TokenServer
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/bash
|
||||||
|
diesel --database-url "mysql://${MARIADB_USER}:${MARIADB_PASSWORD}@localhost/${MARIADB_DATABASE}" migration --migration-dir /app/tokenserver-db/migrations run
|
|
@ -0,0 +1,10 @@
|
||||||
|
mariadb -u$MARIADB_USER -p$MARIADB_PASSWORD -D $MARIADB_DATABASE <<EOF
|
||||||
|
INSERT INTO services
|
||||||
|
(service, pattern)
|
||||||
|
SELECT 'sync-1.5', '{node}/1.5/{uid}'
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM services);
|
||||||
|
|
||||||
|
INSERT INTO nodes
|
||||||
|
(\`service\`, node, capacity, available, current_load, downed, backoff)
|
||||||
|
VALUES (LAST_INSERT_ID(), '${SYNC_PUBLIC_URL}', 1, 1, 0, 0, 0)
|
||||||
|
EOF
|
|
@ -0,0 +1,64 @@
|
||||||
|
#!/bin/env fish
|
||||||
|
begin
|
||||||
|
set -l domain sync
|
||||||
|
set -l dir (status dirname)
|
||||||
|
set -l source "$dir/docker-compose.overrides.yml"
|
||||||
|
source "$dir/../service.fish"
|
||||||
|
|
||||||
|
function installSW -V dir -V source
|
||||||
|
set -l root (getServiceRoot $argv)
|
||||||
|
initializeServiceInstallation $argv
|
||||||
|
|
||||||
|
sudo cp \
|
||||||
|
"$dir/"{docker-compose.{base,overrides}.yml,.dockerignore,Dockerfile,public-url.patch} \
|
||||||
|
"$root"
|
||||||
|
|
||||||
|
sudo cp -r "$dir/init" "$root"
|
||||||
|
installDockerService $argv
|
||||||
|
end
|
||||||
|
|
||||||
|
function configureSW -V dir -V domain
|
||||||
|
set -l overrides (getServiceOverrides $argv)
|
||||||
|
set -l envKey ".services.sync-server.environment"
|
||||||
|
set -l url https://$domain.nuth.ch/firefox
|
||||||
|
|
||||||
|
set databases sync SYNCSTORAGE \
|
||||||
|
token TOKENSERVER
|
||||||
|
|
||||||
|
for variable in masterSecret metricsSecret syncPW tokenPW
|
||||||
|
set $variable (nix-shell -p keepassxc --run "keepassxc-cli generate --length 64")
|
||||||
|
end
|
||||||
|
|
||||||
|
sudo SECRET="$masterSecret" yq -i "$envKey.SYNC_MASTER_SECRET = env(SECRET)" "$overrides"
|
||||||
|
sudo SECRET="$metricsSecret" yq -i "$envKey.SYNC_TOKENSERVER__FXA_METRICS_HASH_SECRET = env(SECRET)" "$overrides"
|
||||||
|
sudo URL=$url yq -i "$envKey.SYNC_PUBLIC_URL = env(URL)" "$overrides"
|
||||||
|
sudo URL=$url yq -i ".services.token-db.environment.SYNC_PUBLIC_URL = env(URL)" "$overrides"
|
||||||
|
configureDockerService $argv
|
||||||
|
|
||||||
|
for i in $(seq 1 2 (count $databases))
|
||||||
|
set -l prefix $databases[$i]
|
||||||
|
set -l section $databases[(math "$i + 1")]
|
||||||
|
set -l pwVar "$prefix""PW"
|
||||||
|
set -l pw $$pwVar
|
||||||
|
set -l dbEnv ".services.$prefix-db.environment"
|
||||||
|
set -l db (yq "$dbEnv.MARIADB_DATABASE" "$overrides")
|
||||||
|
set -l user (yq "$dbEnv.MARIADB_USER" "$overrides")
|
||||||
|
sudo PW=$pw yq -i "$dbEnv.MARIADB_PASSWORD = env(PW)" "$overrides"
|
||||||
|
sudo CONNECTION="mysql://$user:$pw@$prefix-db/$db" yq -i ".services.sync-server.environment.SYNC_"$section"__DATABASE_URL = env(CONNECTION)" "$overrides"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function getServiceServers -V domain
|
||||||
|
printf "$domain" ""
|
||||||
|
end
|
||||||
|
|
||||||
|
function getServiceLocations -V domain
|
||||||
|
printf "%s\0" "sync-server" /firefox/ "Firefox Sync"
|
||||||
|
end
|
||||||
|
|
||||||
|
function getServiceLocationConfig -a domain s location -V service -V flood
|
||||||
|
getServiceDefaultProxy $argv --path "/"
|
||||||
|
end
|
||||||
|
|
||||||
|
runInstaller --force $argv
|
||||||
|
end
|
|
@ -0,0 +1,173 @@
|
||||||
|
diff --git a/syncserver-settings/src/lib.rs b/syncserver-settings/src/lib.rs
|
||||||
|
index 6732ffc..1c9ef83 100644
|
||||||
|
--- a/syncserver-settings/src/lib.rs
|
||||||
|
+++ b/syncserver-settings/src/lib.rs
|
||||||
|
@@ -18,6 +18,7 @@ static PREFIX: &str = "sync";
|
||||||
|
pub struct Settings {
|
||||||
|
pub port: u16,
|
||||||
|
pub host: String,
|
||||||
|
+ pub public_url: Option<String>,
|
||||||
|
/// Keep-alive header value (seconds)
|
||||||
|
pub actix_keep_alive: Option<u32>,
|
||||||
|
/// The master secret, from which are derived
|
||||||
|
@@ -182,6 +183,7 @@ impl Default for Settings {
|
||||||
|
Settings {
|
||||||
|
port: 8000,
|
||||||
|
host: "127.0.0.1".to_string(),
|
||||||
|
+ public_url: None,
|
||||||
|
actix_keep_alive: None,
|
||||||
|
master_secret: Secrets::default(),
|
||||||
|
statsd_host: Some("localhost".to_owned()),
|
||||||
|
diff --git a/syncserver/src/server/mod.rs b/syncserver/src/server/mod.rs
|
||||||
|
index f1e0a18..d686693 100644
|
||||||
|
--- a/syncserver/src/server/mod.rs
|
||||||
|
+++ b/syncserver/src/server/mod.rs
|
||||||
|
@@ -80,7 +80,7 @@ pub struct Server;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! build_app {
|
||||||
|
- ($syncstorage_state: expr, $tokenserver_state: expr, $secrets: expr, $limits: expr, $cors: expr, $metrics: expr) => {
|
||||||
|
+ ($public_url: expr, $syncstorage_state: expr, $tokenserver_state: expr, $secrets: expr, $limits: expr, $cors: expr, $metrics: expr) => {
|
||||||
|
App::new()
|
||||||
|
.configure(|cfg| {
|
||||||
|
cfg.app_data(Data::new($syncstorage_state));
|
||||||
|
@@ -90,6 +90,7 @@ macro_rules! build_app {
|
||||||
|
cfg.app_data(Data::new(state));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
+ .app_data(Data::new($public_url))
|
||||||
|
.app_data(Data::new($secrets))
|
||||||
|
// Middleware is applied LIFO
|
||||||
|
// These will wrap all outbound responses with matching status codes.
|
||||||
|
@@ -336,6 +337,7 @@ impl Server {
|
||||||
|
};
|
||||||
|
|
||||||
|
build_app!(
|
||||||
|
+ settings.public_url.clone(),
|
||||||
|
syncstorage_state,
|
||||||
|
tokenserver_state.clone(),
|
||||||
|
Arc::clone(&secrets),
|
||||||
|
diff --git a/syncserver/src/server/test.rs b/syncserver/src/server/test.rs
|
||||||
|
index 8690526..7efbe98 100644
|
||||||
|
--- a/syncserver/src/server/test.rs
|
||||||
|
+++ b/syncserver/src/server/test.rs
|
||||||
|
@@ -127,6 +127,7 @@ macro_rules! init_app {
|
||||||
|
let state = get_test_state(&$settings).await;
|
||||||
|
let metrics = state.metrics.clone();
|
||||||
|
test::init_service(build_app!(
|
||||||
|
+ $settings.public_url.clone(),
|
||||||
|
state,
|
||||||
|
None::<tokenserver::ServerState>,
|
||||||
|
Arc::clone(&SECRETS),
|
||||||
|
@@ -248,6 +249,7 @@ where
|
||||||
|
let state = get_test_state(&settings).await;
|
||||||
|
let metrics = state.metrics.clone();
|
||||||
|
let app = test::init_service(build_app!(
|
||||||
|
+ settings.public_url.clone(),
|
||||||
|
state,
|
||||||
|
None::<tokenserver::ServerState>,
|
||||||
|
Arc::clone(&SECRETS),
|
||||||
|
@@ -292,6 +294,7 @@ async fn test_endpoint_with_body(
|
||||||
|
let state = get_test_state(&settings).await;
|
||||||
|
let metrics = state.metrics.clone();
|
||||||
|
let app = test::init_service(build_app!(
|
||||||
|
+ settings.public_url.clone(),
|
||||||
|
state,
|
||||||
|
None::<tokenserver::ServerState>,
|
||||||
|
Arc::clone(&SECRETS),
|
||||||
|
diff --git a/syncserver/src/web/auth.rs b/syncserver/src/web/auth.rs
|
||||||
|
index 9153d38..ca1d123 100644
|
||||||
|
--- a/syncserver/src/web/auth.rs
|
||||||
|
+++ b/syncserver/src/web/auth.rs
|
||||||
|
@@ -170,6 +170,7 @@ impl HawkPayload {
|
||||||
|
pub fn extrude(
|
||||||
|
header: &str,
|
||||||
|
method: &str,
|
||||||
|
+ public_url: &Option<String>,
|
||||||
|
secrets: &Secrets,
|
||||||
|
ci: &ConnectionInfo,
|
||||||
|
uri: &Uri,
|
||||||
|
@@ -190,6 +191,15 @@ impl HawkPayload {
|
||||||
|
} else {
|
||||||
|
80
|
||||||
|
};
|
||||||
|
+
|
||||||
|
+ let prefix = match &public_url {
|
||||||
|
+ None => "".to_owned(),
|
||||||
|
+ Some(url) => match url.parse::<Uri>() {
|
||||||
|
+ Err(_) => "".to_owned(),
|
||||||
|
+ Ok(uri) => uri.path().to_owned()
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
let path = uri.path_and_query().ok_or(HawkErrorKind::MissingPath)?;
|
||||||
|
let expiry = if path.path().ends_with("/info/collections") {
|
||||||
|
0
|
||||||
|
@@ -197,7 +207,7 @@ impl HawkPayload {
|
||||||
|
Utc::now().timestamp() as u64
|
||||||
|
};
|
||||||
|
|
||||||
|
- HawkPayload::new(header, method, path.as_str(), host, port, secrets, expiry)
|
||||||
|
+ HawkPayload::new(header, method, (prefix + path.as_str()).as_str(), host, port, secrets, expiry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/syncserver/src/web/extractors.rs b/syncserver/src/web/extractors.rs
|
||||||
|
index 4bd617b..b9aee1c 100644
|
||||||
|
--- a/syncserver/src/web/extractors.rs
|
||||||
|
+++ b/syncserver/src/web/extractors.rs
|
||||||
|
@@ -1056,6 +1056,7 @@ impl HawkIdentifier {
|
||||||
|
method: &str,
|
||||||
|
uri: &Uri,
|
||||||
|
ci: &ConnectionInfo,
|
||||||
|
+ public_url: &Option<String>,
|
||||||
|
secrets: &Secrets,
|
||||||
|
) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
@@ -1072,6 +1073,7 @@ impl HawkIdentifier {
|
||||||
|
.to_str()
|
||||||
|
.map_err(|e| -> ApiError { HawkErrorKind::Header(e).into() })?;
|
||||||
|
let identifier = Self::generate(
|
||||||
|
+ public_url,
|
||||||
|
secrets,
|
||||||
|
method,
|
||||||
|
auth_header,
|
||||||
|
@@ -1084,6 +1086,7 @@ impl HawkIdentifier {
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate(
|
||||||
|
+ public_url: &Option<String>,
|
||||||
|
secrets: &Secrets,
|
||||||
|
method: &str,
|
||||||
|
header: &str,
|
||||||
|
@@ -1091,7 +1094,7 @@ impl HawkIdentifier {
|
||||||
|
uri: &Uri,
|
||||||
|
exts: &mut Extensions,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
- let payload = HawkPayload::extrude(header, method, secrets, connection_info, uri)?;
|
||||||
|
+ let payload = HawkPayload::extrude(header, method, &public_url, secrets, connection_info, uri)?;
|
||||||
|
let puid = Self::uid_from_path(uri)?;
|
||||||
|
if payload.user_id != puid {
|
||||||
|
warn!("⚠️ Hawk UID not in URI: {:?} {:?}", payload.user_id, uri);
|
||||||
|
@@ -1145,6 +1148,12 @@ impl FromRequest for HawkIdentifier {
|
||||||
|
// NOTE: `connection_info()` will get a mutable reference lock on `extensions()`
|
||||||
|
let connection_info = req.connection_info().clone();
|
||||||
|
let method = req.method().clone();
|
||||||
|
+
|
||||||
|
+ let public_url: &Option<String> = match &req.app_data::<Data<Option<String>>>() {
|
||||||
|
+ Some(data) => data,
|
||||||
|
+ None => &None
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
// Tried collapsing this to a `.or_else` and hit problems with the return resolving
|
||||||
|
// to an appropriate error state. Can't use `?` since the function does not return a result.
|
||||||
|
let secrets = match req.app_data::<Data<Arc<Secrets>>>() {
|
||||||
|
@@ -1155,7 +1164,7 @@ impl FromRequest for HawkIdentifier {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
- let result = Self::extrude(&req, method.as_str(), uri, &connection_info, secrets);
|
||||||
|
+ let result = Self::extrude(&req, method.as_str(), uri, &connection_info, &public_url, secrets);
|
||||||
|
|
||||||
|
if let Ok(ref hawk_id) = result {
|
||||||
|
// Store the origin of the token as an extra to be included when emitting a Sentry error
|
Loading…
Reference in a new issue