Niver > ServNest
This commit is contained in:
parent
3b97b3cc2f
commit
b2bfbb7bf8
|
@ -1,2 +1,2 @@
|
||||||
/db/niver.db
|
/db/servnest.db
|
||||||
/locales/*/C/LC_MESSAGES/messages.mo
|
/locales/*/C/LC_MESSAGES/messages.mo
|
||||||
|
|
|
@ -14,7 +14,7 @@ Allowed server names. Used to make the authentication tokens specific to the ser
|
||||||
|
|
||||||
You can specify multiple domains:
|
You can specify multiple domains:
|
||||||
```
|
```
|
||||||
public_domains[] = "niver.example"
|
public_domains[] = "servnest.example"
|
||||||
public_domains[] = "4example4example4example4example4example4example4example.onion"
|
public_domains[] = "4example4example4example4example4example4example4example.onion"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ public_domains[] = "4example4example4example4example4example4example4example.oni
|
||||||
|
|
||||||
Path that is prepended to the HTTP root where the service can be reached. Used for redirections and emitting cookies.
|
Path that is prepended to the HTTP root where the service can be reached. Used for redirections and emitting cookies.
|
||||||
|
|
||||||
If the service answers at `https://niver.example/niver/`, you need to set `prefix = "/niver"`.
|
If the service answers at `https://servnest.example/servnest/`, you need to set `prefix = "/servnest"`.
|
||||||
|
|
||||||
### `service_name`
|
### `service_name`
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ Filesystem path to the `kzonecheck` binary. Used to check sent plaintext zonefil
|
||||||
|
|
||||||
### `public_soa_email`
|
### `public_soa_email`
|
||||||
|
|
||||||
Administrator email address published in every SOA record. Ends with a `.`, `@` is replaced by a `.`, an hypothetical `.` in the first part of the address is escaped using a `\` before, thus `contact.admin@niver.example` becomes `contact\.admin.niver.example.`
|
Administrator email address published in every SOA record. Ends with a `.`, `@` is replaced by a `.`, an hypothetical `.` in the first part of the address is escaped using a `\` before, thus `contact.admin@servnest.example` becomes `contact\.admin.servnest.example.`
|
||||||
|
|
||||||
## `[ht]`
|
## `[ht]`
|
||||||
|
|
||||||
|
@ -170,6 +170,10 @@ Filesystem path to the certbot binary. It is used through sudo to get a Let's En
|
||||||
|
|
||||||
Filesystem paths to the corresponding GNU coreutils binary (other implementations are not tested). (Their PHP counterpart can't be used as they need to act as another user through sudo.)
|
Filesystem paths to the corresponding GNU coreutils binary (other implementations are not tested). (Their PHP counterpart can't be used as they need to act as another user through sudo.)
|
||||||
|
|
||||||
|
### `acme_path`
|
||||||
|
|
||||||
|
Filesystem path to the root directory that is served when a request hits `.well-known/acme-challenge` on port 80. Certbot places ACME authentication files here to get Let's Encrypt certificates through the HTTP-01 challenge.
|
||||||
|
|
||||||
### `sftpgo_group`
|
### `sftpgo_group`
|
||||||
|
|
||||||
Linux group as who runs SFTPGo. (Gets full permissions on users directories.)
|
Linux group as who runs SFTPGo. (Gets full permissions on users directories.)
|
||||||
|
@ -178,7 +182,6 @@ Linux group as who runs SFTPGo. (Gets full permissions on users directories.)
|
||||||
|
|
||||||
Linux user as who runs SFTPGo. (Used to delete files that users created.)
|
Linux user as who runs SFTPGo. (Used to delete files that users created.)
|
||||||
|
|
||||||
|
|
||||||
### `ipv6_address`, `ipv4_address`
|
### `ipv6_address`, `ipv4_address`
|
||||||
|
|
||||||
Public IPv6 and IPv4 addresses that users must set in their AAAA and A records for a site with dedicated domain.
|
Public IPv6 and IPv4 addresses that users must set in their AAAA and A records for a site with dedicated domain.
|
||||||
|
|
36
README.md
36
README.md
|
@ -1,20 +1,16 @@
|
||||||
# Niver
|
# ServNest
|
||||||
|
|
||||||
Niver is a set of 3 network services:
|
ServNest (formerly Niver) is software providing a web interface allowing users to manage 3 independent services:
|
||||||
|
|
||||||
* Public suffix registry
|
* Public suffix registry
|
||||||
* Nameserver
|
* Domain name server
|
||||||
* Static HTTP site hosting
|
* Static HTTP site hosting
|
||||||
|
|
||||||
## Demo
|
|
||||||
|
|
||||||
<https://niver.4.niv.re/>
|
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
I plan to create and maintain a public stable instance of Niver, but I haven't done so yet. Thus Niver is not yet tested with real world and long-term usages, and is **alpha software**.
|
I plan to create and maintain a public stable instance of ServNest, but I haven't done so yet. Thus it is not yet tested with real world and long-term usages, and is **alpha software**.
|
||||||
|
|
||||||
## Detailed features
|
## Detailed services features
|
||||||
|
|
||||||
### Public suffix registry (`reg`)
|
### Public suffix registry (`reg`)
|
||||||
|
|
||||||
|
@ -23,6 +19,7 @@ I plan to create and maintain a public stable instance of Niver, but I haven't d
|
||||||
* Set a DS record to enable DNSSEC
|
* Set a DS record to enable DNSSEC
|
||||||
* Set Glue records
|
* Set Glue records
|
||||||
* Display your records
|
* Display your records
|
||||||
|
* Transfer domain to another account
|
||||||
|
|
||||||
### Name server (`ns`)
|
### Name server (`ns`)
|
||||||
|
|
||||||
|
@ -31,11 +28,14 @@ I plan to create and maintain a public stable instance of Niver, but I haven't d
|
||||||
* Dedicated forms to set/unset `A`, `AAAA`, `NS`, `TXT`, `CAA`, `SRV`, `MX`, `SRV`, `SSHFP`, `TLSA`, `CNAME`, `DNAME` and `LOC` records
|
* Dedicated forms to set/unset `A`, `AAAA`, `NS`, `TXT`, `CAA`, `SRV`, `MX`, `SRV`, `SSHFP`, `TLSA`, `CNAME`, `DNAME` and `LOC` records
|
||||||
* Display your records or the full zone file
|
* Display your records or the full zone file
|
||||||
|
|
||||||
### HTTP hosting (`ht`)
|
### Static HTTP site hosting (`ht`)
|
||||||
|
|
||||||
* Upload your site's files using SFTP
|
Upload site's files to the server using SFTP. The way the site is accessed can then be choosed:
|
||||||
* Host a static site with a domain name and a Let's Encrypt certificate
|
|
||||||
* Host a static site with an Onion service (through Tor)
|
* Dedicated domain name and Let's Encrypt certificate
|
||||||
|
* Dedicated onion service (through Tor)
|
||||||
|
* Subdomain of a shared root domain
|
||||||
|
* HTTP subpath of a shared domain
|
||||||
|
|
||||||
## Software used
|
## Software used
|
||||||
|
|
||||||
|
@ -68,13 +68,13 @@ Tor
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
There is currently no proper documentation to install Niver, but you can create a system image or look at configuration files and scripts from [niver-mkosi](https://code.antopie.org/niver/niver-mkosi).
|
There is currently no proper documentation to install ServNest, but you can create a system image or look at configuration files and scripts from [servnest-mkosi](https://code.antopie.org/servnest/servnest-mkosi).
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
- Git repository : <https://code.antopie.org/niver/niver>
|
- Git repository : <https://code.antopie.org/servnest/servnest>
|
||||||
- Issue tracker : <https://code.antopie.org/niver/niver/issues>
|
- Issue tracker : <https://code.antopie.org/servnest/servnest/issues>
|
||||||
- Matrix channel : [#niver:matrix.antopie.org](matrix:r/niver:matrix.antopie.org)
|
- Matrix channel : [#servnest:matrix.antopie.org](matrix:r/servnest:matrix.antopie.org)
|
||||||
|
|
||||||
## Direct contact details
|
## Direct contact details
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ See <https://miraty.antopie.org/>.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Niver is ethical libre software: you can use, redistribute or modify it under the terms of the CNPL-NAv7+ as found in LICENSE.md or at <https://git.pixie.town/thufie/npl-builder>.
|
ServNest is ethical libre software: you can use, redistribute or modify it under the terms of the CNPL-NAv7+ as found in LICENSE.md or at <https://git.pixie.town/thufie/npl-builder>.
|
||||||
|
|
||||||
## Similar projects
|
## Similar projects
|
||||||
|
|
||||||
|
|
44
config.ini
44
config.ini
|
@ -1,11 +1,11 @@
|
||||||
; Directives here are described in DOCS/configuration.md
|
; Directives here are described in DOCS/configuration.md
|
||||||
|
|
||||||
[common]
|
[common]
|
||||||
root_path = "/srv/niver/core"
|
root_path = "/srv/servnest/core"
|
||||||
public_domains[] = "niver.test"
|
public_domains[] = "servnest.test"
|
||||||
prefix = ""
|
prefix = ""
|
||||||
service_name = "Niver"
|
service_name = "ServNest"
|
||||||
service_emoji = "🪐"
|
service_emoji = "🪺"
|
||||||
|
|
||||||
[dns]
|
[dns]
|
||||||
knotc_path = "/usr/sbin/knotc"
|
knotc_path = "/usr/sbin/knotc"
|
||||||
|
@ -13,37 +13,37 @@ kdig_path = "/usr/bin/kdig"
|
||||||
|
|
||||||
[reg]
|
[reg]
|
||||||
enabled = true
|
enabled = true
|
||||||
suffixes[niver.test.] = "approved"
|
suffixes[servnest.test.] = "approved"
|
||||||
suffixes[test.niver.test.] = "all"
|
suffixes[test.servnest.test.] = "all"
|
||||||
suffixes[old.niver.test.] = "none"
|
suffixes[old.sernnest.test.] = "none"
|
||||||
suffixes_path = "/srv/niver/reg"
|
suffixes_path = "/srv/servnest/reg"
|
||||||
ttl = 86400
|
ttl = 86400
|
||||||
address = "[::1]:42053"
|
address = "[::1]:42053"
|
||||||
|
|
||||||
[ns]
|
[ns]
|
||||||
enabled = true
|
enabled = true
|
||||||
knot_zones_path = "/srv/niver/ns"
|
knot_zones_path = "/srv/servnest/ns"
|
||||||
servers[] = "ns1.niver.test."
|
servers[] = "ns1.servnest.test."
|
||||||
servers[] = "ns2.niver.test."
|
servers[] = "ns2.servnest.test."
|
||||||
kzonecheck_path = "/usr/bin/kzonecheck"
|
kzonecheck_path = "/usr/bin/kzonecheck"
|
||||||
public_soa_email = "hostmaster.niver.invalid."
|
public_soa_email = "hostmaster.invalid."
|
||||||
|
|
||||||
[ht]
|
[ht]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
||||||
ht_path = "/srv/niver/ht"
|
ht_path = "/srv/servnest/ht"
|
||||||
|
|
||||||
subpath_domain = "ht.niver.test"
|
subpath_domain = "ht.servnest.test"
|
||||||
subpath_path = "/srv/niver/subpath"
|
subpath_path = "/srv/servnest/subpath"
|
||||||
|
|
||||||
subdomain_domain = "ht.niver.test"
|
subdomain_domain = "ht.servnest.test"
|
||||||
subdomain_path = "/srv/niver/subdomain"
|
subdomain_path = "/srv/servnest/subdomain"
|
||||||
|
|
||||||
nginx_config_path = "/srv/niver/nginx"
|
nginx_config_path = "/srv/servnest/nginx"
|
||||||
nginx_reload_cmd = "/usr/bin/systemctl reload nginx"
|
nginx_reload_cmd = "/usr/bin/systemctl reload nginx"
|
||||||
|
|
||||||
tor_config_path = "/srv/niver/tor-config"
|
tor_config_path = "/srv/servnest/tor-config"
|
||||||
tor_keys_path = "/srv/niver/tor-keys"
|
tor_keys_path = "/srv/servnest/tor-keys"
|
||||||
tor_user = "tor"
|
tor_user = "tor"
|
||||||
tor_reload_cmd = "/usr/bin/systemctl reload tor"
|
tor_reload_cmd = "/usr/bin/systemctl reload tor"
|
||||||
|
|
||||||
|
@ -54,6 +54,8 @@ cat_path = "/usr/bin/cat"
|
||||||
rm_path = "/usr/bin/rm"
|
rm_path = "/usr/bin/rm"
|
||||||
mkdir_path = "/usr/bin/mkdir"
|
mkdir_path = "/usr/bin/mkdir"
|
||||||
|
|
||||||
|
acme_path = "/srv/servnest/acme"
|
||||||
|
|
||||||
sftpgo_group = "sftpgo"
|
sftpgo_group = "sftpgo"
|
||||||
sftpgo_user = "sftpgo"
|
sftpgo_user = "sftpgo"
|
||||||
|
|
||||||
|
@ -63,7 +65,7 @@ ipv4_address = "127.0.0.1"
|
||||||
sftp_pub = "/etc/sftpgo/ed25519.pub"
|
sftp_pub = "/etc/sftpgo/ed25519.pub"
|
||||||
sftp_fp = "/etc/sftpgo/ed25519.fp"
|
sftp_fp = "/etc/sftpgo/ed25519.fp"
|
||||||
sftp_asciiart = "/etc/sftpgo/ed25519.asciiart"
|
sftp_asciiart = "/etc/sftpgo/ed25519.asciiart"
|
||||||
sftp_domain = "sftp.niver.test"
|
sftp_domain = "sftp.servnest.test"
|
||||||
public_sftp_port = 2022
|
public_sftp_port = 2022
|
||||||
|
|
||||||
; Will be used in configuration files
|
; Will be used in configuration files
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
:root, .niver {
|
:root, .common {
|
||||||
--svc-color: var(--foreground-color);
|
--svc-color: var(--foreground-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ function output($code, $msg = '', $logs = ['']) {
|
||||||
http_response_code($code);
|
http_response_code($code);
|
||||||
$shortCode = $code / 100 % 10;
|
$shortCode = $code / 100 % 10;
|
||||||
if ($shortCode === 5)
|
if ($shortCode === 5)
|
||||||
error_log('Niver internal error: ' . strip_tags($msg) . implode(LF, $logs));
|
error_log('Internal error: ' . strip_tags($msg) . implode(LF, $logs));
|
||||||
$final_message = match ($shortCode) {
|
$final_message = match ($shortCode) {
|
||||||
2 => ($msg === '') ? '' : '<p><output>' . _('<strong>Success</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
|
2 => ($msg === '') ? '' : '<p><output>' . _('<strong>Success</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
|
||||||
4 => '<p><output>' . _('<strong>User error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
|
4 => '<p><output>' . _('<strong>User error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
|
||||||
|
|
|
@ -23,7 +23,7 @@ function regDeleteDomain($domain) {
|
||||||
if (file_put_contents($path, $content) === false)
|
if (file_put_contents($path, $content) === false)
|
||||||
output(500, 'Failed to write new registry file.');
|
output(500, 'Failed to write new registry file.');
|
||||||
|
|
||||||
// Delete from Niver's database
|
// Delete from database
|
||||||
query('delete', 'registry', [
|
query('delete', 'registry', [
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
'username' => $_SESSION['id'],
|
'username' => $_SESSION['id'],
|
||||||
|
|
|
@ -32,7 +32,7 @@ rateLimit();
|
||||||
|
|
||||||
addSite($_SESSION['id'], $_POST['dir'], $_POST['domain'], 'dns');
|
addSite($_SESSION['id'], $_POST['dir'], $_POST['domain'], 'dns');
|
||||||
|
|
||||||
exec('2>&1 ' . CONF['ht']['sudo_path'] . ' ' . CONF['ht']['certbot_path'] . ' certonly' . (($_SESSION['type'] === 'approved') ? '' : ' --test-cert') . ' --key-type rsa --rsa-key-size 3072 --webroot --webroot-path /srv/niver/acme --domain ' . $_POST['domain'], $output, $returnCode);
|
exec('2>&1 ' . CONF['ht']['sudo_path'] . ' ' . CONF['ht']['certbot_path'] . ' certonly' . (($_SESSION['type'] === 'approved') ? '' : ' --test-cert') . ' --key-type rsa --rsa-key-size 3072 --webroot --webroot-path ' . CONF['ht']['acme_path'] . ' --domain ' . $_POST['domain'], $output, $returnCode);
|
||||||
if ($returnCode !== 0)
|
if ($returnCode !== 0)
|
||||||
output(500, 'Certbot failed to get a Let\'s Encrypt certificate.', $output);
|
output(500, 'Certbot failed to get a Let\'s Encrypt certificate.', $output);
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ if (chmod($knotZonePath, 0660) !== true)
|
||||||
|
|
||||||
knotcConfExec([
|
knotcConfExec([
|
||||||
"set 'zone[" . $_POST['domain'] . "]'",
|
"set 'zone[" . $_POST['domain'] . "]'",
|
||||||
"set 'zone[" . $_POST['domain'] . "].template' 'niver'",
|
"set 'zone[" . $_POST['domain'] . "].template' 'servnest'",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
output(200, _('Zone created.'));
|
output(200, _('Zone created.'));
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
# List of subdomains not available to register
|
# List of subdomains not available to register
|
||||||
#
|
#
|
||||||
# They may be forbidden because:
|
# They may be forbidden because:
|
||||||
# - they may be privileged for impersonating Niver, spamming or fishing
|
# - they may be privileged for impersonating, spamming or fishing
|
||||||
# - they are reserved for a project asking for it and deserving such a well-known name
|
# - they are reserved for a project asking for it and deserving such a well-known name
|
||||||
|
|
||||||
|
servnest
|
||||||
|
serv
|
||||||
|
nest
|
||||||
|
srvnst
|
||||||
niver
|
niver
|
||||||
|
|
||||||
# Registry-related
|
# Registry-related
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label for="subdomain"><?= _('Subdomain') ?></label>
|
<label for="subdomain"><?= _('Subdomain') ?></label>
|
||||||
<br>
|
<br>
|
||||||
<input id="subdomain" pattern="<?= SUBDOMAIN_REGEX ?>" required="" placeholder="niver" name="subdomain" type="text">
|
<input id="subdomain" pattern="<?= SUBDOMAIN_REGEX ?>" required="" placeholder="servnest" name="subdomain" type="text">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="suffix"><?= _('Suffix') ?></label>
|
<label for="suffix"><?= _('Suffix') ?></label>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label for="subdomain"><?= _('Subdomain') ?></label>
|
<label for="subdomain"><?= _('Subdomain') ?></label>
|
||||||
<br>
|
<br>
|
||||||
<input id="subdomain" pattern="<?= SUBDOMAIN_REGEX ?>" required="" placeholder="niver" name="subdomain" type="text">
|
<input id="subdomain" pattern="<?= SUBDOMAIN_REGEX ?>" required="" placeholder="servnest" name="subdomain" type="text">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="suffix"><?= _('Suffix') ?></label>
|
<label for="suffix"><?= _('Suffix') ?></label>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
define('CONF', parse_ini_file(__DIR__ . '/config.ini', true, INI_SCANNER_TYPED));
|
define('CONF', parse_ini_file(__DIR__ . '/config.ini', true, INI_SCANNER_TYPED));
|
||||||
|
|
||||||
define('DB', new PDO('sqlite:' . CONF['common']['root_path'] . '/db/niver.db'));
|
define('DB', new PDO('sqlite:' . CONF['common']['root_path'] . '/db/servnest.db'));
|
||||||
|
|
||||||
$locale = 'en';
|
$locale = 'en';
|
||||||
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||||
|
@ -70,7 +70,7 @@ if (!TITLES_LINEAGE[array_key_last(TITLES_LINEAGE)]) {
|
||||||
exit('Page not found.');
|
exit('Page not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const SESSION_COOKIE_NAME = 'niver-session-key';
|
const SESSION_COOKIE_NAME = 'servnest-session-key';
|
||||||
function startSession() {
|
function startSession() {
|
||||||
session_start([
|
session_start([
|
||||||
'name' => SESSION_COOKIE_NAME,
|
'name' => SESSION_COOKIE_NAME,
|
||||||
|
|
4
view.php
4
view.php
|
@ -25,7 +25,7 @@
|
||||||
<?php
|
<?php
|
||||||
foreach (TITLES_LINEAGE as $id => $title) {
|
foreach (TITLES_LINEAGE as $id => $title) {
|
||||||
$lastTitle = (TITLES_LINEAGE[array_key_last(TITLES_LINEAGE)] === $title);
|
$lastTitle = (TITLES_LINEAGE[array_key_last(TITLES_LINEAGE)] === $title);
|
||||||
echo '<ul><li>' . ($lastTitle ? '<h1>' : '') . '<a' . (($id === 0) ? ' class="niver"' : '') . ' href="' . CONF['common']['prefix'] . ($lastTitle ? '/' . PAGE_URL : '/' . implode('/', array_slice(PAGE_LINEAGE, 0, $id)) . (($lastTitle OR $id === 0) ? '' : '/')) . '">' . $title . '</a>' . ($lastTitle ? '</h1>' : '') . LF;
|
echo '<ul><li>' . ($lastTitle ? '<h1>' : '') . '<a' . (($id === 0) ? ' class="common"' : '') . ' href="' . CONF['common']['prefix'] . ($lastTitle ? '/' . PAGE_URL : '/' . implode('/', array_slice(PAGE_LINEAGE, 0, $id)) . (($lastTitle OR $id === 0) ? '' : '/')) . '">' . $title . '</a>' . ($lastTitle ? '</h1>' : '') . LF;
|
||||||
}
|
}
|
||||||
echo str_repeat('</li></ul>', count(TITLES_LINEAGE));
|
echo str_repeat('</li></ul>', count(TITLES_LINEAGE));
|
||||||
?>
|
?>
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
?>
|
?>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
<small><?= sprintf(_('%sSource code%s available under %s.'), '<a rel="external" href="https://code.antopie.org/niver/niver" class="niver">', '</a>', '<abbr title="Cooperative Nonviolent Public License No Attribution version 7 or more">CNPL-NAv7+</abbr>') ?></small>
|
<small><?= sprintf(_('%sSource code%s available under %s.'), '<a rel="external" href="https://code.antopie.org/servnest/servnest" class="common">', '</a>', '<abbr title="Cooperative Nonviolent Public License No Attribution version 7 or more">CNPL-NAv7+</abbr>') ?></small>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue