Compare commits

...

141 commits

Author SHA1 Message Date
Hans van Zijst 11a33bd6af Documented how to create an admin user in Synapse. 2024-11-13 15:30:06 +01:00
Valentin Gagarin 184e89e586 Add 'matrix/' from commit '0a991a5140236eda995e05b5e1a5c38ed54b7a60'
git-subtree-dir: matrix
git-subtree-mainline: c1d2cdc7c1
git-subtree-split: 0a991a5140
2024-11-13 15:28:58 +01:00
Hans van Zijst c1d2cdc7c1 Added users laurens and valentin, system.stateVersion changed from 24.11 to 23.11. 2024-11-13 15:28:48 +01:00
Kevin Muller 35d78bac22 add nixos server config from the server running the fediversity.eu website 2024-11-07 13:41:33 +01:00
Valentin Gagarin c6cc92f5dc Add 'services/' from commit 'a13b1e9372412d03f7b629694ecef5242e6e568d'
git-subtree-dir: services
git-subtree-mainline: e41e0daa82
git-subtree-split: a13b1e9372
2024-10-07 11:57:24 +02:00
Valentin Gagarin e41e0daa82 Add 'website/' from commit 'd208ee83f80467e25c662b4680ed2d6161d88d9e'
git-subtree-dir: website
git-subtree-mainline: 83b1c9ac3b
git-subtree-split: d208ee83f8
2024-10-07 11:53:11 +02:00
Valentin Gagarin 83b1c9ac3b Add 'infra/' from commit 'cf086dc3e86039a07915445aa66bb82aebf9db0a'
git-subtree-dir: infra
git-subtree-mainline: 9d6a0f0a33
git-subtree-split: cf086dc3e8
2024-10-07 11:51:21 +02:00
Valentin Gagarin 9d6a0f0a33 Initial commit 2024-10-02 12:13:02 +02:00
Nicolas Jeannerod a13b1e9372 Faster compression and note on isoName 2024-10-01 13:29:06 +02:00
Nicolas Jeannerod 108949d3e1 Expose mkInstaller 2024-10-01 13:14:56 +02:00
Nicolas Jeannerod cf086dc3e8 Import vm02186's configuration as a flake 2024-10-01 13:07:09 +02:00
Nicolas Jeannerod 1e3360ebc0 Ring out wild bells to the wild sky,
The flying cloud, the frosty light:
The year is dying in the night;
Ring out, wild bells, and let him die.

Ring out the old, ring in the new,
Ring, happy bells, across the snow:
The year is going, let him go;
Ring out the false, ring in the true.

Ring out the grief that saps the mind,
For those that here we see no more;
Ring out the feud of rich and poor,
Ring in redress to all mankind.

Ring out a slowly dying cause,
And ancient forms of party strife;
Ring in the nobler modes of life,
With sweeter manners, purer laws.

Ring out the want, the care, the sin,
The faithless coldness of the times;
Ring out, ring out my mournful rhymes,
But ring the fuller minstrel in.

Ring out false pride in place and blood,
The civic slander and the spite;
Ring in the love of truth and right,
Ring in the common love of good.

Ring out old shapes of foul disease,
Ring out the narrowing lust of gold;
Ring out the thousand wars of old,
Ring in the thousand years of peace.

Ring in the valiant man and free,
The larger heart, the kindlier hand;
Ring out the darkness of the land,
Ring in the Christ that is to be.

