259 lines
10 KiB
Text
259 lines
10 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 title="Run this on your server"
|
||
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) |
|
||
|
||
<Aside type="note" title="About passwords">
|
||
|
||
For now, create these database users with a placeholder password such as `MAILADMIN-PASSWORD-HERE`. Leave them as they
|
||
are—it will make copying and pasting the upcoming steps much easier. After you’ve confirmed everything is working
|
||
correctly, I’ll show you how to update the passwords securely with a single command.
|
||
|
||
</Aside>
|
||
|
||
Create the database users:
|
||
|
||
```sql
|
||
grant all privileges on mailserver.* to 'mailadmin'@'localhost' identified by 'MAILADMIN-PASSWORD-HERE';
|
||
grant select on mailserver.* to 'mailserver'@'127.0.0.1' identified by 'MAILSERVER-PASSWORD-HERE';
|
||
```
|
||
|
||
<Aside type="tip" title="127.0.0.1 versus localhost">
|
||
|
||
Wait a minute. Why is there “127.0.0.1" instead of “localhost" in the second SQL command? Is that a typo? No, it’s not.
|
||
Well, in network terminology those two are identical. But MariaDB distinguishes between the two. If you initiate a
|
||
database connection to "localhost" then you talk to the socket file which lives at /var/run/mysqld/mysqld.sock on your
|
||
server. But if you connect to “127.0.0.1" it will create a network connection talking to the TCP socket on port 3306 on
|
||
your server. The difference is that any process on your server can talk to 127.0.0.1. But the socket file has certain
|
||
user/group/other permissions just like any other file on your file system. Postfix will be restricted to its
|
||
/var/spool/postfix directory and cannot by default access that socket file. So by using 127.0.0.1 we circumvent that
|
||
limitation.
|
||
|
||
</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`),
|
||
UNIQUE KEY (`name`)
|
||
);
|
||
|
||
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';
|
||
```
|
||
|
||
TODO: move to final step
|