It does not really add much value and makes things more complicated during migration. Plus the table contains passwords so it's more than just the mailboxes.
250 lines
9.1 KiB
Text
250 lines
9.1 KiB
Text
---
|
||
title: Database setup
|
||
lastUpdated: 2025-08-14
|
||
slug: ispmail-trixie/database
|
||
sidebar:
|
||
order: 155
|
||
---
|
||
|
||
import { Aside } from "@astrojs/starlight/components";
|
||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||
|
||
Now it’s time to prepare the MariaDB database that stores the information that controls your mail server. In the process
|
||
you will have to enter [SQL](http://en.wikipedia.org/wiki/SQL) queries – the language of relational database servers.
|
||
You may enter them in a terminal window using the ‘mariadb’ command. But if you are less experienced with SQL you may
|
||
prefer using a web interface. That’s what you installed _[Adminer](https://www.adminer.org/)_ for. TODO: link to Adminer
|
||
setup in a later section
|
||
|
||
<Aside title="MariaDB versus MySQL">
|
||
|
||
We are using MariaDB here. However, you’ll still see mysql mentioned in places – for example in the postfix-mysql
|
||
package. The reason goes back to 2009, when Oracle acquired MySQL. Concerned about Oracle’s stewardship, the original
|
||
MySQL developers forked the project and created MariaDB. To remain fully compatible, they kept providing a
|
||
libmysqlclient library with the same name and API as MySQL’s client library. That compatibility layer ensured a smooth
|
||
transition, but it also meant the mysql name stuck around — so you’ll still see it appear from time to time, even when
|
||
you’re actually using MariaDB.
|
||
|
||
</Aside>
|
||
|
||
## Create the ‘mailserver’ database
|
||
|
||
This step is simple. Connect to the database using the `mariadb` command in your shell:
|
||
|
||
```sh
|
||
mariadb
|
||
```
|
||
|
||
You should see the MariaDB prompt that allows you to enter further SQL commands:
|
||
|
||
```
|
||
MariaDB [(none)]>
|
||
```
|
||
|
||
To create the new database, send this SQL command:
|
||
|
||
```sql
|
||
CREATE DATABASE mailserver;
|
||
```
|
||
|
||
## Create the database users
|
||
|
||
In this section you will create the basic database `mailserver` and two users:
|
||
|
||
| User | Permissions | Purpose |
|
||
| :--------- | :---------- | :--------------------------------------------------------- |
|
||
| mailserver | read | Used by Postfix/Dovecot |
|
||
| mailadmin | read/write | Used by you <br/> Used by Roundcube (for password changes) |
|
||
|
||
Use the _pwgen_ tool to create two random passwords for these users:
|
||
|
||
```sh
|
||
pwgen -s1 30 2
|
||
```
|
||
|
||
Take a note of the passwords or store them somewhere safe.
|
||
|
||
Create the database users:
|
||
|
||
```sql
|
||
grant all privileges on mailserver.* to 'mailadmin'@'localhost' identified by 'FIRST-PASSWORD-HERE';
|
||
grant select on mailserver.* to 'mailserver'@'localhost' identified by 'SECOND-PASSWORD-HERE';
|
||
```
|
||
|
||
<Aside type="note" title="Changed since Debian Bookworm">
|
||
|
||
In the past, we used 127.0.0.1 instead of localhost because Postfix was running in a chroot environment. Starting with
|
||
Debian Trixie, we’ve disabled chroot mode for Postfix, which means we can now use just `localhost`.
|
||
|
||
</Aside>
|
||
|
||
## Creating the database tables
|
||
|
||
By now you have an empty database and two user accounts to access it. Now you need three tables:
|
||
|
||
- virtual_domains
|
||
- virtual_aliases
|
||
- virtual_users
|
||
|
||
Let's create the entire database schema in one go:
|
||
|
||
{/* <!-- prettier-ignore-start --> */}
|
||
|
||
```sql
|
||
USE mailserver;
|
||
|
||
CREATE TABLE IF NOT EXISTS `virtual_domains` (
|
||
`id` int(11) NOT NULL auto_increment,
|
||
`name` varchar(50) NOT NULL,
|
||
PRIMARY KEY (`id`)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS `virtual_aliases` (
|
||
`id` int(11) NOT NULL auto_increment,
|
||
`domain_id` int(11) NOT NULL,
|
||
`source` varchar(100) NOT NULL,
|
||
`destination` varchar(100) NOT NULL,
|
||
PRIMARY KEY (`id`),
|
||
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS `virtual_users` (
|
||
`id` int(11) NOT NULL auto_increment,
|
||
`domain_id` int(11) NOT NULL,
|
||
`email` varchar(100) NOT NULL,
|
||
`password` varchar(150) NOT NULL,
|
||
`quota` bigint(11) NOT NULL DEFAULT 0,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `email` (`email`),
|
||
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
|
||
);
|
||
```
|
||
|
||
{/* <!-- prettier-ignore-end --> */}
|
||
|
||
### virtual_domains
|
||
|
||
The first table keeps the list of domains that your mail server will be responsible for. Each row in this table
|
||
represents a domain.
|
||
|
||
- **id**: A unique number identifying each row. It is set automatically.
|
||
- **name**: Domain name.
|
||
|
||
<Aside type="tip" icon="right-caret" title="Example">
|
||
|
||
| id | name |
|
||
| :-- | :---------- |
|
||
| 12 | example.org |
|
||
| 15 | example.com |
|
||
|
||
</Aside>
|
||
|
||
### virtual_users
|
||
|
||
The second table contains information about your users. Each mail account requires one row in this table.
|
||
|
||
- **id**: A unique number identifying each row. It is set automatically.
|
||
- **domain_id**: A [reference](https://en.wikipedia.org/wiki/Foreign_key) to the domain in the `virtual_domains` table.
|
||
So each domain can have many mailboxes.
|
||
- **email**: The email address of the mailbox.
|
||
- **password**: The hashed password of the mail account. It is prepended by the
|
||
[password scheme](https://doc.dovecot.org/main/core/config/auth/schemes.html). By default it is `{BLF-CRYPT}` also
|
||
known as _bcrypt_ which is considered very secure. Older ISPmail guides used `{SHA256-CRYPT}` or even older crypt
|
||
schemes. Prepending the password field the hashing algorithm in curly brackets allows you to have different kinds of
|
||
hashes. So you can easily migrate your old passwords without locking out users. Users with older schemes should get a
|
||
new password if possible to increase security.
|
||
- **quota**: The number of bytes that this mailbox can store. You can use this value to limit how much space a mailbox
|
||
can take up. The default value is 0 which means that there is no limit. This is an optional feature that is discussed
|
||
later. TODO: link
|
||
|
||
<Aside type="tip" icon="right-caret" title="Example">
|
||
|
||
| id | domain_id | email | password | quota |
|
||
| :-- | :-------- | :------------------ | :------------- | :---- |
|
||
| 8 | 12 | `joe@example.org` | `{BLF-CRYPT}…` | 0 |
|
||
| 9 | 12 | `tom@example.org` | `{BLF-CRYPT}…` | 0 |
|
||
| 11 | 15 | `boris@example.com` | `{BLF-CRYPT}…` | 0 |
|
||
|
||
</Aside>
|
||
|
||
### virtual_aliases
|
||
|
||
The third table contain optional forwardings from one email address to another – or several others.
|
||
|
||
- **id**: A unique number identifying each row. It is set automatically.
|
||
- **domain_id**: References the domain in the `virtual_domains` table.
|
||
- **source**: The alias email address.
|
||
- **destination**: The email address where this alias gets forwarded to.
|
||
|
||
<Aside type="tip" icon="right-caret" title="Example">
|
||
|
||
| id | domain_id | source | destination |
|
||
| :-- | :-------- | :-------------------------- | :------------------------ |
|
||
| 1 | 12 | `info@example.org` | `boris@example.com` |
|
||
| 2 | 12 | `info@example.org` | `tom@example.org` |
|
||
| 3 | 15 | `info@example.com` | `info@example.com` |
|
||
| 4 | 15 | `boris-at-home@example.com` | `bevans@external.address` |
|
||
| 5 | 15 | `@example.com` | `joe@example.org` |
|
||
|
||
This means:
|
||
|
||
- (1+2+3) Email for `info@example.org` is redirected to both `boris@example.com` and `tom@example.org`. As
|
||
`info@example.com` is also listed as a destination address, it will keep a copy of the email. Multiple rows for a
|
||
source are totally fine – Postfix will consider all matching rows.
|
||
- (4) Email for `boris-at-home@example.com` is redirected to `bevans@external.address`.
|
||
- (5) Email for any other address in the `example.com` domain is redirected to `joe@example.com`. TODO: link to catchall
|
||
section
|
||
|
||
</Aside>
|
||
|
||
## Example data to play with
|
||
|
||
Too much theory so far? I can imagine. Let’s populate the database with an `example.org` domain, a `john@example.org`
|
||
email account and a forwarding of `jack@example.org` to `john@example.org`. We will use that information in the next
|
||
chapter to play with.
|
||
|
||
To add that sample data just run these SQL queries:
|
||
|
||
{/* <!-- prettier-ignore-start --> */}
|
||
|
||
```sql
|
||
REPLACE INTO virtual_domains
|
||
(id, name)
|
||
VALUES (10,'example.org');
|
||
|
||
REPLACE INTO virtual_users
|
||
(id, domain_id, password, email)
|
||
VALUES (
|
||
1,
|
||
10,
|
||
'{BLF-CRYPT}$2y$05$.WedBCNZiwxY1CG3aleIleu6lYjup2CIg0BP4M4YCZsO204Czz07W',
|
||
'john@example.org');
|
||
|
||
REPLACE INTO virtual_aliases
|
||
(id, domain_id, source, destination)
|
||
VALUES (
|
||
1,
|
||
10,
|
||
'jack@example.org',
|
||
'john@example.org');
|
||
```
|
||
|
||
{/* <!-- prettier-ignore-end --> */}
|
||
|
||
Do you wonder how I got the long cryptic password? I ran…
|
||
|
||
```
|
||
doveadm pw -s BLF-CRYPT
|
||
```
|
||
|
||
…to create a secure hash of the simple password “summersun”. Once you have installed Dovecot you can try that yourself
|
||
but you will get a different output. The reason is that the passwords are
|
||
[salted](<https://en.wikipedia.org/wiki/Salt_(cryptography)>). Every time you will get a different hash. That prevents
|
||
reverse-engineering the original password.
|
||
|
||
Remember to remove that sample data before you go live with your mail server. Thanks to the _delete cascade_ you just
|
||
need to remove the virtual_domain. The alias and the mailbox will be deleted automatically. This would be the SQL query
|
||
you should run before taking your mail server into production:
|
||
|
||
```sql
|
||
DELETE FROM mailserver.virtual_domains WHERE name='example.org';
|
||
```
|