Compare commits

..

1 Commits

Author SHA1 Message Date
Aravinth Manivannan cb6bcf8754
fix: Gite credentials email: fix subject and let user know instance is
ci/woodpecker/push/woodpecker Pipeline was successful Details
being provisioned

fixes: https://gitea.hostea.org/Hostea/dashboard/issues/44
2022-07-08 22:48:33 +05:30
97 changed files with 528 additions and 1209 deletions

View File

@ -3,5 +3,5 @@ export db=""
export STRIPE_SECRET_KEY=""
export STRIPE_PUBLIC_KEY=""
export HOSTEA_INFRA_HOSTEA_REPO_REMOTE="ssh://git@localhost:22/hostea/fleet.git"
export HOSTEA_META_FORGEJO_INSTANCE="http://localhost:3000"
export HOSTEA_META_GITEA_INSTANCE="http://localhost:3000"
export HOSTEA_INFRA_HOSTEA_REPO_SSH_KEY="$(realpath ./tests/fleet-deploy-key)"

View File

@ -4,8 +4,8 @@ pipeline:
environment:
- DATABSE_URL=postgres://postgres:password@database:5432/postgres
- EMAIL_URL=smtp://admin:password@smtp:10025
- HOSTEA_INFRA_HOSTEA_REPO_REMOTE=ssh://git@forgejo:22/hostea/
- HOSTEA_META_FORGEJO_INSTANCE=http://forgejo:3000
- HOSTEA_INFRA_HOSTEA_REPO_REMOTE=ssh://git@gitea:22/hostea/
- HOSTEA_META_GITEA_INSTANCE=http://gitea:3000
commands:
- export HOSTEA_INFRA_HOSTEA_REPO_SSH_KEY="$(realpath ./tests/fleet-deploy-key)"
- pip install virtualenv
@ -21,9 +21,9 @@ services:
environment:
- POSTGRES_PASSWORD=password
forgejo:
image: codeberg.org/forgejo/forgejo:1.18.0-1
container_name: hostea-dash-forgejo
gitea:
image: gitea/gitea:1.16.5
container_name: hostea-dash-gitea
smtp:
image: maildev/maildev:latest

View File

@ -1,6 +1,6 @@
FROM python
LABEL org.opencontainers.image.source https://forgejo.hostea.org/Hostea/dashboard
LABEL org.opencontainers.image.source https://gitea.hostea.org/Hostea/dashboard
RUN useradd -ms /bin/bash -u 1001 hostea
RUN apt-get update && apt-get install -y ca-certificates git

View File

@ -9,7 +9,6 @@ endef
default: ## Run app
$(call run_migrations)
. ./venv/bin/activate && yes yes | python manage.py collectstatic
. ./venv/bin/activate && python manage.py runserver
coverage: ## Generate test coverage report

View File

