26.4 ferdig: Postfix som receive-only MTA med pipe til synops-mail
Postfix installert og konfigurert som lokal MTA kun for epost-mottak. Ingen relay, ingen utgående kø — utgående bruker msmtp/Brevo som før. Konfigurasjon: - virtual_mailbox_domains: synops.no, sidelinja.org, vegard.info - Catch-all: alle adresser under domenene aksepteres - virtual_transport → synops-pipe: pipe(8) leverer til synops-mail - default_transport = error: blokkerer utgående SMTP - synops-mail --receive stub: leser stdin, logger, exit 0 Verifisert: lokal SMTP-test viser at epost aksepteres, pipes til synops-mail, og logges korrekt i /var/log/mail.log.
This commit is contained in:
parent
8e8c9ba1dd
commit
a6740f82e3
4 changed files with 148 additions and 22 deletions
|
|
@ -453,7 +453,70 @@ Caddy (Docker) proxyer `api.sidelinja.org` til `host.docker.internal:3100`.
|
|||
Dockerfile (`maskinrommet/Dockerfile`) beholdes for referanse, men brukes
|
||||
ikke i produksjon.
|
||||
|
||||
## 13. Verifisering etter oppsett
|
||||
## 13. Postfix (innkommende epost)
|
||||
|
||||
Postfix kjører som lokal MTA **kun for mottak**. Ingen relay, ingen
|
||||
utgående kø — utgående epost sendes via msmtp/Brevo (se synops-mail --send).
|
||||
|
||||
### Installasjon
|
||||
|
||||
```bash
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y postfix
|
||||
```
|
||||
|
||||
### Konfigurasjon
|
||||
|
||||
**`/etc/postfix/main.cf`** — nøkkelinnstillinger:
|
||||
- `myhostname = mail.synops.no`
|
||||
- `virtual_mailbox_domains = synops.no, sidelinja.org, vegard.info`
|
||||
- `virtual_transport = synops-pipe:` — all epost pipes til synops-mail
|
||||
- `mydestination =` — tom, all levering via virtual
|
||||
- `default_transport = error:outbound mail is disabled` — blokkerer utgående
|
||||
- `relayhost =` — ingen relay
|
||||
|
||||
**`/etc/postfix/virtual_mailbox`** — catch-all for alle domener:
|
||||
```
|
||||
@synops.no OK
|
||||
@sidelinja.org OK
|
||||
@vegard.info OK
|
||||
```
|
||||
Etter endring: `sudo postmap /etc/postfix/virtual_mailbox`
|
||||
|
||||
**`/etc/postfix/master.cf`** — pipe transport (lagt til på slutten):
|
||||
```
|
||||
synops-pipe unix - n n - - pipe
|
||||
flags=DRhu user=vegard argv=/usr/local/bin/synops-mail --receive --recipient ${recipient} --sender ${sender}
|
||||
```
|
||||
|
||||
### Drift
|
||||
|
||||
```bash
|
||||
# Status
|
||||
sudo postfix status
|
||||
|
||||
# Restart etter konfig-endring
|
||||
sudo systemctl restart postfix
|
||||
|
||||
# Se epost-logg
|
||||
sudo tail -f /var/log/mail.log
|
||||
|
||||
# Sjekk kø (bør alltid være tom)
|
||||
sudo postqueue -p
|
||||
```
|
||||
|
||||
### Arkitektur
|
||||
|
||||
```
|
||||
Internett → port 25 → Postfix (smtpd)
|
||||
→ virtual_transport = synops-pipe
|
||||
→ pipe: synops-mail --receive --recipient <mottaker> --sender <avsender>
|
||||
→ (leser rå epost fra stdin, prosesserer, oppretter node)
|
||||
```
|
||||
|
||||
Postfix eier kun transport — all forretningslogikk (brukeroppslag,
|
||||
validering, node-opprettelse) ligger i synops-mail.
|
||||
|
||||
## 14. Verifisering etter oppsett
|
||||
|
||||
### Lag A (minimum fungerende server)
|
||||
- [ ] `https://auth.sidelinja.org` viser Authentik login
|
||||
|
|
|
|||
3
tasks.md
3
tasks.md
|
|
@ -348,8 +348,7 @@ sidelinja.org, vegard.info) ruter til samme bruker basert på username.
|
|||
- [x] 26.1 Username i auth_identities: legg til `username`-kolonne, populer fra Authentik `preferred_username` ved login. Unik constraint. Oppdater auth-callback i SvelteKit til å lagre username.
|
||||
- [x] 26.2 msmtp oppsett: konfigurer utgående epost via SMTP-relay. Avsender: `vaktmester@synops.no`. Tilgjengelig som `synops-mail --send --to <epost> --subject <emne>` CLI-verktøy.
|
||||
- [x] 26.3 MX-records: sett opp MX for synops.no, sidelinja.org, vegard.info som peker til serveren.
|
||||
- [~] 26.4 Postfix minimal: installer Postfix som lokal MTA kun for mottak. Ingen relay, ingen kø for utgående. Pipe innkommende epost til `synops-mail --receive`.
|
||||
> Påbegynt: 2026-03-19T01:26
|
||||
- [x] 26.4 Postfix minimal: installer Postfix som lokal MTA kun for mottak. Ingen relay, ingen kø for utgående. Pipe innkommende epost til `synops-mail --receive`.
|
||||
- [ ] 26.5 `synops-mail --receive`: Rust CLI som leser raw epost fra stdin. Sjekk 1: avsender-epost matcher `auth_identities.email`? Sjekk 2: innhold starter med "Kjære vaktmester" (eller konfigurerbar frase)? Begge må matche. Opprett `content`-node i brukerens innboks med epostinnholdet. Alt annet → /dev/null, ingen bounce.
|
||||
- [ ] 26.6 Domene-alias: `vegard@synops.no`, `vegard@sidelinja.org`, `vegard@vegard.info` ruter alle til samme bruker via username-oppslag i PG. Domenet er irrelevant.
|
||||
- [ ] 26.7 Utgående varsler: vaktmesteren kan sende epost-varsler til brukere (ny oppgave tildelt, innsendt artikkel godkjent, etc.) via `synops-mail --send`. Konfigurerbart per bruker i metadata.preferences.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ eller maskinrommet-API. Ligger i PATH via symlink eller direkte kall.
|
|||
| `synops-node` | Hent/vis en node med edges (UUID, --depth, --format json/md) | Ferdig |
|
||||
| `synops-ai` | LLM-verktøy: `prompt` (direkte LLM-kall) + `script` (orkestreringsscript fra fritekst) | Ferdig |
|
||||
| `synops-clip` | Hent og parse webartikler (Readability + Playwright-fallback, paywall-deteksjon) | Ferdig |
|
||||
| `synops-mail` | Send epost via msmtp (vaktmester@synops.no) | Ferdig (venter SMTP-credentials) |
|
||||
| `synops-mail` | Send epost via msmtp, motta via Postfix pipe (`--send` / `--receive`) | Ferdig |
|
||||
| `synops-notify` | Send varsel via epost, WebSocket-push, eller begge | Ferdig |
|
||||
| `synops-validate` | Valider at en node matcher forventet skjema for sin node_kind | Ferdig |
|
||||
| `synops-backup` | PG-dump + CAS-filiste + metadata-snapshot (`--full` / `--incremental`) | Ferdig |
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
// synops-mail — Send epost via msmtp.
|
||||
// synops-mail — Send og motta epost for Synops.
|
||||
//
|
||||
// Sender epost via systemets msmtp-konfigurasjon.
|
||||
// Avsender er alltid vaktmester@synops.no.
|
||||
// Send-modus: sender epost via msmtp (Brevo SMTP-relay).
|
||||
// Motta-modus: tar imot epost fra Postfix via pipe transport.
|
||||
//
|
||||
// Bruk:
|
||||
// synops-mail --send --to bruker@eksempel.no --subject "Emne"
|
||||
// echo "Brødtekst" | synops-mail --send --to bruker@eksempel.no --subject "Emne"
|
||||
// synops-mail --send --to bruker@eksempel.no --subject "Emne" --body "Tekst her"
|
||||
// cat raw_email.eml | synops-mail --receive --recipient bruker@synops.no --sender avsender@test.no
|
||||
//
|
||||
// Output: Bekreftelse til stdout ved suksess.
|
||||
// Feil: stderr + exit code != 0.
|
||||
|
|
@ -17,7 +18,7 @@
|
|||
//
|
||||
// Ref: docs/retninger/unix_filosofi.md
|
||||
|
||||
use clap::Parser;
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::io::{self, Read};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
|
|
@ -25,43 +26,79 @@ const DEFAULT_FROM: &str = "vaktmester@synops.no";
|
|||
const DEFAULT_FROM_NAME: &str = "Synops Vaktmester";
|
||||
const DEFAULT_CONFIG: &str = "/srv/synops/config/msmtp/msmtprc";
|
||||
|
||||
/// Send epost via msmtp.
|
||||
/// Synops epost-verktøy: send og motta epost.
|
||||
#[derive(Parser)]
|
||||
#[command(name = "synops-mail", about = "Send epost via msmtp")]
|
||||
#[command(name = "synops-mail", about = "Send og motta epost for Synops")]
|
||||
struct Cli {
|
||||
/// Send-modus (påkrevd)
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
|
||||
// --- Legacy flag-basert API (bakoverkompatibel) ---
|
||||
|
||||
/// Send-modus (legacy: bruk `send` subcommand i stedet)
|
||||
#[arg(long)]
|
||||
send: bool,
|
||||
|
||||
/// Mottaker-epostadresse
|
||||
/// Motta-modus (legacy: bruk `receive` subcommand i stedet)
|
||||
#[arg(long)]
|
||||
to: String,
|
||||
receive: bool,
|
||||
|
||||
/// Emne
|
||||
/// Mottaker-epostadresse (--send)
|
||||
#[arg(long)]
|
||||
subject: String,
|
||||
to: Option<String>,
|
||||
|
||||
/// Brødtekst (valgfritt — leses fra stdin om ikke oppgitt)
|
||||
/// Emne (--send)
|
||||
#[arg(long)]
|
||||
subject: Option<String>,
|
||||
|
||||
/// Brødtekst (--send, valgfritt — leses fra stdin om ikke oppgitt)
|
||||
#[arg(long)]
|
||||
body: Option<String>,
|
||||
|
||||
/// Avsender (default: vaktmester@synops.no)
|
||||
/// Avsender (--send default: vaktmester@synops.no, --receive: envelope sender)
|
||||
#[arg(long, default_value = DEFAULT_FROM)]
|
||||
from: String,
|
||||
|
||||
/// msmtp config-fil (default: /srv/synops/config/msmtp/msmtprc)
|
||||
#[arg(long, env = "MSMTP_CONFIG")]
|
||||
config: Option<String>,
|
||||
|
||||
/// Envelope-mottaker fra Postfix (--receive)
|
||||
#[arg(long)]
|
||||
recipient: Option<String>,
|
||||
|
||||
/// Envelope-avsender fra Postfix (--receive)
|
||||
#[arg(long)]
|
||||
sender: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
// Subcommands kan legges til senere
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
if cli.receive {
|
||||
run_receive(&cli);
|
||||
return;
|
||||
}
|
||||
|
||||
if !cli.send {
|
||||
eprintln!("Feil: --send er påkrevd");
|
||||
eprintln!("Feil: --send eller --receive er påkrevd");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let to = cli.to.as_deref().unwrap_or_else(|| {
|
||||
eprintln!("Feil: --to er påkrevd for --send");
|
||||
std::process::exit(1);
|
||||
});
|
||||
let subject = cli.subject.as_deref().unwrap_or_else(|| {
|
||||
eprintln!("Feil: --subject er påkrevd for --send");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
// Les body fra --body eller stdin
|
||||
let body = match cli.body {
|
||||
Some(b) => b,
|
||||
|
|
@ -93,14 +130,14 @@ fn main() {
|
|||
{body}",
|
||||
from_name = DEFAULT_FROM_NAME,
|
||||
from = cli.from,
|
||||
to = cli.to,
|
||||
subject = cli.subject,
|
||||
to = to,
|
||||
subject = subject,
|
||||
body = body,
|
||||
);
|
||||
|
||||
// Send via msmtp
|
||||
let mut child = Command::new("msmtp")
|
||||
.args(["--file", &config_path, "--from", &cli.from, &cli.to])
|
||||
.args(["--file", &config_path, "--from", &cli.from, to])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
|
|
@ -125,10 +162,37 @@ fn main() {
|
|||
});
|
||||
|
||||
if output.status.success() {
|
||||
println!("Epost sendt til {}", cli.to);
|
||||
println!("Epost sendt til {}", to);
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
eprintln!("msmtp feilet (exit {}): {}", output.status, stderr);
|
||||
std::process::exit(output.status.code().unwrap_or(1));
|
||||
}
|
||||
}
|
||||
|
||||
/// Motta epost fra Postfix pipe transport.
|
||||
///
|
||||
/// Leser rå RFC 5322-epost fra stdin og logger mottaket.
|
||||
/// Faktisk prosessering (brukeroppslag, node-opprettelse) implementeres i oppgave 26.5.
|
||||
fn run_receive(cli: &Cli) {
|
||||
let recipient = cli.recipient.as_deref().unwrap_or("(ukjent)");
|
||||
let sender = cli.sender.as_deref().unwrap_or("(ukjent)");
|
||||
|
||||
// Les rå epost fra stdin
|
||||
let mut raw_email = String::new();
|
||||
if let Err(e) = io::stdin().read_to_string(&mut raw_email) {
|
||||
eprintln!("synops-mail receive: feil ved lesing fra stdin: {e}");
|
||||
// Exit 0 slik at Postfix ikke prøver på nytt
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let size = raw_email.len();
|
||||
|
||||
// Logg mottaket (syslog via stderr som Postfix fanger)
|
||||
eprintln!(
|
||||
"synops-mail receive: mottatt epost fra={sender} til={recipient} størrelse={size} bytes"
|
||||
);
|
||||
|
||||
// Stub: aksepter og logg. Oppgave 26.5 implementerer brukeroppslag og node-opprettelse.
|
||||
// Exit 0 = levert OK (Postfix sletter fra kø)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue