This commit is contained in:
jason
2026-03-13 13:39:18 -05:00
parent 5263353fe1
commit ef5c2fb379
6 changed files with 117 additions and 6 deletions

73
INSTALL.md Normal file
View File

@@ -0,0 +1,73 @@
# Unraid Installation Guide - Email Signature Manager
Follow these steps to deploy the Email Signature Manager on your Unraid server using a local build and the Unraid Docker GUI.
## Step 1: Clone the Repository
SSH into your Unraid server (or use the Terminal in the WebUI) and run:
```bash
cd /mnt/user/appdata
git clone https://github.com/JasonStedwell/email-sig-manager.git email-sigs
cd email-sigs
```
## Step 2: Add your Google Service Account Key
Place your `sa.json` file (acquired from Google Cloud Console) into the `secrets` folder.
```bash
# Example if copying via SMB from Windows:
# Copy sa.json to \\UNRAID-IP\appdata\email-sigs\secrets\sa.json
```
## Step 3: Build the Image Locally
In the terminal, run the build command to create your local Docker image:
```bash
cd /mnt/user/appdata/email-sigs
docker build -t email-sigs:latest .
```
## Step 4: Configure the Container in Unraid GUI
Now, go to the **Docker** tab in your Unraid WebUI and click **Add Container** at the bottom. Fill out the fields exactly as follows:
- **Name:** `email-sig-manager`
- **Repository:** `email-sigs:latest`
- **Network Type:** `Bridge`
- **WebUI Port:** `3000`
### Add Path Mappings
Click **+ Add another Path, Port, Variable, Label or Device** for each of these:
1. **Secrets Path**
- **Name:** `Secrets`
- **Container Path:** `/app/secrets`
- **Host Path:** `/mnt/user/appdata/email-sigs/secrets`
- **Access Mode:** `Read Only`
2. **Data Path**
- **Name:** `Data`
- **Container Path:** `/app/data`
- **Host Path:** `/mnt/user/appdata/email-sigs/data`
- **Access Mode:** `Read/Write`
### Add Variables
Click **+ Add another Path, Port, Variable, Label or Device** for each of these:
1. **Admin Email**
- **Key:** `GOOGLE_ADMIN_EMAIL`
- **Value:** *(your Workspace admin email, e.g., jason@messagepoint.tv)*
2. **Admin Username**
- **Key:** `ADMIN_USERNAME`
- **Value:** `admin`
3. **Admin Password**
- **Key:** `ADMIN_PASSWORD`
- **Value:** *(your chosen password)*
4. **Cron Schedule** (Optional)
- **Key:** `CRON_SCHEDULE`
- **Value:** `0 2 * * *`
## Step 5: Click Apply
Once all fields are filled, click **Apply**. Unraid will start the container using your locally built image. You can then access the UI at `http://UNRAID-IP:3000`.

View File

@@ -2,6 +2,7 @@
A self-hosted, Dockerized Google Workspace email signature manager. A self-hosted, Dockerized Google Workspace email signature manager.
Runs as a single container — designed for Unraid but works on any Docker host. Runs as a single container — designed for Unraid but works on any Docker host.
See [INSTALL.md](INSTALL.md) for Unraid-specific instructions.
## Features ## Features

34
email-sig-manager.xml Normal file
View File

