ISPConfig 3 issueshttps://git.ispconfig.org/ispconfig/ispconfig3/-/issues2023-12-03T20:53:58Zhttps://git.ispconfig.org/ispconfig/ispconfig3/-/issues/504Proftpd support2023-12-03T20:53:58ZPaoloProftpd supportI'm using ispconfig 3 with proftpd (instead of pure-ftp).
To support it i make the following small hack:
1 - in table ftp_user added two int column nguid and nuid to store numeric uid/gid corresponding to existing uid/gid (user and grou...I'm using ispconfig 3 with proftpd (instead of pure-ftp).
To support it i make the following small hack:
1 - in table ftp_user added two int column nguid and nuid to store numeric uid/gid corresponding to existing uid/gid (user and group names). I set this fields manually when create a new ftp account (but could be done by a script)
2 - in /etc/proftpd.conf make the very simple config:
<IfModule mod_sql.c>
SQLAuthTypes Crypt
SQLAuthenticate users*
SQLConnectInfo db-ispconfig@127.0.0.1 ispconfig <password>
SQLUserInfo ftp_user username password nuid ngid dir '/bin/false'
SQLUserWhereClause "active = 'y' AND server_id = '1'"
#SQLLog PASS login
#SQLLogFile /var/log/proftpd/mod_sql.log
</IfModule>
You could also track connections count, ul/dl traffic, limit ul/dl rate, etc.
IMHO is a very good idea to store nuid/ngid in table web_domain (corresponding to system_user/system_group) when a new web/user is created: is a very usefull info!
This nuid/ngid can then be copied in table ftp_user when a new ftp account is created/edited.Horst FickelHorst Fickelhttps://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6615Add New Prefix Type CUSTOMERNO2023-12-03T16:44:36ZChristopher KaschigAdd New Prefix Type CUSTOMERNO\[This is a feature request but even with following the link 'http://bugtracker.ispconfig.org/index.php?do=newtask&project=3&task_type=2' I am not able to get a non-issue ticke inserted here - sorry\]
I know / assume I could remove the ...\[This is a feature request but even with following the link 'http://bugtracker.ispconfig.org/index.php?do=newtask&project=3&task_type=2' I am not able to get a non-issue ticke inserted here - sorry\]
I know / assume I could remove the prefixes in whole by modifying the FTP user prefix (eg) in the Main Config. But I am a fan of automatisms, as they reduce faulty input.
For this I would like to suggest - in addition to existing 'CLIENTNAME', 'CLIENTID', 'DOMAINID' - a new prefix key 'CUSTOMERNO', which replaces the keyword \[CUSTOMERNO\] by the customer number of the current selected (or user assigned) client.
Following changes work for me, but I would really prefer if this could be adjusted to fit into ISPConfig development best practices:
\[modifiying **/interface/lib/classes/tools_sites.inc.php**\]
\[line 37\]
old:
```plaintext
$keywordlist=array('CLIENTNAME', 'CLIENTID', 'DOMAINID');
```
new:
```plaintext
$keywordlist=array('CLIENTNAME', 'CLIENTID', 'DOMAINID', 'CUSTOMERNO');
```
\[line 40 foreach added case\]
```plaintext
case 'CUSTOMERNO':
$name=str_replace('['.$keyword.']', $this->getCustomerNo($dataRecord), $name);
break;
```
\[new\]
```plaintext
function getCustomerNo($dataRecord) {
global $app, $conf;
$clientId=$this->getClientID($dataRecord);
if ($clientId == '[CLIENTID]') {
return '[CUSTOMERNO]';
} elseif ($clientId == '') {
return 'default';
}
$tmp = $app->db->queryOneRecord("SELECT customer_no FROM client WHERE client_id = ?", $clientId);
$customerNo = $tmp['customer_no'];
if ($customerNo == '') $customerNo = 'default';
$customerNo = $this->convertCustomerNo($customerNo);
return $customerNo;
}
```
\[new - duplicated from function convertClientName for further flexibility, not necessarily needed to be a separate function\]
```plaintext
function convertCustomerNo($customerNo){
$allowed = 'abcdefghijklmnopqrstuvwxyz0123456789_';
$res = '';
$customerNo = strtolower(trim($customerNo));
for ($i=0; $i < strlen($customerNo); $i++){
if ($customerNo[$i] == ' ') continue;
if (strpos($allowed, $customerNo[$i]) !== false){
$res .= $customerNo[$i];
}
else {
$res .= '_';
}
}
return $res;
}
```https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/4400Add support for keys in BIND configuration2023-12-03T16:23:51ZTill BrehmAdd support for keys in BIND configuration1. To use keys is very simple you see the motivation is that as an ISP there is pri/sec servers that you don’t own and need to accommodate. See the key part that is one small entry in each server conf (see below named.conf).
To use th...1. To use keys is very simple you see the motivation is that as an ISP there is pri/sec servers that you don’t own and need to accommodate. See the key part that is one small entry in each server conf (see below named.conf).
To use the key the zonefile in slave looks like this
zone "domain.tld" IN {
type slave;
file "/etc/bind/slave/sec. domain.tld ";
masters {primary server; };
allow-transfer { key key-name; };
};
On the primary side
options {
allow-transfer {
key key-name;
};
key {key-name} {
algorithm hmac-md5;
secret "xxxxxxxxxxxxxxxxxxxxxxx"; < some key
};
2. The second issue is that Most ISP's have secondary hosting of DNS that I could not add using the UI
I added them in a separate file "/etc/bind/named.conf.local.extended" and included it in the named.conf.
You see these domains is "only" secondary/slave DNS and there is no web nor mail on this server.
root@server1:/etc/bind# cat named.conf
key {key-name} {
algorithm hmac-md5;
secret "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="; < some key
};
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.local.extended";
include "/etc/bind/named.conf.default-zones";https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6587Rspamd config overrides for rbl_group.conf & surbl_group.conf2023-12-02T10:25:28ZZakRspamd config overrides for rbl_group.conf & surbl_group.confIn ISPConfig up to 3.2.11 two overrides for rspamd are generated. Namely rbl_group.conf & surbl_group.conf.
These files render the stock "scores" and local configuration useless, since it activly overwrites the whole config from scores.d...In ISPConfig up to 3.2.11 two overrides for rspamd are generated. Namely rbl_group.conf & surbl_group.conf.
These files render the stock "scores" and local configuration useless, since it activly overwrites the whole config from scores.d/rbl_group.conf & scores.d/surbl_group.conf - and even more problematic it ignores the config under local.d. \
I consider this a bug, since it interferes with stock and a possibly present custom configuration as well. \
\
I do see, that a couple of symbols where scored down, a lot of symbols where removed and only three where added (RBL_SPAMHAUS_XBL_ANY, RAMBLER_URIBL & RAMBLER_EMAILBL).
\
I can only guess that this is a remnant from the introduction of rspamd support. \
\
Please remove the generation of the overrides. It's the admins job to get the scores right and customize the spam protection.3.2.12ThomThomhttps://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6609Make website http and https port configurable for Apache servers2023-11-19T17:48:23ZTill BrehmMake website http and https port configurable for Apache serversThe website ports are already configurable for Nginx on the options tab. This request is to port this feature to the Apache plugin too.The website ports are already configurable for Nginx on the options tab. This request is to port this feature to the Apache plugin too.3.2.12https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6608Set account in certbot if multiple accounts are present2023-11-18T12:44:55ZTill BrehmSet account in certbot if multiple accounts are presenthttps://forum.howtoforge.com/threads/playing-with-debian-12-some-issues.91311/page-3https://forum.howtoforge.com/threads/playing-with-debian-12-some-issues.91311/page-33.2.12https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6602Add a post-update hook script2023-11-09T09:40:17ZTill BrehmAdd a post-update hook scriptAdd a script that runs after an ISPConfig update to make customizations easier.Add a script that runs after an ISPConfig update to make customizations easier.3.2.12Till BrehmTill Brehmhttps://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6601Unify, validate and/or robustly parse autoinstall.ini syntax2023-11-09T08:05:27ZJohan EhnbergUnify, validate and/or robustly parse autoinstall.ini syntax## Summary
Currently the syntax of autoinstall.ini varies especially for yes/no statements. This can be seen in the example file: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/docs/autoinstall_samples/autoinstall.ini.sam...## Summary
Currently the syntax of autoinstall.ini varies especially for yes/no statements. This can be seen in the example file: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/docs/autoinstall_samples/autoinstall.ini.sample?ref_type=heads. Currently the value is also not validated.
Looking at the code, it is also likely that syntax mistake failures vary; I stumbled on one mistake that caused a silent hang.
## Example failing case:
```
echo "reconfigure_permissions_in_master_database=n" >> autoinstall.ini
php -q update.php --autoinstall=autoinstall.ini
<hangs forever with php process at 100% CPU>
```
The fix was obviously to instead declare `reconfigure_permissions_in_master_database=no` (note last character) but it took quite long to figure that out.
## Suggested approaches
Any combination of:
- Unify syntax
- Add validator function
- Pre-parse using the common [Yy]* and [Nn]* approachhttps://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6600Support for SVCB and HTTPS DNS types (for HTTP/3)2023-11-04T08:31:40ZTill BrehmSupport for SVCB and HTTPS DNS types (for HTTP/3)Bind9 and PowerDNS are supporting SVCB and HTTPS types in the last years.
- Bind9 -> from 9.16.21 (minimum: Ubuntu Jammy)
- PowerDNS -> from 4.4.x (minimum: Ubuntu Jammy)
Nginx is supporting HTTP/3 in the last mainline versions (will be ...Bind9 and PowerDNS are supporting SVCB and HTTPS types in the last years.
- Bind9 -> from 9.16.21 (minimum: Ubuntu Jammy)
- PowerDNS -> from 4.4.x (minimum: Ubuntu Jammy)
Nginx is supporting HTTP/3 in the last mainline versions (will be default in stable in weeks or months).
https://forum.howtoforge.com/threads/support-for-svcb-and-https-dns-types-for-http-3.91390/https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6598Please add option to disable Traffic Quota2023-11-02T14:55:50ZCollin MachinePlease add option to disable Traffic QuotaIt would be great to have an option in the Server Config under the Web tab to disable the Traffic Quota and related fields, so that all sites can just be "unlimited" - and avoid confusing clients with an unnecessary field containing a ne...It would be great to have an option in the Server Config under the Web tab to disable the Traffic Quota and related fields, so that all sites can just be "unlimited" - and avoid confusing clients with an unnecessary field containing a negative number (as most non-tech-savy individuals would not know this means unlimited). This could probably be placed above the Traffic Quota notification settings. When disabled/unlimited, the notification fields could be hidden/disabled.https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/5226Add option to remove Let's Encrypt certs when site gets deleted.2023-10-27T14:51:22ZTill BrehmAdd option to remove Let's Encrypt certs when site gets deleted.https://www.howtoforge.com/community/threads/removing-site-does-not-remove-it-from-certbot.81143/https://www.howtoforge.com/community/threads/removing-site-does-not-remove-it-from-certbot.81143/3.2.12ThomThomhttps://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6594Feature Request: Option to automatically renew DKIM (for improved plausible d...2023-10-21T18:45:21ZAbacop UGFeature Request: Option to automatically renew DKIM (for improved plausible deniability)after reading an article of the magazine c't (https://www.heise.de/select/ct/2023/24/2325412023268630029 or https://www.heise.de/ratgeber/Kaputt-und-unersetzbar-So-steht-es-um-das-dezentrale-System-E-Mail-9328711.html) I realized that it...after reading an article of the magazine c't (https://www.heise.de/select/ct/2023/24/2325412023268630029 or https://www.heise.de/ratgeber/Kaputt-und-unersetzbar-So-steht-es-um-das-dezentrale-System-E-Mail-9328711.html) I realized that it would be great if ISPConfig would have an option to automatically renew the DKIM-keys after publishing the the old private keys (for example by loading them into a specific directory or by forwarding them them to a script that does the rest.
Since the linked articles are behind a paywall here a quote (german) of the relevant parts that let to this feature request:
> Nicht meine Mail!
>
> Manche Neuerung, die an SMTP angebaut wurde, bringt ungeahnte Nebenwirkungen mit. Eine solche ist eine eher unbekannte Eigenschaft von DKIM, der „Domain Keys Identified Mail“.
>
> [...]
>
> Um das Problem zu erfassen, das diese serverseitige Signatur auslöst, muss man eine Ecke weiterdenken: Der Nutzer hat in diesem Verfahren keinerlei Kontrolle, ob eine Nachricht mit DKIM signiert wird. Sobald er die Mail über den
> Server abschicken lässt, enthält sie einen kryptografischen Beweis, dass sie von einem Server verschickt wurde, den er nutzt. Gerät die Mail später mal an die Öffentlichkeit, ist es für ihn verdammt schwer, glaubhaft abzustreiten, dass er sie geschrieben hat. „Plausible deniability“ nennen Sicherheitsforscher diese wünschenswerte Eigenschaft eines Systems. Denn sobald Mails DKIM-Header enthalten, ist es für Angreifer verdammt attraktiv, Mailpostfächer zu erbeuten und zu veröffentlichen – zum Beispiel von Politikern und Prominenten. Zu verifizieren, dass die Inhalte wirklich von einem Mailserver verschickt wurden und keine plumpen Fälschungen sind, ist dank DKIM leicht. Für solche Fälle hat das Investigativ-Team von Associated Press sogar ein Open-Source-Werkzeug gebaut (siehe ct.de/ybrc). 2020 traf es Hunter Biden, den Sohn des US-Präsidenten Joe Biden, der zusehen musste, wie Experten anhand von DKIM bestätigten, dass geleakte Mails authentisch, weil signiert, sind.
>
> Wie man dieses Problem umgeht?
> Dafür müssten die Betreiber von Mailservern das Problem zunächst mal als Problem anerkennen. Was gegen fehlende Deniability hilft, wäre eine simple Automatik: Wenn die alle paar Monate das Schlüsselpaar automatisch austauscht, einen neuen öffentlichen Schlüssel im DNS hinterlegt und kurz darauf den alten privaten Schlüssel für alle Welt veröffentlicht, ist es vorbei mit dem späteren Echtheitsbeweis. Die Nachricht hätte dann jeder fälschen und signieren können. Die Funktion von DKIM gefährdet das nachträgliche Veröffentlichen indes nicht, weil der Schlüssel nur im Moment des Versands geheim sein muss.https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6583DKIM/SPF/DMARC on Subdomain2023-10-02T11:15:45ZHannesDKIM/SPF/DMARC on Subdomain## Summary
It is not possible to add a DKIM/DMARC for an subdomain in den DNS config.
## Steps to reproduce
1. try to create a DKIM or DMARC for any subdomain like app.testdom.xx
Not every time the the DKIM / DMARC for Domain and...## Summary
It is not possible to add a DKIM/DMARC for an subdomain in den DNS config.
## Steps to reproduce
1. try to create a DKIM or DMARC for any subdomain like app.testdom.xx
Not every time the the DKIM / DMARC for Domain and subdomain are the same. It should be possible to add diffrent DKIM / DMARC for subdomain too.
2. Example the company with domain.xx get a software on app.domain.xx and this software solution has its own mail handling with own dkim system
## Environment
Ubu 22.04 + ISP 3.2.11
```plaintext
```
## Proposed fix
optional, of course.\
if you want to post code snippets, please use
```plaintext
maybe simple remove the checks in interface/web/dns/form/dns_txt.tform.php (my temp solution) and let create the user his custom records.
'data' => array (
'datatype' => 'VARCHAR',
'formtype' => 'TEXT',
'validators' => array (
0 => array (
'type' => 'NOTEMPTY',
'errmsg'=> 'data_error_empty'
),
/* 1 => array (
'type' => 'REGEX',
'regex' => "/^((?!v=DKIM).)*$/s",
'errmsg'=> 'invalid_type_dkim'
),
2 => array (
'type' => 'REGEX',
'regex' => "/^((?!v=DMARC1; ).)*$/s",
'errmsg'=> 'invalid_type_dmarc'),
3 => array (
'type' => 'REGEX',
'regex' => "/^((?!v=spf).)*$/s",
'errmsg'=> 'invalid_type_spf'
),
*/
),
```https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/5470DNS RECORDS - Add DANE Bindings for OpenPGP : OPENPGPKEY (TYPE 61)2023-09-18T17:53:11ZTomasz kapiasDNS RECORDS - Add DANE Bindings for OpenPGP : OPENPGPKEY (TYPE 61)Now that DNSSEC is implemented in a functional way, I would like to use a very promising new feature for OpenPGP public key exchange.
This is a new type of DNS record TYPE (TYPE number 61) : OPENPGPKEY.
The RFC is here: [RFC7929](https...Now that DNSSEC is implemented in a functional way, I would like to use a very promising new feature for OpenPGP public key exchange.
This is a new type of DNS record TYPE (TYPE number 61) : OPENPGPKEY.
The RFC is here: [RFC7929](https://tools.ietf.org/html/rfc7929)
An article on its use is here: [pgp-key-distribution-via-dnssec-openpgpkey](https://blog.webernetz.net/pgp-key-distribution-via-dnssec-openpgpkey/)
I am not in a position to help write the modification and I hope that my request will attract your interest.
Thank you.https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6571Bind9 security improvement2023-09-16T14:51:19ZBruno MeirellesBind9 security improvementIf possible, add these 3 lines to the named.conf.options template:
allow-recursion { 127.0.0.1; ::1; fe80::/10; };
allow-query-cache { 127.0.0.1; ::1; fe80::/10; };
rate-limit { responses-per-second 15; window 5; };
The first and secon...If possible, add these 3 lines to the named.conf.options template:
allow-recursion { 127.0.0.1; ::1; fe80::/10; };
allow-query-cache { 127.0.0.1; ::1; fe80::/10; };
rate-limit { responses-per-second 15; window 5; };
The first and second lines limits recursive queries to the server itself. Without this line, anyone can use dns to browse the internet if they configure the ip on the device.
The third line, Serves as a mitigation tool for the problem of DNS amplification attacks (https://kb.isc.org/docs/aa-00994)https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6531Handle wildcard aliases2023-09-16T14:49:44ZBaptiste RichardHandle wildcard aliases# What is a wildcard alias ?
A wildcard alias is an alias containing an asterisk `*` as a placeholder for "anything unspecified". For example, if I have an alias `gitlab*@example.org` pointing to `john@example.org`, any incoming email m...# What is a wildcard alias ?
A wildcard alias is an alias containing an asterisk `*` as a placeholder for "anything unspecified". For example, if I have an alias `gitlab*@example.org` pointing to `john@example.org`, any incoming email matching this pattern will be redirected to john, such as `gitlabispconfig@example.org` or `gitlab-support@example.org`
Obiviously, wildcard aliases should not intercept direct aliases nor inbox emails, and catchall should still capture anything that does not match any (wildcard or not) alias.
Apart from this, wildcard aliases allows to give a unique email to each service you suscribe to in order to either filter incoming mail easily based on the `From:` address, or, when you start receiving spam, know who the hell sold your address (and denylist this specific address)
# How is it different from + aliasing ?
Using `+` as a separator has some issues :
- Some systems (website or otherwise) still don't recognize the `+` character as valid in an email, so bye bye filtering.
- Some systems (I encountered at least 1 so far) allow the `+` in the submission process but removes it entirely. So bye bye filtering (again)
Wildcard aliases can use only "regular" characters. In fact, there is no way to know if a given adress is an alias or not, wether this alias is wildcarding or not.
# What should be done in ISPconfig to make this available ?
From the webUI we can submit wildcard aliases (aliases having an asterisk * in part of the name) but they don't work as intended (not at all actually).
Having them to work is only a matter of editing the `/etc/postfix/mysql-virtual_forwardings.cf` file to lookup for wildcard characters.
I've already done the development require and will push a MR with this.https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6538Enable DKIM when generating a Private-key2023-09-16T14:49:32ZHelmoEnable DKIM when generating a Private-keyWhen I click on 'Generate DKIM Private-key' I would expect the 'enable DKIM' checkbox to be also checked.
![image](/uploads/ea53e0d4a68d1532811b3ab25d793275/image.png)When I click on 'Generate DKIM Private-key' I would expect the 'enable DKIM' checkbox to be also checked.
![image](/uploads/ea53e0d4a68d1532811b3ab25d793275/image.png)https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6537Initialize SPF record with the zone name.2023-09-16T14:49:23ZHelmoInitialize SPF record with the zone name.The first field of the spf record form currently defaults to empty.
It's technically ok to leave it blank as '@' '' and '<zonename>.' are effectively the same.
However in our [default dns template](https://git.ispconfig.org/ispconfig/is...The first field of the spf record form currently defaults to empty.
It's technically ok to leave it blank as '@' '' and '<zonename>.' are effectively the same.
However in our [default dns template](https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/install/sql/ispconfig3.sql#L2467) example we suggest to put the zonename for all dns_rr's in the Name column.
Lets also apply that to spf.
![image](/uploads/771cf0ff0fc55808a8e18c28283adc5b/image.png)
And lets extend the check for existing records to detect this variation.HelmoHelmohttps://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6154Extra single quote when creating wildcart certs in SSL tab2023-09-16T14:46:24ZHj Ahmad Rasyid Hj IsmailExtra single quote when creating wildcart certs in SSL tab## Summary
Wildcard subdomain created certs has single quotes in uts filename instead of not having it.
## Steps to reproduce
1. Go to Sites tab
1. Click on any website e.g. domain.tld
1. Select its SSL tab
1. Select \*.domain.tld
1. ...## Summary
Wildcard subdomain created certs has single quotes in uts filename instead of not having it.
## Steps to reproduce
1. Go to Sites tab
1. Click on any website e.g. domain.tld
1. Select its SSL tab
1. Select \*.domain.tld
1. Create SSL
1. Certs created in ssl folder but with single quote in its file name e.g. '\*.domain.tld.ext'
## Correct behaviour
The files' name should just be \*.domain.tld.ext (without any quotes) instead of '\*.domain.tld.ext' (with single quotes)
## Environment
Server OS + version: Ubuntu 20.04 ISPConfig version: 3.2.4https://git.ispconfig.org/ispconfig/ispconfig3/-/issues/6578[feature request] Set custom theme via main config2023-09-16T13:06:48ZNathaniel Mitchell[feature request] Set custom theme via main configAs discussed in https://forum.howtoforge.com/threads/themes-for-ispconfig.91148/#post-449554, it would be good to set a system wide theme that applies to the login page as well. This option would use the database to reference the theme, ...As discussed in https://forum.howtoforge.com/threads/themes-for-ispconfig.91148/#post-449554, it would be good to set a system wide theme that applies to the login page as well. This option would use the database to reference the theme, so allowing for upgrades to occur without needing to modify local php files for each upgrade or trying to remember to modify the right one.
Possible staged deployment
Stage 1
* Configure ISPConfig's base DB to store the site theme
* Set default to be the standard ISPConfig theme
* Allow configuration change from SYSTEM --\> INTERFACE --\> MAIN CONFIG
* If the setting is missing (e.g. upgrade) create the required objects in the DB and default to the ISPconfig theme
* Fix the current CSS and JS scripts to be either independant of Bootstrap / jQuery / etc. OR upgrade them to a more current version
* This item is related to me writing a new theme and using the latest versions of Bootstrap & jQuery.
Stage 2
* Allow for per-user / per-reseller theme settings (e.g. Light mode VS Dark mode)
Stage 3
* Write some documentation on how to build a theme from scratch