Skip to content

Commit

Permalink
Install script fixes, moved SMTP management API to JMAP listener.
Browse files Browse the repository at this point in the history
  • Loading branch information
mdecimus committed Jul 14, 2023
1 parent 81a5c2f commit c1ae11c
Show file tree
Hide file tree
Showing 15 changed files with 212 additions and 116 deletions.
140 changes: 93 additions & 47 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
pull_request:
push:
tags:
- '*'
- "v*.*.*"

jobs:
build:
Expand Down Expand Up @@ -240,67 +240,113 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


docker-amd:
name: Build Docker AMD64 images
if: '!cancelled()'
build_docker:
name: Build Docker image for ${{ matrix.platform }}
runs-on: ubuntu-latest
if: '!cancelled()'
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- name: Checkout
-
name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
-
name: Checkout
uses: actions/checkout@v3

- name: Set up QEMU
-
name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY_IMAGE }}
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2

- name: Set up Docker Buildx
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Login to DockerHub
-
name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v3
-
name: Build and push by digest
id: build
uses: docker/build-push-action@v4
with:
context: .
push: true
platforms: linux/amd64
tags: stalwartlabs/mail-server:latest
cache-from: type=registry,ref=stalwartlabs/mail-server:buildcache
cache-to: type=registry,ref=stalwartlabs/mail-server:buildcache,mode=max
#cache-from: type=gha
#cache-to: type=gha,mode=max

docker-arm:
name: Build Docker ARM64 images
if: '!cancelled()'
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
#cache-from: type=registry,ref=${{ env.REGISTRY_IMAGE }}:buildcache-${{ env.PLATFORM_PAIR }}
#cache-to: type=registry,ref=s${{ env.REGISTRY_IMAGE }}:buildcache-${{ env.PLATFORM_PAIR }},mode=max
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
cache-to: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
-
name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
-
name: Upload digest
uses: actions/upload-artifact@v3
with:
name: digests
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

merge_docker:
name: Merge and push Docker manifest
runs-on: ubuntu-latest
needs:
- build_docker
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Set up QEMU
uses: docker/setup-qemu-action@v2

