Update install.sh
This commit is contained in:
@@ -14,17 +14,12 @@ use crate::{
|
||||
manager::defaults::BootstrapDefaults,
|
||||
};
|
||||
use arc_swap::ArcSwap;
|
||||
use pwhash::sha512_crypt;
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
use store::{
|
||||
RegistryStore,
|
||||
rand::{Rng, distr::Alphanumeric, rng},
|
||||
registry::bootstrap::Bootstrap,
|
||||
};
|
||||
use store::{RegistryStore, registry::bootstrap::Bootstrap};
|
||||
use tokio::sync::{Notify, mpsc};
|
||||
use utils::{UnwrapFailure, failed};
|
||||
|
||||
@@ -54,7 +49,6 @@ Options:
|
||||
-e, --export <PATH> Export all store data to a specific path
|
||||
-i, --import <PATH> Import store data from a specific path
|
||||
-o, --console Open the store console
|
||||
-I, --init <PATH> Initialize a new server at a specific path
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
"#
|
||||
@@ -99,10 +93,6 @@ impl BootManager {
|
||||
("config" | "c", Some(value)) => {
|
||||
config_path = Some(value);
|
||||
}
|
||||
("init" | "I", Some(value)) => {
|
||||
quickstart(value);
|
||||
std::process::exit(0);
|
||||
}
|
||||
("export" | "e", Some(value)) => {
|
||||
import_export = StoreOp::Export(BackupParams::new(value.into()));
|
||||
}
|
||||
@@ -308,189 +298,3 @@ pub fn build_ipc(has_pubsub: bool) -> (Ipc, IpcReceivers) {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn quickstart(path: impl Into<PathBuf>) {
|
||||
let path = path.into();
|
||||
|
||||
if !path.exists() {
|
||||
std::fs::create_dir_all(&path).failed("Failed to create directory");
|
||||
}
|
||||
|
||||
for dir in &["etc", "data", "logs"] {
|
||||
let sub_path = path.join(dir);
|
||||
if !sub_path.exists() {
|
||||
std::fs::create_dir(sub_path).failed(&format!("Failed to create {dir} directory"));
|
||||
}
|
||||
}
|
||||
|
||||
let admin_pass = std::env::var("STALWART_ADMIN_PASSWORD").unwrap_or_else(|_| {
|
||||
rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(10)
|
||||
.map(char::from)
|
||||
.collect::<String>()
|
||||
});
|
||||
|
||||
std::fs::write(
|
||||
path.join("etc").join("registry.json"),
|
||||
QUICKSTART_CONFIG
|
||||
.replace("_P_", &path.to_string_lossy())
|
||||
.replace("_S_", &sha512_crypt::hash(&admin_pass).unwrap()),
|
||||
)
|
||||
.failed("Failed to write configuration file");
|
||||
|
||||
eprintln!(
|
||||
"✅ Local registry initialized at {}/etc/registry.json",
|
||||
path.to_string_lossy()
|
||||
);
|
||||
eprintln!("🔑 Your administrator account is 'admin' with password '{admin_pass}'.");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "foundation"))]
|
||||
const QUICKSTART_CONFIG: &str = r#"[server.listener.smtp]
|
||||
bind = "[::]:25"
|
||||
protocol = "smtp"
|
||||
|
||||
[server.listener.submission]
|
||||
bind = "[::]:587"
|
||||
protocol = "smtp"
|
||||
|
||||
[server.listener.submissions]
|
||||
bind = "[::]:465"
|
||||
protocol = "smtp"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener.imap]
|
||||
bind = "[::]:143"
|
||||
protocol = "imap"
|
||||
|
||||
[server.listener.imaptls]
|
||||
bind = "[::]:993"
|
||||
protocol = "imap"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener.pop3]
|
||||
bind = "[::]:110"
|
||||
protocol = "pop3"
|
||||
|
||||
[server.listener.pop3s]
|
||||
bind = "[::]:995"
|
||||
protocol = "pop3"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener.sieve]
|
||||
bind = "[::]:4190"
|
||||
protocol = "managesieve"
|
||||
|
||||
[server.listener.https]
|
||||
protocol = "http"
|
||||
bind = "[::]:443"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener.http]
|
||||
protocol = "http"
|
||||
bind = "[::]:8080"
|
||||
|
||||
[storage]
|
||||
data = "rocksdb"
|
||||
fts = "rocksdb"
|
||||
blob = "rocksdb"
|
||||
lookup = "rocksdb"
|
||||
directory = "internal"
|
||||
|
||||
[store.rocksdb]
|
||||
type = "rocksdb"
|
||||
path = "_P_/data"
|
||||
compression = "lz4"
|
||||
|
||||
[directory.internal]
|
||||
type = "internal"
|
||||
store = "rocksdb"
|
||||
|
||||
[tracer.log]
|
||||
type = "log"
|
||||
level = "info"
|
||||
path = "_P_/logs"
|
||||
prefix = "stalwart.log"
|
||||
rotate = "daily"
|
||||
ansi = false
|
||||
enable = true
|
||||
|
||||
[authentication.fallback-admin]
|
||||
user = "admin"
|
||||
secret = "_S_"
|
||||
"#;
|
||||
|
||||
#[cfg(feature = "foundation")]
|
||||
const QUICKSTART_CONFIG: &str = r#"[server.listener.smtp]
|
||||
bind = "[::]:25"
|
||||
protocol = "smtp"
|
||||
|
||||
[server.listener.submission]
|
||||
bind = "[::]:587"
|
||||
protocol = "smtp"
|
||||
|
||||
[server.listener.submissions]
|
||||
bind = "[::]:465"
|
||||
protocol = "smtp"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener.imap]
|
||||
bind = "[::]:143"
|
||||
protocol = "imap"
|
||||
|
||||
[server.listener.imaptls]
|
||||
bind = "[::]:993"
|
||||
protocol = "imap"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener.pop3]
|
||||
bind = "[::]:110"
|
||||
protocol = "pop3"
|
||||
|
||||
[server.listener.pop3s]
|
||||
bind = "[::]:995"
|
||||
protocol = "pop3"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener.sieve]
|
||||
bind = "[::]:4190"
|
||||
protocol = "managesieve"
|
||||
|
||||
[server.listener.https]
|
||||
protocol = "http"
|
||||
bind = "[::]:443"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener.http]
|
||||
protocol = "http"
|
||||
bind = "[::]:8080"
|
||||
|
||||
[storage]
|
||||
data = "foundation-db"
|
||||
fts = "foundation-db"
|
||||
blob = "foundation-db"
|
||||
lookup = "foundation-db"
|
||||
directory = "internal"
|
||||
|
||||
[store.foundation-db]
|
||||
type = "foundationdb"
|
||||
compression = "lz4"
|
||||
|
||||
[directory.internal]
|
||||
type = "internal"
|
||||
store = "foundation-db"
|
||||
|
||||
[tracer.log]
|
||||
type = "log"
|
||||
level = "info"
|
||||
path = "_P_/logs"
|
||||
prefix = "stalwart.log"
|
||||
rotate = "daily"
|
||||
ansi = false
|
||||
enable = true
|
||||
|
||||
[authentication.fallback-admin]
|
||||
user = "admin"
|
||||
secret = "_S_"
|
||||
"#;
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::{
|
||||
now,
|
||||
},
|
||||
};
|
||||
use rand::{Rng, distr::Alphanumeric, rng};
|
||||
use registry::{
|
||||
schema::{enums::ClusterNodeStatus, structs::ClusterNode},
|
||||
types::datetime::UTCDateTime,
|
||||
@@ -36,6 +37,33 @@ impl RegistryStore {
|
||||
RegistryInit::Err(err) => return Err(err),
|
||||
RegistryInit::Bootstrap => {
|
||||
inner.env_recovery_mode = true;
|
||||
|
||||
if inner.env_recovery_admin.is_none() {
|
||||
let password = rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(16)
|
||||
.map(char::from)
|
||||
.collect::<String>();
|
||||
eprintln!();
|
||||
eprintln!("════════════════════════════════════════════════════════════");
|
||||
eprintln!("🔑 Stalwart bootstrap mode — temporary administrator account");
|
||||
eprintln!();
|
||||
eprintln!(" username: admin");
|
||||
eprintln!(" password: {password}");
|
||||
eprintln!();
|
||||
eprintln!("Use these credentials to complete the initial setup at the");
|
||||
eprintln!("/admin web UI. Once setup is done, Stalwart will provision a");
|
||||
eprintln!("permanent administrator and this temporary account will no");
|
||||
eprintln!("longer apply.");
|
||||
eprintln!();
|
||||
eprintln!("This password is shown only once. To pin a credential");
|
||||
eprintln!("instead, set STALWART_RECOVERY_ADMIN=admin:<password> in the");
|
||||
eprintln!("env file.");
|
||||
eprintln!("════════════════════════════════════════════════════════════");
|
||||
eprintln!();
|
||||
inner.env_recovery_admin = Some(("admin".to_string(), password));
|
||||
}
|
||||
|
||||
EphemeralStore::open()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,15 +26,13 @@ impl RegistryStoreInner {
|
||||
.ok()
|
||||
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
|
||||
.unwrap_or(false),
|
||||
env_recovery_admin: std::env::var("STALWART_ADMIN_ACCOUNT")
|
||||
env_recovery_admin: std::env::var("STALWART_RECOVERY_ADMIN")
|
||||
.ok()
|
||||
.filter(|a| !a.is_empty())
|
||||
.and_then(|a| {
|
||||
std::env::var("STALWART_ADMIN_PASS")
|
||||
.ok()
|
||||
.filter(|p| !p.is_empty())
|
||||
.map(|p| (a, p))
|
||||
}),
|
||||
.and_then(|v| {
|
||||
v.split_once(':')
|
||||
.map(|(a, p)| (a.trim().to_string(), p.trim().to_string()))
|
||||
})
|
||||
.filter(|(a, p)| !a.is_empty() && !p.is_empty()),
|
||||
env_cluster_role: std::env::var("STALWART_ROLE")
|
||||
.ok()
|
||||
.filter(|r| !r.is_empty()),
|
||||
|
||||
431
install.sh
431
install.sh
@@ -19,136 +19,256 @@ main() {
|
||||
need_cmd uname
|
||||
need_cmd mktemp
|
||||
need_cmd chmod
|
||||
need_cmd chown
|
||||
need_cmd mkdir
|
||||
need_cmd rm
|
||||
need_cmd rmdir
|
||||
need_cmd tar
|
||||
need_cmd cp
|
||||
need_cmd hostname
|
||||
|
||||
# Make sure we are running as root
|
||||
if [ "$(id -u)" -ne 0 ] ; then
|
||||
# Require root
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
err "❌ Install failed: This program needs to run as root."
|
||||
fi
|
||||
|
||||
# Detect OS
|
||||
local _os="unknown"
|
||||
local _uname="$(uname)"
|
||||
local _os _uname _account
|
||||
_uname="$(uname)"
|
||||
_account="stalwart"
|
||||
if [ "${_uname}" = "Linux" ]; then
|
||||
_os="linux"
|
||||
elif [ "${_uname}" = "Darwin" ]; then
|
||||
_os="macos"
|
||||
_account="_stalwart"
|
||||
fi
|
||||
case "$_uname" in
|
||||
Linux) _os="linux" ;;
|
||||
Darwin) _os="macos"; _account="_stalwart" ;;
|
||||
*) err "❌ Install failed: Unsupported OS: $_uname" ;;
|
||||
esac
|
||||
|
||||
# Read arguments
|
||||
local _dir="/opt/stalwart"
|
||||
|
||||
# Default component setting
|
||||
# Parse arguments
|
||||
local _component="stalwart"
|
||||
|
||||
# Loop through the arguments
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
local _prefix=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--fdb)
|
||||
_component="stalwart-foundationdb"
|
||||
;;
|
||||
-h|--help)
|
||||
print_usage
|
||||
exit 0
|
||||
;;
|
||||
--*|-*)
|
||||
err "❌ Unknown flag: $1 (try --help)"
|
||||
;;
|
||||
*)
|
||||
if [ -n "$arg" ]; then
|
||||
_dir=$arg
|
||||
if [ -n "$_prefix" ]; then
|
||||
err "❌ Only one prefix argument is allowed, got: $_prefix $1"
|
||||
fi
|
||||
_prefix="$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Detect platform architecture
|
||||
# Derive install paths — FHS by default, self-contained under a custom prefix
|
||||
local _bin_dir _bin_file _conf_dir _log_dir _data_dir _env_file _config_file
|
||||
if [ -z "$_prefix" ]; then
|
||||
_bin_dir="/usr/local/bin"
|
||||
_conf_dir="/etc/stalwart"
|
||||
_log_dir="/var/log/stalwart"
|
||||
_data_dir="/var/lib/stalwart"
|
||||
else
|
||||
_bin_dir="${_prefix}/bin"
|
||||
_conf_dir="${_prefix}/etc"
|
||||
_log_dir="${_prefix}/logs"
|
||||
_data_dir="${_prefix}/data"
|
||||
fi
|
||||
_bin_file="${_bin_dir}/stalwart"
|
||||
_config_file="${_conf_dir}/config.json"
|
||||
_env_file="${_conf_dir}/stalwart.env"
|
||||
|
||||
# Detect architecture
|
||||
get_architecture || return 1
|
||||
local _arch="$RETVAL"
|
||||
assert_nz "$_arch" "arch"
|
||||
|
||||
# Create service account
|
||||
create_account "$_os" "$_account"
|
||||
|
||||
# Create directories
|
||||
ensure mkdir -p "$_dir" "$_dir/bin" "$_dir/etc" "$_dir/logs"
|
||||
ensure mkdir -p "$_bin_dir" "$_conf_dir" "$_log_dir" "$_data_dir"
|
||||
|
||||
# Download latest binary
|
||||
# Download and install the binary
|
||||
say "⏳ Downloading ${_component} for ${_arch}..."
|
||||
local _file="${_dir}/bin/stalwart.tar.gz"
|
||||
local _url="${BASE_URL}/${_component}-${_arch}.tar.gz"
|
||||
ensure mkdir -p "$_dir"
|
||||
ensure downloader "$_url" "$_file" "$_arch"
|
||||
ensure tar zxvf "$_file" -C "$_dir/bin"
|
||||
local _tmp _tar _src_name
|
||||
_tmp="$(mktemp -d)"
|
||||
_tar="${_tmp}/stalwart.tar.gz"
|
||||
ensure downloader "${BASE_URL}/${_component}-${_arch}.tar.gz" "$_tar" "$_arch"
|
||||
ensure tar zxf "$_tar" -C "$_tmp"
|
||||
_src_name="stalwart"
|
||||
if [ "$_component" = "stalwart-foundationdb" ]; then
|
||||
ignore mv "$_dir/bin/stalwart-foundationdb" "$_dir/bin/stalwart"
|
||||
_src_name="stalwart-foundationdb"
|
||||
fi
|
||||
ignore chmod +x "$_dir/bin/stalwart"
|
||||
ignore rm "$_file"
|
||||
ensure cp "${_tmp}/${_src_name}" "$_bin_file"
|
||||
ensure chmod 0755 "$_bin_file"
|
||||
ensure rm -rf "$_tmp"
|
||||
|
||||
# Create system account
|
||||
if ! id -u ${_account} > /dev/null 2>&1; then
|
||||
say "🖥️ Creating '${_account}' account..."
|
||||
if [ "${_os}" = "macos" ]; then
|
||||
local _last_uid="$(dscacheutil -q user | grep uid | awk '{print $2}' | sort -n | tail -n 1)"
|
||||
local _last_gid="$(dscacheutil -q group | grep gid | awk '{print $2}' | sort -n | tail -n 1)"
|
||||
local _uid="$((_last_uid+1))"
|
||||
local _gid="$((_last_gid+1))"
|
||||
|
||||
ensure dscl /Local/Default -create Groups/_stalwart
|
||||
ensure dscl /Local/Default -create Groups/_stalwart Password \*
|
||||
ensure dscl /Local/Default -create Groups/_stalwart PrimaryGroupID $_gid
|
||||
ensure dscl /Local/Default -create Groups/_stalwart RealName "Stalwart service"
|
||||
ensure dscl /Local/Default -create Groups/_stalwart RecordName _stalwart stalwart
|
||||
|
||||
ensure dscl /Local/Default -create Users/_stalwart
|
||||
ensure dscl /Local/Default -create Users/_stalwart NFSHomeDirectory /Users/_stalwart
|
||||
ensure dscl /Local/Default -create Users/_stalwart Password \*
|
||||
ensure dscl /Local/Default -create Users/_stalwart PrimaryGroupID $_gid
|
||||
ensure dscl /Local/Default -create Users/_stalwart RealName "Stalwart service"
|
||||
ensure dscl /Local/Default -create Users/_stalwart RecordName _stalwart stalwart
|
||||
ensure dscl /Local/Default -create Users/_stalwart UniqueID $_uid
|
||||
ensure dscl /Local/Default -create Users/_stalwart UserShell /bin/bash
|
||||
|
||||
ensure dscl /Local/Default -delete /Users/_stalwart AuthenticationAuthority
|
||||
ensure dscl /Local/Default -delete /Users/_stalwart PasswordPolicyOptions
|
||||
else
|
||||
ensure useradd ${_account} -s /usr/sbin/nologin -M -r -U
|
||||
fi
|
||||
# Create env file if absent (preserve user edits on reinstall)
|
||||
if [ ! -e "$_env_file" ]; then
|
||||
say "📝 Writing env file at ${_env_file}..."
|
||||
write_env_file "$_env_file"
|
||||
fi
|
||||
|
||||
# Run init
|
||||
ignore $_dir/bin/stalwart --init "$_dir"
|
||||
|
||||
# Set permissions
|
||||
# Ownership and permissions
|
||||
say "🔐 Setting permissions..."
|
||||
ensure chown -R ${_account}:${_account} "$_dir"
|
||||
ensure chmod -R 755 "$_dir"
|
||||
ensure chmod 700 "$_dir/etc/config.toml"
|
||||
ensure chown "${_account}:${_account}" "$_conf_dir" "$_log_dir" "$_data_dir"
|
||||
ensure chmod 0750 "$_conf_dir" "$_log_dir" "$_data_dir"
|
||||
ensure chown "root:${_account}" "$_env_file"
|
||||
ensure chmod 0640 "$_env_file"
|
||||
|
||||
# Create service file
|
||||
# Install and start the service
|
||||
say "🚀 Starting service..."
|
||||
if [ "${_os}" = "linux" ]; then
|
||||
local _issystemdlinux=$(command -v systemctl)
|
||||
if [ -n "$_issystemdlinux" ]; then
|
||||
create_service_linux_systemd "$_dir"
|
||||
else
|
||||
create_service_linux_initd "$_dir"
|
||||
fi
|
||||
elif [ "${_os}" = "macos" ]; then
|
||||
create_service_macos "$_dir"
|
||||
fi
|
||||
local _service_type=""
|
||||
case "$_os" in
|
||||
linux)
|
||||
if check_cmd systemctl; then
|
||||
create_service_linux_systemd "$_bin_file" "$_config_file" "$_env_file" "$_account"
|
||||
_service_type="systemd"
|
||||
else
|
||||
create_service_linux_initd "$_bin_file" "$_config_file" "$_env_file" "$_account"
|
||||
_service_type="initd"
|
||||
fi
|
||||
;;
|
||||
macos)
|
||||
create_service_macos "$_bin_file" "$_config_file" "$_env_file" "$_account"
|
||||
_service_type="launchd"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Installation complete
|
||||
local _host=$(hostname -f)
|
||||
say "🎉 Installation complete! Continue the setup at http://$_host:8080/login"
|
||||
# Completion message
|
||||
local _host
|
||||
_host="$(hostname -f 2>/dev/null || hostname)"
|
||||
say ""
|
||||
say "🎉 Installation complete!"
|
||||
say ""
|
||||
say "Stalwart is running in bootstrap mode. A temporary administrator"
|
||||
say "password was generated at startup and printed to the service logs."
|
||||
say ""
|
||||
say "👉 To find the password, inspect the service logs:"
|
||||
case "$_service_type" in
|
||||
systemd)
|
||||
say " journalctl -u stalwart -n 200 | grep -A8 'bootstrap mode'"
|
||||
;;
|
||||
initd)
|
||||
say " grep -A8 'bootstrap mode' /var/log/syslog 2>/dev/null \\"
|
||||
say " || grep -A8 'bootstrap mode' /var/log/messages"
|
||||
;;
|
||||
launchd)
|
||||
say " sudo log show --predicate 'process == \"stalwart\"' --last 5m"
|
||||
;;
|
||||
esac
|
||||
say ""
|
||||
say " Or set STALWART_RECOVERY_ADMIN=admin:<password> in"
|
||||
say " ${_env_file} and restart the service to pin a credential."
|
||||
say ""
|
||||
say " Finish setup at: http://${_host}:8080/admin"
|
||||
say ""
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Functions to create service files
|
||||
print_usage() {
|
||||
cat <<'EOF'
|
||||
Usage: install.sh [--fdb] [PREFIX]
|
||||
|
||||
Install Stalwart into standard FHS paths or under a custom prefix.
|
||||
|
||||
Options:
|
||||
--fdb Install the FoundationDB build.
|
||||
-h, --help Show this help.
|
||||
|
||||
With no PREFIX, Stalwart is installed under standard FHS paths:
|
||||
binary /usr/local/bin/stalwart
|
||||
config /etc/stalwart/config.json (created by the daemon on first run)
|
||||
env /etc/stalwart/stalwart.env
|
||||
logs /var/log/stalwart/
|
||||
data /var/lib/stalwart/
|
||||
|
||||
When PREFIX is provided, a self-contained layout is used instead:
|
||||
binary $PREFIX/bin/stalwart
|
||||
config $PREFIX/etc/config.json
|
||||
env $PREFIX/etc/stalwart.env
|
||||
logs $PREFIX/logs/
|
||||
data $PREFIX/data/
|
||||
EOF
|
||||
}
|
||||
|
||||
write_env_file() {
|
||||
cat > "$1" <<'EOF'
|
||||
# Environment variables for the Stalwart service.
|
||||
# Uncomment and edit an entry to override its default.
|
||||
|
||||
# Enable bootstrap / recovery mode on startup. Accepted: 1, true. Default: false.
|
||||
#STALWART_RECOVERY_MODE=true
|
||||
|
||||
# Log level while in recovery mode. Default: info.
|
||||
#STALWART_RECOVERY_MODE_LOG_LEVEL=debug
|
||||
|
||||
# HTTP port used in recovery mode. Default: 8080.
|
||||
#STALWART_RECOVERY_MODE_PORT=9090
|
||||
|
||||
# Fixed administrator credentials — format: username:password
|
||||
# Default: a temporary random password is generated and printed to the logs.
|
||||
#STALWART_RECOVERY_ADMIN=admin:changeme
|
||||
|
||||
# Cluster role assigned to this node. Must match a role name defined in the
|
||||
# cluster registry. Leave unset for a standalone (non-clustered) deployment.
|
||||
#STALWART_ROLE=primary
|
||||
|
||||
# Push-notification shard this node is responsible for, when running in a
|
||||
# cluster.
|
||||
#STALWART_PUSH_SHARD=1
|
||||
EOF
|
||||
}
|
||||
|
||||
create_account() {
|
||||
local _os="$1"
|
||||
local _account="$2"
|
||||
if id -u "$_account" > /dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
say "🖥️ Creating '${_account}' account..."
|
||||
if [ "$_os" = "macos" ]; then
|
||||
local _last_uid _last_gid _uid _gid
|
||||
_last_uid="$(dscacheutil -q user | grep uid | awk '{print $2}' | sort -n | tail -n 1)"
|
||||
_last_gid="$(dscacheutil -q group | grep gid | awk '{print $2}' | sort -n | tail -n 1)"
|
||||
_uid="$((_last_uid+1))"
|
||||
_gid="$((_last_gid+1))"
|
||||
|
||||
ensure dscl /Local/Default -create Groups/_stalwart
|
||||
ensure dscl /Local/Default -create Groups/_stalwart Password \*
|
||||
ensure dscl /Local/Default -create Groups/_stalwart PrimaryGroupID $_gid
|
||||
ensure dscl /Local/Default -create Groups/_stalwart RealName "Stalwart service"
|
||||
ensure dscl /Local/Default -create Groups/_stalwart RecordName _stalwart stalwart
|
||||
|
||||
ensure dscl /Local/Default -create Users/_stalwart
|
||||
ensure dscl /Local/Default -create Users/_stalwart NFSHomeDirectory /var/empty
|
||||
ensure dscl /Local/Default -create Users/_stalwart Password \*
|
||||
ensure dscl /Local/Default -create Users/_stalwart PrimaryGroupID $_gid
|
||||
ensure dscl /Local/Default -create Users/_stalwart RealName "Stalwart service"
|
||||
ensure dscl /Local/Default -create Users/_stalwart RecordName _stalwart stalwart
|
||||
ensure dscl /Local/Default -create Users/_stalwart UniqueID $_uid
|
||||
ensure dscl /Local/Default -create Users/_stalwart UserShell /usr/bin/false
|
||||
|
||||
ensure dscl /Local/Default -delete /Users/_stalwart AuthenticationAuthority
|
||||
ensure dscl /Local/Default -delete /Users/_stalwart PasswordPolicyOptions
|
||||
else
|
||||
ensure useradd "$_account" -s /usr/sbin/nologin -M -r -U
|
||||
fi
|
||||
}
|
||||
|
||||
create_service_linux_systemd() {
|
||||
local _dir="$1"
|
||||
cat <<EOF | sed "s|__PATH__|$_dir|g" > /etc/systemd/system/stalwart.service
|
||||
local _bin="$1" _config="$2" _env="$3" _user="$4"
|
||||
cat > /etc/systemd/system/stalwart.service <<EOF
|
||||
[Unit]
|
||||
Description=Stalwart
|
||||
Conflicts=postfix.service sendmail.service exim4.service
|
||||
ConditionPathExists=__PATH__/etc/config.toml
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
@@ -158,10 +278,11 @@ KillMode=process
|
||||
KillSignal=SIGINT
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
ExecStart=__PATH__/bin/stalwart --config=__PATH__/etc/config.toml
|
||||
EnvironmentFile=-${_env}
|
||||
ExecStart=${_bin} --config=${_config}
|
||||
SyslogIdentifier=stalwart
|
||||
User=stalwart
|
||||
Group=stalwart
|
||||
User=${_user}
|
||||
Group=${_user}
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
|
||||
[Install]
|
||||
@@ -173,13 +294,13 @@ EOF
|
||||
}
|
||||
|
||||
create_service_linux_initd() {
|
||||
local _dir="$1"
|
||||
cat <<"EOF" | sed "s|__PATH__|$_dir|g" > /etc/init.d/stalwart
|
||||
local _bin="$1" _config="$2" _env="$3" _user="$4"
|
||||
cat > /etc/init.d/stalwart <<EOF
|
||||
#!/bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: stalwart
|
||||
# Required-Start: $network
|
||||
# Required-Stop: $network
|
||||
# Required-Start: \$network
|
||||
# Required-Stop: \$network
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Stalwart Server
|
||||
@@ -192,91 +313,75 @@ PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
. /lib/init/vars.sh
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# Service Config
|
||||
DAEMON=__PATH__/bin/stalwart
|
||||
DAEMON_ARGS="--config=__PATH__/etc/config.toml"
|
||||
DAEMON=${_bin}
|
||||
DAEMON_ARGS="--config=${_config}"
|
||||
ENV_FILE=${_env}
|
||||
PIDFILE=/var/run/stalwart.pid
|
||||
ULIMIT_NOFILE=65536
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
[ -x "\$DAEMON" ] || exit 0
|
||||
|
||||
# Exit if config file doesn't exist
|
||||
[ -f "__PATH__/etc/config.toml" ] || exit 0
|
||||
if [ -r "\$ENV_FILE" ]; then
|
||||
set -a
|
||||
. "\$ENV_FILE"
|
||||
set +a
|
||||
fi
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/stalwart ] && . /etc/default/stalwart
|
||||
|
||||
# Increase file descriptor limit
|
||||
ulimit -n $ULIMIT_NOFILE
|
||||
ulimit -n \$ULIMIT_NOFILE
|
||||
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||
start-stop-daemon --start --quiet --pidfile \$PIDFILE --exec \$DAEMON --test > /dev/null \\
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
|
||||
--background --make-pidfile --chuid stalwart:stalwart \
|
||||
-- $DAEMON_ARGS \
|
||||
start-stop-daemon --start --quiet --pidfile \$PIDFILE --exec \$DAEMON \\
|
||||
--background --make-pidfile --chuid ${_user}:${_user} \\
|
||||
-- \$DAEMON_ARGS \\
|
||||
|| return 2
|
||||
}
|
||||
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=INT/30/KILL/5 --pidfile $PIDFILE --name stalwart
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# Wait for children to finish too if this is a daemon that forks
|
||||
# and if the daemon is only ever run from this initscript.
|
||||
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||
[ "$?" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
start-stop-daemon --stop --quiet --retry=INT/30/KILL/5 --pidfile \$PIDFILE --name stalwart
|
||||
RETVAL="\$?"
|
||||
[ "\$RETVAL" = 2 ] && return 2
|
||||
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec \$DAEMON
|
||||
[ "\$?" = 2 ] && return 2
|
||||
rm -f \$PIDFILE
|
||||
return "\$RETVAL"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
case "\$1" in
|
||||
start)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting Stalwart Server" "stalwart"
|
||||
[ "\$VERBOSE" != no ] && log_daemon_msg "Starting Stalwart Server" "stalwart"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
case "\$?" in
|
||||
0|1) [ "\$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "\$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping Stalwart Server" "stalwart"
|
||||
[ "\$VERBOSE" != no ] && log_daemon_msg "Stopping Stalwart Server" "stalwart"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
case "\$?" in
|
||||
0|1) [ "\$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "\$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "stalwart" && exit 0 || exit $?
|
||||
status_of_proc "\$DAEMON" "stalwart" && exit 0 || exit \$?
|
||||
;;
|
||||
restart)
|
||||
log_daemon_msg "Restarting Stalwart Server" "stalwart"
|
||||
do_stop
|
||||
case "$?" in
|
||||
case "\$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
case "\$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
*) log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
@@ -290,34 +395,40 @@ esac
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x /etc/init.d/stalwart
|
||||
|
||||
cat <<EOF > /etc/default/stalwart
|
||||
# Configuration for Stalwart init script being run during
|
||||
# the boot sequence
|
||||
|
||||
# Set to 'yes' to enable additional verbosity
|
||||
#VERBOSE=no
|
||||
EOF
|
||||
update-rc.d stalwart defaults
|
||||
service stalwart start
|
||||
}
|
||||
|
||||
create_service_macos() {
|
||||
local _dir="$1"
|
||||
cat <<EOF | sed "s|__PATH__|$_dir|g" > /Library/LaunchAgents/stalwart.mail.plist
|
||||
local _bin="$1" _config="$2" _env="$3" _user="$4"
|
||||
local _plist="/Library/LaunchDaemons/stalwart.plist"
|
||||
|
||||
# Remove any legacy LaunchAgent from a prior install
|
||||
if [ -f /Library/LaunchAgents/stalwart.mail.plist ]; then
|
||||
launchctl unload /Library/LaunchAgents/stalwart.mail.plist 2>/dev/null || true
|
||||
rm -f /Library/LaunchAgents/stalwart.mail.plist
|
||||
fi
|
||||
|
||||
# launchd has no EnvironmentFile equivalent — wrap with sh to source the env file
|
||||
cat > "$_plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>stalwart.mail</string>
|
||||
<string>stalwart</string>
|
||||
<key>ServiceDescription</key>
|
||||
<string>Stalwart</string>
|
||||
<key>UserName</key>
|
||||
<string>${_user}</string>
|
||||
<key>GroupName</key>
|
||||
<string>${_user}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>__PATH__/bin/stalwart</string>
|
||||
<string>--config=__PATH__/etc/config.toml</string>
|
||||
<string>/bin/sh</string>
|
||||
<string>-c</string>
|
||||
<string>set -a; if [ -r "${_env}" ]; then . "${_env}"; fi; set +a; exec "${_bin}" --config="${_config}"</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
@@ -326,9 +437,11 @@ create_service_macos() {
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
launchctl load /Library/LaunchAgents/stalwart.mail.plist
|
||||
launchctl enable system/stalwart.mail
|
||||
launchctl start system/stalwart.mail
|
||||
chmod 0644 "$_plist"
|
||||
chown root:wheel "$_plist"
|
||||
launchctl bootout system "$_plist" 2>/dev/null || true
|
||||
launchctl bootstrap system "$_plist"
|
||||
launchctl enable system/stalwart
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -49,4 +49,4 @@ RUN useradd stalwart -s /sbin/nologin -M
|
||||
RUN mkdir -p /opt/stalwart
|
||||
RUN chown stalwart:stalwart /opt/stalwart
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/stalwart", "--config", "/opt/stalwart/etc/config.toml"]
|
||||
ENTRYPOINT ["/usr/local/bin/stalwart", "--config", "/opt/stalwart/etc/config.json"]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
# shellcheck shell=dash
|
||||
|
||||
# If the configuration file exists, start the server.
|
||||
exec /usr/local/bin/stalwart --config /opt/stalwart/etc/config.json
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[Unit]
|
||||
Description=Stalwart Server
|
||||
Conflicts=postfix.service sendmail.service exim4.service
|
||||
ConditionPathExists=__PATH__/etc/config.toml
|
||||
ConditionPathExists=__PATH__/etc/config.json
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
@@ -11,7 +11,7 @@ KillMode=process
|
||||
KillSignal=SIGINT
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
ExecStart=__PATH__/bin/stalwart --config=__PATH__/etc/config.toml
|
||||
ExecStart=__PATH__/bin/stalwart --config=__PATH__/etc/config.json
|
||||
SyslogIdentifier=stalwart
|
||||
User=stalwart
|
||||
Group=stalwart
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>__PATH__/bin/stalwart</string>
|
||||
<string>--config=__PATH__/etc/config.toml</string>
|
||||
<string>--config=__PATH__/etc/config.json</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
Reference in New Issue
Block a user