— Alfred Tennyson
2024-10-01 12:47:37 +02:00
Nicolas Jeannerod 656b31e729 Add missing module in tests 2024-10-01 09:40:38 +00:00
Nicolas Jeannerod 8412a3f1fe Create automatic installation ISOs (#26)
Co-authored-by: Taeer Bar-Yam <taeer.bar-yam@moduscreate.com>
Co-authored-by: Valentin Gagarin <valentin.gagarin@tweag.io>
Reviewed-on: Fediversity/simple-nixos-fediverse#26
Co-authored-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
Co-committed-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
2024-10-01 10:02:01 +02:00
Nicolas Jeannerod ff75f07f66 We are way past that! 2024-09-27 11:48:49 +02:00
Taeer Bar-Yam 401bf59e44 fix frivolous errors in garage test 2024-09-26 01:41:06 -04:00
Taeer Bar-Yam 1f4b4d62e0 fix the overlay 2024-09-25 11:25:21 -04:00
Taeer Bar-Yam 473cc7dea0 take bleeding edge pixelfed 2024-09-25 00:40:53 -04:00
Nicolas Jeannerod 4be54ae67b Not localhost 2024-09-24 16:59:37 +02:00
Nicolas Jeannerod ed95912726 Make Garage API domain be localhost 2024-09-24 16:42:53 +02:00
Nicolas Jeannerod f160bc3f4c Remove SSL in Garage VM 2024-09-24 14:56:33 +02:00
Nicolas Jeannerod 14de9dec55 Remove SSL in VM 2024-09-24 14:52:13 +02:00
Nicolas Jeannerod af4f77a767 s/port/internalPort 2024-09-24 14:42:18 +02:00
Nicolas Jeannerod 08eac749dd Move Garage VM stuff out of main file 2024-09-24 14:40:35 +02:00
Nicolas Jeannerod 00384a5948 domainForBucket 2024-09-24 14:23:29 +02:00
Nicolas Jeannerod 7f1e5b56d0 s/urlFor/urlForBucket 2024-09-24 14:17:56 +02:00
Taeer Bar-Yam 72fa686540 acme fixup 2 2024-09-23 12:39:55 -04:00
Taeer Bar-Yam 2a28e0289d acme fixup 2024-09-23 12:39:15 -04:00
Taeer Bar-Yam 2364e122a2 httpS 2024-09-23 12:22:40 -04:00
Taeer Bar-Yam a65c55f7a1 ADD http:// to proxypass 2024-09-23 12:18:22 -04:00
Taeer Bar-Yam 682f8d6776 remove http:// from nginx server name 2024-09-23 12:14:40 -04:00
Taeer Bar-Yam c15009f490 mv {,internal}port 2024-09-23 12:11:04 -04:00
Taeer Bar-Yam 581b64b77b had two 'cfg's. changed one to 'fedicfg' 2024-09-23 12:09:16 -04:00
Taeer Bar-Yam 8d612a712d ; 2024-09-23 11:58:49 -04:00
Taeer Bar-Yam a02a741ec4 proxy garage web to port 80 2024-09-23 11:55:54 -04:00
Nicolas Jeannerod 900c92ac01 Exceptionally use non-staging LetsEncrypt servers 2024-09-20 18:55:00 +02:00
Nicolas Jeannerod a2c54ff172 ACME 2024-09-20 18:51:21 +02:00
Nicolas Jeannerod d1f58573d8 Also open HTTPS port 2024-09-20 18:44:47 +02:00
Nicolas Jeannerod c47256d62c Ignore errors of garage key import 2024-09-20 18:39:32 +02:00
Nicolas Jeannerod 351649c2dd [HACK] comment out virtualisation 2024-09-20 18:25:21 +02:00
Nicolas Jeannerod 81ab439777 Move stuff from pixelfed-vm to pixelfed 2024-09-20 17:56:40 +02:00
Nicolas Jeannerod 7665609827 Use common options also in tests 2024-09-20 15:45:53 +00:00
Nicolas Jeannerod bbf5f50432 Note on style choice for eg. fediversity.internal.pixelfed.domain 2024-09-20 17:20:31 +02:00
Nicolas Jeannerod ecda4f249d Rework definition of “constants”
- make things such as `fediversity.garage.api.port` into actual options
  with the right default value
- move them under `fediversity.internal`

Co-authored-by: Taeer Bar-Yam <taeer.bar-yam@moduscreate.com>
2024-09-20 17:13:35 +02:00
Nicolas Jeannerod 29c86aedff s/types.string/types.str/
`types.string` was being used for too many thing so it got deprecated
and now there are several different string types. `types.str` is the one
where you don't want to merge definitions if it's defined in more than
one place

Co-authored-by: Taeer Bar-Yam <taeer.bar-yam@moduscreate.com>
2024-09-20 16:35:21 +02:00
Nicolas Jeannerod cf5139836e s/mkOption/mkEnableOption 2024-09-20 16:34:08 +02:00
Nicolas Jeannerod 492a199866 Factorise services URIs 2024-09-17 17:58:09 +02:00
Nicolas Jeannerod 61eb2d9081 Factorise Garage URIs 2024-09-17 17:52:54 +02:00
Nicolas Jeannerod 069b1ddb6c Move Fediversity modules under top-level module 2024-09-17 15:16:11 +02:00
Nicolas Jeannerod 4b379af4ff Move Fediversity modules into own subdirectory 2024-09-17 14:27:24 +02:00
Nicolas Jeannerod 059a76d933 Move VM-specific stuff in a subdirectory 2024-09-17 14:26:21 +02:00
Laurens d208ee83f8 img update for nordunet conf 2024-09-17 13:48:00 +02:00
Nicolas Jeannerod 813c1ca879 Some fixes to the Pixelfed/Garage test 2024-09-17 13:35:51 +02:00
Laurens ee2e89d2e3 nordunet conference 2024-09-17 13:34:04 +02:00
Laurens 3b94c7a4ae banner image fix 2024-09-11 09:20:32 +02:00
Taeer Bar-Yam c2fd51baac stop threading email and password around as arguments 2024-09-10 08:50:54 -04:00
Nicolas Jeannerod 2dca2caca3 Wait until Garage is up by polling port 3900 2024-09-10 12:41:53 +00:00
Nicolas Jeannerod 9346bcca3d Check that src points to Garage 2024-09-10 12:17:32 +00:00
Taeer Bar-Yam 3652a443b1 test image gets uploaded to garage 2024-09-09 10:13:23 -04:00
Taeer Bar-Yam 5b26cfc7a9 nicer timing for video 2024-09-09 10:13:13 -04:00
Taeer Bar-Yam a2da2f48b2 use fediversity logo 2024-09-09 10:12:54 -04:00
Nicolas Jeannerod a49a2b57df Run Magick on the server but with right path 2024-09-09 14:25:42 +02:00
Nicolas Jeannerod 2f44a729c2 Follow things graphically 2024-09-09 14:23:05 +02:00
Nicolas Jeannerod 680f54f7d5 Fix logging and Selenium script 2024-09-09 14:23:05 +02:00
Nicolas Jeannerod b38ec1a62a Cleanup 2024-09-09 14:23:05 +02:00
Taeer Bar-Yam a245729765 patch pixelfed to give nginx read permissions
this way we don't need DANGEROUSLY_SET_FILESYSTEM_DRIVER
2024-09-05 12:03:35 -04:00
Taeer Bar-Yam b24411f44b this configuration also works (without nginx config) 2024-09-05 11:06:55 -04:00
Nicolas Jeannerod e4d84d7881 Run pixelfed-data-setup only after ensure-garage 2024-09-05 15:33:47 +02:00
Nicolas Jeannerod 60cf77e66e Make ensure-garage a oneshot service 2024-09-05 15:32:34 +02:00
Nicolas Jeannerod 1932a131e0 Proxy Garage backend 2024-09-05 13:05:29 +02:00
Nicolas Jeannerod afb8d56dd6 DANGEROUSLY fix everything 2024-09-04 18:30:55 +02:00
Taeer Bar-Yam 7b204eb2ce some more ignores 2024-09-02 12:10:01 -04:00
Taeer Bar-Yam 1a92108475 attempt to access garage storage correctly
nginx was trying to access the files on disk, rather than via s3 storage
2024-09-02 12:09:10 -04:00
Taeer Bar-Yam f8af95f9ab garage defaults
without this ensureKeys and ensureBuckets Must be set or nixos won't
build
2024-09-02 12:08:14 -04:00
Taeer Bar-Yam d1691e4c8b fix typo 2024-09-02 12:07:25 -04:00
Nicolas Jeannerod cd277c0e98 WIP 2024-08-30 17:23:55 +02:00
Taeer Bar-Yam 5fd5c37834 use rebuildable_tests branch of nixpkgs for now 2024-08-28 08:39:36 -04:00
Taeer Bar-Yam e6dde31148 don't use local nixpkgs 2024-08-28 08:38:02 -04:00
Taeer Bar-Yam 353c0a7ffa separate vm.nix files for vm-specific configuration 2024-08-28 08:35:48 -04:00
Laurens 6cc1b35e5e img fix 2024-08-19 14:59:11 +02:00
Laurens 0fd3e0e990 tech session kubernetes art 2024-08-19 14:58:32 +02:00
Laurens 83cafe29d1 publicmap delete 2024-07-30 17:54:10 +02:00
Laurens db7b208e63 blog publicspaces 2024-07-30 17:46:01 +02:00
Taeer Bar-Yam 366a67e112 update readme 2024-07-25 07:49:22 -04:00
Taeer Bar-Yam 941d3bf2a9 fix CSP check 2024-07-25 07:45:57 -04:00
Taeer Bar-Yam bddfd95ee4 cleanup 2024-07-25 06:06:02 -04:00
Taeer Bar-Yam acc4a1a2ef better error messages 2024-07-23 09:43:18 -04:00
Taeer Bar-Yam 0f8972a8f0 for now, we have to stop using vmVariant so the test works 2024-07-18 08:22:47 -04:00
Taeer Bar-Yam dab12bc2b8 interactive test is working 2024-07-18 06:44:13 -04:00
Taeer Bar-Yam 693e21b1a8 first stab at a nixos test
for now, had to get rid of vmVariant. we can figure out how to add it
back when we understand how we should actually distinguish between
real machines and VMs
2024-06-25 06:39:04 -04:00
Taeer Bar-Yam 4e719da9d9 address roberth comments
SEE
https://git.fediversity.eu/taeer/simple-nixos-fediverse/compare/main...roberth:review
2024-06-06 07:10:19 -04:00
Laurens 541e49c7a2 events fixed 2024-05-28 13:11:00 +02:00
Laurens bb7de00d00 logo and icon to svg 2024-05-28 12:17:37 +02:00
Laurens 4e4c5042fa blog 2024-05-28 11:43:30 +02:00
Taeer Bar-Yam 2c7e3603b8 better documentation and readme 2024-05-24 19:02:12 -04:00
Laurens dbdd9322f5 news berichten, img fix, ready for livegang 2024-05-14 17:24:00 +02:00
Laurens 4d0279a137 lining up icons 2024-05-13 16:36:08 +02:00
Laurens f49bcf79b8 frontpage event and blog visual update 2024-05-10 14:43:37 +02:00
Laurens ce2830274e fixed the stupid line between event items 2024-05-09 16:47:15 +02:00
Laurens 2ddc599ce1 contactpage update 2024-05-08 15:44:32 +02:00
Laurens 1c672827f4 menu bar cleanup 2024-05-08 09:52:36 +02:00
Laurens b66e5165a8 event list messing around again 2024-05-08 09:46:15 +02:00
Laurens 5b338c3068 events page update 2024-05-07 15:29:50 +02:00
Laurens 7cbe3544a3 center colums events and news 2024-05-06 17:53:11 +02:00
Laurens f59d013869 read more button to text 2024-05-01 16:45:28 +02:00
Laurens 7cb78f5097 consortium front page grid 2024-05-01 15:22:32 +02:00
Laurens cf5f4d1520 header fix 2024-05-01 13:12:55 +02:00
Laurens db669bd84e grid works now 2024-05-01 13:06:43 +02:00
Laurens 19e9b0e13a front page to grid wip 2024-04-30 16:54:13 +02:00
Laurens e98ccf9984 front page nieuws container werkend 2024-04-24 11:31:07 +02:00
Laurens 5daf70fce9 grid for news on frontpage wip 2024-04-23 16:11:39 +02:00
Laurens 599dd3f1b9 blog pagination fix 2024-04-23 11:07:01 +02:00
Laurens f45a97ddae news articles image to side 2024-04-16 17:18:15 +02:00
Laurens 6acf6f9e25 footer 2024-04-15 17:49:50 +02:00
Laurens 5f29bbb18f footer update 2024-04-05 16:53:16 +02:00
Taeer Bar-Yam af6e76134a peertube data in s3 storage 2024-04-03 08:40:19 -04:00
Laurens ab2ae452c1 events framework 2024-04-03 12:14:44 +02:00
Laurens 1304cb4689 partial events 2024-04-03 11:51:52 +02:00
Laurens fab2c6e946 more edits news 2024-04-02 17:40:22 +02:00
Laurens 0d6108babe news section 2024-04-02 14:55:53 +02:00
Laurens 8b5150f3de front page further 2024-04-02 12:35:39 +02:00
Laurens f4afa0cf30 consortium frontpage 2024-04-02 11:12:11 +02:00
Laurens 41f23751ab image placeholders 2024-04-02 10:29:00 +02:00
Laurens a8aed7c7e7 color changes 2024-03-27 17:14:44 +01:00
Laurens f2ef1b3637 favicon 2024-03-27 12:28:51 +01:00
Laurens e3d6fc655a dark mode changes 2024-03-27 12:24:18 +01:00
Taeer Bar-Yam 48084fa688 options for ensuring garage buckets 2024-03-27 05:59:50 -04:00
Taeer Bar-Yam 5fd1e115a0 basic s3 garage setup for mastodon
it's still having trouble fetching stored images for some reason
2024-03-27 05:58:38 -04:00
Laurens ad8a244881 rename contact 2024-03-26 17:27:20 +01:00
Laurens 8a9bf15f2e personas added 2024-03-26 17:11:07 +01:00
Laurens d649103df4 consortium pages 2024-03-26 17:02:19 +01:00
Laurens 522c4e7b51 init commit 2024-03-26 16:28:28 +01:00
Taeer Bar-Yam 907a9c9494 get all the services working together 2024-03-20 05:24:31 -04:00
Taeer Bar-Yam 1b0fcff9fb fix mastodon (why was it broken??) 2024-03-20 05:23:57 -04:00
Taeer Bar-Yam 3e4ab1ecf6 simple pixelfed & redo readme 2024-03-19 20:39:59 -04:00
Taeer Bar-Yam 8c40168532 minimal peertube VM 2024-03-19 19:43:20 -04:00
Taeer Bar-Yam dc6e4936ed don't require proxy server 2024-03-06 09:16:35 -05:00
Taeer Bar-Yam 230810bf6f refactor & cleanup 2024-03-06 04:48:01 -05:00
Taeer Bar-Yam a4cb05d8a1 account creation 2024-03-06 04:40:22 -05:00
Taeer Bar-Yam ecf89fc0d0 tweaks 2024-02-28 16:49:16 -05:00
Taeer Bar-Yam 6942d1dcf2 mastodon vm 2024-02-22 04:56:31 -05:00
346 changed files with 103491 additions and 50 deletions

190
LICENSE Normal file
View 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.

View file

@ -1,51 +1,2 @@
--- # Fediversity
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.

24
infra/flake.nix Normal file
View 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
View 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
'';
}

