Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ go.work.local
models/
# Purged from git history. Do not re-add.
docs/crm-associations-proof.md

# mount scaffold for the 8TB volume, never part of the repo
mnt/

# go build ./bin/mail/sync.go drops a binary named `sync` in cwd
/sync
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ RUN python -m pip install --no-cache-dir -r /tmp/requirements.lock.txt \
COPY . .
RUN chmod +x /app/bin/docker-entrypoint \
&& chown -R 2dph:2dph /app
COPY --from=mail-build /mail-sync /app/bin/mail-sync
USER 2dph

ENV PATH="/app/bin:${PATH}" \
Expand All @@ -37,6 +38,14 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD python -c "import model2vec, ladybug, mistune; print('ok')" || exit 1
ENTRYPOINT ["/app/bin/docker-entrypoint"]

# --- mail-sync: standalone M365/OnlyOffice/Gmail puller (pure Go, no CGO) ---
FROM golang:1.26-bookworm AS mail-build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY bin/mail ./bin/mail
RUN CGO_ENABLED=0 go build -o /mail-sync ./bin/mail/sync.go

# --- Go API: CGO with Zig, not gcc ---
FROM golang:1.26-bookworm AS api-build
WORKDIR /src
Expand Down
20 changes: 20 additions & 0 deletions bin/docker-entrypoint
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# serve | search | watch
# Index image (Python write path, compose profile `index`):
# index | extract | audit | search (deprecated python wrapper)
# mail-sync [N] pull loop: sync -> import -> index every N s (default 10)
#
# Usage comment starts at line 2 (self-describing convention).
set -euo pipefail
Expand Down Expand Up @@ -34,5 +35,24 @@ case "$CMD" in
serve) exec /app/bin/serve "$@" ;;
extract) exec "$KB_PY" /app/bin/facts/extract "$@" ;;
audit) exec "$KB_PY" /app/bin/facts/audit "$@" ;;
mail-sync)
# loop: pull mail (M365/OnlyOffice/Gmail) every N s, convert to md,
# rebuild the brain index. Index only when something new arrived.
interval="${1:-10}"
[ "$interval" -gt 0 ] 2>/dev/null || interval=10
: "${MAIL_SYNC_ENV:=/secret/m365.env}"
: "${MAIL_SYNC_SRC:=m365}"
: "${MAIL_SYNC_OUT:=/app/var/mail}"
while true; do
out="$("/app/bin/mail-sync" --source "$MAIL_SYNC_SRC" --env "$MAIL_SYNC_ENV" --out "$MAIL_SYNC_OUT" 2>&1)"
echo "$out"
new="$(printf '%s\n' "$out" | sed -n 's/.*new=\([0-9]*\).*/\1/p' | tail -1)"
if [ -n "$new" ] && [ "$new" -gt 0 ] 2>/dev/null; then
"$KB_PY" /app/bin/mail/import --from-raw "$MAIL_SYNC_OUT" 2>&1 | tail -1
"$KB_PY" /app/bin/kb/index --rebuild --with-mail 2>&1 | tail -1
fi
sleep "$interval"
done
;;
*) echo "unknown command: $CMD" >&2; exit 2 ;;
esac
5 changes: 3 additions & 2 deletions bin/mail/sync.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//usr/bin/env go run "$0" "$@"; exit
// bin/mail/sync.go - async download of OnlyOffice and Gmail mail to var/mail/.
// bin/mail/sync.go - async download of OnlyOffice, Gmail and M365 mail to var/mail/.
//
// ./bin/mail/sync.go --source onlyoffice,gmail --limit 50 --workers 8
// ./bin/mail/sync.go --source onlyoffice,gmail,m365 --limit 50 --workers 8
// ./bin/mail/sync.go --source gmail --force
// ./bin/mail/sync.go --source m365 --env .secrets/m365.env
// ./bin/mail/sync.go --dry-run
//
// Writes raw message.json + attachments under var/mail/<folder>/<id>/; run
Expand Down
25 changes: 22 additions & 3 deletions bin/mail/sync/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func bind(v *flagVals) *flaggy.Parser {
p.Bool(&v.force, "", "force", "overwrite existing message.json")
p.Bool(&v.dryRun, "", "dry-run", "list counts without writing")
p.String(&v.query, "", "query", "Gmail search query")
p.String(&v.srcs, "", "source", "comma list: onlyoffice,gmail")
p.String(&v.srcs, "", "source", "comma list: onlyoffice,gmail,m365")
return p
}

Expand Down Expand Up @@ -110,6 +110,24 @@ func ParseCLI(args []string) (CLIConfig, int, error) {
CredentialsPath: filepath.Join(home, ".gmail-mcp", "credentials.json"),
KeysPath: filepath.Join(home, ".gmail-mcp", "gcp-oauth.keys.json"),
}
case "m365":
tenant := pick(envVars["M365_TENANT"], envVars["MS_TENANT"])
cid := pick(envVars["M365_CLIENT_ID"], envVars["MS_CLIENT_ID"])
sec := pick(envVars["M365_CLIENT_SECRET"], envVars["MS_CLIENT_SECRET"])
users := pick(envVars["M365_USERS"], envVars["MS_USERS"])
if tenant == "" || cid == "" || sec == "" || users == "" {
return CLIConfig{}, 2, fmt.Errorf("m365 source needs M365_TENANT/CLIENT_ID/CLIENT_SECRET/USERS in %s", v.env)
}
var userList []string
for _, u := range strings.Split(users, ",") {
if u = strings.TrimSpace(u); u != "" {
userList = append(userList, u)
}
}
if len(userList) == 0 {
return CLIConfig{}, 2, fmt.Errorf("m365 source: M365_USERS empty")
}
cfg.M365 = &M365Credentials{Tenant: tenant, ClientID: cid, ClientSecret: sec, Users: userList}
default:
return CLIConfig{}, 2, fmt.Errorf("unknown source %q", s)
}
Expand All @@ -126,7 +144,7 @@ func Main(args []string) int {
return code
}
if cfg.Help {
fmt.Fprintln(os.Stderr, "usage: bin/mail/sync.go [--source onlyoffice,gmail] [--query GMAIL_Q] [--limit N] [--offset N] [--workers N] [--force] [--dry-run]")
fmt.Fprintln(os.Stderr, "usage: bin/mail/sync.go [--source onlyoffice,gmail,m365] [--query GMAIL_Q] [--limit N] [--offset N] [--workers N] [--force] [--dry-run]")
return 0
}
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Hour)
Expand Down Expand Up @@ -169,7 +187,8 @@ func readEnv(path string) map[string]string {
if !ok {
continue
}
if strings.HasPrefix(k, "ONLYOFFICE_") || strings.HasPrefix(k, "OO_") {
if strings.HasPrefix(k, "ONLYOFFICE_") || strings.HasPrefix(k, "OO_") ||
strings.HasPrefix(k, "M365_") || strings.HasPrefix(k, "MS_") {
out[k] = v
}
}
Expand Down
Loading
Loading