forked from fediversity/fediversity
Compare commits
No commits in common. "8f0bcc35f0c0f5909215db7dea01b5ef8c2bc9a9" and "9dd92b4cc180e88ffc14a96ec979a90c84e1676f" have entirely different histories.
8f0bcc35f0
...
9dd92b4cc1
21 changed files with 189 additions and 319 deletions
|
@ -33,14 +33,6 @@
|
|||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDHTIqF4CAylSxKPiSo5JOPuocn0y2z38wOSsQ1MUaZ2"
|
||||
];
|
||||
};
|
||||
|
||||
Lois = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" ];
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKVQ7yYXr4ZguGWYHZ7v2L3kPmYjaFo46PTgAEviW5D8 lois@mouse"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
security.sudo.wheelNeedsPassword = false;
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKVQ7yYXr4ZguGWYHZ7v2L3kPmYjaFo46PTgAEviW5D8 lois@mouse
|
|
@ -1 +1 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKBpnV6zzgdJN5pjw2oWryneE6kZ5rQ343Ut4ed12Cm9 root@fedi201
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILhSlUo7L/TjoAILfLv/BDxlBT+rGudh9VoK50Uiu2lZ root@fedi201
|
||||
|
|
|
@ -58,13 +58,13 @@ let
|
|||
text =
|
||||
''
|
||||
systemd-run --pty \
|
||||
--same-dir \
|
||||
--wait \
|
||||
--collect \
|
||||
--service-type=exec \
|
||||
--unit "manage-${name}.service" \
|
||||
--property "User=${name}" \
|
||||
--property "Group=${name}" \
|
||||
--property "WorkingDirectory=/var/lib/${name}" \
|
||||
--property "Environment=DATABASE_URL=${database-url} USER_SETTINGS_FILE=${configFile}" \
|
||||
''
|
||||
+ optionalString (credentials != [ ]) (
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from importlib import import_module
|
||||
from importlib.util import find_spec
|
||||
from django.utils.functional import classproperty
|
||||
from django_pydantic_field import SchemaField
|
||||
|
||||
from django.apps import apps
|
||||
from django.db import models
|
||||
from django import forms
|
||||
|
||||
|
||||
class Version():
|
||||
|
||||
model: models.Model
|
||||
form: forms.ModelForm
|
||||
|
||||
@classproperty
|
||||
def latest(cls):
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
version_pattern = re.compile(r'v(\d+)\.py$')
|
||||
versions = []
|
||||
for filename in os.listdir(current_dir):
|
||||
match = version_pattern.match(filename)
|
||||
if match:
|
||||
versions.append(int(match.group(1)))
|
||||
|
||||
return max(versions)
|
||||
|
||||
def __init__(self, version: int):
|
||||
module = import_module(f"{__name__}.v{version}")
|
||||
self.model = getattr(module, "Configuration")
|
||||
|
||||
self.form = getattr(module, "Form")
|
|
@ -1,118 +0,0 @@
|
|||
from django import forms
|
||||
from django.db import models
|
||||
from pydantic import BaseModel, Field
|
||||
from enum import Enum
|
||||
from django_pydantic_field import SchemaField
|
||||
|
||||
# TODO(@fricklerhandwerk):
|
||||
# Eventually should probably maintain a separate series of configuration schema versions for each service.
|
||||
# I didn't start it here yet, mainly to keep it readable.
|
||||
|
||||
|
||||
class PeerTube(BaseModel):
|
||||
enable: bool = Field(
|
||||
default=False,
|
||||
description="Enable PeerTube",
|
||||
)
|
||||
|
||||
|
||||
class Pixelfed(BaseModel):
|
||||
enable: bool = Field(
|
||||
default=False,
|
||||
description="Enable Pixelfed",
|
||||
)
|
||||
|
||||
|
||||
class Mastodon(BaseModel):
|
||||
enable: bool = Field(
|
||||
default=False,
|
||||
description="Enable Mastodon",
|
||||
)
|
||||
|
||||
|
||||
class Configuration(BaseModel):
|
||||
enable: bool = Field(
|
||||
default=False,
|
||||
description="Enable the configuration",
|
||||
)
|
||||
|
||||
# XXX: hard-code available apex domains for now,
|
||||
# they will be prefixed by the user name
|
||||
class Domain(Enum):
|
||||
EU = "fediversity.eu"
|
||||
NET = "fediversity.net"
|
||||
|
||||
domain: Domain = Field(
|
||||
default=Domain.EU,
|
||||
description="DNS domain where to expose services"
|
||||
)
|
||||
|
||||
peertube: PeerTube = Field(
|
||||
default=PeerTube(),
|
||||
description="Configuration for PeerTube",
|
||||
)
|
||||
|
||||
pixelfed: Pixelfed = Field(
|
||||
default=Pixelfed(),
|
||||
description="Configuration for Pixelfed",
|
||||
)
|
||||
|
||||
mastodon: Mastodon = Field(
|
||||
default=Mastodon(),
|
||||
description="Configuration for Mastodon",
|
||||
)
|
||||
|
||||
# TODO@(fricklerhandwerk):
|
||||
# generate this automatically from the Pydantic model so this code can go away:
|
||||
# - add custom types, splicing `forms.Form` into `pydantic.BaseModel` and `forms.Field` into `pydantic.Field` so we can attach Django validators to Pydantic models
|
||||
# - map primitive or well-known field types as captured by `pydantic.Field` to `forms.Field` constructors, and `BaseModel` to `Form` using `BaseModel.model_fields`
|
||||
# - use a custom'{"name": "John Doe", "age": 30 }' widget that renders nested forms in e.g. a `<div>`
|
||||
# - likely this also needs overriding `as_p()`, `as_li()`
|
||||
# more inspiration: https://levelup.gitconnected.com/how-to-export-pydantic-models-as-django-forms-c1b59ddca580
|
||||
# that work goes through the JSON Schema generated by Pydantic, which seems unnecessary to me, but it follows the same princple
|
||||
# TODO(@fricklerhandwerk):
|
||||
# eventually we probably want to validate each field separately,
|
||||
# so we should also auto-generate views per field that will be called by htmx directives defined in the templates.
|
||||
# those htmx parts can be generated by each form field using `attrs`
|
||||
|
||||
|
||||
class Form(forms.Form):
|
||||
enable = forms.BooleanField(required=False)
|
||||
domain = forms.ChoiceField(
|
||||
required=False,
|
||||
choices=[(d.name, d.value) for d in Configuration.Domain],
|
||||
)
|
||||
peertube_enable = forms.BooleanField(
|
||||
required=False,
|
||||
label=Configuration.model_fields["peertube"].annotation.model_fields["enable"].description,
|
||||
)
|
||||
pixelfed_enable = forms.BooleanField(
|
||||
required=False,
|
||||
label=Configuration.model_fields["pixelfed"].annotation.model_fields["enable"].description,
|
||||
)
|
||||
mastodon_enable = forms.BooleanField(
|
||||
required=False,
|
||||
label=Configuration.model_fields["mastodon"].annotation.model_fields["enable"].description,
|
||||
)
|
||||
|
||||
# HACK: take out nested dict fields manually
|
||||
# TODO: make this generic
|
||||
def __init__(self, *args, **kwargs):
|
||||
initial = kwargs.pop('initial')
|
||||
|
||||
initial["pixelfed_enable"] = initial.pop('pixelfed')["enable"]
|
||||
initial["peertube_enable"] = initial.pop('peertube')["enable"]
|
||||
initial["mastodon_enable"] = initial.pop('mastodon')["enable"]
|
||||
|
||||
kwargs["initial"] = initial
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_python(self):
|
||||
return Configuration(
|
||||
enable=self.cleaned_data['enable'],
|
||||
domain=Configuration.Domain[self.cleaned_data['domain']],
|
||||
peertube=PeerTube(enable=self.cleaned_data['peertube_enable']),
|
||||
pixelfed=Pixelfed(enable=self.cleaned_data['pixelfed_enable']),
|
||||
mastodon=Mastodon(enable=self.cleaned_data['mastodon_enable']),
|
||||
)
|
44
panel/src/panel/configuration/v1.py
Normal file
44
panel/src/panel/configuration/v1.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from django import forms
|
||||
from django.db import models
|
||||
from pydantic import BaseModel, Field
|
||||
from enum import Enum
|
||||
from django_pydantic_field import SchemaField
|
||||
|
||||
class Configuration(BaseModel):
|
||||
enable: bool = Field(
|
||||
default=False,
|
||||
description="Enable the configuration",
|
||||
)
|
||||
|
||||
# XXX: hard-code available apex domains for now,
|
||||
# they will be prefixed by the user name
|
||||
class Domain(Enum):
|
||||
EU = "fediversity.eu"
|
||||
NET = "fediversity.net"
|
||||
|
||||
domain: Domain = Field(
|
||||
default=Domain.EU,
|
||||
description="DNS domain where to expose services"
|
||||
)
|
||||
|
||||
# TODO@(fricklerhandwerk):
|
||||
# generate this automatically from the Pydantic model so this code can go away:
|
||||
# - add custom types, splicing `forms.Form` into `pydantic.BaseModel` and `forms.Field` into `pydantic.Field` so we can attach Django validators to Pydantic models
|
||||
# - map primitive or well-known field types as captured by `pydantic.Field` to `forms.Field` constructors, and `BaseModel` to `Form` using `BaseModel.model_fields`
|
||||
# - use a custom widget that renders nested forms in e.g. a `<div>`
|
||||
# - likely this also needs overriding `as_p()`, `as_li()`
|
||||
# more inspiration: https://levelup.gitconnected.com/how-to-export-pydantic-models-as-django-forms-c1b59ddca580
|
||||
# that work goes through the JSON Schema generated by Pydantic, which seems unnecessary to me, but it follows the same princple
|
||||
# TODO(@fricklerhandwerk):
|
||||
# eventually we probably want to validate each field separately,
|
||||
# so we should also auto-generate views per field that will be called by htmx directives defined in the templates.
|
||||
# those htmx parts can be generated by each form field using `attrs`
|
||||
class Form(forms.Form):
|
||||
enable = forms.BooleanField(required=False)
|
||||
domain = forms.ChoiceField(choices=[(d.name, d.value) for d in Configuration.Domain])
|
||||
|
||||
def to_python(self):
|
||||
return Configuration(
|
||||
enable=self.cleaned_data['enable'],
|
||||
domain=Configuration.Domain[self.cleaned_data['domain']]
|
||||
)
|
|
@ -17,12 +17,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Configuration',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True,
|
||||
primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.JSONField(
|
||||
help_text='Stored configuration value', null=True)),
|
||||
('operator', models.ForeignKey(help_text='Operator who owns the configuration', null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL, related_name='configurations', to=settings.AUTH_USER_MODEL)),
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('version', models.PositiveIntegerField(default=1, help_text='Configuration schema version')),
|
||||
('value', models.JSONField(help_text='Stored configuration value', null=True)),
|
||||
('operator', models.ForeignKey(help_text='Operator who owns the configuration', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='configurations', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
# Generated by Django 4.2.16 on 2025-03-09 21:25
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from panel.configuration.forms import Configuration
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('panel', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='configuration',
|
||||
name='value',
|
||||
field=models.JSONField(default=Configuration(
|
||||
).model_dump_json(), help_text='Stored configuration value'),
|
||||
),
|
||||
]
|
|
@ -1,15 +1,10 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from panel.configuration import forms
|
||||
from panel.configuration import Version
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def get_default_config():
|
||||
return forms.Configuration().model_dump_json()
|
||||
|
||||
|
||||
class Configuration(models.Model):
|
||||
operator = models.ForeignKey(
|
||||
User,
|
||||
|
@ -19,11 +14,16 @@ class Configuration(models.Model):
|
|||
help_text="Operator who owns the configuration",
|
||||
)
|
||||
|
||||
version = models.PositiveIntegerField(
|
||||
help_text="Configuration schema version",
|
||||
default=Version.latest,
|
||||
)
|
||||
|
||||
value = models.JSONField(
|
||||
help_text="Stored configuration value",
|
||||
default=get_default_config,
|
||||
default=Version(Version.latest).model().model_dump_json,
|
||||
)
|
||||
|
||||
@property
|
||||
def parsed_value(self) -> BaseModel:
|
||||
return forms.Configuration.model_validate_json(self.value)
|
||||
return Version(self.version).model.model_validate_json(self.value)
|
||||
|
|
|
@ -8,4 +8,6 @@
|
|||
<button class="button" disabled>Deploy</button>
|
||||
<button class="button" type="submit" >Save</button>
|
||||
</form>
|
||||
|
||||
<p><sub>Configuration schema version {{ version }}</sub></p>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
from panel.models import Configuration
|
||||
|
||||
|
||||
class ConfigurationForm(TestCase):
|
||||
def setUp(self):
|
||||
self.username = 'testuser'
|
||||
self.password = 'securepassword123'
|
||||
self.user = User.objects.create_user(
|
||||
username=self.username,
|
||||
password=self.password
|
||||
)
|
||||
|
||||
self.config_url = reverse('configuration_form')
|
||||
|
||||
def test_configuration_form_submission(self):
|
||||
config = Configuration.objects.create(
|
||||
operator=self.user,
|
||||
)
|
||||
|
||||
self.client.login(username=self.username, password=self.password)
|
||||
response = self.client.get(self.config_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
context = response.context[0]
|
||||
|
||||
# configuration should be disabled by default
|
||||
self.assertFalse(context['view'].get_object().parsed_value.enable)
|
||||
# ...and be displayed as such
|
||||
self.assertFalse(context['form'].initial["enable"])
|
||||
|
||||
form_data = context['form'].initial.copy()
|
||||
form_data.update(
|
||||
enable=True,
|
||||
mastodon_enable=True,
|
||||
)
|
||||
|
||||
response = self.client.post(self.config_url, data=form_data)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
config.refresh_from_db()
|
||||
|
||||
self.assertTrue(config.parsed_value.enable)
|
||||
self.assertTrue(config.parsed_value.mastodon.enable)
|
||||
# this should not have changed
|
||||
self.assertFalse(config.parsed_value.peertube.enable)
|
|
@ -7,40 +7,37 @@ from django.views.generic import TemplateView, DetailView
|
|||
from django.views.generic.edit import FormView
|
||||
|
||||
from panel import models
|
||||
from panel.configuration import forms
|
||||
|
||||
from panel.configuration import Version
|
||||
|
||||
class Index(TemplateView):
|
||||
template_name = 'index.html'
|
||||
|
||||
|
||||
class AccountDetail(LoginRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'account_detail.html'
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
|
||||
class ServiceList(TemplateView):
|
||||
template_name = 'service_list.html'
|
||||
|
||||
|
||||
class ConfigurationForm(LoginRequiredMixin, FormView):
|
||||
template_name = 'configuration_form.html'
|
||||
success_url = reverse_lazy('configuration_form')
|
||||
form_class = forms.Form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["version"] = self.get_object().version
|
||||
return context
|
||||
|
||||
def get_form_class(self):
|
||||
config = self.get_object()
|
||||
return Version(config.version).form
|
||||
|
||||
def get_object(self):
|
||||
"""Get or create the configuration object for the current user"""
|
||||
obj, created = models.Configuration.objects.get_or_create(
|
||||
operator=self.request.user,
|
||||
)
|
||||
|
||||
operator=self.request.user)
|
||||
return obj
|
||||
|
||||
# TODO(@fricklerhandwerk):
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 Jpc21A JzLWMEH98I5/A8O55mKUMy5zo2kg3Qk8SfXnHvkjwT4
|
||||
8f7zDHSp3AHoAQy0dVWMa1TurCBLnsHNtbNjaD++7ow
|
||||
-> ssh-ed25519 BAs8QA eCD3saYXdv1bjAoQghmyVqHjMBu/o2lWgu7grk1vgRs
|
||||
//pOnkzqQTK3xmeCjruo46ju2X136KEt6DpsegMouFQ
|
||||
-> ssh-ed25519 ofQnlg ePjq7GmM36qaGxcJ0qnW8FdKDjwlXtFqOBK8OgWY3Co
|
||||
gVmsDP9rMcQD/B6BpNhCn+avdgjhyyohNUXlatXpXo0
|
||||
-> ssh-ed25519 COspvA lrQB/NEmMUR2RWxfRzE2iTDkjMYsrIaiKn8thxZR+RA
|
||||
MU23Z28v+cNk2VxpAYaYoFb53js2Zr9/KAM9uMe6+EA
|
||||
-> ssh-ed25519 2XrTgw z1ixx5dYCNbgw6wWV45b4wn69X/5/4MzesTomWa4WB4
|
||||
eNSlP6+nUW9rpsGyzqOEQ+7IVpGeU3UcZpyfB9XT2/4
|
||||
-> ssh-ed25519 1MUEqQ c6ps9RB6Dw9JtR0+4eB1NDx44uUes8YjLrY7RCpD0jg
|
||||
GwVRqR5t07ctbWhwH76T+SAe2Y6Vv1uY/AHkzd/gw/c
|
||||
-> ssh-ed25519 Fa25Dw jTqtV2RWsXBH4zgWAYr9tBGC/BbXKBvr3uyL8IgmI1o
|
||||
qBirnzIpi9hB61xwyS+5U6XBobAquEJrV3cleDtG8/4
|
||||
--- j/vJgDV+47UmKokdvztXntBIhCLEyUm2aYoGJ2WMKbU
|
||||
¢¹ŽÀi‹±ËQõf §¥ÐÅ·DN§àB"—ÍvsëB6PùQžorF‡<46>Å
|
||||
-> ssh-ed25519 Jpc21A 5HeEmeKYFHH3Wko4afd4SG2LcQfxvvCv81dnfUMkCzw
|
||||
B4CuJx6eGFU3bdz4y/V1M+XW5lfao99Lig1+rrq//9s
|
||||
-> ssh-ed25519 BAs8QA AJtZJIwSZTFXccbPVrXwGsqD3CT7n7PRTCpkE3OQiQs
|
||||
gVlxB8ABw83lqOFr2rBSh2Y212/yr1SheZSjW3d2bqw
|
||||
-> ssh-ed25519 ofQnlg jZtGq/Xqr9pXMvGJqIBAeIlB1BxXyxR9HKljETvkqTg
|
||||
6RikmxoLwUOy7IOp4V9zTCC8UMWyjS5KyLxWXvuuxC0
|
||||
-> ssh-ed25519 COspvA 0tlSQU5/iVenCwuu9CPK5EQC39FYfCho8bmBOCyZGXw
|
||||
1dOShM2CM1fJqqV1e1LgEfmxLq+e9tt71eWhvSUixsE
|
||||
-> ssh-ed25519 1MUEqQ O/1idQox9vPu19mdLyLoqLQWImBg1pC+PPMfItQDhz0
|
||||
eBkqIelOFQ+CUPSwPdI5SsQhBZ3JI9gD0b3SVUIK9ag
|
||||
-> ssh-ed25519 Fa25Dw ZT+LO4ml0P6DeKi9u7QBP9e/UWkQRN2gDYJ6g1bbg2s
|
||||
CNULJZIGfU2bnjzQ28Ozkhs6HlM0964vDLBi5hRirPQ
|
||||
--- aHFDJRy4XUQCLbDF9Yeb8iXFvEutu3PS0oM5l3oW3r8
|
||||
Ž‚„×hí¨¢;ÎB—
|
||||
<EFBFBD>e)¿÷qÝo©ù‚IºÇ¹ÁFÁïY½-çX?kà
|
|
@ -1,17 +1,16 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 Jpc21A l6Xwv4JBlTeRTC7RgjxY9gDrCk96atMUH/62P+u55Qs
|
||||
CrYsLZgDFAiR8up87lhGZqsbAEZtOXG+l5IzLh2uaqg
|
||||
-> ssh-ed25519 BAs8QA lgtmfoc4vKfRpI/XbIS258BMyIB4mTdquEx/Kxm5OTI
|
||||
3gQL8Rnqc7JfqsRmKYU3rD0cWMKdnIeVXbY3eFM07RU
|
||||
-> ssh-ed25519 ofQnlg 0vwuCrduMLjssA3CK3gfVPMSPYKO9cF7HH1JF/oJv18
|
||||
2KrZgQmpvw/tNDJrDArinnbEjopkkmuG8s7t6klBXcQ
|
||||
-> ssh-ed25519 COspvA NT+/h2KsiZN2XbaWAlrTlDwyAPmHWrwgr6f0uhSbEGs
|
||||
QpoAd+69VYrZwAC0LwDm1m/zfslVgzxpVFihQWDcqzE
|
||||
-> ssh-ed25519 2XrTgw QoJ/74FOqYFxHJYXJEkyzbGY0xptSjorNvnyUS1p6zk
|
||||
0sJ2F6IFuTrRvXO5ND1QL4CZ2lr1BAU3iQffC6Uc3h4
|
||||
-> ssh-ed25519 1MUEqQ xxgEUIhvWN/ZfRMGfu3fKQ+fWM5WSz8OexXPm6jaXDk
|
||||
RXe0JMZ0sYMdQvrbi+zAs9F3d98ocRFnsSGUuUWccRk
|
||||
-> ssh-ed25519 Fa25Dw tw4sqQcO86Gh0FGUD+O3bJ+8OcaN5rm8R6qocXvDbRg
|
||||
7hiWa4qznHTV45kvC7ucj7j7FbPrqYK5OcCcByrcSxg
|
||||
--- kvZDYq5n/OXu7xe2Kf5vGN0zosl9fgH4CAf3K0Tq3U4
|
||||
¤D!´Ój]2ï‹ÝƒT+•
ÃUó¬æcgÝoP”‰ ˆF.Þ¿_vbÕÓÔdª9
|
||||
-> ssh-ed25519 Jpc21A KDoPj7ywZDlw2tVIWBtuypVarOUdBOrLmuBdoWV9FGY
|
||||
gnEtIo5tfBAQeDztEK4i6+ZHx02WG/cqWjTJ/BNS3OM
|
||||
-> ssh-ed25519 BAs8QA 8nmAEHH9zLjrWdjSxlW6c3xWP/kBZQ+P7SmJo5umC1o
|
||||
9WLBny4Hk1llZB/mwQ9nzG80atCHkOtqMElJuCL7yt8
|
||||
-> ssh-ed25519 ofQnlg qEiN2Xn5P9UhzosnZYwsk/pv0Gwg+qDQIjEi7Y6R+ls
|
||||
4Ka0LaC8jWwzkMdwHVT4KW8ALGU7I2XoWChSkX/4ibA
|
||||
-> ssh-ed25519 COspvA fcT8kt8mukX73QHcH+a2sbsmUc8U3U6fKM2n5U3+Szc
|
||||
TC9JFZo6YWohCskzy0vCSSKVctnpLQUlyMfC+08MDWM
|
||||
-> ssh-ed25519 1MUEqQ x+lo3jlXQc5hJUVmIk594j8RZkgHAfxoivWqte/Nd0o
|
||||
rmC0Y2yL/d3iZH3LKtyY5ggY3bza4+F0xJBLXPsH2yU
|
||||
-> ssh-ed25519 Fa25Dw McE5h1x23+JjZRI2IJ9FvElghshij/+cZxT82RorjkM
|
||||
vMZSy1a4BE0XEaTTVBAkbBViCuYcPOsV/FOnEn1Sh8I
|
||||
--- cP4vWMTBTfLLh8Xm0D/4ArTE4xmq2w4/kj8VijWSGt4
|
||||
þ½HE4jDËr&üo<C3BC>Ñ«·ëŸ±tER+áÂ8[¶ý
|
||||
ò ÐS„òż%jilÚ2
|
|
@ -1,16 +1,14 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 Jpc21A 7QzPABl0IuQlZ9nsqfdbmWT9zb6iHGEOQiJ5sBpuLWU
|
||||
zusphKpt0yVxQj0zh4AGtYFORIhUoJHJBYfEAHb/VN0
|
||||
-> ssh-ed25519 BAs8QA PBfmSc0pag5zCqB/EKvirDXyleNi2sgMZ0xTAMiANFo
|
||||
gugtjl9TSsGQuPypQiKZZqr7JfMKSdwVHGGKevyfF+k
|
||||
-> ssh-ed25519 ofQnlg RQd9jPyn8nv1zCLbETa9/JB39fwX3X0X9gElcHWEfCA
|
||||
7ZEBE4qaZUtrXLc0caZ/tfJFYT9UDYkVvuaBc0SVBhc
|
||||
-> ssh-ed25519 COspvA plM6PJykVR0NuSrOkRkA6ucOzUpijFaKbe99jaVrQSw
|
||||
7AkcdijjOSckx3GxXwXo1K34ReU3x0yWamlxdaA1FWA
|
||||
-> ssh-ed25519 2XrTgw +2ZOwvZRUmnuHVV8poyMR6eIvPQoxWQRngKJdL2kVHc
|
||||
rzWJIuZUtuurvIdV/47N2CNu8x4T/vca8IeRqi+mk/I
|
||||
-> ssh-ed25519 1MUEqQ 9U13b5LO4pSKhlvWEtkdrjTmVO5oGqcdT3Ime1AHjH4
|
||||
8wC87WZZYMZaR2YlG3oEt79QcZMA86TrUbvTekAUkqw
|
||||
--- X8Szdu5EyyPKR6xaL/8uKdHRc/D7wVGizk5k0XanreQ
|
||||
¯<EFBFBD>欿Æj{Ò“¦!q-ò˜G:£ÂjQò[_Ä„=ò<>#ÇŸd¾(i@/f3º’n°¦ý
|
||||
r :Ú‰ ¾×tx–ð¬|<<øX¡ÜºÊ¼™¨
|
||||
-> ssh-ed25519 Jpc21A v5ystrO0dW9a/n31TpDkXwgEYYPkd5NNNsrhA57rSQc
|
||||
tjGQl8KXiDfxe1dOuVPu8Yl7jOcfolisRdzgO4lLpYI
|
||||
-> ssh-ed25519 BAs8QA y0O4//knxtwtowt8DAhvlXZ8JaX7qK/bkbDcDc3NXBA
|
||||
sTKHklMRut3C+L81Er3tZJrIhpRjeHihfXEbavqjf9I
|
||||
-> ssh-ed25519 ofQnlg bRrkoMi+Yb4MURd6e6rzzGKaf4ZH+zOLsFdfA+MLu0E
|
||||
P4P3VLWsbs1gC0L4ULpvdtMQF0DAGLIqueZDGPKxfcg
|
||||
-> ssh-ed25519 COspvA BgFgHb9u73/zrkn6mfd3tgS1cxEJ1YWcByZZbQMl9gs
|
||||
L5NTd1sQ6xbdvD3XnhlIJ33PANCl5owhsXm2wMDp0C0
|
||||
-> ssh-ed25519 1MUEqQ K/sLMv7neETy3a9MXgPoNnG9+hLb3HWRSL1u/y/5v0o
|
||||
mF8ipNxm2J0P/Gact0ixmJ9IccG0+ftUwBiWlhjuAHg
|
||||
--- HXsWuDGdzSMQii48ZDaIKOuKeD8JcJT6PKhYgJCMirs
|
||||
9熏)]ナ<>ホ气Y。明<EFBDA1>?シ嗜7薙m懌I枴勧<E69EB4><E58BA7>GDX&」ケ<EFBDA3>tシ<74>2エ゚、V:<3A>.ォ胤<><E883A4>ホ:0じ%<25>
|
||||
T
|
|
@ -1,17 +1,14 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 Jpc21A fBhVzGFs61K63QtA8RdOuuGfHFjMe/Dp0M6TXGLGWDU
|
||||
qppnUZ+LQCXhuMCFMYv2D2CkmEfjb7mpmJufIeVjjaM
|
||||
-> ssh-ed25519 BAs8QA PNicZCWLkbvM4ih77/F4z6FzHomL9EsJCuSCjbdRTwA
|
||||
qIpTl/v7Xl08qBB//dFeW9qQiZg10YrYLnfyQrgDRfQ
|
||||
-> ssh-ed25519 ofQnlg 9/vSN3V25ysXBOvS4UJQEzm0734zqO0gXjhgzX63tTs
|
||||
AH9Q1lWr+RgICfW3h+D2SgCTFr+azI0x3J3eFnaz/XA
|
||||
-> ssh-ed25519 COspvA IB1nWOMaVZVcvEog6UaqCak2fcKxIUN2yXvvRSTDxGw
|
||||
Ti7JuBgU6phlI+oXfDDvx42dRu95kTwesRUKu4QsXZ4
|
||||
-> ssh-ed25519 2XrTgw 7S9ZhJvUFMw9tDCc0HvkRsRqjvmn47GFGVg/jkxIy1I
|
||||
cj27gqqihSZG3Jcab9h9FyNJ1J8FjlUiyVlDot+sbWQ
|
||||
-> ssh-ed25519 1MUEqQ l9mVTLD9rZXisBEz0sU2AdFNrJQ/+zuFTiIod5R/HCI
|
||||
2q3csSEvMW5vtzqGHYTtZ1nZ0J1vT23bjhuj9HTsdWk
|
||||
-> ssh-ed25519 kXy85Q BCrDvkPZLvx2Kvgapa3BT+AmpS6Fa5kpkgBnRVso2BE
|
||||
ZBi+x/2ilJIzhzGipdZQJoGOjSqCuAttsqCDVFlYJ8Y
|
||||
--- iWtseKyfUMBkQTUl9QzwXXLQcodEJeZt1Wuj5sR18yY
|
||||
+2,エ ゚恝Jrメィ>ア<7F>z?ホi<EFBE8E><69>ェ<EFBFBD>x<EFBFBD>0z<30>ヤ智メ劬呱^ゥネコ食・濤礫+1」ァ<EFBDA3>
|
||||
-> ssh-ed25519 BAs8QA 0TS+HcjtKeUAsLyzrsnCbj53GAq7pvXF12yQSxaxuFs
|
||||
IjmmZV2Zh4cwj1+7r/fAKnuftpl46P5fO6SxtRMevIM
|
||||
-> ssh-ed25519 ofQnlg b4maqJdxyyi7b3arE9sxySwqeFjFlC6oT+PgQjIGj0Y
|
||||
Gi5d4sJa0te/MsbkKYIOByIQ+TXBgu7hh2InES1pvXw
|
||||
-> ssh-ed25519 COspvA RiXEgUbPi3vep/8fM/RuRUYhCfBHO1XZt6Ov3WPnkV0
|
||||
tTMLMb92ct5Zkqt42y8R3UI/zblAbsuEammavVcwGOU
|
||||
-> ssh-ed25519 1MUEqQ XxxSvZrI9S6FI7CwYOSKDlfVBdLTur7/07Sm2HHLJwg
|
||||
iW5PduiY/7N2kSJpBzmfnt8aNWKPfLZ43Kq6fyLeydw
|
||||
-> ssh-ed25519 ChtTUw zixDXeL07d4+pzFBSt/1f8yB+QxXOMv6sE6h469YzVs
|
||||
rSC9S8v9gmtBw9FMKLg0h0muCmfMRuFD24JpTVw3ALc
|
||||
--- vf2SwG1rpxjri3TGARwdMBc/mccj6RSTgf54YeQeR/8
|
||||
În9…K±¼‰îÁäÞÈ9÷y¼¿«dMÈdWn@õYç0ì.ü½ž1uÜ›oÚ«¨Á¾jý<6A>iý`
|
||||
<EFBFBD>;1
|
|
@ -1,18 +1,16 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 Jpc21A BfHJN3vILbsfY91kEjSQ+STrn6vQfn83Fx3cBCNshRQ
|
||||
0O8GJYfF8WFS4Xsgj5v1cly4JP1MgSN40OgRdW/i0rA
|
||||
-> ssh-ed25519 BAs8QA Ue0NLMpmZDSTGvwZ8lhzes7pcmit9F6uwzeT4XhiwC0
|
||||
jsvvuOW344i8GR4B139SX0LwTqzKQEgBvsy8oRppqBU
|
||||
-> ssh-ed25519 ofQnlg 9iSMQeTJn1OUqTF+M2sHpp69lblb8E6TVbgZs7vgD2U
|
||||
uMQI1gTTMvYW7ea9xBAln118JEeNvv3nqbq32zJoat8
|
||||
-> ssh-ed25519 COspvA YxCyfe0li23JoI2q4XFVUx4vrWApLwSnJD31PHXuPBg
|
||||
8xuT9+W2mnTag9tm6F6LXzHkIh2Nou/8lgxd64OpvWk
|
||||
-> ssh-ed25519 2XrTgw jEzw0A9Wd1b1Zoryzp/W/QZ6bd99E7sySnr/W2xcnDs
|
||||
IyMrojJ3AChS6lhj599caNM+02i16qtpc6cocln14b4
|
||||
-> ssh-ed25519 1MUEqQ haiI/5EkuTZ2YHxsqSVlqfM0VVR24DIDrMS3RmXwAhU
|
||||
qVIAvLp2qG4A3f3OKUqAKqH1eOicJz54nfblPSUKrSw
|
||||
-> ssh-ed25519 dgBsjw /vCnznu73U99onCWcM0aQlW0azscyUe4BB2kKeZvtHs
|
||||
MPnvXR/WVsl/tJ1YPoc7nk2Ls2x9bbtJdNp3CQTuuWI
|
||||
--- OzkqKlw4xu3McMk20orQN0h+VPYfUUSDC+DsgRU1tSw
|
||||
ü^@ÃmŽÚé`B˜³#{¡Ÿ‚ GUu´‹|¹Á œ¡ rÚïjb¥:Ô“d²ù] GØÎ©¦ú²-«Â–MÁ³ŸÜálÙ3mÌí)½š@¢—±e?¯ªêe¤üZ
|
||||
Y}ÿ!fÐ
|
||||
-> ssh-ed25519 Jpc21A oYtKMwPB/M034KiJAmYsjg46+IC525Uul+VoFzHWwDE
|
||||
v6YVVia1UVBwd4RoJsvozbCdy/3jYHMmFKyiDfaGR+g
|
||||
-> ssh-ed25519 BAs8QA 42kHHxx7k6jkazovkEIJ+uhOKr485RCQgvTXKY/+eWQ
|
||||
4YAGNgdsWk7dC32SMRyicifvrkz/wCor/98lCQ2Kl/I
|
||||
-> ssh-ed25519 ofQnlg nfPgeBQ9674MP0dKJLLkvcJSTSAOLs0cnRc/Pp2Z1E8
|
||||
Lm+GLkCAQ2B9wxVIaDjw18Ru3KQat79mJVF8nLFb7+A
|
||||
-> ssh-ed25519 COspvA u07qlzPhj4e6qoilGLa9L7x2QMgjD6D40ROeUKiFGyk
|
||||
gpPwOMfZM/DMxnzg67mxZCyetSxJEmqpiKs1WqdLO7E
|
||||
-> ssh-ed25519 1MUEqQ x8aGrPIHJymzUAXrQHeYrG3SeeEkc6bQcRcKCdHmJH0
|
||||
5C3tswXVRpEzj+Plkt+F4DLhhX/m1jJoKv9iwIWfxlk
|
||||
-> ssh-ed25519 dgBsjw 4pSsRKJIJWaDAOPrdnNM5O/u0mh99ZAniUHjjiT59jM
|
||||
EhEjGusUWQ//3r62GklyjuPSS3VXfzivDkYhrAh4tRE
|
||||
--- fr5CmQ26pYk4qLbSXyVjEP20BYeALQ0sD5mPW0JsJpM
|
||||
æ&E9êùñIÙð¾TÄŽ4êÆ¶§es4Fø‘·‹’2ôÃÓ;v7ÇÐIuQÒ
|
||||
|åQݸ°ˆVÅò®¿Æ\,K£Íý¼ŠÐ·nùí`WÉl2RÓªä¤TGN$
|
|
@ -1,17 +1,16 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 Jpc21A RFzPu3fD28STex7ND5lE9bfCxQq/xeHEb7h7BFt9pVA
|
||||
8K+ECDGs71V91jEVQjrRVQNbdTzBb6W9jkp0+K1trzw
|
||||
-> ssh-ed25519 BAs8QA F2L9Eh9OItaPfAcR4qNnOQnvCyTeGdR5lSu0WqXiuU0
|
||||
3jLlt4qAL3/VKyfbP7R0/7SUwwPpWf5YUWwzjDONEy8
|
||||
-> ssh-ed25519 ofQnlg BLdBNuJExNlYED/XFU5zmYPtO1bxumuyPPgcs8qSLEY
|
||||
wIi31st4WS9O2a7VJmYpE8PimgzLvwU6zWkvHCy84yQ
|
||||
-> ssh-ed25519 COspvA GmC8YZVv3ZwidaDKUkLhx0l8UOmRw5ZBiM8r4/Ub7xA
|
||||
wK994Zs1aLspqY84Ik77qdMaEsjs0ZFNuKQDOGXsnmM
|
||||
-> ssh-ed25519 2XrTgw s62q7KOHZRqimCTwazX9LUvnpcYuzxwflumXe6NVF38
|
||||
WnOpHI9ejvRrZrQuasTEYyqP8ny3Hx9Q9bJzbK0pOI4
|
||||
-> ssh-ed25519 1MUEqQ 9NLHR5OwOngiLRguTkf5KnUHrc80mambCw19dPPKPQ8
|
||||
GDg9yRoRUaP1KOa/pOCFiLCCAxuFsuCIiDP/ERl8YLA
|
||||
-> ssh-ed25519 dgBsjw NulCMPtc2miJlHYpXMjQHUlc/HIaX4AqzxXZxt8cWkE
|
||||
tCh3WD91A89258F8THeddXvab77tTIjNjGxYNDVoaBQ
|
||||
--- oIUm35maOqmHL0nifKpyEvLpHSKmthxIT5DDueCVZDc
|
||||
ÈaT~œú£Úôf4÷„Ô$\—ì;¿@Äý`¦<18>(<28>3{VWgM}#<1A>ï9aû6¢½ª^öúT6éÞ!Þ %¿
|
||||
-> ssh-ed25519 Jpc21A 6LN8aSeHyRfQv+eGVgtopTb/WpAF1zR1dwW0YVt0qxg
|
||||
wSM6R7r9SjKmhjryhEZ1+JSetqfYNlRv/uZi2ME8UAM
|
||||
-> ssh-ed25519 BAs8QA x5ttKskqaHhf/StOzSwprTutcneG/S06nZ1w6+B/YkI
|
||||
UwlynUOoncgg1JU6qP/EzaPZrAP28fZq5q6vdXlHBss
|
||||
-> ssh-ed25519 ofQnlg Vrs8dDVekAMZNyFQvijC/Q3xqtJ3+elMYE89t8D4L14
|
||||
AxfYaTk8JGDgef6tpB/SgeY5u0Jt3Mz7FWBVseAVQX4
|
||||
-> ssh-ed25519 COspvA 3xCdPxnF003niI8XFzgO9Y7QD5bru6faLWeVUu6b8xI
|
||||
5vjR7d9xkWepGhfLyJAhBrun87yC9NSSxmD9WOls6W0
|
||||
-> ssh-ed25519 1MUEqQ KkiwnNTIZngy8A4m3DE3Bd3XqTBC+gq/StwW/0iBMXg
|
||||
6ElKKheqr0wedd9SBdaGXgVoJJiyvwmVptB3LMf8Yzw
|
||||
-> ssh-ed25519 dgBsjw tmd9R9c/Im+e4vDOZ5jBbfKnaALVOZQ9d1q2gp+HNEY
|
||||
vOQn5KuKYO0ObfxqCHcNnzaBSOjj4Sonca/cEyBNppw
|
||||
--- zKHl+VjurB7fjxLskhi+XBaqFrbL6TNv6uL/fPMLffw
|
||||
Šåp¼ ,bv=ÉÙ÷Âæh“Âj¼S<>’¸_AáÛÚ]ü2
|
||||
]óŠ<15>ɨˆÇ!døáCkW5F>èÄjš
|
Binary file not shown.
Loading…
Add table
Reference in a new issue