@ -2,7 +2,7 @@
# Hostea dashboard
[![status-badge](https://woodpecker.gna.org/api/badges/Hostea/dashboard/status.svg)](https://woodpecker.gna.org/Hostea/dashboard)
[![status-badge](https://woodpecker.hostea.org/api/badges/Hostea/dashboard/status.svg)](https://woodpecker.hostea.org/Hostea/dashboard)
[![AGPL License](https://img.shields.io/badge/license-AGPL-blue.svg?style=flat-square)](http://www.gnu.org/licenses/agpl-3.0)
[![Chat](https://img.shields.io/badge/matrix-hostea:matrix.batsense.net-purple?style=flat-square)](https://matrix.to/#/#hostea:matrix.batsense.net)

View File

@ -1,52 +0,0 @@
# Generated by Django 4.0.3 on 2022-07-10 06:14
import accounts.utils
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("accounts", "0005_accountconfirmchallenge_created_at"),
]
operations = [
migrations.CreateModel(
name="PasswordResetChallenge",
fields=[
(
"public_ref",
models.CharField(
default=accounts.utils.gen_secret,
editable=False,
max_length=32,
unique=True,
verbose_name="Public referece to challenge text",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
(
"challenge_text",
models.CharField(
default=accounts.utils.gen_secret,
editable=False,
max_length=32,
primary_key=True,
serialize=False,
unique=True,
verbose_name="Challenge text",
),
),
(
"owned_by",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@ -52,36 +52,3 @@ class AccountConfirmChallenge(models.Model):
def pending_url(self):
return reverse("accounts.verify.pending", args=(self.public_ref,))
class PasswordResetChallenge(models.Model):
owned_by = models.OneToOneField(User, on_delete=models.CASCADE)
public_ref = models.CharField(
"Public referece to challenge text",
unique=True,
max_length=32,
default=gen_secret,
editable=False,
)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
challenge_text = models.CharField(
"Challenge text",
unique=True,
max_length=32,
default=gen_secret,
editable=False,
primary_key=True,
)
def __str__(self):
return f"{self.challenge_text}"
def verification_link(self):
"""
Get verification link
"""
return reverse("accounts.password.reset", args=(self.challenge_text,))
def pending_url(self):
return reverse("accounts.password.reset.resend", args=(self.public_ref,))

View File

@ -1,23 +1,24 @@
{% extends "common/components/base.html" %}
{% block title %}{% block title_name %} {% endblock %} | Gna! Dashboard{% endblock %}
{% block title %}{% block title_name %} {% endblock %} | Hostea Dashbaord{% endblock %}
{% block nav %} {% include "common/components/nav/pub.html" %} {% endblock %}
{% block main %}
<main class="auth__main">
<section class="main">
<div class="title">
<h1><a href="https://forgejo.org">Forgejo</a> hosting and <a href="/forgejo-clinic/">service</a></h1>
<h1>Free Forge Ecosystem for Free Developers</h1>
<p class="welcome">
A free forge ecosystem for free developers.
Hostea is a self-hostable libre software development suite comprising Gitea and Woodpecker CI with payments integration.
</p>
<ul class="index-banner__features-list">
<li class="index-banner__features">Dedicated <a href="https://hosteadashboard.gna.org/register/">Forgejo hosting</a> and <a href="https://woodpecker-ci.org/">Woodpecker CI</a> from 10€/month</li>
<li class="index-banner__features">Clinic to <a href="https://gna.org/forgejo-clinic/">heal sick Forgejo</a> instances</li>
<li class="index-banner__features">100% <a href="https://www.gnu.org/philosophy/free-sw.html">Free Software</a></li>
<li class="index-banner__features">Radically <a href="https://forum.gna.org/t/about-governance-and-decisions-in-hostea/55">Transparent</a></li>
<li class="index-banner__features">Run by a <a href="https://forum.gna.org/t/about-governance-and-decisions-in-hostea/55">horizontal collective</a></li>
<li class="index-banner__features">25% of the income <a href="https://forum.gna.org/t/decision-revenue-sharing-model/92">dedicated to sustain Free Software dependencies</a></li>
<li class="index-banner__features">Committed to <a href="https://forgefriends.org/blog/2022/06/30/2022-06-state-forge-federation/">further forge federation</a></li>
<li class="index-banner__features">Fully managed</li>
<li class="index-banner__features">100% Free Software</li>
<li class="index-banner__features">Fully Self-Hostable</li>
<li class="index-banner__features">Observable and reliable</li>
<li class="index-banner__features">Federation when available</li>
<li class="index-banner__features">Radically transparent</li>
<li class="index-banner__features">Horizontal community</li>
<li class="index-banner__features">Run Hostea and become a service provider!</li>
</ul>
</div>
</section>

View File

@ -34,12 +34,12 @@
/>
</label>
<div class="form__action-container">
<a href="{% url 'accounts.password.reset.new' %}">Forgot password?</a>
<a href="/forgot-password">Forgot password?</a>
<button class="form__submit" type="submit">Login</button>
</div>
</form>
<p class="form__alt-action">
New to Gna!?
New to Hostea?
<a href="{% url 'accounts.register' %}">Create an account</a>
</p>
{% endblock %}

View File

@ -1,28 +0,0 @@
{% extends "common/components/base.html" %}
{% block title %} Reset Password| Gna! Dashboard{% endblock %}
{% block nav %} {% include "common/components/nav/pub.html" %} {% endblock %}
{% block main %}
<div class="dialogue-box__container">
<h2>Reset password</h2>
<form
action="{% url 'accounts.password.reset.new' %}"
method="POST"
class="form"
accept-charset="utf-8"
>
{% include "common/components/error.html" %} {% csrf_token %}
<label class="form__label" for="email">
Email
<input
class="form__input"
name="email"
id="email"
type="email"
/>
</label>
<div class="form__action-container">
<button class="form__submit" type="submit">Send Password Reset Link</button>
</div>
</form>
</div>
{% endblock %}

View File

@ -1,20 +0,0 @@
{% extends "common/components/base.html" %}
{% block title %} Reset Password | Gna! Dashboard{% endblock %}
{% block nav %} {% include "common/components/nav/pub.html" %} {% endblock %}
{% block main %}
<div class="dialogue-box__container">
<h2>Reset password</h2>
<p>Verification link is sent to email address: {{email}}</p>
<form
action="{% url 'accounts.password.reset.resend' public_ref=public_ref %}"
method="POST"
class="form"
accept-charset="utf-8"
>
{% include "common/components/error.html" %} {% csrf_token %}
<div class="form__action-container">
<button class="form__submit" type="submit">Click here to resend email</button>
</div>
</form>
</div>
{% endblock %}

View File

@ -1,40 +0,0 @@
{% extends "common/components/base.html" %}
{% block title %} Reset Password | Gna! Dashboard{% endblock %}
{% block nav %} {% include "common/components/nav/pub.html" %} {% endblock %}
{% block main %}
<div class="dialogue-box__container">
<h2>Reset Password</h2>
<form
action="{% url 'accounts.password.reset' challenge=challenge %}"
method="POST"
class="form"
accept-charset="utf-8"
>
{% include "common/components/error.html" %} {% csrf_token %}
<label class="form__label" for="password">
password
<input
class="form__input"
name="password"
required
id="password"
type="password"
/>
</label>
<label class="form__label" for="confirm_password">
Re-enter Password
<input
class="form__input"
name="confirm_password"
required
id="confirm_password"
type="password"
/>
</label>
<div class="form__action-container">
<button class="form__submit" type="submit">Reset Password</button>
</div>
</form>
</div>
{% endblock %}

View File

@ -1,5 +1,5 @@
{% extends "common/components/base.html" %}
{% block title %} Confirm Access | Gna! Dashboard{% endblock %}
{% block title %} Confirm Access | Hostea Dashbaord{% endblock %}
{% block nav %} {% include "dash/common/components/primary-nav.html" %} {% endblock %}
{% block main %}

View File

@ -1,5 +1,5 @@
{% extends "common/components/base.html" %}
{% block title %} Confirm Account | Gna! Dashboard{% endblock %}
{% block title %} Confirm Account | Hostea Dashbaord{% endblock %}
{% block nav %} {% include "common/components/nav/pub.html" %} {% endblock %}
{% block main %}
<div class="dialogue-box__container">

View File

@ -1,5 +1,5 @@
{% extends "common/components/base.html" %}
{% block title %} Confirm Account | Gna! Dashboard{% endblock %}
{% block title %} Confirm Account | Hostea Dashbaord{% endblock %}
{% block nav %} {% include "common/components/nav/pub.html" %} {% endblock %}
{% block main %}
<div class="dialogue-box__container">

View File

@ -1,14 +0,0 @@
Hello {{ username }},
You have a new password!
Your password for signing in to Gna! was recently changed. If you made this change, then we're all set.
If you did not make this change, please reset your password to secure your account.
{% url 'accounts.password.reset.new' %}
Either way, feel free to reach out with any questions you might have. We're here to help.
Cheers,
Gna! team

View File

@ -1,9 +0,0 @@
Hello {{ email }},
Please click on the link below to reset your password:
{{ link }}
If you don't recognise this activity, please delete this mail.
Cheers,
Gna! team

View File

@ -6,4 +6,4 @@ Please click on the link below to verify your email.
If you don't recognise this activity, please delete this mail.
Cheers,
Gna! team
Hostea team

View File

@ -10,11 +10,21 @@
title="RSS"
>Home</a>
<span class="footer__column-divider--mobile-visible">|</span>
<a class="license__link" rel="noreferrer" href="https://gna.org/about" target="_blank"
<a class="license__link" rel="noreferrer" href="https://hostea.org/about" target="_blank"
>&nbsp; About</a
>
</span>
</div>
<div class="footer__column">
<a
class="license__link"
rel="noreferrer"
href="https://www.eff.org/issues/do-not-track/amp/"
target="_blank"
>No AMP</a
>
<div class="footer__column-divider">|</div>
<a href="mailto:{{ footer.admin_email }}" class="footer__link"
>Contact Instance Maintainer</a
>

View File

@ -3,8 +3,11 @@
<input type="checkbox" class="nav__toggle" id="nav__toggle" />
<div class="nav__header">
<a class="nav__logo-container" href="/">
<img class="nav__logo-img" src="{% static 'img/logo.png' %}"
alt="Gna! logo"/>
<img src="{% static 'img/android-icon-48x48.png' %}"
alt="Hostea temporary logo"/>
<p class="nav__home-btn">
ostea
</p>
</a>
<label class="nav__hamburger-menu" for="nav__toggle">
<span class="nav__hamburger-inner"></span>

View File

@ -33,7 +33,7 @@ from django.conf import settings
from oauth2_provider.models import get_application_model
from .models import AccountConfirmChallenge, PasswordResetChallenge
from .models import AccountConfirmChallenge
from .management.commands.rm_unverified_users import (
Command as CleanUnverifiedUsersCommand,
)
@ -77,9 +77,7 @@ class LoginTest(TestCase):
Tests if login template renders
"""
resp = self.client.get(reverse("accounts.login"))
self.assertEqual(
b"A free forge ecosystem for free developers" in resp.content, True
)
self.assertEqual(b"Free Forge Ecosystem" in resp.content, True)
def test_login_works(self):
"""
@ -160,71 +158,6 @@ class LoginTest(TestCase):
self.assertEqual(resp.headers["location"], reverse("dash.instances.list"))
class ResetPasswordTest(TestCase):
def setUp(self):
self.username = "reset_password_user"
register_util(t=self, username=self.username)
def reset_password(self):
c = Client()
payload = {
"email": self.email,
}
resp = c.get(reverse("accounts.password.reset.new"))
self.assertEqual(resp.status_code == 200)
resp = c.post(reverse("accounts.password.reset.new"), payload)
self.assertEqual(resp.status_code == 302)
challenge = PasswordResetChallenge.objects.filter(owned_by=self.user)
self.assertEqual(resp.headers["location"] == challenge.pending_url(), True)
password_reset_mail = mail.outbox.pop()
self.assertEqual("reset your password" in password_reset_mail, True)
self.assertEqual(challenge.verification_link() in password_reset_mail, True)
resp = c.get(self.challenge.verification_link())
self.assertEqual(resp.status_code == 200)
new_password = "newpasdasdf234234"
# passwords don't match
payload = {
"password": new_password,
"confirm_password": self.password,
}
resp = c.post(self.challenge.verification_link(), payload)
self.assertEqual(resp.status_code == 400)
# change password
payload["confirm_password"] = new_password
resp = c.post(self.challenge.verification_link(), payload)
self.assertEqual(resp.status_code == 302)
self.assertEqual(resp.headers["location"], reverse("accounts.login"))
# verify password changed notification email was sent
password_updated_email = mail.outbox.pop()
self.assertEqual(
"Your password for signing in to Hostea was recently changed. If you made this change, then we're all set."
in password_updated_email,
True,
)
self.assertEqual(reverse("accounts.reset.new") in password_updated_email, True)
# trying to login with old password
payload = {
"login": self.username,
"password": self.password,
}
resp = self.client.post(reverse("accounts.login"), payload)
self.assertEqual(resp.status_code, 401)
self.assertEqual(b"Login Failed" in resp.content, True)
payload["password"] = new_password
resp = c.post(reverse("accounts.login"), payload)
self.assertEqual(resp.status_code, 302)
self.assertEqual(resp.headers["location"], reverse("accounts.home"))
class RegistrationTest(TestCase):
def setUp(self):
self.username = "register_user"
@ -236,9 +169,7 @@ class RegistrationTest(TestCase):
Tests if register template renders
"""
resp = self.client.get(reverse("accounts.register"))
self.assertEqual(
b"A free forge ecosystem for free developers." in resp.content, True
)
self.assertEqual(b"Free Forge Ecosystem" in resp.content, True)
def test_register_works(self):
"""

View File

@ -25,9 +25,6 @@ from .views import (
resend_verification_email_view,
verification_pending_view,
sudo,
password_reset_send_verificaiton_link,
password_resend_verification_link_pending,
reset_password,
)
urlpatterns = [
@ -47,20 +44,5 @@ urlpatterns = [
),
path("accounts/verify/<str:challenge>/", verify_account, name="accounts.verify"),
path("accounts/sudo/", sudo, name="accounts.sudo"),
path(
"accounts/password/reset/challenge/",
password_reset_send_verificaiton_link,
name="accounts.password.reset.new",
),
path(
"accounts/password/reset/<str:challenge>/",
reset_password,
name="accounts.password.reset",
),
path(
"accounts/password/reset/challenge/<str:public_ref>/",
password_resend_verification_link_pending,
name="accounts.password.reset.resend",
),
path("", protected_view, name="accounts.home"),
]

View File

@ -30,55 +30,6 @@ def gen_secret() -> str:
return get_random_string(32)
def send_password_changed_email(request):
ctx = {
"username": request.user.username,
}
body = render_to_string(
"accounts/emails/password-changed.txt",
context=ctx,
)
email = request.user.email
sender = settings.DEFAULT_FROM_EMAIL
send_mail(
subject="[Gna!] Password changed",
message=body,
from_email=f"No reply Gna!<{sender}>",
recipient_list=[email],
)
def send_password_reset_email(request, challenge):
verification_link = (
f"{request.scheme}://{request.get_host()}{challenge.verification_link()}"
)
ctx = {
"link": verification_link,
"email": challenge.owned_by.email,
}
body = render_to_string(
"accounts/emails/password-reset-link.txt",
context=ctx,
)
email = challenge.owned_by.email
sender = settings.DEFAULT_FROM_EMAIL
send_mail(
subject="[Gna!] Password reset link",
message=body,
from_email=f"No reply Gna!<{sender}>",
recipient_list=[email],
)
def send_verification_email(request, challenge):
verification_link = (
f"{request.scheme}://{request.get_host()}{challenge.verification_link()}"
@ -99,9 +50,9 @@ def send_verification_email(request, challenge):
sender = settings.DEFAULT_FROM_EMAIL
send_mail(
subject="[Gna!] Please confirm your email address",
subject="[Hostea] Please confirm your email address",
message=body,
from_email=f"No reply Gna!<{sender}>",
from_email=f"No reply Hostea<{sender}>", # TODO read from settings.py
recipient_list=[email],
)

View File

@ -25,8 +25,8 @@ from django.urls import reverse
from dash.utils import footer_ctx
from .models import AccountConfirmChallenge, PasswordResetChallenge
from .utils import send_verification_email, ConfirmAccess, send_password_reset_email
from .models import AccountConfirmChallenge
from .utils import send_verification_email, ConfirmAccess
from .decorators import redirect_if_authenticated
@ -242,107 +242,3 @@ def sudo(request):
ConfirmAccess.set(request=request)
return redirect(request.POST["next"])
@redirect_if_authenticated
@csrf_protect
def password_reset_send_verificaiton_link(request):
def default_ctx():
return {
"title": "Reset Password",
"footer": footer_ctx(),
}
if request.method == "GET":
ctx = default_ctx()
return render(request, "accounts/auth/password-reset-form.html", ctx)
challenge = None
email = request.POST["email"]
User = get_user_model()
user = get_object_or_404(User, email=email)
if not PasswordResetChallenge.objects.filter(owned_by=user).exists():
challenge = PasswordResetChallenge(owned_by=user)
challenge.save()
send_password_reset_email(request, challenge=challenge)
else:
challenge = PasswordResetChallenge.objects.get(owned_by=user)
return redirect(challenge.pending_url())
@redirect_if_authenticated
@csrf_protect
def password_resend_verification_link_pending(request, public_ref):
challenge = get_object_or_404(PasswordResetChallenge, public_ref=public_ref)
if request.method == "GET":
ctx = {
"email": challenge.owned_by.email,
"public_ref": challenge.public_ref,
}
return render(
request,
"accounts/auth/password-reset-resend-verification.html",
context=ctx,
)
send_password_reset_email(request, challenge=challenge)
ctx = {
"email": challenge.owned_by.email,
"public_ref": challenge.public_ref,
}
return render(
request, "accounts/auth/password-reset-resend-verification.html", context=ctx
)
@csrf_protect
def reset_password(request, challenge):
def default_ctx(challenge):
return {
"title": "Reset Password",
"footer": footer_ctx(),
"challenge": challenge,
}
challenge = get_object_or_404(PasswordResetChallenge, challenge_text=challenge)
if request.method == "GET":
ctx = default_ctx(challenge=challenge)
return render(request, "accounts/auth/password-reset.html", ctx)
confirm_password = request.POST["confirm_password"]
password = request.POST["password"]
if password != confirm_password:
ctx = default_ctx(challenge=challenge)
ctx["error"] = {
"title": "Reset Password Failed",
"reason": "Passwords don't match, please verify input",
}
return render(
request, "accounts/auth/password-reset.html", status=400, context=ctx
)
user = challenge.owned_by
try:
validate_password(password, user=user)
except ValidationError as err:
ctx = default_ctx(challenge=challenge)
reason = ""
for r in err:
reason += r + " "
ctx["error"] = {"title": "Reset Password Failed", "reason": reason}
return render(
request, "accounts/auth/password-reset.html", status=400, context=ctx
)
user.set_password(password)
user.save()
challenge.delete()
send_password_reset_email(request)
return redirect("accounts.login")

View File

@ -15,8 +15,6 @@
from django.core.management.base import BaseCommand
from django.core.exceptions import ValidationError
from django.conf import settings
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.contrib.auth import get_user_model
from oauth2_provider.models import get_application_model
@ -24,8 +22,7 @@ from oauth2_provider.generators import generate_client_id, generate_client_secre
from accounts.utils import gen_secret
from dash.models import Instance
from infrastructure.models import InstanceCreated
from billing.utils import generate_invoice, payment_fullfilled, get_invoice_link
from billing.utils import generate_invoice, payment_fullfilled
Application = get_application_model()
@ -36,34 +33,10 @@ class Command(BaseCommand):
def handle(self, *args, **options):
instances = Instance.objects.all()
if instances:
for paid_instance in InstanceCreated.objects.all():
self.stdout.write(f"Found instance: {paid_instance.instance}")
if not payment_fullfilled(instance=paid_instance.instance):
self.stdout.write(
f"Payment not fulfilled for instance: {paid_instance.instance}"
)
payment = generate_invoice(instance=paid_instance.instance)
owner = paid_instance.instance.owned_by
ctx = {
"username": owner.username,
"payment": payment,
"link": get_invoice_link(payment=payment),
}
body = render_to_string(
"billing/emails/renew-subscription.txt",
context=ctx,
)
email = owner.email
sender = settings.DEFAULT_FROM_EMAIL
send_mail(
subject="[Gna!] Payment receipt for your Gna! VM",
message=body,
from_email=f"No reply Gna!<{sender}>", # TODO read from settings.py
recipient_list=[email],
)
for instance in Instance.objects.all():
self.stdout.write(f"Found instance: {instance}")
if not payment_fullfilled(instance=instance):
self.stdout.write(f"Payment not fulfilled for instance: {instance}")
payment = generate_invoice(instance=instance)
else:
self.stdout.write("No instances available")

View File

@ -1,18 +0,0 @@
# Generated by Django 4.0.3 on 2022-07-08 13:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("billing", "0004_payment_billing_pay_paid_by_77f57c_idx"),
]
operations = [
migrations.AddField(
model_name="payment",
name="vm_deleted",
field=models.BooleanField(default=False),
),
]

View File

@ -67,8 +67,6 @@ class Payment(BasePayment):
date = models.DateTimeField(auto_now_add=True, blank=True)
objects = PaymentModelManager()
vm_deleted = models.BooleanField(default=False, null=False)
def get_failure_url(self) -> str:
url = urlparse(settings.PAYMENT_HOST)
return urlunparse(

View File

@ -1,6 +1,6 @@
Hello {{ username }}!
An invoice is generated for your Gna! VM {{ payment.instance_name }}.
An invoice is generated for your Hostea VM {{ payment.instance_name }}.
- Configuration: {{payment.instance_configuration_id.name}}
- Invoice generated on: {{payment.date.month}}/{{payment.date.day}}/{{payment.date.year}}
@ -11,4 +11,4 @@ To pay, please click the link below:
{{ link }}
Cheers,
Gna! team
Hostea team

View File

@ -1,11 +1,11 @@
Hello {{ username }}!
This is a receipt for your latest Gna! payment.
This is a receipt for your latest Hostea payment.
-----------------------------------------------------
Gna! Receipt - {{payment.date.month}}/{{payment.date.day}}/{{payment.date.year}}
Hostea Receipt - {{payment.date.month}}/{{payment.date.day}}/{{payment.date.year}}
- Instance Name: {{ payment.instance_name }}
- Configuration: {{payment.instance_configuration_id.name}}
@ -19,4 +19,4 @@ To view the receipt online, please see the following link:
We appreciate your business!
Cheers,
Gna! team
Hostea team

View File

@ -1,17 +0,0 @@
Hello {{ username }}!
Your Gna! VM subscription is due for renewal. Please click the link
below to renew your subscription:
{{link}}
-----------------------------------------------------
- Instance Name: {{ payment.instance_name }}
- Configuration: {{payment.instance_configuration_id.name}}
- Total Amount: {{payment.total}} {{payment.currency|upper}}
We appreciate your business!
Cheers,
Gna! team

View File

@ -49,7 +49,7 @@ class BillingTest(TestCase):
def test_payments(self):
c = Client()
login_util(self, c, "accounts.home")
instance_name = "tpayments"
instance_name = "test_payments"
create_instance_util(
t=self, c=c, instance_name=instance_name, config=self.instance_config[0]
)
@ -95,15 +95,14 @@ class BillingTest(TestCase):
# sent when the invoice is generated and one after payment is made
#
# So we are first checking for the last email that was sent(receipt)
# and then the Forgejo instance credentials notification followed by the
# and then the Gitea instance credentials notification followed by the
# invoice generation email.
receipt_mail = mail.outbox.pop()
print(receipt_mail.body)
self.assertEqual(
all(
[
receipt_mail.to[0] == self.email,
"This is a receipt for your latest Gna! payment"
"This is a receipt for your latest Hostea payment"
in receipt_mail.body,
]
),
@ -115,13 +114,24 @@ class BillingTest(TestCase):
all(
[
instance_notificaiton.to[0] == self.email,
"Congratulations on your new Gna! instance!"
"Congratulations on your new Hostea instance!"
in instance_notificaiton.body,
]
),
True,
)
invoice_generated_mail = mail.outbox.pop()
self.assertEqual(
all(
[
invoice_generated_mail.to[0] == self.email,
"An invoice is generated" in invoice_generated_mail.body,
]
),
True,
)
## payment failure page; no real functionality but user is redirected here
# by stripe if payment is successful
resp = c.get(reverse("billing.invoice.fail", args=(payment.public_ref,)))
@ -138,51 +148,13 @@ class GenerateInvoiceCommand(TestCase):
register_util(t=self, username=self.username)
create_configurations(t=self)
@override_settings(
HOSTEA=infra_custom_config(
test_name="test_dont_send_invoices_to_not_created_vms"
)
)
def test_dont_send_invoices_to_not_created_vms(self):
c = Client()
login_util(self, c, "accounts.home")
instance_name = "tnoinvonocrevm"
payload = {"name": instance_name, "configuration": self.instance_config[0].name}
resp = c.post(reverse("dash.instances.new"), payload)
self.assertEqual(resp.status_code, 302)
self.assertEqual(
resp.headers["location"],
reverse("billing.invoice.generate", args=(instance_name,)),
)
stdout = StringIO()
stderr = StringIO()
instance = Instance.objects.get(name=instance_name)
self.assertEqual(payment_fullfilled(instance=instance), False)
prev_len = len(mail.outbox)
# username exists
call_command(
"generate_invoice",
stdout=stdout,
stderr=stderr,
)
out = stdout.getvalue()
print(out)
self.assertEqual(instance_name not in out, True)
@override_settings(
HOSTEA=infra_custom_config(test_name="test_generate_invoice_cmd")
)
def test_cmd(self):
c = Client()
login_util(self, c, "accounts.home")
instance_name = "tgeninvmd"
instance_name = "test_generate_invoice_cmd"
create_instance_util(
t=self, c=c, instance_name=instance_name, config=self.instance_config[0]
)

View File

@ -45,9 +45,7 @@ def payment_fullfilled(instance: Instance) -> bool:
delta = __get_delta()
payment = None
for p in Payment.objects.filter(
date__gt=(delta), instance_name=instance.name, vm_deleted=False
):
for p in Payment.objects.filter(date__gt=(delta), instance_name=instance.name):
if p.status == PaymentStatus.CONFIRMED:
return True
@ -79,9 +77,7 @@ def generate_invoice(instance: Instance) -> Payment:
delta = __get_delta()
payment = None
for p in Payment.objects.filter(
date__gt=(delta), instance_name=instance.name, vm_deleted=False
):
for p in Payment.objects.filter(date__gt=(delta), instance_name=instance.name):
if p.status == PaymentStatus.CONFIRMED:
raise GenerateInvoiceException(code=GenerateInvoiceErrorCode.ALREADY_PAID)
if any([p.status == PaymentStatus.INPUT, p.status == PaymentStatus.WAITING]):
@ -98,4 +94,27 @@ def generate_invoice(instance: Instance) -> Payment:
instance=instance,
)
invoice_link = get_invoice_link(payment=payment)
ctx = {
"username": instance.owned_by.username,
"link": invoice_link,
"payment": payment,
}
body = render_to_string(
"billing/emails/payment-notification.txt",
context=ctx,
)
email = instance.owned_by.email
sender = settings.DEFAULT_FROM_EMAIL
send_mail(
subject="[Hostea] An invoice is generated for your Hostea VM",
message=body,
from_email=f"No reply Hostea<{sender}>", # TODO read from settings.py
recipient_list=[email],
)
return payment

View File

@ -128,9 +128,9 @@ def payment_success(request, payment_public_id):
sender = settings.DEFAULT_FROM_EMAIL
send_mail(
subject="[Gna!] Payment receipt for your Gna! VM",
subject="[Hostea] Payment receipt your Hostea VM",
message=body,
from_email=f"No reply Gna!<{sender}>", # TODO read from settings.py
from_email=f"No reply Hostea<{sender}>", # TODO read from settings.py
recipient_list=[email],
)

View File

@ -127,10 +127,6 @@ header {
text-decoration: underline;
}
.nav__logo-img {
height: 48px;
}
.nav__toggle {
display: none;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 955 B

After

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="{% static 'css/main.css' %}" />
<title>{{ title }}| Gna! Dashboard</title>
<title>{{ title }}| Hostea Dashbaord</title>
{% include "common/components/meta.html" %}
</head>
<body>

View File

@ -1,41 +1,36 @@
{% load static %}
<nav class="nav__container">
<input type="checkbox" class="nav__toggle" id="nav__toggle" />
<div class="nav__header">
<a class="nav__logo-container" href="https://gna.org">
<img alt="Gna! logo" class="nav__logo-img" src="{% static 'img/logo.png' %}" />
</a>
<label class="nav__hamburger-menu" for="nav__toggle">
<span class="nav__hamburger-inner"></span>
</label>
</div>
<input type="checkbox" class="nav__toggle" id="nav__toggle" />
<div class="nav__header">
<a class="nav__logo-container" href="/">
<img
src="{% static 'img/android-icon-48x48.png' %}"
alt="Hostea temporary logo"
/>
<p class="nav__home-btn">ostea</p>
</a>
<label class="nav__hamburger-menu" for="nav__toggle">
<span class="nav__hamburger-inner"></span>
</label>
</div>
<div class="nav__spacer"></div>
<div class="nav__spacer"></div>
<div class="nav__link-group">
<div class="nav__link-container">
<a
class="nav__link"
rel="noreferrer"
href="{% url 'dash.instances.new' %}"
>Add Instance</a
>
</div>
<div class="nav__link-container">
<a
class="nav__link"
rel="noreferrer"
href="{% url 'support.home' %}"
>Support</a
>
</div>
<div class="nav__link-container">
<a
class="nav__link"
rel="noreferrer"
href="{% url 'accounts.logout' %}"
>Logout</a
>
</div>
</div>
<div class="nav__link-group">
<div class="nav__link-container">
<a class="nav__link" rel="noreferrer" href="{% url 'dash.instances.new' %}"
>Add Instance</a
>
</div>
<div class="nav__link-container">
<a class="nav__link" rel="noreferrer" href="{% url 'support.home' %}"
>Support</a
>
</div>
<div class="nav__link-container">
<a class="nav__link" rel="noreferrer" href="{% url 'accounts.logout' %}"
>Logout</a
>
</div>
</div>
</nav>

View File

@ -12,7 +12,7 @@
</ul>
<p>Created On: {{ instance.created_at }}</p>
<p><a href="{{forgejo_uri}}">Forgejo Instance</a>|<a href="{{woodpecker}}">Woodpecker CI</a></p>
<p><a href="{{gitea_uri}}">Gitea Instance</a>|<a href="{{woodpecker}}">Woodpecker CI</a></p>
<form
action="{% url 'dash.instances.delete' name=instance.name %}"

View File

@ -30,7 +30,6 @@ from payments import get_payment_model, RedirectNeeded, PaymentStatus
from accounts.tests import login_util, register_util
from .models import InstanceConfiguration, Instance
from .utils import create_instance, sanitize_vm_name, VmErrors, VmException
def create_configurations(t: TestCase):
@ -171,7 +170,7 @@ class InstancesConfig(TestCase):
"""
Expects InstancesConfig titled "s1-2", "s1-4" and "s1-8"
ref: https://forgejo.gna.org/Hostea/july-mvp/issues/10#issuecomment-639
ref: https://gitea.hostea.org/Hostea/july-mvp/issues/10#issuecomment-639
"""
self.assertEqual(
InstanceConfiguration.objects.filter(
@ -198,30 +197,6 @@ class CreateInstance(TestCase):
register_util(t=self, username="createinstance_user")
create_configurations(t=self)
def test_sanitize_vm_name(self):
self.assertEqual(sanitize_vm_name(vm_name="LOWERname"), "lowername")
with self.assertRaises(VmException):
sanitize_vm_name(vm_name="12345452131324234234234234")
with self.assertRaises(VmException):
sanitize_vm_name(vm_name="122342$#34234")
@override_settings(
HOSTEA=infra_custom_config(test_name="test_create_instance_util")
)
def test_create_instance_util(self):
configuration = self.instance_config[0].name
with self.assertRaises(VmException):
create_instance(
vm_name="12345452131324234234234234",
configuration_name=configuration,
user=self.user,
)
@override_settings(
HOSTEA=infra_custom_config(test_name="test_create_instance_renders")
)
@ -239,14 +214,14 @@ class CreateInstance(TestCase):
self.assertEqual(str.encode(test) in resp.content, True)
# create instance
instance_name = "testirenrs"
payload = {
"name": instance_name,
"name": "test_create_instance_renders",
"configuration": self.instance_config[0].name,
}
self.assertEqual(Instance.objects.filter(name=payload["name"]).exists(), False)
instance_name = "test_create_instance_renders"
create_instance_util(
t=self, c=c, instance_name=instance_name, config=self.instance_config[0]
)

View File

@ -23,8 +23,6 @@ from .models import Instance, InstanceConfiguration
@unique
class VmErrors(Enum):
NAME_EXISTS = "Instance name exists, please try again with a different name"
ILLEGAL_NAME = "Only alphanumeric characters are allowed in instance name"
NAME_TOO_LONG = "Instance name must be less than 20 characters"
NO_CONFIG = "Configuration doesn't exist, please try again."
def __str__(self) -> str:
@ -43,30 +41,13 @@ class VmException(Exception):
return self.error
def sanitize_vm_name(vm_name: str) -> str:
"""
Sanity checks and normalization of the vm name
"""
vm_name = vm_name.lower()
if len(vm_name) > 20:
raise VmException(code=VmErrors.NAME_TOO_LONG)
if not str.isalnum(vm_name):
raise VmException(code=VmErrors.ILLEGAL_NAME)
if Instance.objects.filter(name=vm_name).exists():
raise VmException(code=VmErrors.NAME_EXISTS)
return vm_name
def create_instance(vm_name: str, configuration_name: str, user: User) -> Instance:
"""
Create instance view
"""
vm_name = sanitize_vm_name(vm_name)
if Instance.objects.filter(name=vm_name).exists():
raise VmException(code=VmErrors.NAME_EXISTS)
if not InstanceConfiguration.objects.filter(name=configuration_name).exists():
raise VmException(code=VmErrors.NO_CONFIG)
@ -89,7 +70,7 @@ def footer_ctx():
"link": settings.HOSTEA["SOURCE_CODE"],
}
else:
link = "https://forgejo.gna.org/Hostea/dashboard"
link = "https://gitea.hostea.org/Hostea/dashboard"
source_code = {"text": "Source Code", "link": link}
try:
r = Repo(".")

View File

@ -89,7 +89,10 @@ def create_instance(request):
return redirect(reverse("billing.invoice.generate", args=(instance.name,)))
except VmException as e:
ctx = get_ctx()
reason = e.code.value
if e.code == VmErrors.NAME_EXISTS:
reason = ("Instance name exists, please try again with a different name",)
elif e.code == VmErrors.NO_CONFIG:
reason = "Configuration doesn't exist, please try again."
ctx["error"] = {
"title": "Can't create instance",
@ -117,12 +120,12 @@ def view_instance(request, name: str):
instance = get_object_or_404(Instance, owned_by=user, name=name)
ctx = default_ctx(title=PAGE_TITLE, username=user.username)
instance.configuration = instance.configuration_id
forgejo_uri = Infra.get_forgejo_uri(instance=instance)
gitea_uri = Infra.get_gitea_uri(instance=instance)
woodpecker = Infra.get_woodpecker_uri(instance=instance)
ctx["instance"] = instance
ctx["woodpecker"] = woodpecker
ctx["forgejo_uri"] = forgejo_uri
ctx["gitea_uri"] = gitea_uri
return render(request, "dash/instances/view/index.html", context=ctx)

View File

@ -44,8 +44,8 @@ PAYMENT_VARIANTS = {
"stripe": (
"payments.stripe.StripeProvider", # please don't change this
{
"secret_key": env.get_value("STRIPE_SECRET_KEY", default="UNSET"),
"public_key": env.get_value("STRIPE_PUBLIC_KEY", default="UNSET"),
"secret_key": env.get_value("STRIPE_SECRET_KEY"),
"public_key": env.get_value("STRIPE_PUBLIC_KEY"),
},
)
}
@ -54,19 +54,19 @@ PAYMENT_VARIANTS = {
### Dashbaord specific configuration options
HOSTEA = {
"SOURCE_CODE": "https://forgejo.gna.org/Hostea/dashboard",
"INSTANCE_MAINTAINER_CONTACT": "contact@gna.example.org",
"SOURCE_CODE": "https://gitea.hostea.org/Hostea/dashboard",
"INSTANCE_MAINTAINER_CONTACT": "contact@hostea.example.org",
"ACCOUNTS": {
"MAX_VERIFICATION_TOLERANCE_PERIOD": 60 * 60 * 24, # in seconds
"SUDO_TTL": 60 * 5,
},
"META": {
"FORGEJO_INSTANCE": env.get_value(
"HOSTEA_META_FORGEJO_INSTANCE"
), # meta Forgejo insatnce
"FORGEJO_ORG_NAME": "Hostea", # Organisation name on Hostea meta instance
"GITEA_INSTANCE": env.get_value(
"HOSTEA_META_GITEA_INSTANCE"
), # meta Gitea insatnce
"GITEA_ORG_NAME": "Hostea", # Organisation name on Hostea meta instance
# Repository dedicated for handling support
# ref: https://forgejo.gna.org/Hostea/july-mvp/issues/17
# ref: https://gitea.hostea.org/Hostea/july-mvp/issues/17
"SUPPORT_REPOSITORY": "support",
},
"INFRA": {
@ -78,14 +78,14 @@ HOSTEA = {
# SSH key that can push to the Git repository remote mentioned above
"SSH_KEY": env.get_value("HOSTEA_INFRA_HOSTEA_REPO_SSH_KEY"),
},
"HOSTEA_DOMAIN": "gna.org", # domain at which Hostea VMs will be spun up
"HOSTEA_DOMAIN": "hostea.org", # domain at which Hostea VMs will be spun up
},
}
# Please see EMAIL_* configuration options:
# https://docs.djangoproject.com/en/4.1/ref/settings/#email-host
EMAIL_CONFIG = env.email("EMAIL_URL", default="smtp://admin:password@localhost:10025")
DEFAULT_FROM_EMAIL = "no-reply@gna.org"
DEFAULT_FROM_EMAIL = "no-reply@hostea.org"
vars().update(EMAIL_CONFIG)

View File

@ -53,17 +53,17 @@ PAYMENT_VARIANTS = {
### Dashbaord specific configuration options
HOSTEA = {
"SOURCE_CODE": "https://forgejo.gna.org/Hostea/dashboard",
"INSTANCE_MAINTAINER_CONTACT": "contact@gna.example.org",
"SOURCE_CODE": "https://gitea.hostea.org/Hostea/dashboard",
"INSTANCE_MAINTAINER_CONTACT": "contact@hostea.example.org",
"ACCOUNTS": {
"MAX_VERIFICATION_TOLERANCE_PERIOD": 60 * 60 * 24, # in seconds
"SUDO_TTL": 60 * 5,
},
"META": {
"FORGEJO_INSTANCE": "https://forgejo.gna.org", # meta Forgejo insatnce
"FORGEJO_ORG_NAME": "Hostea", # Organisation name on Hostea meta instance
"GITEA_INSTANCE": "https://gitea.hostea.org", # meta Gitea insatnce
"GITEA_ORG_NAME": "Hostea", # Organisation name on Hostea meta instance
# Repository dedicated for handling support
# ref: https://forgejo.gna.org/Hostea/july-mvp/issues/17
# ref: https://gitea.hostea.org/Hostea/july-mvp/issues/17
"SUPPORT_REPOSITORY": "support",
},
"INFRA": {
@ -87,4 +87,4 @@ EMAIL_USE_SSL = False
EMAIL_PORT = 10025
EMAIL_HOST_USER = "admin"
EMAIL_HOST_PASSWORD = "password"
DEFAULT_FROM_EMAIL = "no-reply@gna.org"
DEFAULT_FROM_EMAIL = "no-reply@hostea.org"

View File

@ -171,18 +171,18 @@ PAYMENT_VARIANTS = {
### Dashbaord specific configuration options
HOSTEA = {
"SOURCE_CODE": "https://forgejo.gna.org/Hostea/dashboard",
"SOURCE_CODE": "https://gitea.hostea.org/Hostea/dashboard",
"RESTRICT_NEW_INTEGRATION_INSTALLATION": True,
"INSTANCE_MAINTAINER_CONTACT": "contact@gna.example.org",
"INSTANCE_MAINTAINER_CONTACT": "contact@hostea.example.org",
"ACCOUNTS": {
"MAX_VERIFICATION_TOLERANCE_PERIOD": 60 * 60 * 24, # in seconds
"SUDO_TTL": 60 * 5,
},
"META": {
"FORGEJO_INSTANCE": "http://localhost:3000", # meta Forgejo insatnce
"FORGEJO_ORG_NAME": "Hostea", # Organisation name on Hostea meta instance
"GITEA_INSTANCE": "http://localhost:3000", # meta Gitea insatnce
"GITEA_ORG_NAME": "Hostea", # Organisation name on Hostea meta instance
# Repository dedicated for handling support
# ref: https://forgejo.gna.org/Hostea/july-mvp/issues/17
# ref: https://gitea.hostea.org/Hostea/july-mvp/issues/17
"SUPPORT_REPOSITORY": "support",
},
"INFRA": {
@ -194,7 +194,7 @@ HOSTEA = {
# SSH key that can push to the Git repository remote mentioned above
"SSH_KEY": "/srv/hostea/deploy",
},
"HOSTEA_DOMAIN": "vm.gna.org", # domain at which Hostea VMs will be spun up
"HOSTEA_DOMAIN": "vm.hostea.org", # domain at which Hostea VMs will be spun up
},
}
@ -206,7 +206,7 @@ EMAIL_USE_SSL = False
EMAIL_PORT = 10025
EMAIL_HOST_USER = "admin"
EMAIL_HOST_PASSWORD = "password"
DEFAULT_FROM_EMAIL: "no-reply@gna.org"
DEFAULT_FROM_EMAIL: "no-reply@hostea.org"
try:
from dashboard.local_settings import *

View File

@ -1,23 +1,23 @@
version: "3"
#networks:
# hostea-dash-forgejo:
# hostea-dash-gitea:
# external: false
# hostea-dash-smtp:
# external: false
services:
forgejo:
image: codeberg.org/forgejo/forgejo:1.18.0-1
container_name: hostea-dash-forgejo
gitea:
image: gitea/gitea:1.16.5
container_name: hostea-dash-gitea
network_mode: host
environment:
- USER_UID=1000
- USER_GID=1000
restart: always
#networks:
# - hostea-dash-forgejo
# - hostea-dash-gitea
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro

View File

@ -15,7 +15,7 @@
1. Clone the project
```bash
git clone https://forgejo.gna.org/Hostea/dashboard.git && cd dashboard
git clone https://gitea.hostea.org/Hostea/dashboard.git && cd dashboard
```
2. Create `virtualenv` and activate environment

View File

@ -44,8 +44,8 @@ hence the current redundancy in configuration and cronjob duration.
## Support Platform Integration
Hostea Dashbaord delegates support to Hostea's meta Forgejo instance, as
discussed [here](https://forgejo.gna.org/Hostea/july-mvp/issues/17).
Hostea Dashbaord delegates support to Hostea's meta Gitea instance, as
discussed [here](https://gitea.hostea.org/Hostea/july-mvp/issues/17).
To configure support platform integration , please set the following
attributes in `settings.py`:
@ -55,10 +55,10 @@ HOSTEA = {
# <--snip--->
"META": {
# <--snip--->
"FORGEJO_INSTANCE": "https://forgejo.gna.org", # meta Forgejo insatnce
"FORGEJO_ORG_NAME": "Hostea", # Organisation name on Hostea meta instance
"GITEA_INSTANCE": "https://gitea.hostea.org", # meta Gitea insatnce
"GITEA_ORG_NAME": "Hostea", # Organisation name on Hostea meta instance
# Repository dedicated for handling support
# ref: https://forgejo.gna.org/Hostea/july-mvp/issues/17
# ref: https://gitea.hostea.org/Hostea/july-mvp/issues/17
"SUPPORT_REPOSITORY": "support",
},
}

View File

@ -93,9 +93,9 @@ class Command(BaseCommand):
instance = create_instance(
vm_name=vm_name, configuration_name=size, user=user
)
(forgejo_password, _commit) = create_vm_if_not_exists(instance)
(gitea_password, _commit) = create_vm_if_not_exists(instance)
print("Instance created")
print(f"Forgejo admin password: {forgejo_password}")
print(f"Gitea admin password: {gitea_password}")
except VmException as e:
if e.code == VmErrors.NAME_EXISTS:
instance = Instance.objects.get(name=vm_name)
@ -104,9 +104,9 @@ class Command(BaseCommand):
name=size
)
instance.save()
(forgejo_password, _commit) = create_vm_if_not_exists(instance)
(gitea_password, _commit) = create_vm_if_not_exists(instance)
print("Instance created")
print(f"Forgejo admin password: {forgejo_password}")
print(f"Gitea admin password: {gitea_password}")
else:
self.stderr.write(self.style.ERROR(f"error: {str(e)}"))

View File

@ -1,18 +0,0 @@
Hello {{ username }},
Congratulations on your new Gna! instance!
Your Gna! instance is being prepared, you will receive an email
notification when it is ready.
You can use the following credentials to log into an admin account on
your new Gna! Forgejo instance. Great powers come with great
responsibilities, so use the admin credentials wisely. When in doubt,
consult the Forgejo docs or contact support!
- username : root
- password: {{ forgejo_password }}
- Forgejo {{ forgejo_uri }}
Cheers,
Gna! team

View File

@ -0,0 +1,18 @@
Hello {{ username }},
Congratulations on your new Hostea instance!
Your Hostea instance is being prepared, you will receive an email
notification when it is ready.
You can use the following credentials to log into an admin account on
your new Hostea Gitea instance. Great powers come with great
responsibilities, so use the admin credentials wisely. When in doubt,
consult the Gitea docs or contact support!
- username : root
- password: {{ gitea_password }}
- Gitea {{ gitea_uri }}
Cheers,
Hostea team

View File

@ -1,11 +1,11 @@
Hello {{ username }}!,
The deployment job has run to completion and your Gna! instance is now online!
The deployment job has run to completion and your Hostea instance is now online!
Credentials to admin account was sent in an earlier email, please contact
support if didn't receive it.
Forgejo: {{ forgejo_uri }}
Gitea: {{ gitea_uri }}
Woodpecker CI: {{ woodpecker_uri }}
Cheers,
Gna! team
Hostea team

View File

@ -1,10 +0,0 @@
Hello {{ username }}!,
A customer has purchased a new instance. Please find the details below:
Forgejo: {{ forgejo_uri }}
Woodpecker CI: {{ woodpecker_uri }}
Cheers,
Gna! team

View File

@ -1,11 +1,11 @@
{% extends 'dash/common/base.html' %} {% block dash %}
<h1>{{ title }}</h1>
<h2>Forgejo Admin Credentials</h2>
<h2>Gitea Admin Credentials</h2>
<ul>
<li><b>Username:</b> root</li>
<li><b>Password:</b> {{ forgejo_password }}</li>
<li><b>Password:</b> {{ gitea_password }}</li>
</ul>
{% endblock %}

View File

@ -1,2 +1,2 @@
enough --domain $domain host create {{subdomain}}-host
enough --domain $domain service create --host {{subdomain}}-host forgejo
enough --domain $domain service create --host {{subdomain}}-host gitea

View File

@ -2,49 +2,49 @@
#
#######################################
#
# Public hostname of the Forgejo instance
# Public hostname of the Gitea instance
#
#
forgejo_host: "{{ subdomain }}.{{ '{' }}{{ '{' }} domain {{ '}' }}{{ '}' }}"
gitea_host: "{{ subdomain }}.{{ '{' }}{{ '{' }} domain {{ '}' }}{{ '}' }}"
#
#######################################
#
# Mailer from
#
#
forgejo_mailer_from: "noreply@{{ '{' }}{{ '{' }} domain {{ '}' }}{{ '}' }}"
gitea_mailer_from: "noreply@{{ '{' }}{{ '{' }} domain {{ '}' }}{{ '}' }}"
#
#######################################
#
# SSH port of the Forgejo instance
# SSH port of the Gitea instance
#
#
forgejo_ssh_port: "22"
gitea_ssh_port: "22"
#
#######################################
#
# Forgejo version
# Gitea version
#
#
#forgejo_version: "1.18.0-1"
gitea_version: "1.16.8"
#
#######################################
#
# Admin user name
#
forgejo_user: root
gitea_user: root
#
#######################################
#
# Admin user password
#
forgejo_password: "{{ forgejo_password }}"
gitea_password: "{{ gitea_password }}"
#
#######################################
#
# Admin user email
#
forgejo_email: "{{ forgejo_email }}"
gitea_email: "{{ gitea_email }}"
#
#######################################
#
@ -60,9 +60,9 @@ woodpecker_host: "{{ '{' }}{{ '{' }} woodpecker_hostname {{ '}' }}{{ '}' }}.{{ '
#
#######################################
#
# Forgejo users with admin rights on woodpecker
# Gitea users with admin rights on woodpecker
#
woodpecker_admins: "{{ '{' }}{{ '{' }} forgejo_user {{ '}' }}{{ '}' }}"
woodpecker_admins: "{{ '{' }}{{ '{' }} gitea_user {{ '}' }}{{ '}' }}"
#
#######################################
#

View File

@ -1,4 +1,4 @@
forgejo-service-group:
gitea-service-group:
hosts:
{{ subdomain }}-host:
ansible_port: 2222

View File

@ -13,20 +13,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time
from io import StringIO
from django.test import TestCase, Client, override_settings
from django.core.management import call_command
from django.core import mail
from dash.models import Instance, InstanceConfiguration
from accounts.tests import register_util, login_util
from dash.tests import create_configurations, create_instance_util, infra_custom_config
from infrastructure.management.commands.vm import translate_sizes
from billing.utils import payment_fullfilled
from .utils import Infra, Worker, create_vm_if_not_exists, delete_vm
from .utils import Infra, Worker
from .models import InstanceCreated, Job, JobType
@ -38,209 +34,140 @@ class InfraUtilTest(TestCase):
def setUp(self):
self.username = "infrautil_user"
register_util(t=self, username=self.username)
self.user.is_staff = True
self.user.save()
create_configurations(t=self)
# @override_settings(HOSTEA=infra_custom_config(test_name="test_path_util"))
# def test_path_utils(self):
# infra = Infra()
# subdomain = "foo"
# base = infra.repo_path
#
# self.assertEqual(
# base.joinpath(f"inventory/host_vars/{subdomain}-host/"),
# infra._host_vars_dir(subdomain=subdomain),
# )
#
# self.assertEqual(
# base.joinpath(f"inventory/host_vars/{subdomain}-host/forgejo.yml"),
# infra._forgejo_path(subdomain=subdomain),
# )
#
# self.assertEqual(
# base.joinpath(f"inventory/host_vars/{subdomain}-host/provision.yml"),
# infra._provision_path(subdomain=subdomain),
# )
#
# self.assertEqual(
# base.joinpath(f"inventory/{subdomain}-backup.yml"),
# infra._backup_path(subdomain=subdomain),
# )
#
# self.assertEqual(
# base.joinpath(f"hosts-scripts/{subdomain}-host.sh"),
# infra._hostscript_path(subdomain=subdomain),
# )
#
# @override_settings(HOSTEA=infra_custom_config(test_name="test_add_vm"))
# def test_add_vm(self):
# c = Client()
# login_util(self, c, "accounts.home")
# subdomain = "add_vm"
#
# create_instance_util(
# t=self, c=c, instance_name=subdomain, config=self.instance_config[0]
# )
#
# instance = Instance.objects.get(name=subdomain)
#
# infra = Infra()
# before_add = infra._sha()
# (password, after_add) = infra.add_vm(instance=instance)
# self.assertNotEqual(before_add, after_add)
#
# before_rm = after_add
# after_rm = infra.remove_vm(instance=instance)
# self.assertNotEqual(before_rm, after_rm)
#
# @override_settings(HOSTEA=infra_custom_config(test_name="test_cmd"))
# def test_cmd(self):
# subdomain = "cmd_vm"
# infra = Infra()
# c = Client()
# login_util(self, c, "accounts.home")
#
# self.assertEqual(Instance.objects.filter(name=subdomain).exists(), False)
# # username exists
# call_command(
# "vm", "create", subdomain, f"--owner={self.username}", "--flavor=medium"
# )
#
# instance = Instance.objects.get(name=subdomain)
#
# self.assertEqual(infra.get_flavor(instance=instance), "openstack_flavor_medium")
#
# self.assertEqual(instance.owned_by, self.user)
# self.assertEqual(
# instance.configuration_id, InstanceConfiguration.objects.get(name="s1-4")
# )
#
# instance_created = InstanceCreated.objects.get(instance=instance)
# self.assertEqual(instance_created.instance, instance)
#
# self.assertEqual(instance_created.created, True)
#
# # run create vm command again with same configuration to crudely check idempotency
# call_command(
# "vm", "create", subdomain, f"--owner={self.username}", "--flavor=medium"
# )
#
# # run create vm command again with different configuration but same name
# # to crudely check idempotency
# call_command(
# "vm", "create", subdomain, f"--owner={self.username}", "--flavor=large"
# )
# instance.refresh_from_db()
# # verify new size is updated in DB
# self.assertEqual(
# str.strip(instance.configuration_id.name)
# == str.strip(translate_sizes("large")),
# True,
# )
#
# # verify new size is updated in repository
# self.assertEqual(
# str.strip(infra.translate_size(instance=instance))
# == str.strip(infra.get_flavor(instance=instance)),
# True,
# )
#
# call_command("vm", "delete", subdomain)
#
# self.assertEqual(Instance.objects.filter(name=subdomain).exists(), False)
# host_vars_dir = infra._host_vars_dir(subdomain)
# self.assertEqual(host_vars_dir.exists(), False)
#
# # run delete VM command to crudely check idempotency
# call_command("vm", "delete", subdomain)
#
# def test_worker(self):
# subdomain = "forgejo" # yes, forgejo.hostea.org exists. will use it till I
# # figure out how to use requests_mock within django
# c = Client()
# login_util(self, c, "accounts.home")
# create_instance_util(
# t=self, c=c, instance_name=subdomain, config=self.instance_config[0]
# )
#
# instance = Instance.objects.get(name=subdomain)
# job = Job.objects.create(instance=instance, job_type=JobType.PING)
# forgejo_uri = Infra.get_forgejo_uri(instance=instance)
# print(f"mocking {forgejo_uri}")
#
# w = Worker(job=job)
# w.start()
# time.sleep(15)
# self.assertEqual(w.is_alive(), False)
# w.join()
# self.assertEqual(
# Job.objects.filter(instance=instance, job_type=JobType.PING).exists(), True
# )
#
@override_settings(HOSTEA=infra_custom_config(test_name="test_vm_delete_payments"))
def test_vm_delete_payments(self):
"""
Test if the dashboard generates invoices for a VM crated with a name
matching a VM name that was deleted that existed.
@override_settings(HOSTEA=infra_custom_config(test_name="test_path_util"))
def test_path_utils(self):
infra = Infra()
subdomain = "foo"
base = infra.repo_path
ref: https://forgejo.hostea.org/Hostea/dashboard/issues/38#issuecomment-1162
"""
self.assertEqual(
base.joinpath(f"inventory/host_vars/{subdomain}-host/"),
infra._host_vars_dir(subdomain=subdomain),
)
self.assertEqual(
base.joinpath(f"inventory/host_vars/{subdomain}-host/gitea.yml"),
infra._gitea_path(subdomain=subdomain),
)
self.assertEqual(
base.joinpath(f"inventory/host_vars/{subdomain}-host/provision.yml"),
infra._provision_path(subdomain=subdomain),
)
self.assertEqual(
base.joinpath(f"inventory/{subdomain}-backup.yml"),
infra._backup_path(subdomain=subdomain),
)
self.assertEqual(
base.joinpath(f"hosts-scripts/{subdomain}-host.sh"),
infra._hostscript_path(subdomain=subdomain),
)
@override_settings(HOSTEA=infra_custom_config(test_name="test_add_vm"))
def test_add_vm(self):
c = Client()
login_util(self, c, "accounts.home")
instance_name = "trmpayments"
infra = Infra()
subdomain = "add_vm"
create_instance_util(
t=self, c=c, instance_name=instance_name, config=self.instance_config[0]
t=self, c=c, instance_name=subdomain, config=self.instance_config[0]
)
instance = Instance.objects.get(name=instance_name)
self.assertEqual(payment_fullfilled(instance=instance), True)
create_vm_if_not_exists(instance=instance)
instance = Instance.objects.get(name=subdomain)
# delete VM
delete_vm(instance=instance)
self.assertEqual(Instance.objects.filter(name=instance_name).exists(), False)
infra = Infra()
before_add = infra._sha()
(password, after_add) = infra.add_vm(instance=instance)
self.assertNotEqual(before_add, after_add)
# re-create VM with management command as it bypasses payments. We
# usually use create_instance_util but it will pay for the instance too
before_rm = after_add
after_rm = infra.remove_vm(instance=instance)
self.assertNotEqual(before_rm, after_rm)
@override_settings(HOSTEA=infra_custom_config(test_name="test_cmd"))
def test_cmd(self):
subdomain = "cmd_vm"
infra = Infra()
c = Client()
login_util(self, c, "accounts.home")
self.assertEqual(Instance.objects.filter(name=subdomain).exists(), False)
# username exists
call_command(
"vm", "create", instance_name, f"--owner={self.username}", "--flavor=medium"
"vm", "create", subdomain, f"--owner={self.username}", "--flavor=medium"
)
# verify VM is created
self.assertEqual(Instance.objects.filter(name=instance_name).exists(), True)
# verify payment is unfulfilled
instance = Instance.objects.get(name=instance_name)
self.assertEqual(payment_fullfilled(instance=instance), False)
# generate invoice
stdout = StringIO()
stderr = StringIO()
instance = Instance.objects.get(name=subdomain)
self.assertEqual(infra.get_flavor(instance=instance), "openstack_flavor_medium")
self.assertEqual(instance.owned_by, self.user)
self.assertEqual(
instance.configuration_id, InstanceConfiguration.objects.get(name="s1-4")
)
instance_created = InstanceCreated.objects.get(instance=instance)
self.assertEqual(instance_created.instance, instance)
self.assertEqual(instance_created.created, True)
# run create vm command again with same configuration to crudely check idempotency
call_command(
"generate_invoice",
stdout=stdout,
stderr=stderr,
"vm", "create", subdomain, f"--owner={self.username}", "--flavor=medium"
)
out = stdout.getvalue()
print("out")
print(out)
self.assertEqual(instance_name in out, True)
self.assertEqual(f"Payment not fulfilled for instance: {instance}" in out, True)
staff_notification = None
for m in mail.outbox:
if "New instance alert" in m.subject:
staff_notification = m
break
self.assertEqual(staff_notification.to[0], self.email)
self.assertEqual(
"[Gna!] New instance alert" in staff_notification.subject, True
# run create vm command again with different configuration but same name
# to crudely check idempotency
call_command(
"vm", "create", subdomain, f"--owner={self.username}", "--flavor=large"
)
instance.refresh_from_db()
# verify new size is updated in DB
self.assertEqual(
"A customer has purchased a new instance. Please find the details below:"
in staff_notification.body,
str.strip(instance.configuration_id.name)
== str.strip(translate_sizes("large")),
True,
)
# verify new size is updated in repository
self.assertEqual(
str.strip(infra.translate_size(instance=instance))
== str.strip(infra.get_flavor(instance=instance)),
True,
)
call_command("vm", "delete", subdomain)
self.assertEqual(Instance.objects.filter(name=subdomain).exists(), False)
host_vars_dir = infra._host_vars_dir(subdomain)
self.assertEqual(host_vars_dir.exists(), False)
# run delete VM command to crudely check idempotency
call_command("vm", "delete", subdomain)
def test_worker(self):
subdomain = "gitea" # yes, gitea.hostea.org exists. will use it till I
# figure out how to use requests_mock within django
c = Client()
login_util(self, c, "accounts.home")
create_instance_util(
t=self, c=c, instance_name=subdomain, config=self.instance_config[0]
)
instance = Instance.objects.get(name=subdomain)
job = Job.objects.create(instance=instance, job_type=JobType.PING)
gitea_uri = Infra.get_gitea_uri(instance=instance)
print(f"mocking {gitea_uri}")
w = Worker(job=job)
w.start()
time.sleep(15)
self.assertEqual(w.is_alive(), False)
w.join()
self.assertEqual(
Job.objects.filter(instance=instance, job_type=JobType.PING).exists(), True
)

View File

@ -24,10 +24,8 @@ from time import sleep
from django.utils.crypto import get_random_string
from django.template.loader import render_to_string
from django.contrib.auth import get_user_model
from django.core.mail import send_mail
from django.conf import settings
from payments import get_payment_model
from dash.models import Instance
@ -43,12 +41,12 @@ class Worker(Thread):
super().__init__()
def run(self):
forgejo_uri = Infra.get_forgejo_uri(instance=self.job.instance)
gitea_uri = Infra.get_gitea_uri(instance=self.job.instance)
woodpecker = Infra.get_woodpecker_uri(instance=self.job.instance)
while True:
try:
print(f"[ping] Trying to reach {forgejo_uri}")
resp = requests.get(forgejo_uri)
print(f"[ping] Trying to reach {gitea_uri}")
resp = requests.get(gitea_uri)
if resp.status_code == 200:
break
except Exception:
@ -60,7 +58,7 @@ class Worker(Thread):
email = job.instance.owned_by.email
ctx = {
"forgejo_uri": forgejo_uri,
"gitea_uri": gitea_uri,
"woodpecker_uri": woodpecker,
"username": job.instance.owned_by.username,
}
@ -72,9 +70,9 @@ class Worker(Thread):
sender = settings.DEFAULT_FROM_EMAIL
send_mail(
subject="[Gna!] Your Gna! instance is now online!",
subject="[Hostea] Your Hostea instance is now online!",
message=body,
from_email=f"No reply Gna!<{sender}>", # TODO read from settings.py
from_email=f"No reply Hostea<{sender}>", # TODO read from settings.py
recipient_list=[email],
)
job.delete()
@ -82,62 +80,26 @@ class Worker(Thread):
def create_vm_if_not_exists(instance: Instance) -> (str, str):
"""
Create VM utility. Forgejo password is returned
Create VM utility. Gitea password is returned
"""
def notify_staff(instance: Instance):
infra = Infra()
User = get_user_model()
forgejo_uri = Infra.get_forgejo_uri(instance=instance)
woodpecker = Infra.get_woodpecker_uri(instance=instance)
for staff in User.objects.filter(is_staff=True):
ctx = {
"forgejo_uri": forgejo_uri,
"woodpecker_uri": woodpecker,
"username": staff.username,
}
body = render_to_string(
"infrastructure/emails/staff-new-instance-alert.txt",
context=ctx,
)
sender = settings.DEFAULT_FROM_EMAIL
send_mail(
subject="[Gna!] New instance alert",
message=body,
from_email=f"No reply Gna!<{sender}>", # TODO read from settings.py
recipient_list=[staff.email],
)
infra = Infra()
if not InstanceCreated.objects.filter(instance=instance).exists():
(forgejo_password, commit) = infra.add_vm(instance=instance)
(gitea_password, commit) = infra.add_vm(instance=instance)
InstanceCreated.objects.create(instance=instance, created=True)
notify_staff(instance=instance)
job = Job.objects.create(instance=instance, job_type=str(JobType.PING))
Worker(job=job).start()
return (forgejo_password, commit)
return (gitea_password, commit)
else:
if str.strip(infra.get_flavor(instance=instance)) != str.strip(
infra.translate_size(instance=instance)
):
# Worker.init_global()
notify_staff(instance=instance)
return infra.add_vm(instance=instance)
return None
def delete_vm(instance: Instance):
infra = Infra()
Payment = get_payment_model()
for payment in Payment.objects.filter(
paid_by=instance.owned_by, instance_name=instance.name
):
payment.vm_deleted = True
payment.save()
infra.remove_vm(instance=instance)
if InstanceCreated.objects.filter(instance=instance).exists():
InstanceCreated.objects.get(instance=instance).delete()
@ -177,9 +139,9 @@ class Infra:
return self._host_vars_dir(subdomain=subdomain).joinpath("provision.yml")
@classmethod
def get_forgejo_uri(cls, instance: Instance) -> str:
def get_gitea_uri(cls, instance: Instance) -> str:
"""
Get an instance's Forgejo URI
Get an instance's Gitea URI
"""
base = settings.HOSTEA["INFRA"]["HOSTEA_DOMAIN"]
return f"https://{instance.name}.{base}"
@ -194,7 +156,7 @@ class Infra:
@classmethod
def get_woodpecker_uri(cls, instance: Instance) -> str:
"""
Get an instance's Forgejo URI
Get an instance's Gitea URI
"""
base = settings.HOSTEA["INFRA"]["HOSTEA_DOMAIN"]
return f"https://{cls._gen_woodpecker_hostname(instance=instance)}.{base}"
@ -211,12 +173,12 @@ class Infra:
return config["openstack_flavor"].split("{{ ")[1].split(" }}")[0]
return None
def _forgejo_path(self, subdomain: str) -> Path:
def _gitea_path(self, subdomain: str) -> Path:
"""
utility method: get forgejo file for a subdomain
utility method: get gitea file for a subdomain
"""
return self._host_vars_dir(subdomain=subdomain).joinpath("forgejo.yml")
return self._host_vars_dir(subdomain=subdomain).joinpath("gitea.yml")
def _backup_path(self, subdomain: str) -> Path:
"""
@ -284,7 +246,7 @@ class Infra:
"""
Add new VM to infrastructure repository
The forgejo user password is returned
The gitea user password is returned
"""
subdomain = instance.name
@ -298,22 +260,22 @@ class Infra:
os.makedirs(hostscript_path)
woodpecker_agent_secret = get_random_string(64)
forgejo_password = get_random_string(20)
gitea_password = get_random_string(20)
ctx = {
"woodpecker_agent_secret": woodpecker_agent_secret,
"woodpecker_hostname": self._gen_woodpecker_hostname(instance=instance),
"woodpecker_admins": f"{instance.owned_by.username}",
"forgejo_email": instance.owned_by.email,
"forgejo_password": forgejo_password,
"gitea_email": instance.owned_by.email,
"gitea_password": gitea_password,
"subdomain": subdomain,
}
forgejo = self._forgejo_path(subdomain)
with open(forgejo, "w+", encoding="utf-8") as f:
gitea = self._gitea_path(subdomain)
with open(gitea, "w+", encoding="utf-8") as f:
f.write(
render_to_string(
"infrastructure/yml/forgejo.yml",
"infrastructure/yml/gitea.yml",
context=ctx,
)
)
@ -357,7 +319,7 @@ class Infra:
)
commit = self._push(f"add vm {subdomain}")
return (forgejo_password, commit)
return (gitea_password, commit)
def remove_vm(self, instance: Instance):
"""

View File

@ -53,25 +53,25 @@ def create_instance(request, instance_name: str):
res = create_vm_if_not_exists(instance=instance)
if res is not None:
(forgejo_password, commit) = res
(gitea_password, commit) = res
ctx = {
"username": request.user.username,
"forgejo_password": forgejo_password,
"forgejo_uri": Infra.get_forgejo_uri(instance=instance),
"gitea_password": gitea_password,
"gitea_uri": Infra.get_gitea_uri(instance=instance),
}
body = render_to_string(
"infrastructure/emails/forgejo-creds.txt",
"infrastructure/emails/gitea-creds.txt",
context=ctx,
)
sender = settings.DEFAULT_FROM_EMAIL
send_mail(
subject="[Gna!] Forgejo admin credentials",
subject="[Hostea] Gitea admin credentials",
message=body,
from_email=f"No reply Gna!<{sender}>", # TODO read from settings.py
from_email=f"No reply Hostea<{sender}>", # TODO read from settings.py
recipient_list=[request.user.email],
)

View File

@ -16,7 +16,7 @@ init() {
sleep 5
# wait_for_env
fi
forgejo_root
gitea_root
support_repo_init
fleet_repo_init
}

View File

@ -3,10 +3,10 @@ import argparse
from requests import Session
def forgejo_from_args(args, c: Session):
from .forgejo import Forgejo
def gitea_from_args(args, c: Session):
from .gitea import Gitea
return Forgejo(
return Gitea(
host=args.host,
username=args.username,
password=args.password,
@ -15,7 +15,7 @@ def forgejo_from_args(args, c: Session):
)
class Forgejo:
class Gitea:
def __init__(self, parser, c: Session):
self.c = c
self.parser = parser
@ -29,56 +29,56 @@ class Forgejo:
def __add_credentials_parser(self, parser):
group = parser.add_argument_group("credentials", "User credentials")
group.add_argument("username", type=str, help="Forgejo user's username")
group.add_argument("password", type=str, help="Forgejo user's password")
group.add_argument("email", type=str, help="Forgejo user's email")
group.add_argument("host", type=str, help="URI at which Forgejo is running")
group.add_argument("username", type=str, help="Gitea user's username")
group.add_argument("password", type=str, help="Gitea user's password")
group.add_argument("email", type=str, help="Gitea user's email")
group.add_argument("host", type=str, help="URI at which Gitea is running")
def install(self):
def run(args, c: Session):
forgejo = forgejo_from_args(args, c=c)
forgejo.install()
gitea = gitea_from_args(args, c=c)
gitea.install()
self.install_parser = self.subparser.add_parser(
name="install", description="Install Forgejo", help="Install Forgejo"
name="install", description="Install Gitea", help="Install Gitea"
)
self.__add_credentials_parser(self.install_parser)
self.install_parser.set_defaults(func=run)
def register(self):
def run(args, c: Session):
forgejo = forgejo_from_args(args, c=c)
forgejo.register()
gitea = gitea_from_args(args, c=c)
gitea.register()
self.register_parser = self.subparser.add_parser(
name="register",
description="Forgejo user registration",
help="Register a user on Forgejo",
description="Gitea user registration",
help="Register a user on Gitea",
)
self.__add_credentials_parser(self.register_parser)
self.register_parser.set_defaults(func=run)
def login(self):
def run(args, c: Session):
forgejo = forgejo_from_args(args, c=c)
forgejo.login()
gitea = gitea_from_args(args, c=c)
gitea.login()
self.login_parser = self.subparser.add_parser(
name="login", description="Forgejo user login", help="Login on Forgejo"
name="login", description="Gitea user login", help="Login on Gitea"
)
self.__add_credentials_parser(self.login_parser)
self.login_parser.set_defaults(func=run)
def create_repository(self):
def run(args, c: Session):
forgejo = forgejo_from_args(args, c=c)
forgejo.login()
forgejo.create_repository(name=args.repo_name)
gitea = gitea_from_args(args, c=c)
gitea.login()
gitea.create_repository(name=args.repo_name)
self.create_repository_parser = self.subparser.add_parser(
name="create_repo",
description="Create repository on Forgejo",
help="Create repository on Forgejo",
description="Create repository on Gitea",
help="Create repository on Gitea",
)
self.__add_credentials_parser(self.create_repository_parser)
self.create_repository_parser.set_defaults(func=run)
@ -88,10 +88,10 @@ class Forgejo:
def install_sso(self):
def run(args, c: Session):
forgejo = forgejo_from_args(args, c=c)
forgejo.login()
gitea = gitea_from_args(args, c=c)
gitea.login()
print(f"CLIENT ID: {args.client_id}")
forgejo.install_sso(
gitea.install_sso(
sso_name=args.sso_name,
client_id=args.client_id,
client_secret=args.client_secret,
@ -100,8 +100,8 @@ class Forgejo:
self.install_sso_parser = self.subparser.add_parser(
name="install_sso",
description="Install SSO on Forgejo",
help="Install SSO on Forgejo",
description="Install SSO on Gitea",
help="Install SSO on Gitea",
)
self.__add_credentials_parser(self.install_sso_parser)
self.install_sso_parser.add_argument(
@ -123,14 +123,14 @@ class Forgejo:
def add_deploy_key(self):
def run(args, c: Session):
forgejo = forgejo_from_args(args, c=c)
forgejo.login()
forgejo.add_deploy_key(repo=args.repo_name, key=args.key_file)
gitea = gitea_from_args(args, c=c)
gitea.login()
gitea.add_deploy_key(repo=args.repo_name, key=args.key_file)
self.add_deploy_key_parser = self.subparser.add_parser(
name="add_deploy_key",
description="Create repository on Forgejo",
help="Add deploy key to a repository on Forgejo",
description="Create repository on Gitea",
help="Add deploy key to a repository on Gitea",
)
self.__add_credentials_parser(self.add_deploy_key_parser)
self.add_deploy_key_parser.add_argument(
@ -203,21 +203,21 @@ class Hostea:
def support(self):
def run(args, c: Session):
from .forgejo import ForgejoSSO
from .gitea import GiteaSSO
dash = dash_from_args(args, c=c)
dash.login()
forgejo = ForgejoSSO(
gitea = GiteaSSO(
username=dash.username,
email=dash.email,
forgejo_host=args.forgejo_host,
hostea_org=args.forgejo_hostea_org,
gitea_host=args.gitea_host,
hostea_org=args.gitea_hostea_org,
support_repo=args.support_repo,
c=c,
)
dash.new_ticket(forgejo.new_issues_uri)
forgejo.new_issue()
dash.new_ticket(gitea.new_issues_uri)
gitea.new_issue()
self.support_parser = self.subparser.add_parser(
name="support",
@ -226,12 +226,12 @@ class Hostea:
)
self.__add_credentials_parser(self.support_parser)
self.support_parser.add_argument(
"forgejo_host", type=str, help="URI at which Forgejo is running"
"gitea_host", type=str, help="URI at which Gitea is running"
)
self.support_parser.add_argument(
"forgejo_hostea_org",
"gitea_hostea_org",
type=str,
help="Hostea namespace(username/org) on Forgejo, where support repository is hosted",
help="Hostea namespace(username/org) on Gitea, where support repository is hosted",
)
self.support_parser.add_argument(
"support_repo", type=str, help="support repository name"
@ -245,28 +245,28 @@ class Cli:
c = Session()
self.c = c
self.parser = argparse.ArgumentParser(
description="Install and Bootstrap Forgejo and Hostea Dashboard"
description="Install and Bootstrap Gitea and Hostea Dashboard"
)
self.subparser = self.parser.add_subparsers()
self.check_env()
self.forgejo()
self.gitea()
self.hostea()
def __add_credentials_parser(self, parser):
group = parser.add_argument_group("credentials", "User credentials")
group.add_argument("username", type=str, help="Forgejo user's username")
group.add_argument("password", type=str, help="Forgejo user's password")
group.add_argument("email", type=str, help="Forgejo user's email")
group.add_argument("username", type=str, help="Gitea user's username")
group.add_argument("password", type=str, help="Gitea user's password")
group.add_argument("email", type=str, help="Gitea user's email")
def check_env(self):
def run(args, c: Session):
from .forgejo import Forgejo
from .gitea import Gitea
from .hostea import Hostea
Hostea.check_online(
dashboard_host=args.hostea_host, maildev_host=args.maildev_host
)
Forgejo.check_online(host=args.forgejo_host)
Gitea.check_online(host=args.gitea_host)
self.check_env_parser = self.subparser.add_parser(
name="check_env",
@ -275,7 +275,7 @@ class Cli:
)
self.check_env_parser.add_argument(
"forgejo_host", type=str, help="URI at which Forgejo is running"
"gitea_host", type=str, help="URI at which Gitea is running"
)
self.check_env_parser.add_argument(
@ -295,13 +295,13 @@ class Cli:
)
Hostea(parser=self.hostea, c=self.c)
def forgejo(self):
self.forgejo = self.subparser.add_parser(
name="forgejo",
description="Forgejo",
help="Forgejo-related functionality",
def gitea(self):
self.gitea = self.subparser.add_parser(
name="gitea",
description="Gitea",
help="Gitea-related functionality",
)
Forgejo(parser=self.forgejo, c=self.c)
Gitea(parser=self.gitea, c=self.c)
def parse(self):
return self.parser.parse_args()

View File

@ -13,7 +13,7 @@ class ParseCSRF(HTMLParser):
# return cls(name="csrfmiddlewaretoken")
#
# @classmethod
# def forgejo_parser(cls) -> "ParseCSRF":
# def gitea_parser(cls) -> "ParseCSRF":
# return cls(name="_csrf")
#
def handle_starttag(self, tag: str, attrs: (str, str)):

View File

@ -10,15 +10,15 @@ import requests
from .csrf import ParseCSRF
# FORGEJO_USER = "root"
# FORGEJO_EMAIL = "root@example.com"
# FORGEJO_PASSWORD = "foobarpassword"
# GITEA_USER = "root"
# GITEA_EMAIL = "root@example.com"
# GITEA_PASSWORD = "foobarpassword"
# HOST = "http://localhost:8080"
#
# REPOS = []
class Forgejo:
class Gitea:
def __init__(self, host: str, username: str, password: str, email: str, c: Session):
self.host = host
self.username = username
@ -48,7 +48,7 @@ class Forgejo:
@staticmethod
def check_online(host: str):
"""
Check if Forgejo instance is online
Check if Gitea instance is online
"""
count = 0
parsed = urlparse(host)
@ -67,7 +67,7 @@ class Forgejo:
def install(self):
"""
Install Forgejo, first form that a user sees when a new instance is
Install Gitea, first form that a user sees when a new instance is
deployed
"""
# cwd = os.environ.get("PWD")
@ -77,12 +77,12 @@ class Forgejo:
"db_host": "localhost:3306",
"db_user": "root",
"db_passwd": "",
"db_name": "forgejo",
"db_name": "gitea",
"ssl_mode": "disable",
"db_schema": "",
"charset": "utf8",
"db_path": "/data/gitea/gitea.db",
"app_name": "Forgejo:+Beyond+Coding+We+Forge",
"app_name": "Gitea:+Git+with+a+cup+of+tea",
"repo_root_path": "/data/git/repositories",
"lfs_root_path": "/data/git/lfs",
"run_user": "git",
@ -316,26 +316,26 @@ class ParseSSOLogin(HTMLParser):
return
class ForgejoSSO:
class GiteaSSO:
def __init__(
self,
username: str,
email: str,
forgejo_host: str,
gitea_host: str,
hostea_org: str,
support_repo: str,
c: Session,
):
self.c = c
self.username = username
self.forgejo_host = forgejo_host
self.gitea_host = gitea_host
self.hostea_org = hostea_org
self.support_repo = support_repo
self.email = email
self.__csrf_key = "_csrf"
url = urlparse(self.forgejo_host)
url = urlparse(self.gitea_host)
repo = f"{self.hostea_org}/{self.support_repo}"
issues = f"{repo}/issues"
new_issues = f"{issues}/new"
@ -371,8 +371,8 @@ class ForgejoSSO:
parser = ParseSSOLogin()
parser.feed(resp.text)
url = urlparse(self.forgejo_host)
## SSO URL in Forgejo login page
url = urlparse(self.gitea_host)
## SSO URL in Gitea login page
sso = urlunparse((url.scheme, url.netloc, parser.url, "", "", ""))
# redirects are enabled to for a cleaner implementation. Commented out

View File

@ -11,12 +11,12 @@ is_ci(){
if is_ci
then
MAILDEV_URL="http://smtp:1080"
FORGEJO_URL="http://forgejo:3000"
FORGEJO_SSH_URL="ssh://git@forgejo:22"
GITEA_URL="http://gitea:3000"
GITEA_SSH_URL="ssh://git@gitea:22"
else
MAILDEV_URL="http://localhost:1080"
FORGEJO_URL="http://localhost:3000"
FORGEJO_SSH_URL="ssh://git@localhost:22"
GITEA_URL="http://localhost:3000"
GITEA_SSH_URL="ssh://git@localhost:22"
fi
readonly DASHBOARD_URL="http://localhost:8000"
@ -27,22 +27,22 @@ readonly DASHBOARD_OIDC_DISCOVERY_URL="$DASHBOARD_URL/o/.well-known/openid-confi
readonly DASHBOARD_ADMIN_USERNAME=root
readonly DASHBOARD_ADMIN_PASSWORD=supercomplicatedpassword
readonly DASHBOARD_ADMIN_EMAIL="$DASHBOARD_ADMIN_USERNAME@dash.example.org"
readonly DASHBOARD_OIDC_APP_NAME=hostea-forgejo
readonly DASHBOARD_OIDC_APP_NAME=hostea-gitea
readonly FORGEJO_ROOT_USERNAME=root
readonly FORGEJO_ROOT_EMAIL="$FORGEJO_ROOT_USERNAME@example.org"
readonly FORGEJO_ROOT_PASSOWRD=supercomplicatedpassword
readonly FORGEJO_HOSTEA_SSO_NAME=hostea-sso
readonly FORGEJO_OIDC_CALLBACK="$FORGEJO_URL/user/oauth2/$FORGEJO_HOSTEA_SSO_NAME/callback"
readonly FORGEJO_HOSTEA_FLEET_DEPLOY_KEY="$(realpath tests/fleet-deploy-key.pub)"
readonly FORGEJO_HOSTEA_FLEET_DEPLOY_KEY_PRIVATE="$(realpath tests/fleet-deploy-key)"
readonly GITEA_ROOT_USERNAME=root
readonly GITEA_ROOT_EMAIL="$GITEA_ROOT_USERNAME@example.org"
readonly GITEA_ROOT_PASSOWRD=supercomplicatedpassword
readonly GITEA_HOSTEA_SSO_NAME=hostea-sso
readonly GITEA_OIDC_CALLBACK="$GITEA_URL/user/oauth2/$GITEA_HOSTEA_SSO_NAME/callback"
readonly GITEA_HOSTEA_FLEET_DEPLOY_KEY="$(realpath tests/fleet-deploy-key.pub)"
readonly GITEA_HOSTEA_FLEET_DEPLOY_KEY_PRIVATE="$(realpath tests/fleet-deploy-key)"
readonly FORGEJO_HOSTEA_USERNAME=hostea
readonly FORGEJO_HOSTEA_PASSWORD=supercomplicatedpassword
readonly FORGEJO_HOSTEA_EMAIL="$FORGEJO_HOSTEA_USERNAME@example.org"
readonly FORGEJO_HOSTEA_SUPPORT_REPO="support"
readonly FORGEJO_HOSTEA_FLEET_REPO="fleet"
readonly FORGEJO_HOSTEA_FLEET_REPO_REMOTE="$FORGEJO_SSH_URL/$FORGEJO_HOSTEA_USERNAME/$FORGEJO_HOSTEA_FLEET_REPO.git"
readonly GITEA_HOSTEA_USERNAME=hostea
readonly GITEA_HOSTEA_PASSWORD=supercomplicatedpassword
readonly GITEA_HOSTEA_EMAIL="$GITEA_HOSTEA_USERNAME@example.org"
readonly GITEA_HOSTEA_SUPPORT_REPO="support"
readonly GITEA_HOSTEA_FLEET_REPO="fleet"
readonly GITEA_HOSTEA_FLEET_REPO_REMOTE="$GITEA_SSH_URL/$GITEA_HOSTEA_USERNAME/$GITEA_HOSTEA_FLEET_REPO.git"
readonly HOSTEA_CUSTOMER_USERNAME=batman
readonly HOSTEA_CUSTOMER_PASSWORD=supercomplicatedpassword
@ -53,7 +53,7 @@ OIDC_CLIENT_SECRET=""
wait_for_env() {
python -m integration \
check_env $FORGEJO_URL $DASHBOARD_URL $MAILDEV_URL
check_env $GITEA_URL $DASHBOARD_URL $MAILDEV_URL
}
# create OIDC app on Hostea Dashboard
@ -67,74 +67,74 @@ oidc_dashboard_init() {
resp=$(python manage.py create_oidc \
$DASHBOARD_OIDC_APP_NAME $DASHBOARD_ADMIN_USERNAME \
$FORGEJO_OIDC_CALLBACK)
$GITEA_OIDC_CALLBACK)
OIDC_CLIENT_ID=$(echo $resp | cut -d ":" -f 2 | cut -d " " -f 2)
OIDC_CLIENT_SECRET=$(echo $resp | cut -d ":" -f 3 | cut -d " " -f 2)
}
# register root user on Forgejo to simulate Hoste admin and integrate SSO
forgejo_root(){
# register root user on Gitea to simulate Hoste admin and integrate SSO
gitea_root(){
python -m integration \
forgejo install \
$FORGEJO_ROOT_USERNAME $FORGEJO_ROOT_PASSOWRD \
$FORGEJO_ROOT_EMAIL \
$FORGEJO_URL
gitea install \
$GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
$GITEA_ROOT_EMAIL \
$GITEA_URL
python -m integration \
forgejo register \
$FORGEJO_ROOT_USERNAME $FORGEJO_ROOT_PASSOWRD \
$FORGEJO_ROOT_EMAIL \
$FORGEJO_URL
gitea register \
$GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
$GITEA_ROOT_EMAIL \
$GITEA_URL
python -m integration \
forgejo login \
$FORGEJO_ROOT_USERNAME $FORGEJO_ROOT_PASSOWRD \
$FORGEJO_ROOT_EMAIL \
$FORGEJO_URL
gitea login \
$GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
$GITEA_ROOT_EMAIL \
$GITEA_URL
# python -m integration \
# forgejo install_sso \
# $FORGEJO_ROOT_USERNAME $FORGEJO_ROOT_PASSOWRD \
# $FORGEJO_ROOT_EMAIL \
# $FORGEJO_URL \
# $FORGEJO_HOSTEA_SSO_NAME \
# gitea install_sso \
# $GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
# $GITEA_ROOT_EMAIL \
# $GITEA_URL \
# $GITEA_HOSTEA_SSO_NAME \
# $OIDC_CLIENT_ID $OIDC_CLIENT_SECRET \
# $DASHBOARD_OIDC_DISCOVERY_URL
}
# register user "Hostea" on Forgejo and create support repository
# register user "Hostea" on Gitea and create support repository
support_repo_init() {
python -m integration \
forgejo register \
$FORGEJO_HOSTEA_USERNAME $FORGEJO_HOSTEA_PASSWORD \
$FORGEJO_HOSTEA_EMAIL \
$FORGEJO_URL
gitea register \
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_PASSWORD \
$GITEA_HOSTEA_EMAIL \
$GITEA_URL
python -m integration \
forgejo login \
$FORGEJO_HOSTEA_USERNAME $FORGEJO_HOSTEA_PASSWORD \
$FORGEJO_HOSTEA_EMAIL \
$FORGEJO_URL
gitea login \
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_PASSWORD \
$GITEA_HOSTEA_EMAIL \
$GITEA_URL
python -m integration \
forgejo create_repo \
$FORGEJO_HOSTEA_USERNAME $FORGEJO_HOSTEA_PASSWORD \
$FORGEJO_HOSTEA_EMAIL \
$FORGEJO_URL \
$FORGEJO_HOSTEA_SUPPORT_REPO
gitea create_repo \
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_PASSWORD \
$GITEA_HOSTEA_EMAIL \
$GITEA_URL \
$GITEA_HOSTEA_SUPPORT_REPO
}
new_fleet_repo_init() {
python -m integration \
forgejo create_repo \
$FORGEJO_HOSTEA_USERNAME $FORGEJO_HOSTEA_PASSWORD \
$FORGEJO_HOSTEA_EMAIL \
$FORGEJO_URL \
gitea create_repo \
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_PASSWORD \
$GITEA_HOSTEA_EMAIL \
$GITEA_URL \
$1
python -m integration \
forgejo add_deploy_key \
$FORGEJO_HOSTEA_USERNAME $FORGEJO_HOSTEA_PASSWORD \
$FORGEJO_HOSTEA_EMAIL \
$FORGEJO_URL \
gitea add_deploy_key \
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_PASSWORD \
$GITEA_HOSTEA_EMAIL \
$GITEA_URL \
$1 \
$FORGEJO_HOSTEA_FLEET_DEPLOY_KEY
$GITEA_HOSTEA_FLEET_DEPLOY_KEY
tmp_dir=$(mktemp -d)
pushd $tmp_dir
@ -144,13 +144,13 @@ new_fleet_repo_init() {
then
git config user.email "hostea-dashobard-test@example.org"
git config user.name "hoste-dashobard-test"
chmod 600 $FORGEJO_HOSTEA_FLEET_DEPLOY_KEY_PRIVATE
chmod 600 $GITEA_HOSTEA_FLEET_DEPLOY_KEY_PRIVATE
fi
git add README
git commit -m "init"
REMOTE="$FORGEJO_SSH_URL/$FORGEJO_HOSTEA_USERNAME/$1.git"
REMOTE="$GITEA_SSH_URL/$GITEA_HOSTEA_USERNAME/$1.git"
git remote add origin $REMOTE
GIT_SSH_COMMAND="/usr/bin/ssh -oStrictHostKeyChecking=no -i $FORGEJO_HOSTEA_FLEET_DEPLOY_KEY_PRIVATE" \
GIT_SSH_COMMAND="/usr/bin/ssh -oStrictHostKeyChecking=no -i $GITEA_HOSTEA_FLEET_DEPLOY_KEY_PRIVATE" \
git push --set-upstream origin master
popd
rm -rf $tmp_dir
@ -158,20 +158,20 @@ new_fleet_repo_init() {
}
# register user "Hostea" on Forgejo and create support repository
# register user "Hostea" on Gitea and create support repository
fleet_repo_init() {
python -m integration \
forgejo register \
$FORGEJO_HOSTEA_USERNAME $FORGEJO_HOSTEA_PASSWORD \
$FORGEJO_HOSTEA_EMAIL \
$FORGEJO_URL || true
gitea register \
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_PASSWORD \
$GITEA_HOSTEA_EMAIL \
$GITEA_URL || true
python -m integration \
forgejo login \
$FORGEJO_HOSTEA_USERNAME $FORGEJO_HOSTEA_PASSWORD \
$FORGEJO_HOSTEA_EMAIL \
$FORGEJO_URL
gitea login \
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_PASSWORD \
$GITEA_HOSTEA_EMAIL \
$GITEA_URL
new_fleet_repo_init $FORGEJO_HOSTEA_FLEET_REPO
new_fleet_repo_init $GITEA_HOSTEA_FLEET_REPO
}
@ -192,8 +192,8 @@ hostea_customer_simulation() {
$HOSTEA_CUSTOMER_USERNAME $HOSTEA_CUSTOMER_PASSWORD \
$HOSTEA_CUSTOMER_EMAIL \
$DASHBOARD_URL \
$FORGEJO_URL \
$FORGEJO_HOSTEA_USERNAME $FORGEJO_HOSTEA_SUPPORT_REPO
$GITEA_URL \
$GITEA_HOSTEA_USERNAME $GITEA_HOSTEA_SUPPORT_REPO
}
setup_env() {

View File

@ -15,7 +15,7 @@ django-oauth-toolkit==2.0.0
django-payments==1.0.0
django-phonenumber-field==6.3.0
djangorestframework==3.13.1
greenlet==1.1.3.post0
greenlet==1.1.2
idna==3.3
install==1.3.5
isort==5.10.1

View File

@ -2,7 +2,7 @@
<h2>{{ title }}</h2>
<p>
You will be redirected to Gna!'s issue tracker
You will be redirected to Hostea's issue tracker
<span id="timer">momentarily</span>. If not, please click
<a id="redirect-url" href="{{ support.list }}">here.</a>
</p>

View File

@ -2,7 +2,7 @@
<h2>{{ title }}</h2>
<p>
You will be redirected to Gna!'s issue tracker
You will be redirected to Hostea's issue tracker
<span id="timer">momentarily</span>. If not, please click
<a id="redirect-url" href="{{ support.new }}">here.</a>
</p>

View File

@ -24,8 +24,8 @@ from .utils import IssueTracker
hostea_issue_tracker_settings = settings.HOSTEA
hostea_issue_tracker_settings["META"] = {
"FORGEJO_INSTANCE": "https://forgejo.gna.org",
"FORGEJO_ORG_NAME": "Hostea",
"GITEA_INSTANCE": "https://gitea.hostea.org",
"GITEA_ORG_NAME": "Hostea",
"SUPPORT_REPOSITORY": "support",
}
@ -41,8 +41,8 @@ class IssueTrackerTests(TestCase):
Verify default credentials; all further tests are based on defaults set
"""
it = IssueTracker()
self.assertEqual(it.config["FORGEJO_INSTANCE"], "https://forgejo.gna.org")
self.assertEqual(it.config["FORGEJO_ORG_NAME"], "Hostea")
self.assertEqual(it.config["GITEA_INSTANCE"], "https://gitea.hostea.org")
self.assertEqual(it.config["GITEA_ORG_NAME"], "Hostea")
self.assertEqual(it.config["SUPPORT_REPOSITORY"], "support")
def test_uri_builders(self):
@ -51,10 +51,10 @@ class IssueTrackerTests(TestCase):
"""
it = IssueTracker()
self.assertEqual(
it.get_issue_tracker(), "https://forgejo.gna.org/Hostea/support/issues"
it.get_issue_tracker(), "https://gitea.hostea.org/Hostea/support/issues"
)
self.assertEqual(
it.open_issue(), "https://forgejo.gna.org/Hostea/support/issues/new"
it.open_issue(), "https://gitea.hostea.org/Hostea/support/issues/new"
)

View File

@ -24,9 +24,9 @@ class IssueTracker:
def __init__(self):
self.config = settings.HOSTEA["META"]
self.instance = urlparse(self.config["FORGEJO_INSTANCE"])
self.instance = urlparse(self.config["GITEA_INSTANCE"])
self.repo = (
f"{self.config['FORGEJO_ORG_NAME']}/{self.config['SUPPORT_REPOSITORY']}"
f"{self.config['GITEA_ORG_NAME']}/{self.config['SUPPORT_REPOSITORY']}"
)
self.issues = f"{self.repo}/issues"

View File

@ -1,14 +0,0 @@
{% extends 'base.html' %}
{% block title %}Forgot Your Password?{% endblock %}
{% block content %}
<h1>Forgot your password?</h1>
<p>Enter your email address below, and we'll email instructions for setting a new one.</p>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Send me instructions!">
</form>
{% endblock %}