View 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";
}

View 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. Its 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?
}

View file

51
matrix/README.md Normal file
View 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.

View file

@ -112,6 +112,30 @@ After changing the database, restart Synapse and check whether it can connect
and create the tables it needs. 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
Logging is configured in `log.yaml`. Some logging should go to systemd, the Logging is configured in `log.yaml`. Some logging should go to systemd, the

275
server/configuration.nix Normal file
View 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. Its 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?
}

View 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
View file

@ -0,0 +1 @@
use flake

8
services/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
nixos.qcow2
result*
.direnv
.nixos-test-history
*screenshot.png
output
todo

127
services/README.md Normal file
View 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
View 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
View 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 = "/";
};
};
};
};
};
}

View 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";
};
}

View 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
'';
};
};
}

View 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";
};
}

View 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}
'';
}

View 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,
],
],
],

View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
services/tests/green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

View 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.")
'';
}

View 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")
'';
}

View 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
View 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;
}
];
}

View 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
View 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;
}
];
}
];
}

View 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;
}
];
}

View 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

Binary file not shown.

0
website/.hugo_build.lock Normal file
View file

21
website/LICENSE Normal file
View 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.

Binary file not shown.

29
website/amplify.yml Normal file
View 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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
website/assets/images/avatar.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

36
website/assets/js/main.js Executable file
View 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

Binary file not shown.

View 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);
}

View 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;
}

File diff suppressed because it is too large Load diff

59
website/assets/scss/base.scss Executable file
View 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};
}

View 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;
}

View 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
View 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
View 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";

View 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};
}

View 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;
}
}
}

View 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);
}

View file

@ -0,0 +1,6 @@
################ English language ##################
[en]
languageName = "En"
languageCode = "en-us"
contentDir = "content/english"
weight = 1

View 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

View 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"

View 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&amp;id=a0a2c6d074" # replace this url with yours
mailchimp_form_name = "b_463ee871f45d2d93748e77cad_a0a2c6d074"

View 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 euros — 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"
---

View file

@ -0,0 +1,3 @@
---
title: "Authors"
---

View 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.

View 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!

View file

@ -0,0 +1,5 @@
---
title: "News"
meta_title: "News"
description: "News about Fediversity"
---

View 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.

Some files were not shown because too many files have changed in this diff Show more