- name: Set up Docker Buildx
-
name: Download digests
uses: actions/download-artifact@v3
with:
name: digests
path: /tmp/digests
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Login to DockerHub
-
name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY_IMAGE }}
-
name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
push: true
platforms: linux/arm64
tags: stalwartlabs/mail-server:latest
cache-from: type=registry,ref=stalwartlabs/mail-server:buildcache
cache-to: type=registry,ref=stalwartlabs/mail-server:buildcache,mode=max
#cache-from: type=gha
#cache-to: type=gha,mode=max
-
name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
-
name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ try each of these servers individually:
* [Stalwart IMAP Server](https://github.com/stalwartlabs/imap-server/)
* [Stalwart SMTP Server](https://github.com/stalwartlabs/smtp-server/)

## Why choose Stalwart?

Within the field of mail servers, established names like Postfix, Courier and Dovecot have long been the go-to solutions. However, the landscape of internet messaging is evolving, with a need for more efficient, easy to maintain, reliable, and secure systems. Here's why you might consider making the switch to Stalwart Mail Server:

- Designed with the latest internet messaging protocols in mind - JMAP and IMAP4rev2, along with the conventional SMTP.
- Leverages the performance and security benefits of the Rust programming language. This statically typed, compiled language is known for its memory safety and concurrency support, reducing the likelihood of typical security issues like buffer overflows.
- Thanks to its native FoundationDB and S3 storage support, it can be scaled across many servers, accommodating millions of users.
- Available as a single, integrated package that includes JMAP, IMAP, and SMTP servers. This means that you don't have to install, configure and maintain multiple servers to get a complete solution.
- Designed to be easy to install and maintain, with a single configuration file and a simple command-line interface.

## License

Licensed under the terms of the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.en.html) as published by
Expand Down
6 changes: 2 additions & 4 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,16 @@ async fn main() -> std::io::Result<()> {
let is_jmap = args.command.is_jmap();
let credentials = if let Some(credentials) = args.credentials {
parse_credentials(&credentials)
} else if is_jmap {
} else {
let credentials = rpassword::prompt_password(
"\nEnter JMAP admin credentials or press [ENTER] to use OAuth: ",
"\nEnter administrator credentials or press [ENTER] to use OAuth: ",
)
.unwrap();
if !credentials.is_empty() {
parse_credentials(&credentials)
} else {
oauth(&args.url).await
}
} else {
parse_credentials(&rpassword::prompt_password("\nEnter SMTP admin credentials: ").unwrap())
};

if is_jmap {
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/modules/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use serde::Deserialize;
pub struct Cli {
#[clap(subcommand)]
pub command: Commands,
/// JMAP or SMTP server base URL
/// Server base URL
#[clap(short, long)]
pub url: String,
/// Authentication credentials
Expand Down
10 changes: 5 additions & 5 deletions crates/cli/src/modules/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub async fn cmd_queue(url: &str, credentials: Credentials, command: QueueComman
.collect(),
));
for (message, id) in smtp_manage_request::<Vec<Option<Message>>>(
&build_query(url, "/queue/status?ids=", chunk),
&build_query(url, "/admin/queue/status?ids=", chunk),
&credentials,
)
.await
Expand Down Expand Up @@ -172,7 +172,7 @@ pub async fn cmd_queue(url: &str, credentials: Credentials, command: QueueComman
}
QueueCommands::Status { ids } => {
for (message, id) in smtp_manage_request::<Vec<Option<Message>>>(
&build_query(url, "/queue/status?ids=", &parse_ids(&ids)),
&build_query(url, "/admin/queue/status?ids=", &parse_ids(&ids)),
&credentials,
)
.await
Expand Down Expand Up @@ -311,7 +311,7 @@ pub async fn cmd_queue(url: &str, credentials: Credentials, command: QueueComman
std::process::exit(1);
}

let mut query = form_urlencoded::Serializer::new(format!("{url}/queue/retry?"));
let mut query = form_urlencoded::Serializer::new(format!("{url}/admin/queue/retry?"));

if let Some(filter) = &domain {
query.append_pair("filter", filter);
Expand Down Expand Up @@ -365,7 +365,7 @@ pub async fn cmd_queue(url: &str, credentials: Credentials, command: QueueComman
std::process::exit(1);
}

let mut query = form_urlencoded::Serializer::new(format!("{url}/queue/cancel?"));
let mut query = form_urlencoded::Serializer::new(format!("{url}/admin/queue/cancel?"));

if let Some(filter) = &rcpt {
query.append_pair("filter", filter);
Expand Down Expand Up @@ -443,7 +443,7 @@ async fn query_messages(
before: &Option<DateTime>,
after: &Option<DateTime>,
) -> Vec<u64> {
let mut query = form_urlencoded::Serializer::new(format!("{url}/queue/list?"));
let mut query = form_urlencoded::Serializer::new(format!("{url}/admin/queue/list?"));

if let Some(sender) = from {
query.append_pair("from", sender);
Expand Down
8 changes: 4 additions & 4 deletions crates/cli/src/modules/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub async fn cmd_report(url: &str, credentials: Credentials, command: ReportComm
page_size,
} => {
let stdout = Term::buffered_stdout();
let mut query = form_urlencoded::Serializer::new(format!("{url}/report/list?"));
let mut query = form_urlencoded::Serializer::new(format!("{url}/admin/report/list?"));

if let Some(domain) = &domain {
query.append_pair("domain", domain);
Expand All @@ -73,7 +73,7 @@ pub async fn cmd_report(url: &str, credentials: Credentials, command: ReportComm
.collect(),
));
for (report, id) in smtp_manage_request::<Vec<Option<Report>>>(
&format!("{url}/report/status?ids={}", chunk.join(",")),
&format!("{url}/admin/report/status?ids={}", chunk.join(",")),
&credentials,
)
.await
Expand Down Expand Up @@ -110,7 +110,7 @@ pub async fn cmd_report(url: &str, credentials: Credentials, command: ReportComm
}
ReportCommands::Status { ids } => {
for (report, id) in smtp_manage_request::<Vec<Option<Report>>>(
&format!("{url}/report/status?ids={}", ids.join(",")),
&format!("{url}/admin/report/status?ids={}", ids.join(",")),
&credentials,
)
.await
Expand Down Expand Up @@ -164,7 +164,7 @@ pub async fn cmd_report(url: &str, credentials: Credentials, command: ReportComm
let mut success_count = 0;
let mut failed_list = vec![];
for (success, id) in smtp_manage_request::<Vec<bool>>(
&format!("{url}/report/cancel?ids={}", ids.join(",")),
&format!("{url}/admin/report/cancel?ids={}", ids.join(",")),
&credentials,
)
.await
Expand Down
17 changes: 15 additions & 2 deletions crates/install/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,15 +363,20 @@ fn main() -> std::io::Result<()> {

// Obtain TLS certificate path
let (cert_path, pk_path) = if !args.docker {
#[cfg(not(target_env = "msvc"))]
let cert_base_path = format!("/etc/letsencrypt/live/{}/", hostname);
#[cfg(target_env = "msvc")]
let cert_base_path = format!("C:\\Program Files\\Letsencrypt\\live\\{}\\", hostname);

(
input(
&format!("Where is the TLS certificate for '{hostname}' located?"),
&format!("/etc/letsencrypt/live/{hostname}/fullchain.pem"),
&format!("{cert_base_path}fullchain.pem"),
file_exists,
)?,
input(
&format!("Where is the TLS private key for '{hostname}' located?"),
&format!("/etc/letsencrypt/live/{hostname}/privkey.pem"),
&format!("{cert_base_path}privkey.pem"),
file_exists,
)?,
)
Expand Down Expand Up @@ -827,12 +832,20 @@ impl SelectItem for Blob {

impl Component {
fn default_base_path(&self) -> &'static str {
#[cfg(not(target_env = "msvc"))]
match self {
Self::AllInOne => "/opt/stalwart-mail",
Self::Jmap => "/opt/stalwart-jmap",
Self::Imap => "/opt/stalwart-imap",
Self::Smtp => "/opt/stalwart-smtp",
}
#[cfg(target_env = "msvc")]
match self {
Self::AllInOne => "C:\\Program Files\\Stalwart Mail",
Self::Jmap => "C:\\Program Files\\Stalwart JMAP",
Self::Imap => "C:\\Program Files\\Stalwart IMAP",
Self::Smtp => "C:\\Program Files\\Stalwart SMTP",
}
}

fn binary_name(&self) -> &'static str {
Expand Down
6 changes: 6 additions & 0 deletions crates/jmap/src/api/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,12 @@ pub async fn parse_jmap_request(
.into_http_response(),
};
}
(path_1 @ ("queue" | "report"), path_2, &Method::GET) => {
return jmap
.smtp
.handle_manage_request(req.uri(), req.method(), path_1, path_2)
.await;
}
_ => (),
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/main/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use directory::config::ConfigDirectory;
use imap::core::{ImapSessionManager, IMAP};
use jmap::{api::JmapSessionManager, services::IPC_CHANNEL_BUFFER, JMAP};
use managesieve::core::ManageSieveSessionManager;
use smtp::core::{SmtpAdminSessionManager, SmtpSessionManager, SMTP};
use smtp::core::{SmtpSessionManager, SMTP};
use tokio::sync::mpsc;
use utils::{
config::{Config, ServerProtocol},
Expand Down Expand Up @@ -79,7 +79,7 @@ async fn main() -> std::io::Result<()> {
server.spawn(SmtpSessionManager::new(smtp.clone()), shutdown_rx)
}
ServerProtocol::Http => {
server.spawn(SmtpAdminSessionManager::new(smtp.clone()), shutdown_rx)
tracing::debug!("Ignoring HTTP server listener, using JMAP port instead.");
}
ServerProtocol::Jmap => {
server.spawn(JmapSessionManager::new(jmap.clone()), shutdown_rx)
Expand Down

0 comments on commit c1ae11c

Please sign in to comment.