@@ -0,0 +1,34 @@
<?xml version="1.0"?>
<Container version="2.0">
<Name>Email Signature Manager</Name>
<Repository>email-sigs:latest</Repository>
<Registry/>
<Network>bridge</Network>
<MyIP/>
<Shell>sh</Shell>
<Privileged>false</Privileged>
<Support>https://github.com/JasonStedwell/email-sig-manager/issues</Support>
<Project>https://github.com/JasonStedwell/email-sig-manager</Project>
<Overview>A self-hosted, Dockerized Google Workspace email signature manager. Pulls user data from Google Workspace and pushes signatures directly to Gmail.</Overview>
<Category>Tools: Productivity</Category>
<WebUI>http://[IP]:[PORT:3000]</WebUI>
<TemplateURL/>
<Icon>https://raw.githubusercontent.com/google/material-design-icons/master/png/communication/email/materialicons/48dp/1x/baseline_email_black_48dp.png</Icon>
<ExtraParams/>
<PostArgs/>
<CPUset/>
<DateInstalled/>
<DonateText/>
<DonateLink/>
<Description/>
<Config Name="Web Port" Target="3000" Default="3000" Mode="tcp" Description="The port for the Web UI." Type="Port" Display="always" Required="true" Mask="false">3000</Config>
<Config Name="Secrets Path" Target="/app/secrets" Default="/mnt/user/appdata/email-sigs/secrets" Mode="ro" Description="Path to service account JSON (sa.json)." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/email-sigs/secrets</Config>
<Config Name="Data Path" Target="/app/data" Default="/mnt/user/appdata/email-sigs/data" Mode="rw" Description="Path for SQLite database." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/email-sigs/data</Config>
<Config Name="Assets Path" Target="/app/public/assets" Default="/mnt/user/appdata/email-sigs/public/assets" Mode="rw" Description="Path for local assets (optional)." Type="Path" Display="always" Required="false" Mask="false">/mnt/user/appdata/email-sigs/public/assets</Config>
<Config Name="Google Admin Email" Target="GOOGLE_ADMIN_EMAIL" Default="" Description="Workspace admin email (e.g. jason@messagepoint.tv)" Type="Variable" Display="always" Required="true" Mask="false"/>
<Config Name="Google Customer ID" Target="GOOGLE_CUSTOMER_ID" Default="my_customer" Description="Use 'my_customer' for primary domain." Type="Variable" Display="always" Required="false" Mask="false">my_customer</Config>
<Config Name="Admin Username" Target="ADMIN_USERNAME" Default="admin" Description="Web UI login username." Type="Variable" Display="always" Required="true" Mask="false">admin</Config>
<Config Name="Admin Password" Target="ADMIN_PASSWORD" Default="" Description="Web UI login password." Type="Variable" Display="always" Required="true" Mask="true"/>
<Config Name="Cron Schedule" Target="CRON_SCHEDULE" Default="0 2 * * *" Description="Cron expression for nightly push." Type="Variable" Display="always" Required="false" Mask="false">0 2 * * *</Config>
<Config Name="Node Environment" Target="NODE_ENV" Default="production" Description="Set to 'production'." Type="Variable" Display="advanced" Required="false" Mask="false">production</Config>
</Container>

View File

@@ -38,9 +38,13 @@ router.post('/preview', (req, res) => {
const { templateHtml, userData } = req.body; const { templateHtml, userData } = req.body;
try { try {
const Handlebars = require('handlebars'); const Handlebars = require('handlebars');
Handlebars.registerHelper('if_val', function(val, options) { // Helper is registered globally when renderer.js is loaded, but
return val && val.trim() !== '' ? options.fn(this) : options.inverse(this); // since this is a separate require, we'll ensure it's there.
}); if (!Handlebars.helpers.if_val) {
Handlebars.registerHelper('if_val', function(val, options) {
return val && val.trim() !== '' ? options.fn(this) : options.inverse(this);
});
}
const template = Handlebars.compile(templateHtml); const template = Handlebars.compile(templateHtml);
const rendered = template(userData || { const rendered = template(userData || {
fullName: 'Jason Stedwell', fullName: 'Jason Stedwell',

View File

@@ -5,7 +5,8 @@ async function pushSignatureToUser(userEmail, signatureHtml) {
const auth = getAuthClient(userEmail); const auth = getAuthClient(userEmail);
const gmail = google.gmail({ version: 'v1', auth }); const gmail = google.gmail({ version: 'v1', auth });
const sendAsRes = await gmail.users.settings.sendAs.list({ userId: 'me' }); const sendAsRes = await gmail.users.settings.sendAs.list({ userId: 'me' });
const primary = sendAsRes.data.sendAs.find(s => s.isPrimary); const sendAsList = sendAsRes.data.sendAs || [];
const primary = sendAsList.find(s => s.isPrimary);
if (!primary) throw new Error(`No primary sendAs found for ${userEmail}`); if (!primary) throw new Error(`No primary sendAs found for ${userEmail}`);
await gmail.users.settings.sendAs.patch({ await gmail.users.settings.sendAs.patch({
userId: 'me', userId: 'me',

View File

@@ -1,5 +1,3 @@
const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path'); const path = require('path');
Handlebars.registerHelper('if_val', function(val, options) { Handlebars.registerHelper('if_val', function(val, options) {