Compare commits
141 commits
9e77c1ac44
...
11a33bd6af
Author | SHA1 | Date | |
---|---|---|---|
|
11a33bd6af | ||
|
184e89e586 | ||
|
c1d2cdc7c1 | ||
|
35d78bac22 | ||
|
c6cc92f5dc | ||
|
e41e0daa82 | ||
|
83b1c9ac3b | ||
|
9d6a0f0a33 | ||
|
a13b1e9372 | ||
|
108949d3e1 | ||
|
cf086dc3e8 | ||
|
1e3360ebc0 | ||
|
656b31e729 | ||
|
8412a3f1fe | ||
|
ff75f07f66 | ||
![]() |
401bf59e44 | ||
![]() |
1f4b4d62e0 | ||
![]() |
473cc7dea0 | ||
|
4be54ae67b | ||
|
ed95912726 | ||
|
f160bc3f4c | ||
|
14de9dec55 | ||
|
af4f77a767 | ||
|
08eac749dd | ||
|
00384a5948 | ||
|
7f1e5b56d0 | ||
![]() |
72fa686540 | ||
![]() |
2a28e0289d | ||
![]() |
2364e122a2 | ||
![]() |
a65c55f7a1 | ||
![]() |
682f8d6776 | ||
![]() |
c15009f490 | ||
![]() |
581b64b77b | ||
![]() |
8d612a712d | ||
![]() |
a02a741ec4 | ||
|
900c92ac01 | ||
|
a2c54ff172 | ||
|
d1f58573d8 | ||
|
c47256d62c | ||
|
351649c2dd | ||
|
81ab439777 | ||
|
7665609827 | ||
|
bbf5f50432 | ||
|
ecda4f249d | ||
|
29c86aedff | ||
|
cf5139836e | ||
|
492a199866 | ||
|
61eb2d9081 | ||
|
069b1ddb6c | ||
|
4b379af4ff | ||
|
059a76d933 | ||
![]() |
d208ee83f8 | ||
|
813c1ca879 | ||
![]() |
ee2e89d2e3 | ||
![]() |
3b94c7a4ae | ||
![]() |
c2fd51baac | ||
![]() |
2dca2caca3 | ||
![]() |
9346bcca3d | ||
![]() |
3652a443b1 | ||
![]() |
5b26cfc7a9 | ||
![]() |
a2da2f48b2 | ||
|
a49a2b57df | ||
|
2f44a729c2 | ||
|
680f54f7d5 | ||
|
b38ec1a62a | ||
![]() |
a245729765 | ||
![]() |
b24411f44b | ||
|
e4d84d7881 | ||
|
60cf77e66e | ||
|
1932a131e0 | ||
|
afb8d56dd6 | ||
![]() |
7b204eb2ce | ||
![]() |
1a92108475 | ||
![]() |
f8af95f9ab | ||
![]() |
d1691e4c8b | ||
|
cd277c0e98 | ||
![]() |
5fd5c37834 | ||
![]() |
e6dde31148 | ||
![]() |
353c0a7ffa | ||
![]() |
6cc1b35e5e | ||
![]() |
0fd3e0e990 | ||
![]() |
83cafe29d1 | ||
![]() |
db7b208e63 | ||
![]() |
366a67e112 | ||
![]() |
941d3bf2a9 | ||
![]() |
bddfd95ee4 | ||
![]() |
acc4a1a2ef | ||
![]() |
0f8972a8f0 | ||
![]() |
dab12bc2b8 | ||
![]() |
693e21b1a8 | ||
![]() |
4e719da9d9 | ||
![]() |
541e49c7a2 | ||
![]() |
bb7de00d00 | ||
![]() |
4e4c5042fa | ||
![]() |
2c7e3603b8 | ||
![]() |
dbdd9322f5 | ||
![]() |
4d0279a137 | ||
![]() |
f49bcf79b8 | ||
![]() |
ce2830274e | ||
![]() |
2ddc599ce1 | ||
![]() |
1c672827f4 | ||
![]() |
b66e5165a8 | ||
![]() |
5b338c3068 | ||
![]() |
7cbe3544a3 | ||
![]() |
f59d013869 | ||
![]() |
7cb78f5097 | ||
![]() |
cf5f4d1520 | ||
![]() |
db669bd84e | ||
![]() |
19e9b0e13a | ||
![]() |
e98ccf9984 | ||
![]() |
5daf70fce9 | ||
![]() |
599dd3f1b9 | ||
![]() |
f45a97ddae | ||
![]() |
6acf6f9e25 | ||
![]() |
5f29bbb18f | ||
![]() |
af6e76134a | ||
![]() |
ab2ae452c1 | ||
![]() |
1304cb4689 | ||
![]() |
fab2c6e946 | ||
![]() |
0d6108babe | ||
![]() |
8b5150f3de | ||
![]() |
f4afa0cf30 | ||
![]() |
41f23751ab | ||
![]() |
a8aed7c7e7 | ||
![]() |
f2ef1b3637 | ||
![]() |
e3d6fc655a | ||
![]() |
48084fa688 | ||
![]() |
5fd1e115a0 | ||
![]() |
ad8a244881 | ||
![]() |
8a9bf15f2e | ||
![]() |
d649103df4 | ||
![]() |
522c4e7b51 | ||
![]() |
907a9c9494 | ||
![]() |
1b0fcff9fb | ||
![]() |
3e4ab1ecf6 | ||
![]() |
8c40168532 | ||
![]() |
dc6e4936ed | ||
![]() |
230810bf6f | ||
![]() |
a4cb05d8a1 | ||
![]() |
ecf89fc0d0 | ||
![]() |
6942d1dcf2 |
190
LICENSE
Normal file
|
@ -0,0 +1,190 @@
|
|||
EUROPEAN UNION PUBLIC LICENCE v. 1.2
|
||||
EUPL © the European Union 2007, 2016
|
||||
|
||||
This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined below) which is provided under the
|
||||
terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such
|
||||
use is covered by a right of the copyright holder of the Work).
|
||||
The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following
|
||||
notice immediately following the copyright notice for the Work:
|
||||
Licensed under the EUPL
|
||||
or has expressed by any other means his willingness to license under the EUPL.
|
||||
|
||||
1.Definitions
|
||||
In this Licence, the following terms have the following meaning:
|
||||
— ‘The Licence’:this Licence.
|
||||
— ‘The Original Work’:the work or software distributed or communicated by the Licensor under this Licence, available
|
||||
as Source Code and also as Executable Code as the case may be.
|
||||
— ‘Derivative Works’:the works or software that could be created by the Licensee, based upon the Original Work or
|
||||
modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work
|
||||
required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in
|
||||
the country mentioned in Article 15.
|
||||
— ‘The Work’:the Original Work or its Derivative Works.
|
||||
— ‘The Source Code’:the human-readable form of the Work which is the most convenient for people to study and
|
||||
modify.
|
||||
— ‘The Executable Code’:any code which has generally been compiled and which is meant to be interpreted by
|
||||
a computer as a program.
|
||||
— ‘The Licensor’:the natural or legal person that distributes or communicates the Work under the Licence.
|
||||
— ‘Contributor(s)’:any natural or legal person who modifies the Work under the Licence, or otherwise contributes to
|
||||
the creation of a Derivative Work.
|
||||
— ‘The Licensee’ or ‘You’:any natural or legal person who makes any usage of the Work under the terms of the
|
||||
Licence.
|
||||
— ‘Distribution’ or ‘Communication’:any act of selling, giving, lending, renting, distributing, communicating,
|
||||
transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential
|
||||
functionalities at the disposal of any other natural or legal person.
|
||||
|
||||
2.Scope of the rights granted by the Licence
|
||||
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for
|
||||
the duration of copyright vested in the Original Work:
|
||||
— use the Work in any circumstance and for all usage,
|
||||
— reproduce the Work,
|
||||
— modify the Work, and make Derivative Works based upon the Work,
|
||||
— communicate to the public, including the right to make available or display the Work or copies thereof to the public
|
||||
and perform publicly, as the case may be, the Work,
|
||||
— distribute the Work or copies thereof,
|
||||
— lend and rent the Work or copies thereof,
|
||||
— sublicense rights in the Work or copies thereof.
|
||||
Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the
|
||||
applicable law permits so.
|
||||
In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed
|
||||
by law in order to make effective the licence of the economic rights here above listed.
|
||||
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the
|
||||
extent necessary to make use of the rights granted on the Work under this Licence.
|
||||
|
||||
3.Communication of the Source Code
|
||||
The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as
|
||||
Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with
|
||||
each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to
|
||||
the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to
|
||||
distribute or communicate the Work.
|
||||
|
||||
4.Limitations on copyright
|
||||
Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the
|
||||
exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations
|
||||
thereto.
|
||||
|
||||
5.Obligations of the Licensee
|
||||
The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those
|
||||
obligations are the following:
|
||||
|
||||
Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to
|
||||
the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the
|
||||
Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work
|
||||
to carry prominent notices stating that the Work has been modified and the date of modification.
|
||||
|
||||
Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this
|
||||
Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless
|
||||
the Original Work is expressly distributed only under this version of the Licence — for example by communicating
|
||||
‘EUPL v. 1.2 only’. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the
|
||||
Work or Derivative Work that alter or restrict the terms of the Licence.
|
||||
|
||||
Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both
|
||||
the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done
|
||||
under the terms of this Compatible Licence. For the sake of this clause, ‘Compatible Licence’ refers to the licences listed
|
||||
in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with
|
||||
his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail.
|
||||
|
||||
Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide
|
||||
a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available
|
||||
for as long as the Licensee continues to distribute or communicate the Work.
|
||||
Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names
|
||||
of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the copyright notice.
|
||||
|
||||
6.Chain of Authorship
|
||||
The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or
|
||||
licensed to him/her and that he/she has the power and authority to grant the Licence.
|
||||
Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or
|
||||
licensed to him/her and that he/she has the power and authority to grant the Licence.
|
||||
Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions
|
||||
to the Work, under the terms of this Licence.
|
||||
|
||||
7.Disclaimer of Warranty
|
||||
The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work
|
||||
and may therefore contain defects or ‘bugs’ inherent to this type of development.
|
||||
For the above reason, the Work is provided under the Licence on an ‘as is’ basis and without warranties of any kind
|
||||
concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or
|
||||
errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this
|
||||
Licence.
|
||||
This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work.
|
||||
|
||||
8.Disclaimer of Liability
|
||||
Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be
|
||||
liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the
|
||||
Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss
|
||||
of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However,
|
||||
the Licensor will be liable under statutory product liability laws as far such laws apply to the Work.
|
||||
|
||||
9.Additional agreements
|
||||
While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services
|
||||
consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole
|
||||
responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by
|
||||
the fact You have accepted any warranty or additional liability.
|
||||
|
||||
10.Acceptance of the Licence
|
||||
The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ placed under the bottom of a window
|
||||
displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of
|
||||
applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms
|
||||
and conditions.
|
||||
Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You
|
||||
by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution
|
||||
or Communication by You of the Work or copies thereof.
|
||||
|
||||
11.Information to the public
|
||||
In case of any Distribution or Communication of the Work by means of electronic communication by You (for example,
|
||||
by offering to download the Work from a remote location) the distribution channel or media (for example, a website)
|
||||
must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence
|
||||
and the way it may be accessible, concluded, stored and reproduced by the Licensee.
|
||||
|
||||
12.Termination of the Licence
|
||||
The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms
|
||||
of the Licence.
|
||||
Such a termination will not terminate the licences of any person who has received the Work from the Licensee under
|
||||
the Licence, provided such persons remain in full compliance with the Licence.
|
||||
|
||||
13.Miscellaneous
|
||||
Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the
|
||||
Work.
|
||||
If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or
|
||||
enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid
|
||||
and enforceable.
|
||||
The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of
|
||||
the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence.
|
||||
New versions of the Licence will be published with a unique version number.
|
||||
All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take
|
||||
advantage of the linguistic version of their choice.
|
||||
|
||||
14.Jurisdiction
|
||||
Without prejudice to specific agreement between parties,
|
||||
— any litigation resulting from the interpretation of this License, arising between the European Union institutions,
|
||||
bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice
|
||||
of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union,
|
||||
— any litigation arising between other parties and resulting from the interpretation of this License, will be subject to
|
||||
the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business.
|
||||
|
||||
15.Applicable Law
|
||||
Without prejudice to specific agreement between parties,
|
||||
— this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat,
|
||||
resides or has his registered office,
|
||||
— this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside
|
||||
a European Union Member State.
|
||||
|
||||
|
||||
Appendix
|
||||
|
||||
‘Compatible Licences’ according to Article 5 EUPL are:
|
||||
— GNU General Public License (GPL) v. 2, v. 3
|
||||
— GNU Affero General Public License (AGPL) v. 3
|
||||
— Open Software License (OSL) v. 2.1, v. 3.0
|
||||
— Eclipse Public License (EPL) v. 1.0
|
||||
— CeCILL v. 2.0, v. 2.1
|
||||
— Mozilla Public Licence (MPL) v. 2
|
||||
— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
|
||||
— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software
|
||||
— European Union Public Licence (EUPL) v. 1.1, v. 1.2
|
||||
— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+).
|
||||
|
||||
The European Commission may update this Appendix to later versions of the above licences without producing
|
||||
a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the
|
||||
covered Source Code from exclusive appropriation.
|
||||
All other changes or additions to this Appendix require the production of a new EUPL version.
|
51
README.md
|
@ -1,51 +1,2 @@
|
|||
---
|
||||
gitea: none
|
||||
include_toc: true
|
||||
---
|
||||
|
||||
# A complete Matrix installation
|
||||
|
||||
This is going to be a Matrix installation with all bells and whistles. Not
|
||||
just the server, but every other bit that you need or want.
|
||||
|
||||
We're building it with workers, so it will scale.
|
||||
|
||||
## Overview
|
||||
|
||||
A complete Matrix environment consists of many parts. Other than the Matrix
|
||||
server itself (Synapse) there are all kinds of other things that we need:
|
||||
|
||||
* [Synapse](https://element-hq.github.io/synapse/latest/)
|
||||
* Webclient ([Element Web](https://github.com/element-hq/element-web))
|
||||
* [Element Call](https://github.com/element-hq/element-call) for audio/video
|
||||
conferencing
|
||||
* Management with [Synapse-Admin](https://github.com/Awesome-Technologies/synapse-admin)
|
||||
* Moderation with [Draupnir](https://github.com/the-draupnir-project/Draupnir)
|
||||
* [Consent
|
||||
tracking](https://element-hq.github.io/synapse/latest/consent_tracking.html)
|
||||
* Authentication via
|
||||
[OpenID](https://element-hq.github.io/synapse/latest/openid.html)
|
||||
* Several [bridges](https://matrix.org/ecosystem/bridges/)
|
||||
|
||||
|
||||
# Synapse
|
||||
|
||||
This is the core component: the Matrix server itself.
|
||||
|
||||
Installation and configuration is documented under [synapse](synapse).
|
||||
|
||||
|
||||
# TURN
|
||||
|
||||
We may need a TURN server, and we'll use
|
||||
[coturn](https://github.com/coturn/coturn) for that.
|
||||
|
||||
It's apparently also possible to use the built-in TURN server in Livekit,
|
||||
which we'll use if we use [Element Call](call). It's either/or, so make sure
|
||||
you pick the right approach.
|
||||
|
||||
|
||||
# Wiki
|
||||
|
||||
Of course there's a wiki in this repository.
|
||||
# Fediversity
|
||||
|
||||
|
|
24
infra/flake.nix
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05";
|
||||
snf.url = "git+https://git.fediversity.eu/fediversity/simple-nixos-fediverse.git";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, snf }:
|
||||
let
|
||||
vmName = "vm02186";
|
||||
|
||||
in {
|
||||
nixosConfigurations.${vmName} = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
|
||||
modules = [
|
||||
./procolix-configuration.nix
|
||||
./hardware-configuration.nix
|
||||
./gitea-runner.nix
|
||||
];
|
||||
};
|
||||
|
||||
isoInstallers.${vmName} = snf.mkInstaller nixpkgs self.nixosConfigurations.${vmName};
|
||||
};
|
||||
}
|
41
infra/gitea-runner.nix
Normal file
|
@ -0,0 +1,41 @@
|
|||
{ pkgs, config, ... }:
|
||||
|
||||
{
|
||||
services.gitea-actions-runner = {
|
||||
package = pkgs.forgejo-actions-runner;
|
||||
|
||||
instances.default = {
|
||||
enable = true;
|
||||
|
||||
name = config.networking.fqdn;
|
||||
url = "https://git.fediversity.eu";
|
||||
token = "MKmFPY4nxfR4zPYHIRLoiJdrrfkGmcRymj0GWOAk";
|
||||
|
||||
settings = {
|
||||
log.level = "info";
|
||||
runner = {
|
||||
file = ".runner";
|
||||
capacity = 24;
|
||||
timeout = "3h";
|
||||
insecure = false;
|
||||
fetch_timeout = "5s";
|
||||
fetch_interval = "2s";
|
||||
};
|
||||
};
|
||||
|
||||
## This runner supports Docker (with a default Ubuntu image) and native
|
||||
## modes. In native mode, it contains a few default packages.
|
||||
labels = ["docker:docker://node:16-bullseye" "native:host"];
|
||||
hostPackages = with pkgs; [ bash git nix nodejs ];
|
||||
};
|
||||
};
|
||||
|
||||
## For the Docker mode of the runner.
|
||||
virtualisation.docker.enable = true;
|
||||
|
||||
## The Nix configuration of the system influences the Nix configuration
|
||||
## in the workflow, and our workflows are often flake-based.
|
||||
nix.extraOptions = ''
|
||||
experimental-features = nix-command flakes
|
||||
'';
|
||||
}
|
37
infra/hardware-configuration.nix
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||
# and may be overwritten by future invocations. Please make changes
|
||||
# to /etc/nixos/configuration.nix instead.
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ (modulesPath + "/profiles/qemu-guest.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "virtio_scsi" "sd_mod" "sr_mod" ];
|
||||
boot.initrd.kernelModules = [ "dm-snapshot" ];
|
||||
boot.kernelModules = [ ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" =
|
||||
{ device = "/dev/disk/by-uuid/833ac0f9-ad8c-45ae-a9bf-5844e378c44a";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot" =
|
||||
{ device = "/dev/disk/by-uuid/B4D5-3AF9";
|
||||
fsType = "vfat";
|
||||
options = [ "fmask=0022" "dmask=0022" ];
|
||||
};
|
||||
|
||||
swapDevices = [ ];
|
||||
|
||||
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
|
||||
# (the default) this is the recommended approach. When using systemd-networkd it's
|
||||
# still possible to use this option, but it's recommended to use it in conjunction
|
||||
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
|
||||
networking.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.ens18.useDHCP = lib.mkDefault true;
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
}
|
192
infra/procolix-configuration.nix
Normal file
|
@ -0,0 +1,192 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
# Use the systemd-boot EFI boot loader.
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
|
||||
networking = {
|
||||
hostName = "vm02186";
|
||||
domain = "procolix.com";
|
||||
interfaces = {
|
||||
eth0 = {
|
||||
ipv4 = {
|
||||
addresses = [
|
||||
{
|
||||
address = "185.206.232.186";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
ipv6 = {
|
||||
addresses = [
|
||||
{
|
||||
address = "2a00:51c0:12:1201::186";
|
||||
prefixLength = 64;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
defaultGateway = {
|
||||
address = "185.206.232.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
defaultGateway6 = {
|
||||
address = "2a00:51c0:12:1201::1";
|
||||
interface = "eth0";
|
||||
};
|
||||
nameservers = [ "95.215.185.6" "95.215.185.7" ];
|
||||
firewall.enable = false;
|
||||
nftables = {
|
||||
enable = true;
|
||||
ruleset = ''
|
||||
#!/usr/sbin/nft -f
|
||||
|
||||
flush ruleset
|
||||
|
||||
########### define usefull variables here #####################
|
||||
define wan = eth0
|
||||
define ssh_allow = {
|
||||
83.161.147.127/32, # host801 ipv4
|
||||
95.215.185.92/32, # host088 ipv4
|
||||
95.215.185.211/32, # host089 ipv4
|
||||
95.215.185.34/32, # nagios2 ipv4
|
||||
95.215.185.181/32, # ansible.procolix.com
|
||||
95.215.185.235/32, # ansible-hq
|
||||
}
|
||||
define snmp_allow = {
|
||||
95.215.185.31/32, # cacti ipv4
|
||||
}
|
||||
define nrpe_allow = {
|
||||
95.215.185.34/32, # nagios2 ipv4
|
||||
}
|
||||
|
||||
########### here starts the automated bit #####################
|
||||
table inet filter {
|
||||
chain input {
|
||||
type filter hook input priority 0;
|
||||
policy drop;
|
||||
|
||||
# established/related connections
|
||||
ct state established,related accept
|
||||
ct state invalid drop
|
||||
|
||||
# Limit ping requests.
|
||||
ip protocol icmp icmp type echo-request limit rate over 10/second burst 50 packets drop
|
||||
ip6 nexthdr icmpv6 icmpv6 type echo-request limit rate over 10/second burst 50 packets drop
|
||||
|
||||
# loopback interface
|
||||
iifname lo accept
|
||||
|
||||
# icmp
|
||||
ip protocol icmp icmp type { destination-unreachable, echo-reply, echo-request, source-quench, time-exceeded } accept
|
||||
# Without the nd-* ones ipv6 will not work.
|
||||
ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, echo-reply, echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert, packet-too-big, parameter-problem, time-exceeded } accept
|
||||
|
||||
# open tcp ports: sshd (22)
|
||||
tcp dport {ssh} accept
|
||||
|
||||
# open tcp ports: snmp (161)
|
||||
ip saddr $snmp_allow udp dport {snmp} accept
|
||||
|
||||
# open tcp ports: nrpe (5666)
|
||||
ip saddr $nrpe_allow tcp dport {nrpe} accept
|
||||
|
||||
# open tcp ports: http (80,443)
|
||||
tcp dport {http,https} accept
|
||||
}
|
||||
chain forward {
|
||||
type filter hook forward priority 0;
|
||||
}
|
||||
chain output {
|
||||
type filter hook output priority 0;
|
||||
}
|
||||
}
|
||||
|
||||
table ip nat {
|
||||
chain postrouting {
|
||||
}
|
||||
chain prerouting {
|
||||
}
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
# Set your time zone.
|
||||
time.timeZone = "Europe/Amsterdam";
|
||||
|
||||
# Select internationalisation properties.
|
||||
i18n.defaultLocale = "en_US.UTF-8";
|
||||
|
||||
# Define a user account. Don't forget to set a password with ‘passwd’.
|
||||
users.users.root.hashedPassword = "$y$j9T$WXvLAUqArJJusuC017FCW0$.rfMOeyx/BsClkJFi5hLcynrSk.njWmfiB6Uy.9th3A";
|
||||
|
||||
users.users.procolix = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
|
||||
hashedPassword = "$y$j9T$UH8Dh/poTCCZ3PXk43au6/$iYen8VUEVvv7SIPqteNtTPKktLxny3TbqvjUwhvi.6B";
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAotfCIjLoDlHOe+++kVS1xiBPaS8mC5FypgrxDrDVst6SHxMTca2+IScMajzUZajenvNAoZOwIsyAPacT8OHeyFvV5Y7G874Qa+cZVqJxLht9gdXxr1GNabU3RfhhCh272dUeIKIqfgsRsM2HzdnZCMDavS1Yo+f+RhhHhnJIua+NdVFo21vPrpsz+Cd0M1NhojARLajrTHvEXW0KskUnkbfgxT0vL9jeRZxdgMS+a9ZoR5dbzOxQHWfbP8N04Xc+7CweMlvKwlWuAE/xDb5XLNHorfGWFvZuVhptJN8jPaaVS25wsmsF5IbaAuSZfzCtBdFQhIloUhy0L6ZisubHjQ== procolix@sshnode1"
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAuT3C0f3nyQ7SwUvXcFmEYEgwL+crY6iK0Bhoi9yfn4soz3fhfMKyKSwc/0RIlRnrz3xnkyJiV0vFeU7AC1ixbGCS3T9uc0G1x0Yedd9n2yR8ZJmkdyfjZ5KE4YvqZ3f6UZn5Mtj+7tGmyp+ee+clLSHzsqeyDiX0FIgFmqiiAVJD6qeKPFAHeWz9b2MOXIBIw+fSLOpx0rosCgesOmPc8lgFvo+dMKpSlPkCuGLBPj2ObT4sLjc98NC5z8sNJMu3o5bMbiCDR9JWgx9nKj+NlALwk3Y/nzHSL/DNcnP5vz2zbX2CBKjx6ju0IXh6YKlJJVyMsH9QjwYkgDQVmy8amQ== procolix@sshnode2"
|
||||
];
|
||||
packages = with pkgs; [
|
||||
];
|
||||
};
|
||||
|
||||
users.users.niols = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEElREJN0AC7lbp+5X204pQ5r030IbgCllsIxyU3iiKY niols@wallace"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBkQXv/VRZLfV0wNN9PHedmKWyAIfpPUCdaznHZNIDkS niols@orianne/fediversity"
|
||||
];
|
||||
packages = with pkgs; [
|
||||
];
|
||||
};
|
||||
|
||||
users.users.valentin = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOJzgwAYAoMexc1fBJxU08YmsiU9T4Ua8QFeE4/kZNZ5"
|
||||
];
|
||||
packages = with pkgs; [
|
||||
];
|
||||
};
|
||||
|
||||
# List packages installed in system profile. To search, run:
|
||||
# $ nix search wget
|
||||
environment.systemPackages = with pkgs; [
|
||||
(pkgs.vim_configurable.customize {
|
||||
name = "vim";
|
||||
vimrcConfig.packages.myplugins = with pkgs.vimPlugins; {
|
||||
start = [ vim-nix ]; # load plugin on startup
|
||||
};
|
||||
vimrcConfig.customRC = ''
|
||||
" your custom vimrc
|
||||
set nocompatible
|
||||
set backspace=indent,eol,start
|
||||
" Turn on syntax highlighting by default
|
||||
syntax on
|
||||
" ...
|
||||
'';
|
||||
})
|
||||
wget
|
||||
];
|
||||
|
||||
# List services that you want to enable:
|
||||
|
||||
# Enable the OpenSSH daemon.
|
||||
services.openssh.enable = true;
|
||||
|
||||
# This value determines the NixOS release from which the default
|
||||
# settings for stateful data, like file locations and database versions
|
||||
# on your system were taken. It‘s perfectly fine and recommended to leave
|
||||
# this value at the release version of the first install of this system.
|
||||
# Before changing this value read the documentation for this option
|
||||
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
|
||||
system.stateVersion = "24.05"; # Did you read the comment?
|
||||
}
|
0
.gitignore → matrix/.gitignore
vendored
51
matrix/README.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
gitea: none
|
||||
include_toc: true
|
||||
---
|
||||
|
||||
# A complete Matrix installation
|
||||
|
||||
This is going to be a Matrix installation with all bells and whistles. Not
|
||||
just the server, but every other bit that you need or want.
|
||||
|
||||
We're building it with workers, so it will scale.
|
||||
|
||||
## Overview
|
||||
|
||||
A complete Matrix environment consists of many parts. Other than the Matrix
|
||||
server itself (Synapse) there are all kinds of other things that we need:
|
||||
|
||||
* [Synapse](https://element-hq.github.io/synapse/latest/)
|
||||
* Webclient ([Element Web](https://github.com/element-hq/element-web))
|
||||
* [Element Call](https://github.com/element-hq/element-call) for audio/video
|
||||
conferencing
|
||||
* Management with [Synapse-Admin](https://github.com/Awesome-Technologies/synapse-admin)
|
||||
* Moderation with [Draupnir](https://github.com/the-draupnir-project/Draupnir)
|
||||
* [Consent
|
||||
tracking](https://element-hq.github.io/synapse/latest/consent_tracking.html)
|
||||
* Authentication via
|
||||
[OpenID](https://element-hq.github.io/synapse/latest/openid.html)
|
||||
* Several [bridges](https://matrix.org/ecosystem/bridges/)
|
||||
|
||||
|
||||
# Synapse
|
||||
|
||||
This is the core component: the Matrix server itself.
|
||||
|
||||
Installation and configuration is documented under [synapse](synapse).
|
||||
|
||||
|
||||
# TURN
|
||||
|
||||
We may need a TURN server, and we'll use
|
||||
[coturn](https://github.com/coturn/coturn) for that.
|
||||
|
||||
It's apparently also possible to use the built-in TURN server in Livekit,
|
||||
which we'll use if we use [Element Call](call). It's either/or, so make sure
|
||||
you pick the right approach.
|
||||
|
||||
|
||||
# Wiki
|
||||
|
||||
Of course there's a wiki in this repository.
|
||||
|
|
@ -112,6 +112,30 @@ After changing the database, restart Synapse and check whether it can connect
|
|||
and create the tables it needs.
|
||||
|
||||
|
||||
# Create admin
|
||||
|
||||
Synapse doesn't create an admin account at install time, so you'll have to do
|
||||
that yourself.
|
||||
|
||||
You need to set a `registration_shared_secret` for this, set that in
|
||||
`conf.d/keys.yaml` like this:
|
||||
|
||||
```
|
||||
registration_shared_secret: xxxx
|
||||
```
|
||||
|
||||
You can create such a key by running `pwgen -csn 52 1`. Restart Synapse after
|
||||
setting this key.
|
||||
|
||||
Now create an admin user. Login and issue this command:
|
||||
|
||||
```
|
||||
register_new_matrix_user -u admin -a -c /etc/matrix-synapse/conf.d/keys.yaml
|
||||
```
|
||||
|
||||
This will ask for a password, choose a safe one.
|
||||
|
||||
|
||||
# Logging
|
||||
|
||||
Logging is configured in `log.yaml`. Some logging should go to systemd, the
|
275
server/configuration.nix
Normal file
|
@ -0,0 +1,275 @@
|
|||
|
||||
# Edit this configuration file to define what should be installed on
|
||||
# your system. Help is available in the configuration.nix(5) man page
|
||||
# and in the NixOS manual (accessible by running ‘nixos-help’).
|
||||
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ # Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
# Use the systemd-boot EFI boot loader.
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."www.oid.foundation" = {
|
||||
useACMEHost = "oid.foundation";
|
||||
forceSSL = true;
|
||||
globalRedirect = "oid.foundation";
|
||||
};
|
||||
services.nginx.virtualHosts."oid.foundation" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
root = "/var/www/oid.foundation";
|
||||
|
||||
};
|
||||
services.nginx.virtualHosts."fediversity.eu" = {
|
||||
useACMEHost = "www.fediversity.eu";
|
||||
forceSSL = true;
|
||||
globalRedirect = "www.fediversity.eu";
|
||||
locations."/.well-known/matrix/client" = {
|
||||
extraConfig = ''
|
||||
return 200 '{"m.homeserver": {"base_url": "https://matrix.fediversity.eu", "public_baseurl": "https://matrix.fediversity.eu"}}';
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization";
|
||||
'';
|
||||
};
|
||||
locations."/.well-known/matrix/server" = {
|
||||
extraConfig = ''
|
||||
return 200 '{"m.server": "matrix.fediversity.eu:443"}';
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization";
|
||||
'';
|
||||
};
|
||||
};
|
||||
services.nginx.virtualHosts."www.fediversity.eu" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
root = "/var/www/www.fediversity.eu/fediversity.eu/public";
|
||||
locations."/.well-known/matrix/client" = {
|
||||
extraConfig = ''
|
||||
return 200 '{"m.homeserver": {"base_url": "https://matrix.fediversity.eu", "public_baseurl": "https://matrix.fediversity.eu"}}';
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization";
|
||||
'';
|
||||
};
|
||||
locations."/.well-known/matrix/server" = {
|
||||
extraConfig = ''
|
||||
return 200 '{"m.server": "matrix.fediversity.eu:443"}';
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization";
|
||||
'';
|
||||
};
|
||||
};
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "beheer@procolix.com";
|
||||
certs."www.fediversity.eu".extraDomainNames = [ "fediversity.eu" ];
|
||||
certs."oid.foundation".extraDomainNames = [ "www.oid.foundation" ];
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostName = "vm02117";
|
||||
domain = "procolix.com";
|
||||
interfaces = {
|
||||
eth0 = {
|
||||
ipv4 = {
|
||||
addresses = [
|
||||
{
|
||||
address = "185.206.232.106";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
ipv6 = {
|
||||
addresses = [
|
||||
{
|
||||
address = "2a00:51c0:12:1201::106";
|
||||
prefixLength = 64;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
defaultGateway = {
|
||||
address = "185.206.232.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
defaultGateway6 = {
|
||||
address = "2a00:51c0:12:1201::1";
|
||||
interface = "eth0";
|
||||
};
|
||||
nameservers = [ "95.215.185.6" "95.215.185.7" ];
|
||||
firewall.enable = false;
|
||||
nftables = {
|
||||
enable = true;
|
||||
ruleset = ''
|
||||
#!/usr/sbin/nft -f
|
||||
|
||||
flush ruleset
|
||||
|
||||
########### define usefull variables here #####################
|
||||
define wan = eth0
|
||||
define ssh_allow = {
|
||||
83.161.147.127/32, # host801 ipv4
|
||||
95.215.185.92/32, # host088 ipv4
|
||||
95.215.185.211/32, # host089 ipv4
|
||||
95.215.185.34/32, # nagios2 ipv4
|
||||
95.215.185.181/32, # ansible.procolix.com
|
||||
95.215.185.235, # ansible-hq
|
||||
185.206.232.76, # vpn4
|
||||
}
|
||||
define snmp_allow = {
|
||||
95.215.185.31/32, # cacti ipv4
|
||||
}
|
||||
define nrpe_allow = {
|
||||
95.215.185.34/32, # nagios2 ipv4
|
||||
}
|
||||
|
||||
########### here starts the automated bit #####################
|
||||
table inet filter {
|
||||
chain input {
|
||||
type filter hook input priority 0;
|
||||
policy drop;
|
||||
|
||||
# established/related connections
|
||||
ct state established,related accept
|
||||
ct state invalid drop
|
||||
|
||||
# Limit ping requests.
|
||||
ip protocol icmp icmp type echo-request limit rate over 10/second burst 50 packets drop
|
||||
ip6 nexthdr icmpv6 icmpv6 type echo-request limit rate over 10/second burst 50 packets drop
|
||||
|
||||
# loopback interface
|
||||
iifname lo accept
|
||||
|
||||
# icmp
|
||||
ip protocol icmp icmp type { destination-unreachable, echo-reply, echo-request, source-quench, time-exceeded } accept
|
||||
# Without the nd-* ones ipv6 will not work.
|
||||
ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, echo-reply, echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert, packet-too-big, parameter-problem, time-exceeded } accept
|
||||
|
||||
# open tcp ports: sshd (22)
|
||||
ip saddr $ssh_allow tcp dport {ssh} accept
|
||||
|
||||
# open tcp ports: snmp (161)
|
||||
ip saddr $snmp_allow udp dport {snmp} accept
|
||||
|
||||
# open tcp ports: nrpe (5666)
|
||||
ip saddr $nrpe_allow tcp dport {nrpe} accept
|
||||
|
||||
# open tcp ports: http (80,443)
|
||||
tcp dport {http,https} accept
|
||||
}
|
||||
chain forward {
|
||||
type filter hook forward priority 0;
|
||||
}
|
||||
chain output {
|
||||
type filter hook output priority 0;
|
||||
}
|
||||
}
|
||||
|
||||
table ip nat {
|
||||
chain postrouting {
|
||||
}
|
||||
chain prerouting {
|
||||
}
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
# Set your time zone.
|
||||
time.timeZone = "Europe/Amsterdam";
|
||||
|
||||
# Select internationalisation properties.
|
||||
i18n.defaultLocale = "en_US.UTF-8";
|
||||
|
||||
# Define a user account. Don't forget to set a password with ‘passwd’.
|
||||
users.users.procolix = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAotfCIjLoDlHOe+++kVS1xiBPaS8mC5FypgrxDrDVst6SHxMTca2+IScMajzUZajenvNAoZOwIsyAPacT8OHeyFvV5Y7G874Qa+cZVqJxLht9gdXxr1GNabU3RfhhCh272dUeIKIqfgsRsM2HzdnZCMDavS1Yo+f+RhhHhnJIua+NdVFo21vPrpsz+Cd0M1NhojARLajrTHvEXW0KskUnkbfgxT0vL9jeRZxdgMS+a9ZoR5dbzOxQHWfbP8N04Xc+7CweMlvKwlWuAE/xDb5XLNHorfGWFvZuVhptJN8jPaaVS25wsmsF5IbaAuSZfzCtBdFQhIloUhy0L6ZisubHjQ== procolix@sshnode1"
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAuT3C0f3nyQ7SwUvXcFmEYEgwL+crY6iK0Bhoi9yfn4soz3fhfMKyKSwc/0RIlRnrz3xnkyJiV0vFeU7AC1ixbGCS3T9uc0G1x0Yedd9n2yR8ZJmkdyfjZ5KE4YvqZ3f6UZn5Mtj+7tGmyp+ee+clLSHzsqeyDiX0FIgFmqiiAVJD6qeKPFAHeWz9b2MOXIBIw+fSLOpx0rosCgesOmPc8lgFvo+dMKpSlPkCuGLBPj2ObT4sLjc98NC5z8sNJMu3o5bMbiCDR9JWgx9nKj+NlALwk3Y/nzHSL/DNcnP5vz2zbX2CBKjx6ju0IXh6YKlJJVyMsH9QjwYkgDQVmy8amQ== procolix@sshnode2"
|
||||
];
|
||||
packages = with pkgs; [
|
||||
];
|
||||
};
|
||||
users.users.laurens = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBbK4ZB0Xnpf8yyK4QOI2HvjgQINI3GKi7/O2VEsYXUb laurenshof@Laurenss-MacBook-Air.local"
|
||||
];
|
||||
packages = with pkgs; [
|
||||
];
|
||||
};
|
||||
users.users.valentin = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJg5TlS1NGCRZwMjDgBkXeFUXqooqRlM8fJdBAQ4buPg"
|
||||
];
|
||||
packages = with pkgs; [
|
||||
];
|
||||
};
|
||||
# List packages installed in system profile. To search, run:
|
||||
# $ nix search wget
|
||||
environment.systemPackages = with pkgs; [
|
||||
(pkgs.vim_configurable.customize {
|
||||
name = "vim";
|
||||
vimrcConfig.packages.myplugins = with pkgs.vimPlugins; {
|
||||
start = [ vim-nix ]; # load plugin on startup
|
||||
};
|
||||
vimrcConfig.customRC = ''
|
||||
" your custom vimrc
|
||||
set nocompatible
|
||||
set backspace=indent,eol,start
|
||||
" Turn on syntax highlighting by default
|
||||
syntax on
|
||||
" ...
|
||||
'';
|
||||
})
|
||||
wget
|
||||
git
|
||||
hugo
|
||||
go
|
||||
nodejs
|
||||
];
|
||||
|
||||
# List services that you want to enable:
|
||||
|
||||
# Enable the OpenSSH daemon.
|
||||
services.openssh.enable = true;
|
||||
|
||||
# Enable xe-guest-utilities
|
||||
services.xe-guest-utilities.enable = true;
|
||||
|
||||
# Copy the NixOS configuration file and link it from the resulting system
|
||||
# (/run/current-system/configuration.nix). This is useful in case you
|
||||
# accidentally delete configuration.nix.
|
||||
system.copySystemConfiguration = true;
|
||||
|
||||
# This value determines the NixOS release from which the default
|
||||
# settings for stateful data, like file locations and database versions
|
||||
# on your system were taken. It‘s perfectly fine and recommended to leave
|
||||
# this value at the release version of the first install of this system.
|
||||
# Before changing this value read the documentation for this option
|
||||
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
|
||||
system.stateVersion = "23.11"; # Did you read the comment?
|
||||
|
||||
}
|
||||
|
34
server/hardware-configuration.nix
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||
# and may be overwritten by future invocations. Please make changes
|
||||
# to /etc/nixos/configuration.nix instead.
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports = [ ];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "sr_mod" "xen_blkfront" ];
|
||||
boot.initrd.kernelModules = [ "dm-snapshot" ];
|
||||
boot.kernelModules = [ ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" =
|
||||
{ device = "/dev/disk/by-uuid/5aa392a8-c9ba-4181-976f-b3b30db350a1";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot" =
|
||||
{ device = "/dev/disk/by-uuid/FC6D-610F";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
swapDevices = [ ];
|
||||
|
||||
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
|
||||
# (the default) this is the recommended approach. When using systemd-networkd it's
|
||||
# still possible to use this option, but it's recommended to use it in conjunction
|
||||
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
|
||||
networking.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.enX0.useDHCP = lib.mkDefault true;
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
}
|
1
services/.envrc
Normal file
|
@ -0,0 +1 @@
|
|||
use flake
|
8
services/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
nixos.qcow2
|
||||
result*
|
||||
.direnv
|
||||
.nixos-test-history
|
||||
*screenshot.png
|
||||
output
|
||||
todo
|
||||
|
127
services/README.md
Normal file
|
@ -0,0 +1,127 @@
|
|||
# Fediverse VMs
|
||||
|
||||
This repo is, for now, an attempt to familiarize myself with NixOS options for Fediverse applications, and build up a configuration layer that will set most of the relevant options for you (in a semi-opinionated way) given some high-level configuration. The goal is something in the same vein as [nixos-mailserver](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver) but for fediversity.
|
||||
|
||||
Eventually, this will be tailored to high-throughput multi-machine setups. For now, it's just a small set of configurations to run in VMs.
|
||||
|
||||
## Running the VMs
|
||||
|
||||
you can build a VM using
|
||||
|
||||
```bash
|
||||
nixos-rebuild build-vm --flake .#<vm_name>
|
||||
```
|
||||
|
||||
where `<vm_name>` is one of `mastodon`, `peertube`, `pixelfed`, or `all`
|
||||
|
||||
and then run it with
|
||||
```bash
|
||||
./result/bin/run-nixos-vm
|
||||
```
|
||||
|
||||
After the machine boots, you should be dropped into a root shell.
|
||||
|
||||
Note that state will be persisted in the `nixos.cqow2` file. Delete that and restart the VM to reset the state.
|
||||
|
||||
With the VM running, you can then access the apps on your local machine's web browser (using the magic of port forwarding) at the following addresses
|
||||
|
||||
NOTE: it sometimes takes a while for the services to start up, and in the meantime you will get 502 Bad Gateway.
|
||||
|
||||
- Mastodon: through the reverse proxy at <https://mastodon.localhost:8443> and directly at <http://mastodon.localhost:55001>
|
||||
- You can create accounts on the machine itself by running `mastodon-tootctl accounts create test --email test@test.com --confirmed --approve`
|
||||
- Account-related activities (logging in/out; preferences) can only be done on the insecure direct page <http://mastodon.localhost:55001>
|
||||
- After you've logged in, you can go back to the secure page and you will remain logged in
|
||||
- some operations may remove the port number from the URL. You'll have to add that back in manually
|
||||
|
||||
- PeerTube: <http://peertube.localhost:9000>
|
||||
- The root account can be accessed with username "root". The password can be obtained by running the following command on the VM:
|
||||
```bash
|
||||
journalctl -u peertube | perl -ne '/password: (.*)/ && print $1'
|
||||
```
|
||||
- Creating other accounts has to be enabled via the admin interface. `Administration > Configuration > Basic > Enable Signup` or just add an account directly from `Administration > Create user`. But functionality can also be tested from the root account.
|
||||
|
||||
- Pixelfed: <http://pixelfed.localhost:8000>
|
||||
- Account creation via the web interface won't work until we figure out email
|
||||
- For now, they can be created on the VM command line
|
||||
```bash
|
||||
pixelfed-manage user:create --name=test --username=test --email=test@test.com --password=testtest --confirm_email=1
|
||||
```
|
||||
# Building an installer image
|
||||
|
||||
Build an installer image for the desired configuration, e.g. for `peertube`:
|
||||
|
||||
```bash
|
||||
nix build .#installers.peertube
|
||||
```
|
||||
|
||||
Upload the image in `./result` to Proxmox when creating a VM.
|
||||
Booting the image will format the disk and install NixOS with the desired configuration.
|
||||
|
||||
# Deploying an updated machine configuration
|
||||
|
||||
> TODO: There is currently no way to specify an actual target machine by name.
|
||||
|
||||
Assuming you have SSH configuration with access to the remote `root` user stored for a machine called e.g. `peertube`, deploy the configuration by the same name:
|
||||
|
||||
```bash
|
||||
nix run .#deploy.peertube
|
||||
```
|
||||
|
||||
## debugging notes
|
||||
|
||||
- it is sometimes useful to `cat result/bin/run-nixos-vm` to see what's really going on (e.g. which ports are getting forwarded)
|
||||
- relevant systemd services:
|
||||
- mastodon-web.service
|
||||
- peertube.service
|
||||
- the `garage` CLI command gives information about garage storage, but cannot be used to actually inspect the contents. use `mc` (minio) for that
|
||||
- run `mc alias set garage http://s3.garage.localhost:3900 --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY`
|
||||
- in the chromium devtools, you can go to the networking tab and change things like response headers in a way that persists through reloads. this is much faster iteration time if that's what you need to epxeriment with.
|
||||
|
||||
## NixOS Tests
|
||||
|
||||
Tests live in the aptly named `tests/` directory, and can be accessed at the flake URI `.#checks.<system>.<test-name>` e.g. `nix build .#checks.x86_64-linux.mastodon-garage`.
|
||||
They can also be run interactively with
|
||||
```
|
||||
nix build .#checks.<system>.<test>.driverInteractive
|
||||
./result/bin/nixos-test-driver 2>output
|
||||
````
|
||||
you can `less output` and then `F` from a different terminal to follow along.
|
||||
|
||||
These tests are also equiped with the same port forwarding as the VMs, so when running interactively you should be able to access services through a browser running on your machine.
|
||||
|
||||
While running interactively, `rebuildableTests` allows you to modify the test nodes and then redeploy without restarting the test and waiting for the VMs to start up again. To do this you must start the jumphost by running `redeploy_jumphost.start()` inside the driver. Then from the command line
|
||||
|
||||
```
|
||||
nix build .#checks.<system>.<test>.driverInteractive
|
||||
./result/bin/rebuild
|
||||
```
|
||||
|
||||
# questions
|
||||
|
||||
- what is meant to be shared between instances?
|
||||
- this is relevant to the security model. If garage is being shared between instances, we have to be careful having configurations depend on each other.
|
||||
- they are to be shared, BUT the user will have no direct control over configuration.
|
||||
|
||||
# resources
|
||||
|
||||
- Tutorial for setting up better logging: https://krisztianfekete.org/self-hosting-mastodon-on-nixos-a-proof-of-concept/
|
||||
- Setting up mastodon development environment: https://docs.joinmastodon.org/dev/setup/
|
||||
|
||||
- Tutorial for PeerTube that doesn't use `createLocally`: https://nixos.wiki/wiki/PeerTube
|
||||
|
||||
- garage settings for specific apps: https://garagehq.deuxfleurs.fr/documentation/connect/apps/
|
||||
|
||||
- pixelfed has terrible / mostly non-existent documentation)
|
||||
|
||||
- for when we start worry about scaling up: https://docs.joinmastodon.org/admin/scaling/
|
||||
|
||||
# notes
|
||||
|
||||
When mastodon is running in production mode, we have a few problems:
|
||||
- you have to click "accept the security risk"
|
||||
- it takes a while for the webpage to come online. Until then you see "502 Bad Gateway"
|
||||
- email sent from the mastodon instance (e.g. for account confirmation) should be accessible at <https://mastodon.localhost:55001/letter_opener>, but it's not working.
|
||||
- mastodon is trying to fetch `missing.png` without ssl (`http://`). This isn't allowed, and i'm not sure why it's doing it.
|
||||
- mastodon is trying to fetch `custom.css` from https://mastodon.localhost (no port), which is not the configured `LOCAL_DOMAIN`, so it's unclear why.
|
||||
|
||||
NixOS tests do not take the configuration from `virtualisation.vmVariant`. This seems like an oversight since people don't tend to mix normal NixOS configurations with the ones they're using for tests. This should be pretty easy to rectify upstream.
|
13
services/deploy.nix
Normal file
|
@ -0,0 +1,13 @@
|
|||
{ writeShellApplication }:
|
||||
name: config:
|
||||
writeShellApplication {
|
||||
name = "deploy";
|
||||
text = ''
|
||||
result="$(nix build --print-out-paths ${./.}#nixosConfigurations#${name} --eval-store auto --store ssh-ng://${name})"
|
||||
# shellcheck disable=SC2087
|
||||
ssh ${name} << EOF
|
||||
nix-env -p /nix/var/nix/profiles/system --set "$result"
|
||||
"$result"/bin/switch-to-configuration switch
|
||||
EOF
|
||||
'';
|
||||
}
|
36
services/disk-layout.nix
Normal file
|
@ -0,0 +1,36 @@
|
|||
{ ... }:
|
||||
{
|
||||
disko.devices.disk.main = {
|
||||
device = "/dev/sda";
|
||||
type = "disk";
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
MBR = {
|
||||
priority = 0;
|
||||
size = "1M";
|
||||
type = "EF02";
|
||||
};
|
||||
ESP = {
|
||||
priority = 1;
|
||||
size = "500M";
|
||||
type = "EF00";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "vfat";
|
||||
mountpoint = "/boot";
|
||||
};
|
||||
};
|
||||
root = {
|
||||
priority = 2;
|
||||
size = "100%";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "ext4";
|
||||
mountpoint = "/";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
118
services/fediversity/default.nix
Normal file
|
@ -0,0 +1,118 @@
|
|||
{ lib, config, ... }:
|
||||
|
||||
let
|
||||
inherit (builtins) toString;
|
||||
inherit (lib) mkOption mkEnableOption mkForce;
|
||||
inherit (lib.types) types;
|
||||
|
||||
in {
|
||||
imports = [
|
||||
./garage.nix
|
||||
./mastodon.nix
|
||||
./pixelfed.nix
|
||||
./peertube.nix
|
||||
];
|
||||
|
||||
options = {
|
||||
fediversity = {
|
||||
enable = mkEnableOption "the collection of services bundled under Fediversity";
|
||||
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
root domain for the Fediversity services
|
||||
|
||||
For instance, if this option is set to `foo.example.com`, then
|
||||
Pixelfed might be under `pixelfed.foo.example.com`.
|
||||
'';
|
||||
};
|
||||
|
||||
mastodon.enable = mkEnableOption "default Fediversity Mastodon configuration";
|
||||
pixelfed.enable = mkEnableOption "default Fediversity Pixelfed configuration";
|
||||
peertube.enable = mkEnableOption "default Fediversity PeerTube configuration";
|
||||
|
||||
internal = mkOption {
|
||||
description = "options that are only meant to be used internally; change at your own risk";
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options = {
|
||||
garage = {
|
||||
api = {
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
default = "s3.garage.${config.fediversity.domain}";
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 3900;
|
||||
};
|
||||
url = mkOption {
|
||||
type = types.str;
|
||||
default = "http://${config.fediversity.internal.garage.api.domain}:${toString config.fediversity.internal.garage.api.port}";
|
||||
};
|
||||
};
|
||||
|
||||
rpc = {
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 3901;
|
||||
};
|
||||
};
|
||||
|
||||
web = {
|
||||
rootDomain = mkOption {
|
||||
type = types.str;
|
||||
default = "web.garage.${config.fediversity.domain}";
|
||||
};
|
||||
internalPort = mkOption {
|
||||
type = types.int;
|
||||
default = 3902;
|
||||
};
|
||||
domainForBucket = mkOption {
|
||||
type = types.functionTo types.str;
|
||||
default = bucket: "${bucket}.${config.fediversity.internal.garage.web.rootDomain}";
|
||||
};
|
||||
urlForBucket = mkOption {
|
||||
type = types.functionTo types.str;
|
||||
default = bucket: "http://${config.fediversity.internal.garage.web.domainForBucket bucket}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
## REVIEW: Do we want to recreate options under
|
||||
## `fediversity.internal` or would we rather use the options from
|
||||
## the respective services? See Taeer's comment:
|
||||
## https://git.fediversity.eu/taeer/simple-nixos-fediverse/pulls/22#issuecomment-124
|
||||
pixelfed.domain = mkOption {
|
||||
type = types.str;
|
||||
default = "pixelfed.${config.fediversity.domain}";
|
||||
};
|
||||
mastodon.domain = mkOption {
|
||||
type = types.str;
|
||||
default = "mastdodon.${config.fediversity.domain}";
|
||||
};
|
||||
peertube.domain = mkOption {
|
||||
type = types.str;
|
||||
default = "peertube.${config.fediversity.domain}";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
## FIXME: This should clearly go somewhere else; and we should have a
|
||||
## `staging` vs. `production` setting somewhere.
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "nicolas.jeannerod+fediversity@moduscreate.com";
|
||||
# defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory";
|
||||
};
|
||||
|
||||
## NOTE: For a one-machine deployment, this removes the need to provide an
|
||||
## `s3.garage.<domain>` domain. However, this will quickly stop working once
|
||||
## we go to multi-machines deployment.
|
||||
fediversity.internal.garage.api.domain = mkForce "s3.garage.localhost";
|
||||
};
|
||||
}
|
213
services/fediversity/garage.nix
Normal file
|
@ -0,0 +1,213 @@
|
|||
let
|
||||
# generate one using openssl (somehow)
|
||||
# XXX: when importing, garage tells you importing is only meant for keys previously generated by garage. is it okay to generate them using openssl? it seems to work fine
|
||||
snakeoil_key = {
|
||||
id = "GK22a15201acacbd51cd43e327";
|
||||
secret = "82b2b4cbef27bf8917b350d5b10a87c92fa9c8b13a415aeeea49726cf335d74e";
|
||||
};
|
||||
in
|
||||
|
||||
# TODO: expand to a multi-machine setup
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (builtins) toString;
|
||||
inherit (lib) types mkOption mkEnableOption optionalString concatStringsSep;
|
||||
inherit (lib.strings) escapeShellArg;
|
||||
cfg = config.services.garage;
|
||||
fedicfg = config.fediversity.internal.garage;
|
||||
concatMapAttrs = scriptFn: attrset: concatStringsSep "\n" (lib.mapAttrsToList scriptFn attrset);
|
||||
ensureBucketScriptFn = bucket: { website, aliases, corsRules }:
|
||||
let
|
||||
bucketArg = escapeShellArg bucket;
|
||||
corsRulesJSON = escapeShellArg (builtins.toJSON {
|
||||
CORSRules = [{
|
||||
AllowedHeaders = corsRules.allowedHeaders;
|
||||
AllowedMethods = corsRules.allowedMethods;
|
||||
AllowedOrigins = corsRules.allowedOrigins;
|
||||
}];
|
||||
});
|
||||
in ''
|
||||
# garage bucket info tells us if the bucket already exists
|
||||
garage bucket info ${bucketArg} || garage bucket create ${bucketArg}
|
||||
|
||||
# TODO: should this --deny the website if `website` is false?
|
||||
${optionalString website ''
|
||||
garage bucket website --allow ${bucketArg}
|
||||
''}
|
||||
|
||||
${concatStringsSep "\n" (map (alias: ''
|
||||
garage bucket alias ${bucketArg} ${escapeShellArg alias}
|
||||
'') aliases)}
|
||||
|
||||
${optionalString corsRules.enable ''
|
||||
garage bucket allow --read --write --owner ${bucketArg} --key tmp
|
||||
# TODO: endpoin-url should not be hard-coded
|
||||
aws --region ${cfg.settings.s3_api.s3_region} --endpoint-url ${fedicfg.api.url} s3api put-bucket-cors --bucket ${bucketArg} --cors-configuration ${corsRulesJSON}
|
||||
garage bucket deny --read --write --owner ${bucketArg} --key tmp
|
||||
''}
|
||||
'';
|
||||
ensureBucketsScript = concatMapAttrs ensureBucketScriptFn cfg.ensureBuckets;
|
||||
ensureAccessScriptFn = key: bucket: { read, write, owner }: ''
|
||||
garage bucket allow ${optionalString read "--read"} ${optionalString write "--write"} ${optionalString owner "--owner"} \
|
||||
${escapeShellArg bucket} --key ${escapeShellArg key}
|
||||
'';
|
||||
ensureKeyScriptFn = key: {id, secret, ensureAccess}: ''
|
||||
## FIXME: Check whether the key exist and skip this step if that is the case. Get rid of this `|| :`
|
||||
garage key import --yes -n ${escapeShellArg key} ${escapeShellArg id} ${escapeShellArg secret} || :
|
||||
${concatMapAttrs (ensureAccessScriptFn key) ensureAccess}
|
||||
'';
|
||||
ensureKeysScript = concatMapAttrs ensureKeyScriptFn cfg.ensureKeys;
|
||||
in
|
||||
|
||||
{
|
||||
# add in options to ensure creation of buckets and keys
|
||||
options = {
|
||||
services.garage = {
|
||||
ensureBuckets = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
website = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
# I think setting corsRules should allow another website to show images from your bucket
|
||||
corsRules = {
|
||||
enable = mkEnableOption "CORS Rules";
|
||||
allowedHeaders = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
allowedMethods = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
allowedOrigins = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
aliases = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
};
|
||||
ensureKeys = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
# TODO: these should be managed as secrets, not in the nix store
|
||||
options = {
|
||||
id = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
secret = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
# TODO: assert at least one of these is true
|
||||
# NOTE: this currently needs to be done at the top level module
|
||||
ensureAccess = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
read = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
write = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
owner = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.fediversity.enable {
|
||||
environment.systemPackages = [ pkgs.minio-client pkgs.awscli ];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
fedicfg.rpc.port
|
||||
];
|
||||
services.garage = {
|
||||
enable = true;
|
||||
package = pkgs.garage_0_9;
|
||||
settings = {
|
||||
replication_mode = "none";
|
||||
# TODO: use a secret file
|
||||
rpc_secret = "d576c4478cc7d0d94cfc127138cbb82018b0155c037d1c827dfb6c36be5f6625";
|
||||
# TODO: why does this have to be set? is there not a sensible default?
|
||||
rpc_bind_addr = "[::]:${toString fedicfg.rpc.port}";
|
||||
rpc_public_addr = "[::1]:${toString fedicfg.rpc.port}";
|
||||
s3_api.api_bind_addr = "[::]:${toString fedicfg.api.port}";
|
||||
s3_web.bind_addr = "[::]:${toString fedicfg.web.internalPort}";
|
||||
s3_web.root_domain = ".${fedicfg.web.rootDomain}";
|
||||
index = "index.html";
|
||||
|
||||
s3_api.s3_region = "garage";
|
||||
s3_api.root_domain = ".${fedicfg.api.domain}";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts.${fedicfg.web.rootDomain} = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
serverAliases = lib.mapAttrsToList (bucket: _: fedicfg.web.domainForBucket bucket) cfg.ensureBuckets; ## TODO: use wildcard certificates?
|
||||
locations."/" = {
|
||||
proxyPass = "http://localhost:3902";
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.ensure-garage = {
|
||||
after = [ "garage.service" ];
|
||||
wantedBy = [ "garage.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
};
|
||||
path = [ cfg.package pkgs.perl pkgs.awscli ];
|
||||
script = ''
|
||||
set -xeuo pipefail
|
||||
|
||||
# Give Garage time to start up by waiting until somethings speaks HTTP
|
||||
# behind Garage's API URL.
|
||||
until ${pkgs.curl}/bin/curl -sio /dev/null ${fedicfg.api.url}; do sleep 1; done
|
||||
|
||||
# XXX: this is very sensitive to being a single instance
|
||||
# (doing the bare minimum to get garage up and running)
|
||||
# also, it's crazy that we have to parse command output like this
|
||||
# TODO: talk to garage maintainer about making this nicer to work with in Nix
|
||||
# before I do that though, I should figure out how setting it up across multiple machines will work
|
||||
GARAGE_ID=$(garage node id 2>/dev/null | perl -ne '/(.*)@.*/ && print $1')
|
||||
garage layout assign -z g1 -c 1G $GARAGE_ID
|
||||
LAYOUT_VER=$(garage layout show | perl -ne '/Current cluster layout version: (\d*)/ && print $1')
|
||||
garage layout apply --version $((LAYOUT_VER + 1))
|
||||
|
||||
# XXX: this is a hack because we want to write to the buckets here but we're not guaranteed any access keys
|
||||
# TODO: generate this key here rather than using a well-known key
|
||||
# TODO: if the key already exists, we get an error; hacked with this `|| :` which needs to be removed
|
||||
garage key import --yes -n tmp ${snakeoil_key.id} ${snakeoil_key.secret} || :
|
||||
export AWS_ACCESS_KEY_ID=${snakeoil_key.id};
|
||||
export AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret};
|
||||
|
||||
${ensureBucketsScript}
|
||||
${ensureKeysScript}
|
||||
|
||||
# garage doesn't like re-adding keys that once existed, so we can't delete / recreate it every time
|
||||
# garage key delete ${snakeoil_key.id} --yes
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
85
services/fediversity/mastodon.nix
Normal file
|
@ -0,0 +1,85 @@
|
|||
let
|
||||
snakeoil_key = {
|
||||
id = "GK3515373e4c851ebaad366558";
|
||||
secret = "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
|
||||
};
|
||||
in
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
|
||||
#### garage setup
|
||||
services.garage = {
|
||||
ensureBuckets = {
|
||||
mastodon = {
|
||||
website = true;
|
||||
corsRules = {
|
||||
enable = true;
|
||||
allowedHeaders = [ "*" ];
|
||||
allowedMethods = [ "GET" ];
|
||||
allowedOrigins = [ "*" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
ensureKeys = {
|
||||
mastodon = {
|
||||
inherit (snakeoil_key) id secret;
|
||||
ensureAccess = {
|
||||
mastodon = {
|
||||
read = true;
|
||||
write = true;
|
||||
owner = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
services.mastodon = {
|
||||
extraConfig = rec {
|
||||
S3_ENABLED = "true";
|
||||
# TODO: this shouldn't be hard-coded, it should come from the garage configuration
|
||||
S3_ENDPOINT = config.fediversity.internal.garage.api.url;
|
||||
S3_REGION = "garage";
|
||||
S3_BUCKET = "mastodon";
|
||||
# use <S3_BUCKET>.<S3_ENDPOINT>
|
||||
S3_OVERRIDE_PATH_STLE = "true";
|
||||
AWS_ACCESS_KEY_ID = snakeoil_key.id;
|
||||
AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
|
||||
S3_PROTOCOL = "http";
|
||||
S3_HOSTNAME = config.fediversity.internal.garage.web.rootDomain;
|
||||
# by default it tries to use "<S3_HOSTNAME>/<S3_BUCKET>"
|
||||
S3_ALIAS_HOST = "${S3_BUCKET}.${S3_HOSTNAME}";
|
||||
# SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/
|
||||
# TODO: can we set up ACLs with garage?
|
||||
S3_PERMISSION = "";
|
||||
};
|
||||
};
|
||||
|
||||
#### mastodon setup
|
||||
|
||||
# open up access to the mastodon web interface
|
||||
networking.firewall.allowedTCPPorts = [ 443 ];
|
||||
|
||||
services.mastodon = {
|
||||
enable = true;
|
||||
|
||||
localDomain = config.fediversity.internal.mastodon.domain;
|
||||
configureNginx = true;
|
||||
|
||||
# TODO: configure a mailserver so this works
|
||||
smtp = {
|
||||
fromAddress = "noreply@${config.fediversity.internal.mastodon.domain}";
|
||||
createLocally = false;
|
||||
};
|
||||
|
||||
# TODO: this is hardware-dependent. let's figure it out when we have hardware
|
||||
# streamingProcesses = 1;
|
||||
};
|
||||
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
preliminarySelfsigned = true;
|
||||
# TODO: configure a mailserver so we can set up acme
|
||||
# defaults.email = "test@example.com";
|
||||
};
|
||||
}
|
97
services/fediversity/peertube.nix
Normal file
|
@ -0,0 +1,97 @@
|
|||
let
|
||||
snakeoil_key = {
|
||||
id = "GK1f9feea9960f6f95ff404c9b";
|
||||
secret = "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
|
||||
};
|
||||
in
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
|
||||
networking.firewall.allowedTCPPorts = [ 80 9000 ];
|
||||
|
||||
services.garage = {
|
||||
ensureBuckets = {
|
||||
peertube-videos = {
|
||||
website = true;
|
||||
# TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
|
||||
corsRules = {
|
||||
enable = true;
|
||||
allowedHeaders = [ "*" ];
|
||||
allowedMethods = [ "GET" ];
|
||||
allowedOrigins = [ "*" ];
|
||||
};
|
||||
};
|
||||
# TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
|
||||
peertube-playlists = {
|
||||
website = true;
|
||||
corsRules = {
|
||||
enable = true;
|
||||
allowedHeaders = [ "*" ];
|
||||
allowedMethods = [ "GET" ];
|
||||
allowedOrigins = [ "*" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
ensureKeys = {
|
||||
peertube = {
|
||||
inherit (snakeoil_key) id secret;
|
||||
ensureAccess = {
|
||||
peertube-videos = {
|
||||
read = true;
|
||||
write = true;
|
||||
owner = true;
|
||||
};
|
||||
peertube-playlists = {
|
||||
read = true;
|
||||
write = true;
|
||||
owner = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.peertube = {
|
||||
enable = true;
|
||||
localDomain = config.fediversity.internal.peertube.domain;
|
||||
|
||||
# TODO: in most of nixpkgs, these are true by default. upstream that unless there's a good reason not to.
|
||||
redis.createLocally = true;
|
||||
database.createLocally = true;
|
||||
configureNginx = true;
|
||||
|
||||
settings = {
|
||||
object_storage = {
|
||||
enabled = true;
|
||||
endpoint = config.fediversity.internal.garage.api.url;
|
||||
region = "garage";
|
||||
|
||||
# not supported by garage
|
||||
# SEE: https://garagehq.deuxfleurs.fr/documentation/connect/apps/#peertube
|
||||
proxy.proxyify_private_files = false;
|
||||
|
||||
web_videos = rec {
|
||||
bucket_name = "peertube-videos";
|
||||
prefix = "";
|
||||
base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name;
|
||||
};
|
||||
videos = rec {
|
||||
bucket_name = "peertube-videos";
|
||||
prefix = "";
|
||||
base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name;
|
||||
};
|
||||
streaming_playlists = rec {
|
||||
bucket_name = "peertube-playlists";
|
||||
prefix = "";
|
||||
base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name;
|
||||
};
|
||||
};
|
||||
};
|
||||
serviceEnvironmentFile = "/etc/peertube-env";
|
||||
};
|
||||
environment.etc.peertube-env.text = ''
|
||||
AWS_ACCESS_KEY_ID=${snakeoil_key.id}
|
||||
AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret}
|
||||
'';
|
||||
}
|
18
services/fediversity/pixelfed-group-permissions.patch
Normal file
|
@ -0,0 +1,18 @@
|
|||
diff --git a/config/filesystems.php b/config/filesystems.php
|
||||
index 00254e93..fc1a58f3 100644
|
||||
--- a/config/filesystems.php
|
||||
+++ b/config/filesystems.php
|
||||
@@ -49,11 +49,11 @@ return [
|
||||
'permissions' => [
|
||||
'file' => [
|
||||
'public' => 0644,
|
||||
- 'private' => 0600,
|
||||
+ 'private' => 0640,
|
||||
],
|
||||
'dir' => [
|
||||
'public' => 0755,
|
||||
- 'private' => 0700,
|
||||
+ 'private' => 0750,
|
||||
],
|
||||
],
|
||||
],
|
84
services/fediversity/pixelfed.nix
Normal file
|
@ -0,0 +1,84 @@
|
|||
let
|
||||
snakeoil_key = {
|
||||
id = "GKb5615457d44214411e673b7b";
|
||||
secret = "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
|
||||
};
|
||||
in
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
|
||||
services.garage = {
|
||||
ensureBuckets = {
|
||||
pixelfed = {
|
||||
website = true;
|
||||
# TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
|
||||
corsRules = {
|
||||
enable = true;
|
||||
allowedHeaders = [ "*" ];
|
||||
allowedMethods = [ "GET" ];
|
||||
allowedOrigins = [ "*" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
ensureKeys = {
|
||||
pixelfed = {
|
||||
inherit (snakeoil_key) id secret;
|
||||
ensureAccess = {
|
||||
pixelfed = {
|
||||
read = true;
|
||||
write = true;
|
||||
owner = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.pixelfed = {
|
||||
enable = true;
|
||||
domain = config.fediversity.internal.pixelfed.domain;
|
||||
|
||||
# TODO: secrets management!!!
|
||||
secretFile = pkgs.writeText "secrets.env" ''
|
||||
APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA
|
||||
'';
|
||||
|
||||
## Taeer feels like this way of configuring Nginx is odd; there should
|
||||
## instead be a `services.pixefed.nginx.enable` option and the actual Nginx
|
||||
## configuration should be in `services.nginx`. See eg. `pretix`.
|
||||
##
|
||||
## TODO: If that indeed makes sense, upstream.
|
||||
nginx = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
# locations."/public/".proxyPass = "${config.fediversity.internal.garage.web.urlForBucket "pixelfed"}/public/";
|
||||
};
|
||||
};
|
||||
|
||||
services.pixelfed.settings = {
|
||||
## NOTE: This depends on the targets, eg. universities might want control
|
||||
## over who has an account. We probably want a universal
|
||||
## `fediversity.openRegistration` option.
|
||||
OPEN_REGISTRATION = true;
|
||||
|
||||
# DANGEROUSLY_SET_FILESYSTEM_DRIVER = "s3";
|
||||
FILESYSTEM_CLOUD = "s3";
|
||||
PF_ENABLE_CLOUD = true;
|
||||
AWS_ACCESS_KEY_ID = snakeoil_key.id;
|
||||
AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
|
||||
AWS_DEFAULT_REGION = "garage";
|
||||
AWS_URL = config.fediversity.internal.garage.web.urlForBucket "pixelfed";
|
||||
AWS_BUCKET = "pixelfed";
|
||||
AWS_ENDPOINT = config.fediversity.internal.garage.api.url;
|
||||
AWS_USE_PATH_STYLE_ENDPOINT = false;
|
||||
};
|
||||
|
||||
## Only ever run `pixelfed-data-setup` after `ensure-garage` has done its job.
|
||||
## Otherwise, everything crashed dramatically.
|
||||
systemd.services.pixelfed-data-setup = {
|
||||
after = [ "ensure-garage.service" ];
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
}
|
96
services/flake.lock
Normal file
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"nodes": {
|
||||
"disko": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1727347829,
|
||||
"narHash": "sha256-y7cW6TjJKy+tu7efxeWI6lyg4VVx/9whx+OmrhmRShU=",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "1879e48907c14a70302ff5d0539c3b9b6f97feaa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1725194671,
|
||||
"narHash": "sha256-tLGCFEFTB5TaOKkpfw3iYT9dnk4awTP/q4w+ROpMfuw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b833ff01a0d694b910daca6e2ff4a3f26dee478c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-latest": {
|
||||
"locked": {
|
||||
"lastModified": 1727220152,
|
||||
"narHash": "sha256-6ezRTVBZT25lQkvaPrfJSxYLwqcbNWm6feD/vG1FO0o=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "24959f933187217890b206788a85bfa73ba75949",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1723726852,
|
||||
"narHash": "sha256-lRzlx4fPRtzA+dgz9Rh4WK5yAW3TsAXx335DQqxY2XY=",
|
||||
"owner": "radvendii",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9286249a1673cf5b14a4793e22dd44b70cb69a0d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "radvendii",
|
||||
"ref": "nixos_rebuild_tests",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pixelfed": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1719823820,
|
||||
"narHash": "sha256-CKjqnxp7p2z/13zfp4HQ1OAmaoUtqBKS6HFm6TV8Jwg=",
|
||||
"owner": "pixelfed",
|
||||
"repo": "pixelfed",
|
||||
"rev": "4c245cf429330d01fcb8ebeb9aa8c84a9574a645",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "pixelfed",
|
||||
"ref": "v0.12.3",
|
||||
"repo": "pixelfed",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"disko": "disko",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs-latest": "nixpkgs-latest",
|
||||
"pixelfed": "pixelfed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
121
services/flake.nix
Normal file
|
@ -0,0 +1,121 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:radvendii/nixpkgs/nixos_rebuild_tests";
|
||||
nixpkgs-latest.url = "github:nixos/nixpkgs";
|
||||
pixelfed = {
|
||||
url = "github:pixelfed/pixelfed?ref=v0.12.3";
|
||||
flake = false;
|
||||
};
|
||||
disko.url = "github:nix-community/disko";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, nixpkgs-latest, pixelfed, disko }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
lib = nixpkgs.lib;
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
pkgsLatest = nixpkgs-latest.legacyPackages.${system};
|
||||
bleedingFediverseOverlay = (self: super: {
|
||||
pixelfed = pkgsLatest.pixelfed.overrideAttrs (old: {
|
||||
src = pixelfed;
|
||||
patches = (old.patches or [ ]) ++ [ ./fediversity/pixelfed-group-permissions.patch ];
|
||||
});
|
||||
## TODO: give mastodon, peertube the same treatment
|
||||
});
|
||||
in {
|
||||
nixosModules = {
|
||||
## Bleeding-edge fediverse packages
|
||||
bleedingFediverse = {
|
||||
nixpkgs.overlays = [ bleedingFediverseOverlay ];
|
||||
};
|
||||
## Fediversity modules
|
||||
fediversity = import ./fediversity;
|
||||
|
||||
## VM-specific modules
|
||||
interactive-vm = import ./vm/interactive-vm.nix;
|
||||
garage-vm = import ./vm/garage-vm.nix;
|
||||
mastodon-vm = import ./vm/mastodon-vm.nix;
|
||||
peertube-vm = import ./vm/peertube-vm.nix;
|
||||
pixelfed-vm = import ./vm/pixelfed-vm.nix;
|
||||
|
||||
disk-layout = import ./disk-layout.nix;
|
||||
};
|
||||
|
||||
nixosConfigurations = {
|
||||
mastodon = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
mastodon-vm
|
||||
];
|
||||
};
|
||||
|
||||
peertube = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
peertube-vm
|
||||
];
|
||||
};
|
||||
|
||||
pixelfed = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
pixelfed-vm
|
||||
];
|
||||
};
|
||||
|
||||
all = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
peertube-vm
|
||||
pixelfed-vm
|
||||
mastodon-vm
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
## Fully-feature ISO installer
|
||||
mkInstaller = import ./installer.nix;
|
||||
installers = lib.mapAttrs (_: config: self.mkInstaller nixpkgs config) self.nixosConfigurations;
|
||||
|
||||
deploy =
|
||||
let
|
||||
deployCommand = (pkgs.callPackage ./deploy.nix { });
|
||||
in
|
||||
lib.mapAttrs (name: config: deployCommand name config) self.nixosConfigurations;
|
||||
|
||||
checks.${system} = {
|
||||
mastodon-garage = import ./tests/mastodon-garage.nix { inherit pkgs self; };
|
||||
pixelfed-garage = import ./tests/pixelfed-garage.nix { inherit pkgs self; };
|
||||
};
|
||||
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
inputs = with pkgs; [
|
||||
nil
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
39
services/installer.nix
Normal file
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
Convert a NixOS configuration to one for a minimal installer ISO
|
||||
|
||||
WARNING: Running this installer will format the target disk!
|
||||
*/
|
||||
nixpkgs: machine:
|
||||
let
|
||||
installer = { config, pkgs, lib, ... }:
|
||||
let
|
||||
bootstrap = pkgs.writeShellApplication {
|
||||
name = "bootstrap";
|
||||
runtimeInputs = with pkgs; [ nixos-install-tools ];
|
||||
text = ''
|
||||
${machine.config.system.build.diskoScript}
|
||||
nixos-install --no-root-password --no-channel-copy --system ${machine.config.system.build.toplevel}
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
|
||||
];
|
||||
nixpkgs.hostPlatform = "x86_64-linux";
|
||||
services.getty.autologinUser = lib.mkForce "root";
|
||||
programs.bash.loginShellInit = ''
|
||||
${nixpkgs.lib.getExe bootstrap}
|
||||
'';
|
||||
|
||||
isoImage = {
|
||||
compressImage = false;
|
||||
squashfsCompression = "lz4";
|
||||
isoName = lib.mkForce "installer.iso";
|
||||
## ^^ FIXME: Use a more interesting name or keep the default name and
|
||||
## use `isoImage.isoName` in the tests.
|
||||
};
|
||||
};
|
||||
in
|
||||
(nixpkgs.lib.nixosSystem { modules = [installer];}).config.system.build.isoImage
|
||||
|
BIN
services/tests/fediversity.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
services/tests/green.png
Normal file
After Width: | Height: | Size: 692 B |
144
services/tests/mastodon-garage.nix
Normal file
|
@ -0,0 +1,144 @@
|
|||
{ pkgs, self }:
|
||||
let
|
||||
lib = pkgs.lib;
|
||||
rebuildableTest = import ./rebuildableTest.nix pkgs;
|
||||
seleniumScript = pkgs.writers.writePython3Bin "selenium-script"
|
||||
{
|
||||
libraries = with pkgs.python3Packages; [ selenium ];
|
||||
} ''
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.firefox.options import Options
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
|
||||
print(1)
|
||||
|
||||
options = Options()
|
||||
options.add_argument("--headless")
|
||||
# devtools don't show up in headless screenshots
|
||||
# options.add_argument("-devtools")
|
||||
service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501
|
||||
|
||||
driver = webdriver.Firefox(options=options, service=service)
|
||||
driver.get("http://mastodon.localhost:55001/public/local")
|
||||
|
||||
# wait until the statuses load
|
||||
WebDriverWait(driver, 90).until(
|
||||
lambda x: x.find_element(By.CLASS_NAME, "status"))
|
||||
|
||||
driver.save_screenshot("/mastodon-screenshot.png")
|
||||
|
||||
driver.close()
|
||||
'';
|
||||
in
|
||||
pkgs.nixosTest {
|
||||
name = "test-mastodon-garage";
|
||||
|
||||
nodes = {
|
||||
server = { config, ... }: {
|
||||
virtualisation.memorySize = lib.mkVMOverride 4096;
|
||||
imports = with self.nixosModules; [
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
garage-vm
|
||||
mastodon-vm
|
||||
];
|
||||
# TODO: pair down
|
||||
environment.systemPackages = with pkgs; [
|
||||
python3
|
||||
firefox-unwrapped
|
||||
geckodriver
|
||||
toot
|
||||
xh
|
||||
seleniumScript
|
||||
helix
|
||||
imagemagick
|
||||
];
|
||||
environment.variables = {
|
||||
POST_MEDIA = ./green.png;
|
||||
AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.mastodon.id;
|
||||
AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.mastodon.secret;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
import re
|
||||
import time
|
||||
|
||||
server.start()
|
||||
|
||||
with subtest("Mastodon starts"):
|
||||
server.wait_for_unit("mastodon-web.service")
|
||||
|
||||
# make sure mastodon is fully up and running before we interact with it
|
||||
# TODO: is there a way to test for this?
|
||||
time.sleep(180)
|
||||
|
||||
with subtest("Account creation"):
|
||||
account_creation_output = server.succeed("mastodon-tootctl accounts create test --email test@test.com --confirmed --approve")
|
||||
password_match = re.match('.*New password: ([^\n]*).*', account_creation_output, re.S)
|
||||
if password_match is None:
|
||||
raise Exception(f"account creation did not generate a password.\n{account_creation_output}")
|
||||
password = password_match.group(1)
|
||||
|
||||
with subtest("TTY Login"):
|
||||
server.wait_until_tty_matches("1", "login: ")
|
||||
server.send_chars("root\n");
|
||||
|
||||
with subtest("Log in with toot"):
|
||||
# toot doesn't provide a way to just specify our login details as arguments, so we have to pretend we're typing them in at the prompt
|
||||
server.send_chars("toot login_cli --instance http://mastodon.localhost:55001 --email test@test.com\n")
|
||||
server.wait_until_tty_matches("1", "Password: ")
|
||||
server.send_chars(password + "\n")
|
||||
server.wait_until_tty_matches("1", "Successfully logged in.")
|
||||
|
||||
with subtest("post text"):
|
||||
server.succeed("echo 'hello mastodon' | toot post")
|
||||
|
||||
with subtest("post image"):
|
||||
server.succeed("toot post --media $POST_MEDIA")
|
||||
|
||||
with subtest("access garage"):
|
||||
server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
|
||||
server.succeed("mc ls garage/mastodon")
|
||||
|
||||
with subtest("access image in garage"):
|
||||
image = server.succeed("mc find garage --regex original")
|
||||
image = image.rstrip()
|
||||
if image == "":
|
||||
raise Exception("image posted to mastodon did not get stored in garage")
|
||||
server.succeed(f"mc cat {image} >/garage-image.webp")
|
||||
garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.webp")
|
||||
image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA")
|
||||
if garage_image_hash != image_hash:
|
||||
raise Exception("image stored in garage did not match image uploaded")
|
||||
|
||||
with subtest("Content security policy allows garage images"):
|
||||
headers = server.succeed("xh -h http://mastodon.localhost:55001/public/local")
|
||||
csp_match = None
|
||||
# I can't figure out re.MULTILINE
|
||||
for header in headers.split("\n"):
|
||||
csp_match = re.match('^Content-Security-Policy: (.*)$', header)
|
||||
if csp_match is not None:
|
||||
break
|
||||
if csp_match is None:
|
||||
raise Exception("mastodon did not send a content security policy header")
|
||||
csp = csp_match.group(1)
|
||||
# the img-src content security policy should include the garage server
|
||||
## TODO: use `nodes.server.fediversity.internal.garage.api.url` same as above, but beware of escaping the regex.
|
||||
garage_csp = re.match(".*; img-src[^;]*web\.garage\.localhost:3902.*", csp)
|
||||
if garage_csp is None:
|
||||
raise Exception("Mastodon's content security policy does not include garage server. image will not be displayed properly on mastodon.")
|
||||
|
||||
# this could in theory give a false positive if mastodon changes it's colorscheme to include pure green.
|
||||
with subtest("image displays"):
|
||||
server.succeed("selenium-script")
|
||||
server.copy_from_vm("/mastodon-screenshot.png", "")
|
||||
displayed_colors = server.succeed("convert /mastodon-screenshot.png -define histogram:unique-colors=true -format %c histogram:info:")
|
||||
# check that the green image displayed somewhere
|
||||
green_check = re.match(".*#00FF00.*", displayed_colors, re.S)
|
||||
if green_check is None:
|
||||
raise Exception("cannot detect the uploaded image on mastodon page.")
|
||||
'';
|
||||
}
|
215
services/tests/pixelfed-garage.nix
Normal file
|
@ -0,0 +1,215 @@
|
|||
{ pkgs, self }:
|
||||
let
|
||||
lib = pkgs.lib;
|
||||
rebuildableTest = import ./rebuildableTest.nix pkgs;
|
||||
|
||||
email = "test@test.com";
|
||||
password = "testtest";
|
||||
|
||||
# FIXME: Replace all the By.XPATH by By.CSS_SELECTOR.
|
||||
|
||||
seleniumImports = ''
|
||||
import sys
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
'';
|
||||
|
||||
seleniumSetup = ''
|
||||
print("Create and configure driver...", file=sys.stderr)
|
||||
options = Options()
|
||||
# options.add_argument("--headless=new")
|
||||
service = webdriver.ChromeService(executable_path="${lib.getExe pkgs.chromedriver}") # noqa: E501
|
||||
driver = webdriver.Chrome(options=options, service=service)
|
||||
driver.implicitly_wait(30)
|
||||
driver.set_window_size(1280, 960)
|
||||
'';
|
||||
|
||||
seleniumPixelfedLogin = ''
|
||||
print("Open login page...", file=sys.stderr)
|
||||
driver.get("http://pixelfed.localhost/login")
|
||||
print("Enter email...", file=sys.stderr)
|
||||
driver.find_element(By.ID, "email").send_keys("${email}")
|
||||
print("Enter password...", file=sys.stderr)
|
||||
driver.find_element(By.ID, "password").send_keys("${password}")
|
||||
# FIXME: This is disgusting. Find instead the input type submit in the form
|
||||
# with action ending in "/login".
|
||||
print("Click “Login” button...", file=sys.stderr)
|
||||
driver.find_element(By.XPATH, "//button[normalize-space()='Login']").click()
|
||||
'';
|
||||
|
||||
## NOTE: `path` must be a valid python string, either a variable or _quoted_.
|
||||
seleniumTakeScreenshot = path: ''
|
||||
print("Take screenshot...", file=sys.stderr)
|
||||
if not driver.save_screenshot(${path}):
|
||||
raise Exception("selenium could not save screenshot")
|
||||
'';
|
||||
|
||||
seleniumQuit = ''
|
||||
print("Quitting...", file=sys.stderr)
|
||||
driver.quit()
|
||||
'';
|
||||
|
||||
seleniumScriptPostPicture = pkgs.writers.writePython3Bin "selenium-script-post-picture"
|
||||
{
|
||||
libraries = with pkgs.python3Packages; [ selenium ];
|
||||
} ''
|
||||
import os
|
||||
import time
|
||||
${seleniumImports}
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
|
||||
${seleniumSetup}
|
||||
${seleniumPixelfedLogin}
|
||||
time.sleep(3)
|
||||
|
||||
media_path = os.environ['POST_MEDIA']
|
||||
|
||||
# Find the new post form, fill it in with our pictureand a caption.
|
||||
print("Click on “Create New Post”...", file=sys.stderr)
|
||||
driver.find_element(By.LINK_TEXT, "Create New Post").click()
|
||||
print("Add file to input element...", file=sys.stderr)
|
||||
driver.find_element(By.XPATH, "//input[@type='file']").send_keys(media_path)
|
||||
print("Add a caption", file=sys.stderr)
|
||||
driver.find_element(By.CSS_SELECTOR, ".media-body textarea").send_keys(
|
||||
"Fediversity test of image upload to pixelfed with garage storage."
|
||||
)
|
||||
time.sleep(3)
|
||||
print("Click on “Post” button...", file=sys.stderr)
|
||||
driver.find_element(By.LINK_TEXT, "Post").click()
|
||||
|
||||
# Wait until the post loads, and in particular its picture, then take a
|
||||
# screenshot of the whole page.
|
||||
print("Wait for post and image to be loaded...", file=sys.stderr)
|
||||
img = driver.find_element(
|
||||
By.XPATH,
|
||||
"//div[@class='timeline-status-component-content']//img"
|
||||
)
|
||||
WebDriverWait(driver, timeout=10).until(
|
||||
lambda d: d.execute_script("return arguments[0].complete", img)
|
||||
)
|
||||
time.sleep(3)
|
||||
|
||||
${seleniumTakeScreenshot "\"/home/selenium/screenshot.png\""}
|
||||
${seleniumQuit}'';
|
||||
|
||||
seleniumScriptGetSrc = pkgs.writers.writePython3Bin "selenium-script-get-src"
|
||||
{
|
||||
libraries = with pkgs.python3Packages; [ selenium ];
|
||||
} ''
|
||||
${seleniumImports}
|
||||
${seleniumSetup}
|
||||
${seleniumPixelfedLogin}
|
||||
|
||||
img = driver.find_element(
|
||||
By.XPATH,
|
||||
"//div[@class='timeline-status-component-content']//img"
|
||||
)
|
||||
# REVIEW: Need to wait for it to be loaded?
|
||||
print(img.get_attribute('src'))
|
||||
|
||||
${seleniumQuit}'';
|
||||
|
||||
in
|
||||
pkgs.nixosTest {
|
||||
name = "test-pixelfed-garage";
|
||||
|
||||
nodes = {
|
||||
server = { config, ... }: {
|
||||
|
||||
services = {
|
||||
xserver = {
|
||||
enable = true;
|
||||
displayManager.lightdm.enable = true;
|
||||
desktopManager.lxqt.enable = true;
|
||||
};
|
||||
|
||||
displayManager.autoLogin = {
|
||||
enable = true;
|
||||
user = "selenium";
|
||||
};
|
||||
};
|
||||
virtualisation.resolution = { x = 1680; y = 1050; };
|
||||
|
||||
|
||||
virtualisation = {
|
||||
memorySize = lib.mkVMOverride 8192;
|
||||
cores = 8;
|
||||
};
|
||||
imports = with self.nixosModules; [
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
garage-vm
|
||||
pixelfed-vm
|
||||
];
|
||||
# TODO: pair down
|
||||
environment.systemPackages = with pkgs; [
|
||||
python3
|
||||
chromium
|
||||
chromedriver
|
||||
xh
|
||||
seleniumScriptPostPicture
|
||||
seleniumScriptGetSrc
|
||||
helix
|
||||
imagemagick
|
||||
];
|
||||
environment.variables = {
|
||||
POST_MEDIA = ./fediversity.png;
|
||||
AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.pixelfed.id;
|
||||
AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.pixelfed.secret;
|
||||
## without this we get frivolous errors in the logs
|
||||
MC_REGION = "garage";
|
||||
};
|
||||
# chrome does not like being run as root
|
||||
users.users.selenium = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
import re
|
||||
|
||||
server.start()
|
||||
|
||||
with subtest("Pixelfed starts"):
|
||||
server.wait_for_unit("phpfpm-pixelfed.service")
|
||||
|
||||
with subtest("Account creation"):
|
||||
server.succeed("pixelfed-manage user:create --name=test --username=test --email=${email} --password=${password} --confirm_email=1")
|
||||
|
||||
# NOTE: This could in theory give a false positive if pixelfed changes it's
|
||||
# colorscheme to include pure green. (see same problem in pixelfed-garage.nix).
|
||||
# TODO: For instance: post a red image and check that the green pixel IS NOT
|
||||
# there, then post a green image and check that the green pixel IS there.
|
||||
|
||||
with subtest("Image displays"):
|
||||
server.succeed("su - selenium -c 'selenium-script-post-picture ${email} ${password}'")
|
||||
server.copy_from_vm("/home/selenium/screenshot.png", "")
|
||||
displayed_colors = server.succeed("magick /home/selenium/screenshot.png -define histogram:unique-colors=true -format %c histogram:info:")
|
||||
# check that the green image displayed somewhere
|
||||
image_check = re.match(".*#FF0500.*", displayed_colors, re.S)
|
||||
if image_check is None:
|
||||
raise Exception("cannot detect the uploaded image on pixelfed page.")
|
||||
|
||||
with subtest("access garage"):
|
||||
server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
|
||||
server.succeed("mc ls garage/pixelfed")
|
||||
|
||||
with subtest("access image in garage"):
|
||||
image = server.succeed("mc find garage --regex '\\.png' --ignore '*_thumb.png'")
|
||||
image = image.rstrip()
|
||||
if image == "":
|
||||
raise Exception("image posted to Pixelfed did not get stored in garage")
|
||||
server.succeed(f"mc cat {image} >/garage-image.png")
|
||||
garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.png")
|
||||
image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA")
|
||||
if garage_image_hash != image_hash:
|
||||
raise Exception("image stored in garage did not match image uploaded")
|
||||
|
||||
with subtest("Check that image comes from garage"):
|
||||
src = server.succeed("su - selenium -c 'selenium-script-get-src ${email} ${password}'")
|
||||
if not src.startswith("${nodes.server.fediversity.internal.garage.web.urlForBucket "pixelfed"}"):
|
||||
raise Exception("image does not come from garage")
|
||||
'';
|
||||
}
|
149
services/tests/rebuildableTest.nix
Normal file
|
@ -0,0 +1,149 @@
|
|||
pkgs: test:
|
||||
let
|
||||
inherit (pkgs.lib) mapAttrsToList concatStringsSep genAttrs mkIf;
|
||||
inherit (builtins) attrNames;
|
||||
|
||||
interactiveConfig = ({ config, ... }: {
|
||||
# so we can run `nix shell nixpkgs#foo` on the machines
|
||||
nix.extraOptions = ''
|
||||
extra-experimental-features = nix-command flakes
|
||||
'';
|
||||
|
||||
# so we can ssh in and rebuild them
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PermitRootLogin = "yes";
|
||||
PermitEmptyPasswords = "yes";
|
||||
UsePAM = false;
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation = mkIf (config.networking.hostName == "jumphost") {
|
||||
forwardPorts = [{
|
||||
from = "host";
|
||||
host.port = 2222;
|
||||
guest.port = 22;
|
||||
}];
|
||||
};
|
||||
});
|
||||
|
||||
sshConfig = pkgs.writeText "ssh-config" ''
|
||||
Host *
|
||||
User root
|
||||
StrictHostKeyChecking no
|
||||
BatchMode yes
|
||||
ConnectTimeout 20
|
||||
UserKnownHostsFile=/dev/null
|
||||
LogLevel Error # no "added to known hosts"
|
||||
Host jumphost
|
||||
Port 2222
|
||||
HostName localhost
|
||||
Host * !jumphost
|
||||
ProxyJump jumphost
|
||||
'';
|
||||
|
||||
# one should first start up the interactive test driver, then start the
|
||||
# machines, then update the config, and then redeploy with the `rebuildScript`
|
||||
# associated with the new config.
|
||||
rebuildScript = pkgs.writeShellScriptBin "rebuild" ''
|
||||
# create an association array from machine names to the path to their
|
||||
# configuration in the nix store
|
||||
declare -A configPaths=(${
|
||||
concatStringsSep " "
|
||||
(mapAttrsToList
|
||||
(n: v: ''["${n}"]="${v.system.build.toplevel}"'')
|
||||
rebuildableTest.driverInteractive.nodes)
|
||||
})
|
||||
|
||||
rebuild_one() {
|
||||
machine="$1"
|
||||
echo "pushing new config to $machine"
|
||||
|
||||
if [ -z ''${configPaths[$machine]+x} ]; then
|
||||
echo 'No machine '"$machine"' in this test.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! ssh -F ${sshConfig} $machine true; then
|
||||
echo 'Couldn'"'"'t connect to '"$machine"'. Make sure you'"'"'ve started it with `'"$machine"'.start()` in the test interactive driver.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# taken from nixos-rebuild (we only want to do the activate part)
|
||||
cmd=(
|
||||
"systemd-run"
|
||||
"-E" "LOCALE_ARCHIVE"
|
||||
"--collect"
|
||||
"--no-ask-password"
|
||||
"--pty"
|
||||
"--quiet"
|
||||
"--same-dir"
|
||||
"--service-type=exec"
|
||||
"--unit=nixos-rebuild-switch-to-configuration"
|
||||
"--wait"
|
||||
"''${configPaths[$machine]}/bin/switch-to-configuration"
|
||||
"test"
|
||||
)
|
||||
|
||||
|
||||
if ! ssh -F ${sshConfig} $machine "''${cmd[@]}"; then
|
||||
echo "warning: error(s) occurred while switching to the new configuration"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
if ! ssh -F ${sshConfig} jumphost true; then
|
||||
echo 'Couldn'"'"'t connect to jump host. Make sure you are running driverInteractive, and that you'"'"'ve run `jumphost.start()` and `jumphost.forward_port(2222,22)`'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$1" ]; then
|
||||
rebuild_one "$1"
|
||||
else
|
||||
for machine in ${concatStringsSep " " (attrNames rebuildableTest.driverInteractive.nodes)}; do
|
||||
rebuild_one $machine
|
||||
done
|
||||
fi
|
||||
'';
|
||||
|
||||
# NOTE: This is awkward because NixOS does not expose the module interface
|
||||
# that is used to build tests. When we upstream this, we can build it into the
|
||||
# system more naturally (and expose more of the interface to end users while
|
||||
# we're at it)
|
||||
rebuildableTest =
|
||||
let
|
||||
preOverride = pkgs.nixosTest (test // {
|
||||
interactive = (test.interactive or { }) // {
|
||||
# no need to // with test.interactive.nodes here, since we are iterating
|
||||
# over all of them, and adding back in the config via `imports`
|
||||
nodes = genAttrs
|
||||
(
|
||||
attrNames test.nodes or { } ++
|
||||
attrNames test.interactive.nodes or { } ++
|
||||
[ "jumphost" ]
|
||||
)
|
||||
(n: {
|
||||
imports = [
|
||||
(test.interactive.${n} or { })
|
||||
interactiveConfig
|
||||
];
|
||||
});
|
||||
};
|
||||
# override with test.passthru in case someone wants to overwrite us.
|
||||
passthru = { inherit rebuildScript sshConfig; } // (test.passthru or { });
|
||||
});
|
||||
in
|
||||
preOverride // {
|
||||
driverInteractive = preOverride.driverInteractive.overrideAttrs (old: {
|
||||
# this comes from runCommand, not mkDerivation, so this is the only
|
||||
# hook we have to override
|
||||
buildCommand = old.buildCommand + ''
|
||||
ln -s ${sshConfig} $out/ssh-config
|
||||
ln -s ${rebuildScript}/bin/rebuild $out/bin/rebuild
|
||||
'';
|
||||
});
|
||||
};
|
||||
in
|
||||
rebuildableTest
|
||||
|
29
services/vm/garage-vm.nix
Normal file
|
@ -0,0 +1,29 @@
|
|||
{ lib, config, modulesPath, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkVMOverride;
|
||||
|
||||
fedicfg = config.fediversity.internal.garage;
|
||||
|
||||
in {
|
||||
imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
|
||||
|
||||
services.nginx.virtualHosts.${fedicfg.web.rootDomain} = {
|
||||
forceSSL = mkVMOverride false;
|
||||
enableACME = mkVMOverride false;
|
||||
};
|
||||
|
||||
virtualisation.diskSize = 2048;
|
||||
virtualisation.forwardPorts = [
|
||||
{
|
||||
from = "host";
|
||||
host.port = fedicfg.rpc.port;
|
||||
guest.port = fedicfg.rpc.port;
|
||||
}
|
||||
{
|
||||
from = "host";
|
||||
host.port = fedicfg.web.internalPort;
|
||||
guest.port = fedicfg.web.internalPort;
|
||||
}
|
||||
];
|
||||
}
|
64
services/vm/interactive-vm.nix
Normal file
|
@ -0,0 +1,64 @@
|
|||
# customize nixos-rebuild build-vm to be a bit more convenient
|
||||
{ pkgs, ... }: {
|
||||
# let us log in
|
||||
users.mutableUsers = false;
|
||||
users.users.root.hashedPassword = "";
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PermitRootLogin = "yes";
|
||||
PermitEmptyPasswords = "yes";
|
||||
UsePAM = false;
|
||||
};
|
||||
};
|
||||
|
||||
# automatically log in
|
||||
services.getty.autologinUser = "root";
|
||||
services.getty.helpLine = ''
|
||||
Type `C-a c` to access the qemu console
|
||||
Type `C-a x` to quit
|
||||
'';
|
||||
# access to convenient things
|
||||
environment.systemPackages = with pkgs; [
|
||||
w3m
|
||||
python3
|
||||
xterm # for `resize`
|
||||
];
|
||||
environment.loginShellInit = ''
|
||||
eval "$(resize)"
|
||||
'';
|
||||
nix.extraOptions = ''
|
||||
extra-experimental-features = nix-command flakes
|
||||
'';
|
||||
|
||||
# no graphics. see nixos-shell
|
||||
virtualisation = {
|
||||
graphics = false;
|
||||
qemu.consoles = [ "tty0" "hvc0" ];
|
||||
qemu.options = [
|
||||
"-serial null"
|
||||
"-device virtio-serial"
|
||||
"-chardev stdio,mux=on,id=char0,signal=off"
|
||||
"-mon chardev=char0,mode=readline"
|
||||
"-device virtconsole,chardev=char0,nr=0"
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
# we can't forward port 80 or 443, so let's run nginx on a different port
|
||||
networking.firewall.allowedTCPPorts = [ 8443 8080 ];
|
||||
services.nginx.defaultSSLListenPort = 8443;
|
||||
services.nginx.defaultHTTPListenPort = 8080;
|
||||
virtualisation.forwardPorts = [
|
||||
{
|
||||
from = "host";
|
||||
host.port = 8080;
|
||||
guest.port = 8080;
|
||||
}
|
||||
{
|
||||
from = "host";
|
||||
host.port = 8443;
|
||||
guest.port = 8443;
|
||||
}
|
||||
];
|
||||
}
|
116
services/vm/mastodon-vm.nix
Normal file
|
@ -0,0 +1,116 @@
|
|||
{ modulesPath, lib, config, ... }: {
|
||||
|
||||
imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
|
||||
|
||||
config = lib.mkMerge [
|
||||
{
|
||||
fediversity = {
|
||||
enable = true;
|
||||
domain = "localhost";
|
||||
mastodon.enable = true;
|
||||
};
|
||||
|
||||
services.mastodon = {
|
||||
extraConfig = {
|
||||
EMAIL_DOMAIN_ALLOWLIST = "example.com";
|
||||
};
|
||||
|
||||
# from the documentation: recommended is the amount of your CPU cores
|
||||
# minus one. but it also must be a positive integer
|
||||
streamingProcesses = lib.max 1 (config.virtualisation.cores - 1);
|
||||
};
|
||||
|
||||
security.acme = {
|
||||
defaults = {
|
||||
# invalid server; the systemd service will fail, and we won't get
|
||||
# properly signed certificates. but let's not spam the letsencrypt
|
||||
# servers (and we don't own this domain anyways)
|
||||
server = "https://127.0.0.1";
|
||||
email = "none";
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation.memorySize = 2048;
|
||||
virtualisation.forwardPorts = [
|
||||
{
|
||||
from = "host";
|
||||
host.port = 44443;
|
||||
guest.port = 443;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
#### run mastodon as development environment
|
||||
{
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 55001 ];
|
||||
services.mastodon = {
|
||||
# needed so we can directly access mastodon at port 55001
|
||||
# otherwise, mastodon has to be accessed *from* port 443, which we can't do via port forwarding
|
||||
enableUnixSocket = false;
|
||||
extraConfig = {
|
||||
RAILS_ENV = "development";
|
||||
# to be accessible from outside the VM
|
||||
BIND = "0.0.0.0";
|
||||
# for letter_opener (still doesn't work though)
|
||||
REMOTE_DEV = "true";
|
||||
LOCAL_DOMAIN = "${config.fediversity.internal.mastodon.domain}:8443";
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
ensureUsers = [
|
||||
{
|
||||
name = config.services.mastodon.database.user;
|
||||
ensureClauses.createdb = true;
|
||||
# ensurePermissions doesn't work anymore
|
||||
# ensurePermissions = {
|
||||
# "mastodon_development.*" = "ALL PRIVILEGES";
|
||||
# "mastodon_test.*" = "ALL PRIVILEGES";
|
||||
# }
|
||||
}
|
||||
];
|
||||
# ensureDatabases = [ "mastodon_development_test" "mastodon_test" ];
|
||||
};
|
||||
|
||||
# Currently, nixos seems to be able to create a single database per
|
||||
# postgres user. This works for the production version of mastodon, which
|
||||
# is what's packaged in nixpkgs. For development, we need two databases,
|
||||
# mastodon_development and mastodon_test. This used to be possible with
|
||||
# ensurePermissions, but that's broken and has been removed. Here I copy
|
||||
# the mastodon-init-db script from upstream nixpkgs, but add the single
|
||||
# line `rails db:setup`, which asks mastodon to create the postgres
|
||||
# databases for us.
|
||||
# FIXME: the commented out lines were breaking things, but presumably they're necessary for something.
|
||||
# TODO: see if we can fix the upstream ensurePermissions stuff. See commented out lines in services.postgresql above for what that config would look like.
|
||||
systemd.services.mastodon-init-db.script = lib.mkForce ''
|
||||
result="$(psql -t --csv -c \
|
||||
"select count(*) from pg_class c \
|
||||
join pg_namespace s on s.oid = c.relnamespace \
|
||||
where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \
|
||||
and s.nspname not like 'pg_temp%';")" || error_code=$?
|
||||
if [ "''${error_code:-0}" -ne 0 ]; then
|
||||
echo "Failure checking if database is seeded. psql gave exit code $error_code"
|
||||
exit "$error_code"
|
||||
fi
|
||||
if [ "$result" -eq 0 ]; then
|
||||
echo "Seeding database"
|
||||
rails db:setup
|
||||
# SAFETY_ASSURED=1 rails db:schema:load
|
||||
rails db:seed
|
||||
# else
|
||||
# echo "Migrating database (this might be a noop)"
|
||||
# rails db:migrate
|
||||
fi
|
||||
'';
|
||||
virtualisation.forwardPorts = [
|
||||
{
|
||||
from = "host";
|
||||
host.port = 55001;
|
||||
guest.port = 55001;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
24
services/vm/peertube-vm.nix
Normal file
|
@ -0,0 +1,24 @@
|
|||
{ pkgs, modulesPath, ... }: {
|
||||
|
||||
imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
|
||||
|
||||
services.peertube = {
|
||||
enableWebHttps = false;
|
||||
settings = {
|
||||
listen.hostname = "0.0.0.0";
|
||||
instance.name = "PeerTube Test VM";
|
||||
};
|
||||
# TODO: use agenix
|
||||
secrets.secretsFile = pkgs.writeText "secret" ''
|
||||
574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24
|
||||
'';
|
||||
};
|
||||
|
||||
virtualisation.forwardPorts = [
|
||||
{
|
||||
from = "host";
|
||||
host.port = 9000;
|
||||
guest.port = 9000;
|
||||
}
|
||||
];
|
||||
}
|
33
services/vm/pixelfed-vm.nix
Normal file
|
@ -0,0 +1,33 @@
|
|||
{ pkgs, lib, modulesPath, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkVMOverride;
|
||||
|
||||
in {
|
||||
imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
|
||||
|
||||
fediversity = {
|
||||
enable = true;
|
||||
domain = "localhost";
|
||||
pixelfed.enable = true;
|
||||
};
|
||||
|
||||
services.pixelfed = {
|
||||
settings = {
|
||||
FORCE_HTTPS_URLS = false;
|
||||
};
|
||||
nginx = {
|
||||
forceSSL = mkVMOverride false;
|
||||
enableACME = mkVMOverride false;
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation.memorySize = 2048;
|
||||
virtualisation.forwardPorts = [
|
||||
{
|
||||
from = "host";
|
||||
host.port = 8000;
|
||||
guest.port = 80;
|
||||
}
|
||||
];
|
||||
}
|
BIN
website/.DS_Store
vendored
Normal file
0
website/.hugo_build.lock
Normal file
21
website/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 - Present, Zeon Studio
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
BIN
website/Montserrat-SemiBold.woff2
Normal file
29
website/amplify.yml
Normal file
|
@ -0,0 +1,29 @@
|
|||
version: 1
|
||||
frontend:
|
||||
phases:
|
||||
preBuild:
|
||||
commands:
|
||||
- yum install -y curl
|
||||
- curl -LO "https://github.com/gohugoio/hugo/releases/download/v0.121.2/hugo_extended_0.121.2_Linux-64bit.tar.gz"
|
||||
- tar -xvf hugo_extended_0.121.2_Linux-64bit.tar.gz
|
||||
- mv hugo /usr/local/bin/
|
||||
- rm hugo_extended_0.121.2_Linux-64bit.tar.gz
|
||||
- echo "HUGO 0.121.2 INSTALLED"
|
||||
- curl -LO "https://dl.google.com/go/go1.20.5.linux-amd64.tar.gz"
|
||||
- tar -C /usr/local -xzf go1.20.5.linux-amd64.tar.gz
|
||||
- export PATH=$PATH:/usr/local/go/bin
|
||||
- rm go1.20.5.linux-amd64.tar.gz
|
||||
- echo "GO 1.20.5 INSTALLED"
|
||||
- npm install
|
||||
build:
|
||||
commands:
|
||||
- npm run project-setup
|
||||
- npm run build
|
||||
artifacts:
|
||||
# IMPORTANT - Please verify your build output directory
|
||||
baseDirectory: /public
|
||||
files:
|
||||
- "**/*"
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/**/*
|
BIN
website/assets/.DS_Store
vendored
Normal file
BIN
website/assets/images/avatar-sm.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
website/assets/images/avatar.png
Executable file
After Width: | Height: | Size: 2.2 KiB |
BIN
website/assets/images/banner.png
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
website/assets/images/bergen-airport.jpeg
Normal file
After Width: | Height: | Size: 3.2 MiB |
BIN
website/assets/images/call-to-action.png
Executable file
After Width: | Height: | Size: 19 KiB |
BIN
website/assets/images/checkbox-illustration-scaled.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
website/assets/images/checkbox-illustration.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
website/assets/images/code.png
Normal file
After Width: | Height: | Size: 769 B |
BIN
website/assets/images/favicon-archive.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
website/assets/images/favicon.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
website/assets/images/globe.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
1
website/assets/images/home.svg
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
website/assets/images/image-placeholder.png
Executable file
After Width: | Height: | Size: 4.8 KiB |
BIN
website/assets/images/logo-archive.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
website/assets/images/logo-darkmode-archive.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
website/assets/images/logo-darkmode.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
website/assets/images/logo.png
Normal file
After Width: | Height: | Size: 19 KiB |
1
website/assets/images/mastodon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="18px" height="18px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M17.038,10.791c-0.247,1.27 -2.211,2.659 -4.466,2.928c-1.176,0.141 -2.334,0.27 -3.569,0.213c-2.019,-0.092 -3.613,-0.482 -3.613,-0.482c0,0.197 0.012,0.384 0.037,0.559c0.262,1.993 1.976,2.112 3.599,2.168c1.638,0.056 3.097,-0.404 3.097,-0.404l0.067,1.481c0,-0 -1.146,0.615 -3.187,0.728c-1.126,0.062 -2.523,-0.028 -4.151,-0.459c-3.531,-0.934 -4.138,-4.698 -4.231,-8.516c-0.028,-1.134 -0.011,-2.203 -0.011,-3.097c0,-3.905 2.559,-5.05 2.559,-5.05c1.29,-0.592 3.503,-0.841 5.804,-0.86l0.057,-0c2.301,0.019 4.516,0.268 5.806,0.86c0,0 2.558,1.145 2.558,5.05c0,-0 0.032,2.881 -0.356,4.881Zm-2.661,-4.578c-0,-0.967 -0.246,-1.735 -0.74,-2.303c-0.51,-0.568 -1.178,-0.859 -2.006,-0.859c-0.959,0 -1.684,0.368 -2.164,1.105l-0.467,0.783l-0.467,-0.783c-0.48,-0.737 -1.205,-1.105 -2.164,-1.105c-0.828,0 -1.496,0.291 -2.005,0.859c-0.495,0.568 -0.741,1.336 -0.741,2.303l0,4.728l1.873,-0l0,-4.589c0,-0.968 0.407,-1.459 1.222,-1.459c0.9,0 1.351,0.583 1.351,1.734l0,2.512l1.862,0l0,-2.512c0,-1.151 0.451,-1.734 1.351,-1.734c0.815,0 1.222,0.491 1.222,1.459l-0,4.589l1.873,-0l-0,-4.728Z" style="fill:#fff;"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
62
website/assets/images/ngi_fedi_full.svg
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="280px" height="66px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(-92.5839,89.7794,89.9728,92.3849,108.412,-11.8899)">
|
||||
<clipPath id="_clip1">
|
||||
<path d="M0.626,-0.479C0.648,-0.501 0.684,-0.501 0.706,-0.478L0.984,-0.192C1.006,-0.17 1.005,-0.134 0.983,-0.112L0.374,0.479C0.352,0.501 0.316,0.501 0.294,0.478L0.228,0.411C0.221,0.403 0.211,0.399 0.201,0.399L0.135,0.398C0.116,0.398 0.101,0.383 0.102,0.364L0.103,0.298C0.103,0.288 0.099,0.278 0.092,0.27L0.016,0.192C-0.006,0.17 -0.005,0.134 0.017,0.112L0.626,-0.479Z" clip-rule="nonzero"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#_clip1)">
|
||||
<g transform="matrix(-0.00555496,0.0053983,0.00540993,0.00556693,0.666547,-0.51905)">
|
||||
<use xlink:href="#_Image2" x="0" y="0" width="134px" height="66px"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-281.839)">
|
||||
<path d="M66.085,147.792C67.443,147.792 68.544,148.893 68.544,150.251L68.544,166.582C68.544,167.939 67.443,169.04 66.085,169.04C64.728,169.04 63.627,167.939 63.627,166.582L63.627,150.251C63.627,148.893 64.728,147.792 66.085,147.792" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-281.839)">
|
||||
<path d="M58.126,158.082L57.957,158.082C57.936,158.082 57.915,158.085 57.893,158.086C57.871,158.085 57.851,158.082 57.829,158.082L55.401,158.082C54.092,158.082 52.948,159.07 52.863,160.378C52.772,161.81 53.905,163 55.317,163C55.576,163 55.766,163.251 55.688,163.498C55.469,164.193 54.907,164.748 54.172,164.942C53.48,165.125 52.747,165.216 51.971,165.216C50.635,165.216 49.445,164.928 48.403,164.351C47.361,163.774 46.547,162.97 45.96,161.937C45.373,160.905 45.08,159.732 45.08,158.416C45.08,157.081 45.373,155.896 45.96,154.865C46.547,153.833 47.366,153.033 48.419,152.467C49.471,151.9 50.675,151.617 52.031,151.617C53.592,151.617 54.986,152.04 56.214,152.886C56.995,153.423 58.056,153.415 58.8,152.829C59.96,151.915 59.897,150.147 58.695,149.345C58.128,148.966 57.516,148.641 56.857,148.369C55.34,147.742 53.65,147.428 51.788,147.428C49.562,147.428 47.559,147.898 45.778,148.839C43.997,149.78 42.606,151.086 41.605,152.755C40.603,154.425 40.102,156.311 40.102,158.416C40.102,160.521 40.603,162.408 41.605,164.077C42.606,165.747 43.987,167.053 45.748,167.993C47.508,168.935 49.491,169.404 51.697,169.404C53.235,169.404 54.763,169.172 56.281,168.707C56.296,168.702 56.312,168.697 56.326,168.692C58.694,167.959 60.288,165.739 60.288,163.26L60.288,160.244C60.288,159.051 59.32,158.082 58.126,158.082" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-281.837)">
|
||||
<path d="M37.613,150.22L37.613,166.612C37.613,167.952 36.525,169.04 35.184,169.04L34.723,169.04C33.997,169.04 33.308,168.714 32.846,168.153L24.77,158.318C24.169,157.585 22.981,158.011 22.981,158.958L22.981,166.612C22.981,167.952 21.894,169.04 20.552,169.04C19.212,169.04 18.125,167.952 18.125,166.612L18.125,150.22C18.125,148.878 19.212,147.791 20.552,147.791L21.042,147.791C21.77,147.791 22.46,148.117 22.921,148.681L30.966,158.506C31.566,159.24 32.756,158.815 32.756,157.866L32.756,150.22C32.756,148.878 33.842,147.791 35.184,147.791C36.525,147.791 37.613,148.878 37.613,150.22" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-311.37)">
|
||||
<path d="M88.87,165.549L92.565,165.549L92.565,166.714L88.87,166.714L88.87,165.549ZM88.987,169.551L87.61,169.551L87.61,162.14L93.02,162.14L93.02,163.294L88.987,163.294L88.987,169.551Z" style="fill:rgb(111,154,168);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-311.37)">
|
||||
<path d="M99.012,165.221L102.696,165.221L102.696,166.354L99.012,166.354L99.012,165.221ZM99.118,168.397L103.3,168.397L103.3,169.551L97.742,169.551L97.742,162.14L103.152,162.14L103.152,163.294L99.118,163.294L99.118,168.397Z" style="fill:rgb(111,154,168);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-311.37)">
|
||||
<path d="M108.223,169.551L108.223,162.14L111.462,162.14C112.267,162.14 112.973,162.296 113.58,162.606C114.187,162.917 114.659,163.347 114.998,163.898C115.337,164.448 115.506,165.098 115.506,165.846C115.506,166.587 115.337,167.234 114.998,167.788C114.659,168.342 114.187,168.775 113.58,169.085C112.973,169.396 112.267,169.551 111.462,169.551L108.223,169.551ZM109.599,168.386L111.399,168.386C111.956,168.386 112.438,168.281 112.844,168.069C113.25,167.857 113.564,167.561 113.786,167.18C114.008,166.798 114.119,166.354 114.119,165.846C114.119,165.33 114.008,164.884 113.786,164.506C113.564,164.129 113.25,163.834 112.844,163.622C112.438,163.411 111.956,163.305 111.399,163.305L109.599,163.305L109.599,168.386Z" style="fill:rgb(111,154,168);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-311.369)">
|
||||
<rect x="120.355" y="162.14" width="1.376" height="7.411" style="fill:rgb(111,154,168);"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-308.341)">
|
||||
<rect x="133.017" y="164.533" width="2.911" height="1.101" style="fill:rgb(111,154,168);"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-257.169)">
|
||||
<path d="M89.802,155.915L86.562,148.504L88.055,148.504L90.945,155.205L90.088,155.205L93.01,148.504L94.386,148.504L91.157,155.915L89.802,155.915Z" style="fill:rgb(111,154,168);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-257.169)">
|
||||
<path d="M99.827,151.585L103.512,151.585L103.512,152.717L99.827,152.717L99.827,151.585ZM99.933,154.761L104.115,154.761L104.115,155.915L98.557,155.915L98.557,148.504L103.967,148.504L103.967,149.658L99.933,149.658L99.933,154.761Z" style="fill:rgb(111,154,168);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-257.169)">
|
||||
<path d="M109.038,155.915L109.038,148.504L112.087,148.504C112.743,148.504 113.306,148.61 113.775,148.822C114.245,149.033 114.606,149.337 114.861,149.732C115.115,150.127 115.242,150.597 115.242,151.14C115.242,151.684 115.115,152.151 114.861,152.543C114.606,152.935 114.245,153.234 113.775,153.443C113.306,153.651 112.743,153.755 112.087,153.755L109.8,153.755L110.414,153.13L110.414,155.915L109.038,155.915ZM113.887,155.915L112.013,153.226L113.484,153.226L115.369,155.915L113.887,155.915ZM110.414,153.279L109.8,152.622L112.023,152.622C112.63,152.622 113.087,152.492 113.394,152.23C113.701,151.969 113.855,151.606 113.855,151.14C113.855,150.667 113.701,150.304 113.394,150.05C113.087,149.796 112.63,149.669 112.023,149.669L109.8,149.669L110.414,148.991L110.414,153.279Z" style="fill:rgb(111,154,168);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-257.169)">
|
||||
<path d="M122.462,156.021C121.883,156.021 121.329,155.938 120.8,155.772C120.27,155.606 119.85,155.392 119.54,155.131L120.016,154.062C120.313,154.295 120.68,154.489 121.117,154.644C121.555,154.8 122.003,154.877 122.462,154.877C122.85,154.877 123.164,154.835 123.404,154.75C123.644,154.665 123.82,154.551 123.933,154.406C124.046,154.261 124.103,154.097 124.103,153.914C124.103,153.688 124.022,153.506 123.859,153.369C123.697,153.231 123.487,153.122 123.229,153.04C122.972,152.959 122.686,152.883 122.372,152.813C122.058,152.742 121.744,152.659 121.43,152.564C121.116,152.469 120.83,152.345 120.572,152.193C120.314,152.042 120.104,151.839 119.942,151.585C119.78,151.331 119.699,151.006 119.699,150.611C119.699,150.208 119.806,149.84 120.022,149.504C120.237,149.169 120.565,148.901 121.006,148.7C121.447,148.499 122.007,148.398 122.684,148.398C123.129,148.398 123.57,148.455 124.007,148.567C124.445,148.68 124.826,148.843 125.151,149.054L124.717,150.124C124.385,149.926 124.043,149.78 123.69,149.684C123.337,149.589 122.998,149.541 122.674,149.541C122.292,149.541 121.984,149.587 121.747,149.679C121.511,149.771 121.338,149.891 121.228,150.039C121.119,150.187 121.064,150.357 121.064,150.547C121.064,150.773 121.144,150.955 121.303,151.092C121.461,151.23 121.67,151.338 121.927,151.415C122.185,151.493 122.472,151.569 122.79,151.643C123.108,151.717 123.423,151.8 123.737,151.892C124.052,151.983 124.337,152.103 124.595,152.252C124.853,152.4 125.061,152.601 125.22,152.855C125.378,153.109 125.458,153.43 125.458,153.819C125.458,154.214 125.35,154.579 125.135,154.914C124.92,155.249 124.59,155.518 124.145,155.719C123.7,155.92 123.139,156.021 122.462,156.021Z" style="fill:rgb(111,154,168);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-257.169)">
|
||||
<rect x="130.211" y="148.504" width="1.376" height="7.411" style="fill:rgb(111,154,168);"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-257.169)">
|
||||
<path d="M138.479,155.915L138.479,149.669L136.023,149.669L136.023,148.504L142.312,148.504L142.312,149.669L139.856,149.669L139.856,155.915L138.479,155.915Z" style="fill:rgb(111,154,168);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.99169,0,0,1.98741,-24.1895,-257.169)">
|
||||
<path d="M148.452,155.915L148.452,152.982L148.759,153.829L145.541,148.504L147.012,148.504L149.596,152.792L148.77,152.792L151.374,148.504L152.729,148.504L149.511,153.829L149.828,152.982L149.828,155.915L148.452,155.915Z" style="fill:rgb(111,154,168);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<defs>
|
||||
<image id="_Image2" width="134px" height="66px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIYAAABCCAYAAACSAK4zAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFNElEQVR4nO2dUY7TQAyG/6l6ArgI4h1xjRWnAIkHEC8I7oL2cnCEDg/bsMN0xmM7dsbJ1lLUypD6c/6/ycSNtAkA8ntkJOBmO+Epyve9XLlfK2ex7YlnqefFU9ameP48YIn04TGBGaf8DhkZaG6X6/8q3/dy5X6tnGYDZDy92jN4LvDlqfvu8bx6xBL510MGM06hxeA2H02MKDxLHYU5bo2xt+alPGtYJGJE4ClrC81xa4y9NS/lsdi4YszgAfo8AnO0jRG9+Wg8gIzHmqWuTfEwzdE3xloxPJvfmxi9RXpgc9DGWCPG7Oaj8eQqZ721alM8r2lzjI0RSQxp85HEKPfxvrRxeQhzjI0hKYbiNUrzR+YBZDyt2h1zyC4lexCjV9uKB4jDQ60BWzy9BXnDHG1jRGpeykPdjVjwWImh5aFq93IcnsIcAJDyW4x/J9nTbwMcHuvada6uvSee3w/XlPabwXViy+UzvxkeZ4k6Ry3II/KU+1zPHCm/Kc4YL+mbYc1D/crb4rHYHHnO/1wDPLnoBLBzy2sa5LQx4kl4/mZweMrPseYp/43DYxGOPOebg3UXw49nOX5ePD1tFDy3xqiL1bnZYhg2vznPEl48VG0hT9sYZbFoYhg2fwgeiTbLArNVu8r1jQFBsbsYJmKoQnosqDVgkaONURae2fzyOQcXw42nPhYjngzQI3EUxVC8tnKXYp/lfSZya+7dZ/AAMp5c5UY80uMh4alrj3iGxrAWw7N5bzHqOg5iROK5vZTUt5iWpylplHU4PK3akXnqY2/Nw9GmwyOfY0QTY0Xz03ksguLhatPgkc8xoomxovlD8mgGkOo5Rp2b3byUp9O8GY+RGCY81Nmd0kY1x4jWvJRnieBiqMNqAGk+x7iLYSKGOqjarRxV+5obG6MsPLP55XMOLEYknrExrH4kCti8O4/EmJpF+trRAsHDv5TMEsOxeXcxWpc2ikcaZW1jnrYxIonh2LwqrHkizFUaPLo5RjQxlM2H4LEIiqdVm8Gjm2NEE0PZ/GF5NKOFKvhzjDo3u/loPAZimPFQZ3fmAFK2+IzUfDQeAzE25RkMIG3nGNGa9+axmqvM4Cn3afDwjAFmsZcmBlVbIUYkHp4x7mLoebaaBhvzyC4ls8TwnKt481AL4IhzlWuub4xIYjg1/2J4FKMF/RwjWvNSnkhDroBzHv0cI1rzB5k4mvGsvJWXzTHq3Ozmo/EcaK4iX3z2AO5DrtViROKRzzGkYnjFXsWINOQiePjGgKCYtxiR5ipSnnIf70vbCh6+MSKJYdT8YXgcRgu6S8kexIg05NrhnIc2RqTmpTyM5qOJ4cojHC2sm2NEa96bhzJmK+c955HwUGtA1eJzT81784xq1zmhGOKw5in2kT8MLC0WrXlvnq3nKpY8RegeBq5zdzHaay6hGJF49D+i3cXQ83jcynO0YfKkj0jrHwaeJUaEuYqWZwkvHqr2gCd9QgKsHgaeIcaK5g/JI9GmM1pIn59MAVg+DHwXQyWGGY/0WFRn9/Tl2RSA9cPAWw65IvCsFGNzns6tfPr6vykAgPc30VAUQ/Hayl2KfZb3mchJN6r2FjyAjCdXuRGP9HhIeOraaJsC4BrDWgzP5r3FqOsoxCB51nxRhDzpW9sUgOfDwNRpUxplHQ5Pq3Zknp4GVjwNbdL3vikA4Ax0wPYmBseYk8Xo8lgExVMvNH/QpgAWYwA6c0QSg2vMSWJsztNZpKefY1MAwDkBKS+tSc0xu3kpT21kax7NNHjDW3muKQDgLxosMiKefF/BAAAAAElFTkSuQmCC"/>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 11 KiB |
BIN
website/assets/images/no-search-found.png
Executable file
After Width: | Height: | Size: 8.1 KiB |
BIN
website/assets/images/og-image.png
Normal file
After Width: | Height: | Size: 105 KiB |
BIN
website/assets/images/service-1.png
Executable file
After Width: | Height: | Size: 11 KiB |
BIN
website/assets/images/service-2.png
Executable file
After Width: | Height: | Size: 19 KiB |
BIN
website/assets/images/service-3.png
Executable file
After Width: | Height: | Size: 16 KiB |
BIN
website/assets/images/stepping-up.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
website/assets/images/user.png
Normal file
After Width: | Height: | Size: 852 B |
BIN
website/assets/images/users-scaled.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
website/assets/images/users.png
Normal file
After Width: | Height: | Size: 968 B |
1
website/assets/images/users.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" viewBox="0 0 24 24" fill="none" stroke="#ff6e00" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
After Width: | Height: | Size: 397 B |
BIN
website/assets/images/website-new.png
Normal file
After Width: | Height: | Size: 18 KiB |
36
website/assets/js/main.js
Executable file
|
@ -0,0 +1,36 @@
|
|||
// main script
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Dropdown Menu Toggler For Mobile
|
||||
// ----------------------------------------
|
||||
const dropdownMenuToggler = document.querySelectorAll(
|
||||
".nav-dropdown > .nav-link",
|
||||
);
|
||||
|
||||
dropdownMenuToggler.forEach((toggler) => {
|
||||
toggler?.addEventListener("click", (e) => {
|
||||
e.target.closest(".nav-item").classList.toggle("active");
|
||||
});
|
||||
});
|
||||
|
||||
// Testimonial Slider
|
||||
// ----------------------------------------
|
||||
new Swiper(".testimonial-slider", {
|
||||
spaceBetween: 24,
|
||||
loop: true,
|
||||
pagination: {
|
||||
el: ".testimonial-slider-pagination",
|
||||
type: "bullets",
|
||||
clickable: true,
|
||||
},
|
||||
breakpoints: {
|
||||
768: {
|
||||
slidesPerView: 2,
|
||||
},
|
||||
992: {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
},
|
||||
});
|
||||
})();
|
BIN
website/assets/plugins/.DS_Store
vendored
Normal file
179
website/assets/plugins/maps/google-map.js
Normal file
|
@ -0,0 +1,179 @@
|
|||
/*!***************************************************
|
||||
* Google Map
|
||||
*****************************************************/
|
||||
|
||||
window.marker = null;
|
||||
|
||||
function initialize() {
|
||||
var map,
|
||||
mapId = document.getElementById("map");
|
||||
var latitude = mapId.getAttribute("data-latitude");
|
||||
var longitude = mapId.getAttribute("data-longitude");
|
||||
var mapMarker = mapId.getAttribute("data-marker");
|
||||
var mapMarkerName = mapId.getAttribute("data-marker-name");
|
||||
var nottingham = new google.maps.LatLng(latitude, longitude);
|
||||
var style = [
|
||||
{
|
||||
featureType: "administrative",
|
||||
elementType: "all",
|
||||
stylers: [
|
||||
{
|
||||
saturation: "-100",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: "administrative.province",
|
||||
elementType: "all",
|
||||
stylers: [
|
||||
{
|
||||
visibility: "off",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: "landscape",
|
||||
elementType: "all",
|
||||
stylers: [
|
||||
{
|
||||
saturation: -100,
|
||||
},
|
||||
{
|
||||
lightness: 65,
|
||||
},
|
||||
{
|
||||
visibility: "on",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: "poi",
|
||||
elementType: "all",
|
||||
stylers: [
|
||||
{
|
||||
saturation: -100,
|
||||
},
|
||||
{
|
||||
lightness: "50",
|
||||
},
|
||||
{
|
||||
visibility: "simplified",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: "road",
|
||||
elementType: "all",
|
||||
stylers: [
|
||||
{
|
||||
saturation: "-100",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: "road.highway",
|
||||
elementType: "all",
|
||||
stylers: [
|
||||
{
|
||||
visibility: "simplified",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: "road.arterial",
|
||||
elementType: "all",
|
||||
stylers: [
|
||||
{
|
||||
lightness: "30",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: "road.local",
|
||||
elementType: "all",
|
||||
stylers: [
|
||||
{
|
||||
lightness: "40",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: "transit",
|
||||
elementType: "all",
|
||||
stylers: [
|
||||
{
|
||||
saturation: -100,
|
||||
},
|
||||
{
|
||||
visibility: "simplified",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: "water",
|
||||
elementType: "geometry",
|
||||
stylers: [
|
||||
{
|
||||
hue: "#ffff00",
|
||||
},
|
||||
{
|
||||
lightness: -25,
|
||||
},
|
||||
{
|
||||
saturation: -97,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: "water",
|
||||
elementType: "labels",
|
||||
stylers: [
|
||||
{
|
||||
lightness: -25,
|
||||
},
|
||||
{
|
||||
saturation: -100,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
var mapOptions = {
|
||||
center: nottingham,
|
||||
mapTypeId: google.maps.MapTypeId.ROADMAP,
|
||||
backgroundColor: "#000",
|
||||
zoom: 15,
|
||||
panControl: !1,
|
||||
zoomControl: !0,
|
||||
mapTypeControl: !1,
|
||||
scaleControl: !1,
|
||||
streetViewControl: !1,
|
||||
overviewMapControl: !1,
|
||||
zoomControlOptions: {
|
||||
style: google.maps.ZoomControlStyle.LARGE,
|
||||
},
|
||||
};
|
||||
map = new google.maps.Map(document.getElementById("map"), mapOptions);
|
||||
var mapType = new google.maps.StyledMapType(style, {
|
||||
name: "Grayscale",
|
||||
});
|
||||
map.mapTypes.set("grey", mapType);
|
||||
map.setMapTypeId("grey");
|
||||
var marker_image = mapMarker;
|
||||
var pinIcon = new google.maps.MarkerImage(
|
||||
marker_image,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new google.maps.Size(30, 50),
|
||||
);
|
||||
marker = new google.maps.Marker({
|
||||
position: nottingham,
|
||||
map: map,
|
||||
icon: pinIcon,
|
||||
title: mapMarkerName,
|
||||
});
|
||||
}
|
||||
var map = document.getElementById("map");
|
||||
if (map != null) {
|
||||
google.maps.event.addDomListener(window, "load", initialize);
|
||||
}
|
667
website/assets/plugins/swiper/swiper-bundle.css
Normal file
|
@ -0,0 +1,667 @@
|
|||
/**
|
||||
* Swiper 8.0.7
|
||||
* Most modern mobile touch slider and framework with hardware accelerated transitions
|
||||
* https://swiperjs.com
|
||||
*
|
||||
* Copyright 2014-2022 Vladimir Kharlampidi
|
||||
*
|
||||
* Released under the MIT License
|
||||
*
|
||||
* Released on: March 4, 2022
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: "swiper-icons";
|
||||
src: url("data:application/font-woff;charset=utf-8;base64, d09GRgABAAAAAAZgABAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAGRAAAABoAAAAci6qHkUdERUYAAAWgAAAAIwAAACQAYABXR1BPUwAABhQAAAAuAAAANuAY7+xHU1VCAAAFxAAAAFAAAABm2fPczU9TLzIAAAHcAAAASgAAAGBP9V5RY21hcAAAAkQAAACIAAABYt6F0cBjdnQgAAACzAAAAAQAAAAEABEBRGdhc3AAAAWYAAAACAAAAAj//wADZ2x5ZgAAAywAAADMAAAD2MHtryVoZWFkAAABbAAAADAAAAA2E2+eoWhoZWEAAAGcAAAAHwAAACQC9gDzaG10eAAAAigAAAAZAAAArgJkABFsb2NhAAAC0AAAAFoAAABaFQAUGG1heHAAAAG8AAAAHwAAACAAcABAbmFtZQAAA/gAAAE5AAACXvFdBwlwb3N0AAAFNAAAAGIAAACE5s74hXjaY2BkYGAAYpf5Hu/j+W2+MnAzMYDAzaX6QjD6/4//Bxj5GA8AuRwMYGkAPywL13jaY2BkYGA88P8Agx4j+/8fQDYfA1AEBWgDAIB2BOoAeNpjYGRgYNBh4GdgYgABEMnIABJzYNADCQAACWgAsQB42mNgYfzCOIGBlYGB0YcxjYGBwR1Kf2WQZGhhYGBiYGVmgAFGBiQQkOaawtDAoMBQxXjg/wEGPcYDDA4wNUA2CCgwsAAAO4EL6gAAeNpj2M0gyAACqxgGNWBkZ2D4/wMA+xkDdgAAAHjaY2BgYGaAYBkGRgYQiAHyGMF8FgYHIM3DwMHABGQrMOgyWDLEM1T9/w8UBfEMgLzE////P/5//f/V/xv+r4eaAAeMbAxwIUYmIMHEgKYAYjUcsDAwsLKxc3BycfPw8jEQA/gZBASFhEVExcQlJKWkZWTl5BUUlZRVVNXUNTQZBgMAAMR+E+gAEQFEAAAAKgAqACoANAA+AEgAUgBcAGYAcAB6AIQAjgCYAKIArAC2AMAAygDUAN4A6ADyAPwBBgEQARoBJAEuATgBQgFMAVYBYAFqAXQBfgGIAZIBnAGmAbIBzgHsAAB42u2NMQ6CUAyGW568x9AneYYgm4MJbhKFaExIOAVX8ApewSt4Bic4AfeAid3VOBixDxfPYEza5O+Xfi04YADggiUIULCuEJK8VhO4bSvpdnktHI5QCYtdi2sl8ZnXaHlqUrNKzdKcT8cjlq+rwZSvIVczNiezsfnP/uznmfPFBNODM2K7MTQ45YEAZqGP81AmGGcF3iPqOop0r1SPTaTbVkfUe4HXj97wYE+yNwWYxwWu4v1ugWHgo3S1XdZEVqWM7ET0cfnLGxWfkgR42o2PvWrDMBSFj/IHLaF0zKjRgdiVMwScNRAoWUoH78Y2icB/yIY09An6AH2Bdu/UB+yxopYshQiEvnvu0dURgDt8QeC8PDw7Fpji3fEA4z/PEJ6YOB5hKh4dj3EvXhxPqH/SKUY3rJ7srZ4FZnh1PMAtPhwP6fl2PMJMPDgeQ4rY8YT6Gzao0eAEA409DuggmTnFnOcSCiEiLMgxCiTI6Cq5DZUd3Qmp10vO0LaLTd2cjN4fOumlc7lUYbSQcZFkutRG7g6JKZKy0RmdLY680CDnEJ+UMkpFFe1RN7nxdVpXrC4aTtnaurOnYercZg2YVmLN/d/gczfEimrE/fs/bOuq29Zmn8tloORaXgZgGa78yO9/cnXm2BpaGvq25Dv9S4E9+5SIc9PqupJKhYFSSl47+Qcr1mYNAAAAeNptw0cKwkAAAMDZJA8Q7OUJvkLsPfZ6zFVERPy8qHh2YER+3i/BP83vIBLLySsoKimrqKqpa2hp6+jq6RsYGhmbmJqZSy0sraxtbO3sHRydnEMU4uR6yx7JJXveP7WrDycAAAAAAAH//wACeNpjYGRgYOABYhkgZgJCZgZNBkYGLQZtIJsFLMYAAAw3ALgAeNolizEKgDAQBCchRbC2sFER0YD6qVQiBCv/H9ezGI6Z5XBAw8CBK/m5iQQVauVbXLnOrMZv2oLdKFa8Pjuru2hJzGabmOSLzNMzvutpB3N42mNgZGBg4GKQYzBhYMxJLMlj4GBgAYow/P/PAJJhLM6sSoWKfWCAAwDAjgbRAAB42mNgYGBkAIIbCZo5IPrmUn0hGA0AO8EFTQAA");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
:root {
|
||||
--swiper-theme-color: #007aff;
|
||||
}
|
||||
.swiper {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
/* Fix of Webkit flickering */
|
||||
z-index: 1;
|
||||
}
|
||||
.swiper-vertical > .swiper-wrapper {
|
||||
flex-direction: column;
|
||||
}
|
||||
.swiper-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
transition-property: transform;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.swiper-android .swiper-slide,
|
||||
.swiper-wrapper {
|
||||
transform: translate3d(0px, 0, 0);
|
||||
}
|
||||
.swiper-pointer-events {
|
||||
touch-action: pan-y;
|
||||
}
|
||||
.swiper-pointer-events.swiper-vertical {
|
||||
touch-action: pan-x;
|
||||
}
|
||||
.swiper-slide {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
transition-property: transform;
|
||||
}
|
||||
.swiper-slide-invisible-blank {
|
||||
visibility: hidden;
|
||||
}
|
||||
/* Auto Height */
|
||||
.swiper-autoheight,
|
||||
.swiper-autoheight .swiper-slide {
|
||||
height: auto;
|
||||
}
|
||||
.swiper-autoheight .swiper-wrapper {
|
||||
align-items: flex-start;
|
||||
transition-property: transform, height;
|
||||
}
|
||||
.swiper-backface-hidden .swiper-slide {
|
||||
transform: translateZ(0);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
/* 3D Effects */
|
||||
.swiper-3d,
|
||||
.swiper-3d.swiper-css-mode .swiper-wrapper {
|
||||
perspective: 1200px;
|
||||
}
|
||||
.swiper-3d .swiper-wrapper,
|
||||
.swiper-3d .swiper-slide,
|
||||
.swiper-3d .swiper-slide-shadow,
|
||||
.swiper-3d .swiper-slide-shadow-left,
|
||||
.swiper-3d .swiper-slide-shadow-right,
|
||||
.swiper-3d .swiper-slide-shadow-top,
|
||||
.swiper-3d .swiper-slide-shadow-bottom,
|
||||
.swiper-3d .swiper-cube-shadow {
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
.swiper-3d .swiper-slide-shadow,
|
||||
.swiper-3d .swiper-slide-shadow-left,
|
||||
.swiper-3d .swiper-slide-shadow-right,
|
||||
.swiper-3d .swiper-slide-shadow-top,
|
||||
.swiper-3d .swiper-slide-shadow-bottom {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
.swiper-3d .swiper-slide-shadow {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.swiper-3d .swiper-slide-shadow-left {
|
||||
background-image: linear-gradient(
|
||||
to left,
|
||||
rgba(0, 0, 0, 0.5),
|
||||
rgba(0, 0, 0, 0)
|
||||
);
|
||||
}
|
||||
.swiper-3d .swiper-slide-shadow-right {
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
rgba(0, 0, 0, 0.5),
|
||||
rgba(0, 0, 0, 0)
|
||||
);
|
||||
}
|
||||
.swiper-3d .swiper-slide-shadow-top {
|
||||
background-image: linear-gradient(
|
||||
to top,
|
||||
rgba(0, 0, 0, 0.5),
|
||||
rgba(0, 0, 0, 0)
|
||||
);
|
||||
}
|
||||
.swiper-3d .swiper-slide-shadow-bottom {
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
rgba(0, 0, 0, 0.5),
|
||||
rgba(0, 0, 0, 0)
|
||||
);
|
||||
}
|
||||
/* CSS Mode */
|
||||
.swiper-css-mode > .swiper-wrapper {
|
||||
overflow: auto;
|
||||
scrollbar-width: none;
|
||||
/* For Firefox */
|
||||
-ms-overflow-style: none;
|
||||
/* For Internet Explorer and Edge */
|
||||
}
|
||||
.swiper-css-mode > .swiper-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.swiper-css-mode > .swiper-wrapper > .swiper-slide {
|
||||
scroll-snap-align: start start;
|
||||
}
|
||||
.swiper-horizontal.swiper-css-mode > .swiper-wrapper {
|
||||
scroll-snap-type: x mandatory;
|
||||
}
|
||||
.swiper-vertical.swiper-css-mode > .swiper-wrapper {
|
||||
scroll-snap-type: y mandatory;
|
||||
}
|
||||
.swiper-centered > .swiper-wrapper::before {
|
||||
content: "";
|
||||
flex-shrink: 0;
|
||||
order: 9999;
|
||||
}
|
||||
.swiper-centered.swiper-horizontal
|
||||
> .swiper-wrapper
|
||||
> .swiper-slide:first-child {
|
||||
margin-inline-start: var(--swiper-centered-offset-before);
|
||||
}
|
||||
.swiper-centered.swiper-horizontal > .swiper-wrapper::before {
|
||||
height: 100%;
|
||||
min-height: 1px;
|
||||
width: var(--swiper-centered-offset-after);
|
||||
}
|
||||
.swiper-centered.swiper-vertical > .swiper-wrapper > .swiper-slide:first-child {
|
||||
margin-block-start: var(--swiper-centered-offset-before);
|
||||
}
|
||||
.swiper-centered.swiper-vertical > .swiper-wrapper::before {
|
||||
width: 100%;
|
||||
min-width: 1px;
|
||||
height: var(--swiper-centered-offset-after);
|
||||
}
|
||||
.swiper-centered > .swiper-wrapper > .swiper-slide {
|
||||
scroll-snap-align: center center;
|
||||
}
|
||||
.swiper-virtual .swiper-slide {
|
||||
-webkit-backface-visibility: hidden;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
.swiper-virtual.swiper-css-mode .swiper-wrapper::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.swiper-virtual.swiper-css-mode.swiper-horizontal .swiper-wrapper::after {
|
||||
height: 1px;
|
||||
width: var(--swiper-virtual-size);
|
||||
}
|
||||
.swiper-virtual.swiper-css-mode.swiper-vertical .swiper-wrapper::after {
|
||||
width: 1px;
|
||||
height: var(--swiper-virtual-size);
|
||||
}
|
||||
:root {
|
||||
--swiper-navigation-size: 44px;
|
||||
/*
|
||||
--swiper-navigation-color: var(--swiper-theme-color);
|
||||
*/
|
||||
}
|
||||
.swiper-button-prev,
|
||||
.swiper-button-next {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: calc(var(--swiper-navigation-size) / 44 * 27);
|
||||
height: var(--swiper-navigation-size);
|
||||
margin-top: calc(0px - (var(--swiper-navigation-size) / 2));
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--swiper-navigation-color, var(--swiper-theme-color));
|
||||
}
|
||||
.swiper-button-prev.swiper-button-disabled,
|
||||
.swiper-button-next.swiper-button-disabled {
|
||||
opacity: 0.35;
|
||||
cursor: auto;
|
||||
pointer-events: none;
|
||||
}
|
||||
.swiper-button-prev:after,
|
||||
.swiper-button-next:after {
|
||||
font-family: swiper-icons;
|
||||
font-size: var(--swiper-navigation-size);
|
||||
text-transform: none !important;
|
||||
letter-spacing: 0;
|
||||
text-transform: none;
|
||||
font-variant: initial;
|
||||
line-height: 1;
|
||||
}
|
||||
.swiper-button-prev,
|
||||
.swiper-rtl .swiper-button-next {
|
||||
left: 10px;
|
||||
right: auto;
|
||||
}
|
||||
.swiper-button-prev:after,
|
||||
.swiper-rtl .swiper-button-next:after {
|
||||
content: "prev";
|
||||
}
|
||||
.swiper-button-next,
|
||||
.swiper-rtl .swiper-button-prev {
|
||||
right: 10px;
|
||||
left: auto;
|
||||
}
|
||||
.swiper-button-next:after,
|
||||
.swiper-rtl .swiper-button-prev:after {
|
||||
content: "next";
|
||||
}
|
||||
.swiper-button-lock {
|
||||
display: none;
|
||||
}
|
||||
:root {
|
||||
/*
|
||||
--swiper-pagination-color: var(--swiper-theme-color);
|
||||
--swiper-pagination-bullet-size: 8px;
|
||||
--swiper-pagination-bullet-width: 8px;
|
||||
--swiper-pagination-bullet-height: 8px;
|
||||
--swiper-pagination-bullet-inactive-color: #000;
|
||||
--swiper-pagination-bullet-inactive-opacity: 0.2;
|
||||
--swiper-pagination-bullet-opacity: 1;
|
||||
--swiper-pagination-bullet-horizontal-gap: 4px;
|
||||
--swiper-pagination-bullet-vertical-gap: 6px;
|
||||
*/
|
||||
}
|
||||
.swiper-pagination {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
transition: 300ms opacity;
|
||||
transform: translate3d(0, 0, 0);
|
||||
z-index: 10;
|
||||
}
|
||||
.swiper-pagination.swiper-pagination-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
/* Common Styles */
|
||||
.swiper-pagination-fraction,
|
||||
.swiper-pagination-custom,
|
||||
.swiper-horizontal > .swiper-pagination-bullets,
|
||||
.swiper-pagination-bullets.swiper-pagination-horizontal {
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
/* Bullets */
|
||||
.swiper-pagination-bullets-dynamic {
|
||||
overflow: hidden;
|
||||
font-size: 0;
|
||||
}
|
||||
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet {
|
||||
transform: scale(0.33);
|
||||
position: relative;
|
||||
}
|
||||
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active {
|
||||
transform: scale(1);
|
||||
}
|
||||
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-main {
|
||||
transform: scale(1);
|
||||
}
|
||||
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev {
|
||||
transform: scale(0.66);
|
||||
}
|
||||
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev-prev {
|
||||
transform: scale(0.33);
|
||||
}
|
||||
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next {
|
||||
transform: scale(0.66);
|
||||
}
|
||||
.swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next-next {
|
||||
transform: scale(0.33);
|
||||
}
|
||||
.swiper-pagination-bullet {
|
||||
width: var(
|
||||
--swiper-pagination-bullet-width,
|
||||
var(--swiper-pagination-bullet-size, 8px)
|
||||
);
|
||||
height: var(
|
||||
--swiper-pagination-bullet-height,
|
||||
var(--swiper-pagination-bullet-size, 8px)
|
||||
);
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
background: var(--swiper-pagination-bullet-inactive-color, #000);
|
||||
opacity: var(--swiper-pagination-bullet-inactive-opacity, 0.2);
|
||||
}
|
||||
button.swiper-pagination-bullet {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
.swiper-pagination-clickable .swiper-pagination-bullet {
|
||||
cursor: pointer;
|
||||
}
|
||||
.swiper-pagination-bullet:only-child {
|
||||
display: none !important;
|
||||
}
|
||||
.swiper-pagination-bullet-active {
|
||||
opacity: var(--swiper-pagination-bullet-opacity, 1);
|
||||
background: var(--swiper-pagination-color, var(--swiper-theme-color));
|
||||
}
|
||||
.swiper-vertical > .swiper-pagination-bullets,
|
||||
.swiper-pagination-vertical.swiper-pagination-bullets {
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translate3d(0px, -50%, 0);
|
||||
}
|
||||
.swiper-vertical > .swiper-pagination-bullets .swiper-pagination-bullet,
|
||||
.swiper-pagination-vertical.swiper-pagination-bullets
|
||||
.swiper-pagination-bullet {
|
||||
margin: var(--swiper-pagination-bullet-vertical-gap, 6px) 0;
|
||||
display: block;
|
||||
}
|
||||
.swiper-vertical > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic,
|
||||
.swiper-pagination-vertical.swiper-pagination-bullets.swiper-pagination-bullets-dynamic {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 8px;
|
||||
}
|
||||
.swiper-vertical
|
||||
> .swiper-pagination-bullets.swiper-pagination-bullets-dynamic
|
||||
.swiper-pagination-bullet,
|
||||
.swiper-pagination-vertical.swiper-pagination-bullets.swiper-pagination-bullets-dynamic
|
||||
.swiper-pagination-bullet {
|
||||
display: inline-block;
|
||||
transition:
|
||||
200ms transform,
|
||||
200ms top;
|
||||
}
|
||||
.swiper-horizontal > .swiper-pagination-bullets .swiper-pagination-bullet,
|
||||
.swiper-pagination-horizontal.swiper-pagination-bullets
|
||||
.swiper-pagination-bullet {
|
||||
margin: 0 var(--swiper-pagination-bullet-horizontal-gap, 4px);
|
||||
}
|
||||
.swiper-horizontal
|
||||
> .swiper-pagination-bullets.swiper-pagination-bullets-dynamic,
|
||||
.swiper-pagination-horizontal.swiper-pagination-bullets.swiper-pagination-bullets-dynamic {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.swiper-horizontal
|
||||
> .swiper-pagination-bullets.swiper-pagination-bullets-dynamic
|
||||
.swiper-pagination-bullet,
|
||||
.swiper-pagination-horizontal.swiper-pagination-bullets.swiper-pagination-bullets-dynamic
|
||||
.swiper-pagination-bullet {
|
||||
transition:
|
||||
200ms transform,
|
||||
200ms left;
|
||||
}
|
||||
.swiper-horizontal.swiper-rtl
|
||||
> .swiper-pagination-bullets-dynamic
|
||||
.swiper-pagination-bullet {
|
||||
transition:
|
||||
200ms transform,
|
||||
200ms right;
|
||||
}
|
||||
/* Progress */
|
||||
.swiper-pagination-progressbar {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
position: absolute;
|
||||
}
|
||||
.swiper-pagination-progressbar .swiper-pagination-progressbar-fill {
|
||||
background: var(--swiper-pagination-color, var(--swiper-theme-color));
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: scale(0);
|
||||
transform-origin: left top;
|
||||
}
|
||||
.swiper-rtl .swiper-pagination-progressbar .swiper-pagination-progressbar-fill {
|
||||
transform-origin: right top;
|
||||
}
|
||||
.swiper-horizontal > .swiper-pagination-progressbar,
|
||||
.swiper-pagination-progressbar.swiper-pagination-horizontal,
|
||||
.swiper-vertical
|
||||
> .swiper-pagination-progressbar.swiper-pagination-progressbar-opposite,
|
||||
.swiper-pagination-progressbar.swiper-pagination-vertical.swiper-pagination-progressbar-opposite {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.swiper-vertical > .swiper-pagination-progressbar,
|
||||
.swiper-pagination-progressbar.swiper-pagination-vertical,
|
||||
.swiper-horizontal
|
||||
> .swiper-pagination-progressbar.swiper-pagination-progressbar-opposite,
|
||||
.swiper-pagination-progressbar.swiper-pagination-horizontal.swiper-pagination-progressbar-opposite {
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.swiper-pagination-lock {
|
||||
display: none;
|
||||
}
|
||||
/* Scrollbar */
|
||||
.swiper-scrollbar {
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
-ms-touch-action: none;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.swiper-horizontal > .swiper-scrollbar {
|
||||
position: absolute;
|
||||
left: 1%;
|
||||
bottom: 3px;
|
||||
z-index: 50;
|
||||
height: 5px;
|
||||
width: 98%;
|
||||
}
|
||||
.swiper-vertical > .swiper-scrollbar {
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 1%;
|
||||
z-index: 50;
|
||||
width: 5px;
|
||||
height: 98%;
|
||||
}
|
||||
.swiper-scrollbar-drag {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 10px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.swiper-scrollbar-cursor-drag {
|
||||
cursor: move;
|
||||
}
|
||||
.swiper-scrollbar-lock {
|
||||
display: none;
|
||||
}
|
||||
.swiper-zoom-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
.swiper-zoom-container > img,
|
||||
.swiper-zoom-container > svg,
|
||||
.swiper-zoom-container > canvas {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.swiper-slide-zoomed {
|
||||
cursor: move;
|
||||
}
|
||||
/* Preloader */
|
||||
:root {
|
||||
/*
|
||||
--swiper-preloader-color: var(--swiper-theme-color);
|
||||
*/
|
||||
}
|
||||
.swiper-lazy-preloader {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -21px;
|
||||
margin-top: -21px;
|
||||
z-index: 10;
|
||||
transform-origin: 50%;
|
||||
box-sizing: border-box;
|
||||
border: 4px solid var(--swiper-preloader-color, var(--swiper-theme-color));
|
||||
border-radius: 50%;
|
||||
border-top-color: transparent;
|
||||
}
|
||||
.swiper-slide-visible .swiper-lazy-preloader {
|
||||
animation: swiper-preloader-spin 1s infinite linear;
|
||||
}
|
||||
.swiper-lazy-preloader-white {
|
||||
--swiper-preloader-color: #fff;
|
||||
}
|
||||
.swiper-lazy-preloader-black {
|
||||
--swiper-preloader-color: #000;
|
||||
}
|
||||
@keyframes swiper-preloader-spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
/* a11y */
|
||||
.swiper .swiper-notification {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
z-index: -1000;
|
||||
}
|
||||
.swiper-free-mode > .swiper-wrapper {
|
||||
transition-timing-function: ease-out;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.swiper-grid > .swiper-wrapper {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.swiper-grid-column > .swiper-wrapper {
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
}
|
||||
.swiper-fade.swiper-free-mode .swiper-slide {
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
.swiper-fade .swiper-slide {
|
||||
pointer-events: none;
|
||||
transition-property: opacity;
|
||||
}
|
||||
.swiper-fade .swiper-slide .swiper-slide {
|
||||
pointer-events: none;
|
||||
}
|
||||
.swiper-fade .swiper-slide-active,
|
||||
.swiper-fade .swiper-slide-active .swiper-slide-active {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.swiper-cube {
|
||||
overflow: visible;
|
||||
}
|
||||
.swiper-cube .swiper-slide {
|
||||
pointer-events: none;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
z-index: 1;
|
||||
visibility: hidden;
|
||||
transform-origin: 0 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.swiper-cube .swiper-slide .swiper-slide {
|
||||
pointer-events: none;
|
||||
}
|
||||
.swiper-cube.swiper-rtl .swiper-slide {
|
||||
transform-origin: 100% 0;
|
||||
}
|
||||
.swiper-cube .swiper-slide-active,
|
||||
.swiper-cube .swiper-slide-active .swiper-slide-active {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.swiper-cube .swiper-slide-active,
|
||||
.swiper-cube .swiper-slide-next,
|
||||
.swiper-cube .swiper-slide-prev,
|
||||
.swiper-cube .swiper-slide-next + .swiper-slide {
|
||||
pointer-events: auto;
|
||||
visibility: visible;
|
||||
}
|
||||
.swiper-cube .swiper-slide-shadow-top,
|
||||
.swiper-cube .swiper-slide-shadow-bottom,
|
||||
.swiper-cube .swiper-slide-shadow-left,
|
||||
.swiper-cube .swiper-slide-shadow-right {
|
||||
z-index: 0;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
.swiper-cube .swiper-cube-shadow {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.6;
|
||||
z-index: 0;
|
||||
}
|
||||
.swiper-cube .swiper-cube-shadow:before {
|
||||
content: "";
|
||||
background: #000;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
filter: blur(50px);
|
||||
}
|
||||
.swiper-flip {
|
||||
overflow: visible;
|
||||
}
|
||||
.swiper-flip .swiper-slide {
|
||||
pointer-events: none;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
.swiper-flip .swiper-slide .swiper-slide {
|
||||
pointer-events: none;
|
||||
}
|
||||
.swiper-flip .swiper-slide-active,
|
||||
.swiper-flip .swiper-slide-active .swiper-slide-active {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.swiper-flip .swiper-slide-shadow-top,
|
||||
.swiper-flip .swiper-slide-shadow-bottom,
|
||||
.swiper-flip .swiper-slide-shadow-left,
|
||||
.swiper-flip .swiper-slide-shadow-right {
|
||||
z-index: 0;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
.swiper-creative .swiper-slide {
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
overflow: hidden;
|
||||
transition-property: transform, opacity, height;
|
||||
}
|
||||
.swiper-cards {
|
||||
overflow: visible;
|
||||
}
|
||||
.swiper-cards .swiper-slide {
|
||||
transform-origin: center bottom;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
overflow: hidden;
|
||||
}
|
11852
website/assets/plugins/swiper/swiper-bundle.js
Normal file
59
website/assets/scss/base.scss
Executable file
|
@ -0,0 +1,59 @@
|
|||
html {
|
||||
@apply text-base-sm md:text-base;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-body font-primary font-normal leading-relaxed text-text;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@apply font-secondary font-bold leading-tight text-dark;
|
||||
}
|
||||
|
||||
h1,
|
||||
.h1 {
|
||||
@apply text-h1-sm md:text-h1;
|
||||
}
|
||||
|
||||
h2,
|
||||
.h2 {
|
||||
@apply text-h2-sm md:text-h2;
|
||||
}
|
||||
|
||||
h3,
|
||||
.h3 {
|
||||
@apply text-h3-sm md:text-h3;
|
||||
}
|
||||
|
||||
h4,
|
||||
.h4 {
|
||||
@apply text-h4;
|
||||
}
|
||||
|
||||
h5,
|
||||
.h5 {
|
||||
@apply text-h5;
|
||||
}
|
||||
|
||||
h6,
|
||||
.h6 {
|
||||
@apply text-h6;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
code {
|
||||
@apply after:border-none;
|
||||
}
|
||||
|
||||
blockquote > p {
|
||||
@apply my-0 #{!important};
|
||||
}
|
15
website/assets/scss/buttons.scss
Executable file
|
@ -0,0 +1,15 @@
|
|||
.btn {
|
||||
@apply inline-block rounded border border-transparent px-5 py-2 font-semibold capitalize transition;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
@apply rounded-sm px-4 py-1.5 text-sm;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply border-primary bg-primary text-white;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
@apply border-dark text-dark hover:bg-dark bg-transparent hover:text-white;
|
||||
}
|
74
website/assets/scss/components.scss
Executable file
|
@ -0,0 +1,74 @@
|
|||
main {
|
||||
min-height: 70vh;
|
||||
}
|
||||
|
||||
// section style
|
||||
.section {
|
||||
@apply py-24 xl:py-28;
|
||||
&-sm {
|
||||
@apply py-16 xl:py-20;
|
||||
}
|
||||
}
|
||||
|
||||
// container
|
||||
.container {
|
||||
@apply mx-auto px-4 2xl:max-w-[1320px];
|
||||
}
|
||||
|
||||
// form style
|
||||
.form-input {
|
||||
@apply bg-theme-light text-dark placeholder:text-light focus:border-primary w-full rounded border-transparent px-6 py-4 focus:ring-transparent;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
@apply font-secondary text-dark mb-4 block text-xl font-normal;
|
||||
}
|
||||
|
||||
// social icons
|
||||
.social-icons {
|
||||
@apply space-x-4;
|
||||
li {
|
||||
@apply inline-block;
|
||||
a {
|
||||
@apply bg-primary flex h-9 w-9 items-center justify-center rounded text-center leading-9 text-white;
|
||||
svg {
|
||||
@apply h-5 w-5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// swiper pagination
|
||||
.swiper-pagination-bullet {
|
||||
@apply bg-theme-light h-2.5 w-2.5 opacity-100 mx-1.5 #{!important};
|
||||
|
||||
&-active {
|
||||
@apply bg-primary h-4 w-4 #{!important};
|
||||
}
|
||||
}
|
||||
|
||||
// content style
|
||||
.content {
|
||||
@apply prose max-w-none;
|
||||
@apply prose-headings:mb-[.3em] prose-headings:mt-[.6em] prose-headings:text-dark;
|
||||
@apply prose-h1:text-h1-sm md:prose-h1:text-h1;
|
||||
@apply prose-h2:text-h2-sm md:prose-h2:text-h2;
|
||||
@apply prose-h3:text-h3-sm md:prose-h3:text-h3;
|
||||
@apply prose-img:max-w-full prose-img:rounded;
|
||||
@apply prose-hr:border-border;
|
||||
@apply prose-p:text-base prose-p:text-text;
|
||||
@apply prose-blockquote:rounded-lg prose-blockquote:border prose-blockquote:border-l-[10px] prose-blockquote:border-primary prose-blockquote:bg-theme-light prose-blockquote:px-8 prose-blockquote:py-10 prose-blockquote:font-secondary prose-blockquote:text-2xl prose-blockquote:not-italic prose-blockquote:text-dark;
|
||||
@apply prose-pre:rounded-lg prose-pre:bg-theme-light;
|
||||
@apply prose-code:px-1;
|
||||
@apply prose-strong:text-dark;
|
||||
@apply prose-a:text-text prose-a:underline hover:prose-a:text-primary;
|
||||
@apply prose-li:text-text;
|
||||
@apply prose-table:relative prose-table:overflow-hidden prose-table:rounded-lg prose-table:before:absolute prose-table:before:left-0 prose-table:before:top-0 prose-table:before:h-full prose-table:before:w-full prose-table:before:rounded-[inherit] prose-table:before:border prose-table:before:content-[""];
|
||||
@apply prose-thead:border-border prose-thead:bg-theme-light;
|
||||
@apply prose-th:relative prose-th:z-10 prose-th:px-4 prose-th:py-[18px] prose-th:text-dark;
|
||||
@apply prose-tr:border-border;
|
||||
@apply prose-td:relative prose-td:z-10 prose-td:px-3 prose-td:py-[18px];
|
||||
.btn {
|
||||
@apply no-underline hover:text-white #{!important};
|
||||
}
|
||||
}
|
127
website/assets/scss/custom.scss
Executable file
|
@ -0,0 +1,127 @@
|
|||
// Add your own custom styles here
|
||||
.grid-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex-basis: calc(50% - 10px); /* Adjust width as necessary */
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hr-list {
|
||||
border: 0;
|
||||
border-top: 1px solid #ccc;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.link {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.time {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header-with-image {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.header-with-image img {
|
||||
margin-right: 10px;
|
||||
max-width: 100px; /* Adjust as needed */
|
||||
max-height: 100px; /* Adjust as needed */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.read-more-link {
|
||||
color: #FF6E00; /* Use the variable defined in theme.json */
|
||||
}
|
||||
|
||||
.center-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.grid-container-small {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.hr-list {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.center-layout {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.hr-list2 {
|
||||
border: 20;
|
||||
border-top: 1px solid #FF6E00;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.header-with-image2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-with-image2 img {
|
||||
display: inline-block;
|
||||
|
||||
}
|
||||
|
||||
.line {
|
||||
border-top: 1px solid #FF6E00; /* Change color and thickness as needed */
|
||||
margin: 10px 0; /* Adjust spacing between the line and the divs */
|
||||
}
|
30
website/assets/scss/main.scss
Executable file
|
@ -0,0 +1,30 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
@import "base";
|
||||
}
|
||||
|
||||
@layer components {
|
||||
@import "components";
|
||||
@import "navigation";
|
||||
@import "buttons";
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@import "utilities";
|
||||
}
|
||||
|
||||
@import "search";
|
||||
@import "social-share";
|
||||
@import "gallery-slider";
|
||||
@import "images";
|
||||
@import "toc";
|
||||
@import "tab";
|
||||
@import "accordion";
|
||||
@import "modal";
|
||||
@import "notice";
|
||||
|
||||
@import "module-overrides";
|
||||
@import "custom";
|
57
website/assets/scss/module-overrides.scss
Normal file
|
@ -0,0 +1,57 @@
|
|||
// table of contents
|
||||
.table-of-content {
|
||||
@apply overflow-hidden rounded;
|
||||
}
|
||||
|
||||
// share icons
|
||||
.share-icons {
|
||||
.share-link {
|
||||
@apply h-9 w-9 rounded leading-9;
|
||||
@apply bg-primary hover:bg-primary;
|
||||
}
|
||||
.share-icon svg {
|
||||
}
|
||||
}
|
||||
|
||||
// notice
|
||||
.notice {
|
||||
@apply rounded-lg;
|
||||
}
|
||||
|
||||
// tab
|
||||
.tab {
|
||||
@apply border-border overflow-hidden rounded-lg border;
|
||||
&-nav {
|
||||
@apply border-border bg-theme-light pl-4;
|
||||
|
||||
&-item {
|
||||
@apply text-dark px-8 text-lg #{!important};
|
||||
&.active {
|
||||
@apply border-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-content {
|
||||
&-panel {
|
||||
@apply px-4 pt-0 #{!important};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// accordion
|
||||
.accordion {
|
||||
@apply border-border bg-theme-light mb-6 overflow-hidden rounded-lg border;
|
||||
&-header {
|
||||
@apply text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
// cookie consent
|
||||
.cookie-box {
|
||||
@apply rounded-lg #{!important};
|
||||
}
|
||||
|
||||
// slider
|
||||
.gallery-slider {
|
||||
@apply ml-0 #{!important};
|
||||
}
|
87
website/assets/scss/navigation.scss
Executable file
|
@ -0,0 +1,87 @@
|
|||
// navbar toggler
|
||||
input#nav-toggle:checked + label #show-button {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
input#nav-toggle:checked + label #hide-button {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
input#nav-toggle:checked ~ #nav-menu {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply bg-body py-6;
|
||||
}
|
||||
|
||||
// navbar items
|
||||
.navbar {
|
||||
@apply relative flex flex-wrap items-center justify-between;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
@apply text-dark text-xl font-semibold;
|
||||
image {
|
||||
@apply max-h-full max-w-full;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
@apply text-center lg:text-left;
|
||||
}
|
||||
|
||||
// .nav-item {
|
||||
// @apply mx-3;
|
||||
// }
|
||||
|
||||
.nav-link {
|
||||
@apply text-dark hover:text-primary block p-3 cursor-pointer font-semibold transition lg:px-2 lg:py-3;
|
||||
}
|
||||
|
||||
.nav-dropdown {
|
||||
@apply mr-0;
|
||||
& > svg {
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
&.active {
|
||||
.nav-dropdown-list {
|
||||
@apply block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-dropdown-list {
|
||||
@apply bg-body z-10 min-w-[180px] rounded p-4 shadow hidden lg:invisible lg:absolute lg:block lg:opacity-0;
|
||||
}
|
||||
|
||||
.nav-dropdown-item {
|
||||
@apply [&:not(:last-child)]:mb-2;
|
||||
}
|
||||
|
||||
.nav-dropdown-link {
|
||||
@apply text-dark hover:text-primary block py-1 font-semibold transition;
|
||||
}
|
||||
|
||||
//theme-switcher
|
||||
.theme-switcher {
|
||||
@apply inline-flex;
|
||||
|
||||
label {
|
||||
@apply bg-border relative inline-block h-4 w-6 cursor-pointer rounded-2xl lg:w-10;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply absolute opacity-0;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply bg-dark absolute -top-1 left-0 flex h-6 w-6 items-center justify-center rounded-full transition-all duration-300;
|
||||
}
|
||||
|
||||
input:checked + label {
|
||||
span {
|
||||
@apply lg:left-4;
|
||||
}
|
||||
}
|
||||
}
|
20
website/assets/scss/utilities.scss
Executable file
|
@ -0,0 +1,20 @@
|
|||
.bg-gradient {
|
||||
@apply bg-gradient-to-b from-[rgba(249,249,249,1)] from-[0.53%] to-white to-[83.28%];
|
||||
}
|
||||
|
||||
.rounded-sm {
|
||||
@apply rounded-[4px];
|
||||
}
|
||||
.rounded {
|
||||
@apply rounded-[6px];
|
||||
}
|
||||
.rounded-lg {
|
||||
@apply rounded-[12px];
|
||||
}
|
||||
.rounded-xl {
|
||||
@apply rounded-[16px];
|
||||
}
|
||||
|
||||
.shadow {
|
||||
box-shadow: 0px 4px 40px rgba(0, 0, 0, 0.05);
|
||||
}
|
6
website/config/_default/languages.toml
Executable file
|
@ -0,0 +1,6 @@
|
|||
################ English language ##################
|
||||
[en]
|
||||
languageName = "En"
|
||||
languageCode = "en-us"
|
||||
contentDir = "content/english"
|
||||
weight = 1
|
81
website/config/_default/menus.en.toml
Executable file
|
@ -0,0 +1,81 @@
|
|||
############# English navigation ##############
|
||||
|
||||
# main menu
|
||||
[[main]]
|
||||
name = "For You"
|
||||
weight = 1
|
||||
hasChildren = true
|
||||
|
||||
[[main]]
|
||||
parent = "For You"
|
||||
name = "Individuals"
|
||||
pageRef = "/Individuals"
|
||||
|
||||
[[main]]
|
||||
parent = "For You"
|
||||
name = "Developers"
|
||||
pageRef = "/Developers"
|
||||
|
||||
[[main]]
|
||||
parent = "For You"
|
||||
name = "European Commission"
|
||||
pageRef = "/EC"
|
||||
|
||||
[[main]]
|
||||
weight = 2
|
||||
name = "Consortium"
|
||||
hasChildren = true
|
||||
|
||||
[[main]]
|
||||
parent = "Consortium"
|
||||
name = "Open Internet Discourse"
|
||||
pageRef = "/OID"
|
||||
|
||||
[[main]]
|
||||
parent = "Consortium"
|
||||
name = "NLnet"
|
||||
pageRef = "/NLnet"
|
||||
|
||||
[[main]]
|
||||
parent = "Consortium"
|
||||
name = "Tweag"
|
||||
pageRef = "/Tweag"
|
||||
|
||||
[[main]]
|
||||
parent = "Consortium"
|
||||
name = "NORDUnet"
|
||||
pageRef = "/NORDUnet"
|
||||
|
||||
[[main]]
|
||||
weight = 3
|
||||
name = "Fediversity"
|
||||
pageRef = "Fediversity"
|
||||
|
||||
[[main]]
|
||||
weight = 4
|
||||
name = "Grants"
|
||||
pageRef = "Grants"
|
||||
|
||||
[[main]]
|
||||
weight = 5
|
||||
name = "Events"
|
||||
pageRef = "Events"
|
||||
|
||||
[[main]]
|
||||
weight = 6
|
||||
name = "News"
|
||||
pageRef = "Blog"
|
||||
|
||||
|
||||
|
||||
|
||||
# footer menu
|
||||
[[footer]]
|
||||
name = "About"
|
||||
pageRef = "fediversity"
|
||||
weight = 1
|
||||
|
||||
[[footer]]
|
||||
name = "Privacy Policy"
|
||||
pageRef = "/privacy-policy"
|
||||
weight = 3
|
93
website/config/_default/module.toml
Normal file
|
@ -0,0 +1,93 @@
|
|||
[hugoVersion]
|
||||
extended = true
|
||||
min = "0.115.2"
|
||||
|
||||
# [[imports]]
|
||||
# path = "github.com/zeon-studio/hugoplate"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/search"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/pwa"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/images"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/videos"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/icons/font-awesome"
|
||||
|
||||
# [[imports]]
|
||||
# path = "github.com/gethugothemes/hugo-modules/icons/themify-icons"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/gzip-caching"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/adsense"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/accordion"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/table-of-contents"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/tab"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/modal"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/gallery-slider"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/components/preloader"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/components/social-share"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/components/cookie-consent"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/components/custom-script"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/components/render-link"
|
||||
|
||||
# [[imports]]
|
||||
# path = "github.com/gethugothemes/hugo-modules/components/valine-comment"
|
||||
|
||||
# [[imports]]
|
||||
# path = "github.com/gethugothemes/hugo-modules/components/crisp-chat"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/shortcodes/button"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/shortcodes/notice"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/seo-tools/basic-seo"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/seo-tools/site-verifications"
|
||||
|
||||
[[imports]]
|
||||
path = "github.com/gethugothemes/hugo-modules/seo-tools/google-tag-manager"
|
||||
|
||||
# [[imports]]
|
||||
# path = "github.com/gethugothemes/hugo-modules/seo-tools/baidu-analytics"
|
||||
|
||||
# [[imports]]
|
||||
# path = "github.com/gethugothemes/hugo-modules/seo-tools/matomo-analytics"
|
||||
|
||||
# [[imports]]
|
||||
# path = "github.com/gethugothemes/hugo-modules/seo-tools/plausible-analytics"
|
||||
|
||||
# [[imports]]
|
||||
# path = "github.com/gethugothemes/hugo-modules/seo-tools/counter-analytics"
|
101
website/config/_default/params.toml
Executable file
|
@ -0,0 +1,101 @@
|
|||
#################### default parameters ################################
|
||||
# favicon module: https://github.com/gethugothemes/hugo-modules/tree/master/images#favicon-implementation
|
||||
favicon = "images/favicon.png"
|
||||
# logo module: https://github.com/gethugothemes/hugo-modules/tree/master/images#logo-implementation
|
||||
logo = "images/ngi_fedi_full.svg"
|
||||
logo_darkmode = "images/logo-darkmode.png"
|
||||
# use `px` or `x` with logo_width, example: "100px".
|
||||
# Note: logo_width is not work with .svg file
|
||||
logo_width = "160px"
|
||||
logo_height = "32px"
|
||||
# if logo_webp set false, will not generate WEBP version of logo | default is true
|
||||
logo_webp = true
|
||||
# logo text will only show when logo is missing.
|
||||
logo_text = "Hugoplate"
|
||||
# navbar fixed to top
|
||||
navbar_fixed = true
|
||||
# theme-mode
|
||||
theme_switcher = false
|
||||
theme_default = "system" # available options [light/dark/system]
|
||||
# Main Sections
|
||||
mainSections = ["blog"]
|
||||
# contact form action
|
||||
contact_form_action = "#" # contact form works with [https://airform.io/] or [https://formspree.io]
|
||||
# google tag manager, see https://developers.google.com/tag-manager/
|
||||
google_tag_manager = "" # example: G-XXXXXXXXXX
|
||||
google_adsense = "" # example: ca-pub-xxxxxxxxxxxxxxxx
|
||||
# custom script on header, example: custom_script= "<script>console.log(\"Hello World\")</script>"
|
||||
custom_script = ""
|
||||
# copyright
|
||||
# copyright = "Designed & Developed by [Zeon Studio](https://zeon.studio)"
|
||||
|
||||
# Preloader
|
||||
# preloader module: https://github.com/gethugothemes/hugo-modules/tree/master/components/preloader
|
||||
[preloader]
|
||||
enable = false
|
||||
preloader = "" # use jpg, png, svg or gif format.
|
||||
|
||||
# Navigation button
|
||||
[navigation_button]
|
||||
enable = true
|
||||
label = "Contact"
|
||||
link = "contact"
|
||||
|
||||
# search
|
||||
# search module: https://github.com/gethugothemes/hugo-modules/tree/master/search
|
||||
[search]
|
||||
enable = false
|
||||
primary_color = "#121212"
|
||||
include_sections = ["blog"]
|
||||
show_image = true
|
||||
show_description = true
|
||||
show_tags = true
|
||||
show_categories = true
|
||||
|
||||
|
||||
# seo meta data for OpenGraph / Twitter Card
|
||||
# seo module: https://github.com/gethugothemes/hugo-modules/tree/master/seo-tools/basic-seo
|
||||
[metadata]
|
||||
keywords = ["fediverse", "nixos", "open source"]
|
||||
description = "Fediversity Project"
|
||||
author = "NGI Fediversity"
|
||||
image = "images/logo.png"
|
||||
|
||||
|
||||
# site verifications
|
||||
# verification module: https://github.com/gethugothemes/hugo-modules/tree/master/seo-tools/site-verifications
|
||||
[site_verification]
|
||||
google = "" # Your verification code
|
||||
bing = "" # Your verification code
|
||||
baidu = "" # Your verification code
|
||||
facebook = "" # Your verification code
|
||||
mastodon = "" # Your verification code
|
||||
|
||||
# cookies
|
||||
# cookies module: https://github.com/gethugothemes/hugo-modules/tree/master/components/cookie-consent
|
||||
[cookies]
|
||||
enable = false
|
||||
expire_days = 60
|
||||
content = "This site uses cookies. By continuing to use this website, you agree to their use."
|
||||
button = "I Accept"
|
||||
|
||||
######################## sidebar widgets #########################
|
||||
[widgets]
|
||||
sidebar = ["categories", "tags"]
|
||||
|
||||
|
||||
# google map
|
||||
[google_map]
|
||||
enable = false
|
||||
map_api_key = "AIzaSyCcABaamniA6OL5YvYSpB3pFMNrXwXnLwU"
|
||||
map_latitude = "51.5223477"
|
||||
map_longitude = "-0.1622023"
|
||||
map_marker = "images/marker.png"
|
||||
|
||||
|
||||
# Subscription
|
||||
[subscription]
|
||||
enable = false
|
||||
# mailchimp subsciption
|
||||
mailchimp_form_action = "https://gmail.us4.list-manage.com/subscribe/post?u=463ee871f45d2d93748e77cad&id=a0a2c6d074" # replace this url with yours
|
||||
mailchimp_form_name = "b_463ee871f45d2d93748e77cad_a0a2c6d074"
|
89
website/content/english/_index.md
Executable file
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
# Banner
|
||||
banner:
|
||||
title: "Welcome to the Fediversity Project"
|
||||
content: "The Fediversity Project is a comprehensive effort to bring easy-to-use, hosted cloud services that have service portability and personal freedom at their core to everyone."
|
||||
# image: "/images/checkbox-illustration-scaled.png"
|
||||
image: "/images/home.svg"
|
||||
button:
|
||||
enable: true
|
||||
label: "For You"
|
||||
link: "/individuals"
|
||||
|
||||
# Features
|
||||
features3:
|
||||
- title: "Consortium"
|
||||
image: "/images/users.svg"
|
||||
content: "The Consortium behind the Fediversity project is a cooperation between NLnet, Open Internet Discourse Foundation, NORDUnet and Tweag."
|
||||
button:
|
||||
enable: false
|
||||
label: "Learn more"
|
||||
link: ""
|
||||
|
||||
- title: "NLnet"
|
||||
image: "/images/users.svg"
|
||||
content: "NLnet supports organisations and people who contribute to an open internet for all. They fund projects that help fix the internet through open hardware, open software, open standards, open science and open data."
|
||||
button:
|
||||
enable: true
|
||||
label: "Learn more"
|
||||
link: "/nlnet"
|
||||
|
||||
- title: "Open Internet Discourse"
|
||||
image: "/images/users.svg"
|
||||
content: "The Open Internet Discourse Foundation (OID) is founded on the belief that everyone deserves the freedom to express themselves and use the internet without constraints, and is committed to help build a better internet where individuals can truly be who they are."
|
||||
button:
|
||||
enable: true
|
||||
label: "Learn more"
|
||||
link: "/oid"
|
||||
|
||||
- title: "Tweag"
|
||||
image: "/images/users.svg"
|
||||
content: "Tweag is the open source program office (OSPO) of Modus Create, and has extensive experience working with Nix, and many people at the forefront of the Nix community are Tweagers."
|
||||
button:
|
||||
enable: true
|
||||
label: "Learn more"
|
||||
link: "/tweag"
|
||||
|
||||
- title: "NORDUnet"
|
||||
image: "/images/users.svg"
|
||||
content: "NORDUnet is a collaboration of the National Research and Education Networks of the Nordic countries."
|
||||
button:
|
||||
enable: true
|
||||
label: "Learn more"
|
||||
link: "/nordunet"
|
||||
|
||||
features:
|
||||
- title: "Fediversity Grants"
|
||||
image: "/images/stepping-up.png"
|
||||
content: "Fediversity will award 450 000 euro in small to medium-size R&D grants towards solutions that bring the next generation of social networks closer. We are seeking project proposals between 5.000 and 50.000 euro’s — which should get you on your way."
|
||||
button:
|
||||
enable: true
|
||||
label: "Learn more"
|
||||
link: "/grants"
|
||||
|
||||
features2:
|
||||
- title: "Individuals"
|
||||
image: "/images/user.png"
|
||||
content: "Always be in control with your own data on social networks, whether that's with Mastodon, PeerTube or Pixelfed: Fediversity makes it possible."
|
||||
button:
|
||||
enable: true
|
||||
label: "Learn more"
|
||||
link: "/individuals"
|
||||
|
||||
- title: "Developers"
|
||||
image: "/images/code.png"
|
||||
content: "As a developer building the next generation of social platforms, you are looking to make it easier to facilitate your customers to use your product. Fediversity can help."
|
||||
button:
|
||||
enable: true
|
||||
label: "Learn more"
|
||||
link: "/developers"
|
||||
|
||||
- title: "European Commission"
|
||||
image: "/images/globe.png"
|
||||
content: "The Fediversity Project operates on a grant gratiously provided by the HORIZON fund by the EC. Learn more about the accountability of the project."
|
||||
button:
|
||||
enable: true
|
||||
label: "Learn more"
|
||||
link: "/ec"
|
||||
|
||||
---
|
3
website/content/english/authors/_index.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
title: "Authors"
|
||||
---
|
12
website/content/english/authors/laurens-hof.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: Laurens Hof
|
||||
email: laurens@procolix.com
|
||||
image: "/images/avatar.png"
|
||||
description: storyteller
|
||||
social:
|
||||
- name: github
|
||||
icon: fa-brands fa-github
|
||||
link: https://github.com
|
||||
---
|
||||
|
||||
Story teller for the Fediversity Project.
|
15
website/content/english/blog/New-website-launch.md
Executable file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: "Fediversity new website launch"
|
||||
description: "Announcing Our New Website for the Fediversity Project"
|
||||
date: 2024-05-15T05:00:00Z
|
||||
image: "/images/website-new.png"
|
||||
categories: ["News"]
|
||||
author: "Laurens Hof"
|
||||
draft: false
|
||||
---
|
||||
|
||||
We are pleased to introduce the launch of our new website dedicated to the Fediversity project.
|
||||
|
||||
The project is broad in scope, and the website reflects this. Whether you are a developer, an individual interested in the project, or want to know how the grant money is spend, the website keeps you up to date with everything you need to know.
|
||||
|
||||
We're excited to show you more of the progress of the Fediversity project, and how we can build a next generation of the open internet together!
|
5
website/content/english/blog/_index.md
Executable file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: "News"
|
||||
meta_title: "News"
|
||||
description: "News about Fediversity"
|
||||
---
|
17
website/content/english/blog/fediversity-tech-session.md
Executable file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: "Fediversity Tech Session"
|
||||
meta_title: ""
|
||||
description: "Fediversity Tech Session - NixOS and Kubernetes"
|
||||
date: 2024-08-05T05:00:00Z
|
||||
image: "/images/checkbox-illustration-scaled.png"
|
||||
categories: ["News"]
|
||||
author: "Laurens Hof"
|
||||
draft: false
|
||||
---
|
||||
|
||||
Recently Fediversity hosted a tech session on NixOS and Kubernetes. We invited people within the community to discuss some design considerations of the Fediversity project with us.
|
||||
|
||||
One of the core ideas of Fediversity is to build on top of NixOS. NixOS makes upgrading system reliable, and complex deployment reproducable. One of the goals of the Fediversity project that provides an interesting challenge is to help people move away from the cloud hyperscalers. Offering our project on Kubernetes offers easy integration with the storage platforms of the hyperscalers. Easy integration with the hyperscalers is an explicit anti-goal of Fediversity, but we're not sure if we can offer all the functionality with NixOS yet.
|
||||
|
||||
You can check out our entire conversation right here.
|
||||
|