Initial version of the forgejo collection

* Forgejo role for quick deployment in podman
* Forgejo_OAuth module to manage authentication source

Signed-off-by: Nis Wechselberg <enbewe@enbewe.de>
This commit is contained in:
Nis Wechselberg 2024-06-23 15:23:51 +02:00
parent 70646c7ba8
commit 8a8c643e14
Signed by: eNBeWe
GPG key ID: 7B25171F921B9E57
14 changed files with 917 additions and 0 deletions

5
CHANGELOG.md Normal file
View file

@ -0,0 +1,5 @@
# Changelog
## 1.0.0
* Initial Release

22
README.md Normal file
View file

@ -0,0 +1,22 @@
# Ansible Collection - enbewe.forgejo
Collecion to configure forgejo git server.
## Playbooks
### enbewe.forgejo.deployment
Deploys the role `enbewe.forgejo.forgejo` to all hosts in the group `forgejo`.
## Roles
### enbewe.forgejo.forgejo
Deploys [Forgejo](https://forgejo.org/) through a podman container.
Additionally deploys an accompanying postgres database.
When configured the Forgejo instance is automatically connected with some OAuth2 provider,
to implement single-sign-on.
## Modules
### enbewe.forgejo.forgejo_oauth
Manages OAuth sources through the CLI of the forgejo server.

68
galaxy.yml Normal file
View file

@ -0,0 +1,68 @@
---
### REQUIRED
# The namespace of the collection. This can be a company/brand/organization or product namespace under which all
# content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with
# underscores or numbers and cannot contain consecutive underscores
namespace: 'enbewe'
# The name of the collection. Has the same character restrictions as 'namespace'
name: 'forgejo'
# The version of the collection. Must be compatible with semantic versioning
version: '1.0.0'
# The path to the Markdown (.md) readme file. This path is relative to the root of the collection
readme: 'README.md'
# A list of the collection's content authors. Can be just the name or in the format 'Full Name <email> (url)
# @nicks:irc/im.site#channel'
authors:
- 'Nis Wechselberg <enbewe@enbewe.de>'
### OPTIONAL but strongly recommended
# A short summary description of the collection
description: 'Collection to deploy a forgejo server'
# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only
# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file'
license:
- 'MIT'
# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character
# requirements as 'namespace' and 'name'
tags:
- 'linux'
# Collections that this collection requires to be installed for it to be usable. The key of the dict is the
# collection label 'namespace.name'. The value is a version range
# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version
# range specifiers can be set and are separated by ','
dependencies:
containers.podman: '1.13.0'
# The URL of the originating SCM repository
repository: 'https://git.enbewe.de/Coding/ansible-collection-forgejo'
# The URL to any online docs
# documentation: http://docs.example.com
# The URL to the homepage of the collection/project
# homepage: http://example.com
# The URL to the collection issue tracker
# issues: http://example.com/issue/tracker
# A list of file glob-like patterns used to filter any files or directories that should not be included in the build
# artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This
# uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry',
# and '.git' are always filtered. Mutually exclusive with 'manifest'
# build_ignore: []
# A dict controlling use of manifest directives used in building the collection artifact. The key 'directives' is a
# list of MANIFEST.in style
# L(directives,https://packaging.python.org/en/latest/guides/using-manifest-in/#manifest-in-commands). The key
# 'omit_default_directives' is a boolean that controls whether the default directives are used. Mutually exclusive
# with 'build_ignore'
# manifest: null

52
meta/runtime.yml Normal file
View file

@ -0,0 +1,52 @@
---
# Collections must specify a minimum required ansible version to upload
# to galaxy
requires_ansible: '>=2.17.0'
# Content that Ansible needs to load from another location or that has
# been deprecated/removed
# plugin_routing:
# action:
# redirected_plugin_name:
# redirect: ns.col.new_location
# deprecated_plugin_name:
# deprecation:
# removal_version: "4.0.0"
# warning_text: |
# See the porting guide on how to update your playbook to
# use ns.col.another_plugin instead.
# removed_plugin_name:
# tombstone:
# removal_version: "2.0.0"
# warning_text: |
# See the porting guide on how to update your playbook to
# use ns.col.another_plugin instead.
# become:
# cache:
# callback:
# cliconf:
# connection:
# doc_fragments:
# filter:
# httpapi:
# inventory:
# lookup:
# module_utils:
# modules:
# netconf:
# shell:
# strategy:
# terminal:
# test:
# vars:
# Python import statements that Ansible needs to load from another location
# import_redirection:
# ansible_collections.ns.col.plugins.module_utils.old_location:
# redirect: ansible_collections.ns.col.plugins.module_utils.new_location
# Groups of actions/modules that take a common set of options
# action_groups:
# group_name:
# - module1
# - module2

5
playbooks/deployment.yml Normal file
View file

@ -0,0 +1,5 @@
---
- name: 'Deploy collection roles to matching host groups'
hosts: 'forgejo'
roles:
- 'enbewe.forgejo.forgejo'

31
plugins/README.md Normal file
View file

@ -0,0 +1,31 @@
# Collections Plugins Directory
This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that
is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that
would contain module utils and modules respectively.
Here is an example directory of the majority of plugins currently supported by Ansible:
```
└── plugins
├── action
├── become
├── cache
├── callback
├── cliconf
├── connection
├── filter
├── httpapi
├── inventory
├── lookup
├── module_utils
├── modules
├── netconf
├── shell
├── strategy
├── terminal
├── test
└── vars
```
A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible-core/2.17/plugins/plugins.html).

View file

@ -0,0 +1,299 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: Nis Wechselberg <enbewe@enbewe.de>
# MIT License (see https://spdx.org/licenses/MIT.html)
from __future__ import absolute_import, print_function
DOCUMENTATION = r'''
---
module: forgejo_auth
short_description: Module to manage forgejo oauth authentication sources
version_added: "1.0.0"
description: This module uses the forgejo api to manage the configured oauth
authentication sources. It can add, remove or update an authentication source.
This module only works for forgejo running inside containers, created through
this collections role. There are several assumptions regardings names and ids.
author:
- Nis Wechselberg (@eNBeWe)
'''
EXAMPLES = r'''
# Create a new auth provider, but don't touch existing source
- name: 'Add authentication source'
enbewe.forgejo.forgejo_oauth:
state: 'present'
update: true
name: 'eNBeWe.eu SSO'
provider: 'openidConnect'
key: 'clientIdConfiguredInAuthProvider'
secret: 'secretKeyOfTheClientIdInAuthProvider'
auto_discover_url: 'https://<autodiscover-url>'
skip_local_2fa: true
'''
RETURN = r'''
# Return values
'''
from ansible.module_utils.basic import AnsibleModule
class ForgejoOAuth:
'''
Handles the management of forgejo authentication sources.
'''
module = None
def __init__(self, module: AnsibleModule):
'''
Create a new OAuth Handler
'''
self.module = module
self.name = module.params.get('name')
def run(self):
'''
Apply the configuration.
'''
oauth_source_already_exists, oauth_source_id = self.get_oauth_source_with_name()
if self.module.params.get('state') == 'absent':
if oauth_source_already_exists:
return self.remove_auth_source(oauth_source_id)
return {
'failed': False,
'changed': False,
'msg': f'OAuth2 authorization source {self.name} is not configured.'
}
if self.module.params.get('state') == 'present':
if oauth_source_already_exists and self.module.params.get('update'):
return self.update_oauth_source(oauth_source_id)
if oauth_source_already_exists:
return {
'failed': False,
'changed': False,
'msg': f'OAuth2 authorization source {self.name} already exists.'
}
return self.add_oauth_source()
return {
'failed': True,
'changed': False,
'msg': 'Misconfigured plugin, unknown state.'
}
def get_oauth_source_with_name(self):
'''
Check the list of configured authentication sources for
an existing OAuth source of the given name.
'''
args_list = [
'podman', 'exec',
'-u', '1000:1000',
'-it', 'forgejo-app',
'forgejo', 'admin', 'auth', 'list',
'--vertical-bars'
]
_, auth_list_stdout, _ = self.module.run_command(args_list)
auth_table_rows = auth_list_stdout.split("\n")
if len(auth_table_rows) > 2:
for auth_id in range(1, len(auth_table_rows) - 1):
auth_row = auth_table_rows[auth_id].split('|')
auth_id = int(auth_row[0].strip())
auth_name = auth_row[1].strip()
auth_type = auth_row[2].strip()
if auth_type == 'OAuth2' and auth_name == self.name:
return (True, auth_id)
return (False, -1)
def add_oauth_source(self):
'''
Add a new OAuth2 source with the given configuration.
'''
args_list = [
'podman', 'exec',
'-u', '1000:1000',
'-it', 'forgejo-app',
'forgejo', 'admin', 'auth', 'add-oauth',
]
args_list += self.create_command_arguments()
auth_add_rc, auth_add_stdout, _ = self.module.run_command(args_list)
if auth_add_rc == 0:
return {
'failed': False,
'changed': True,
'msg': f'OAuth2 authorization source {self.name} successful created.'
}
return {
'failed': True,
'msg': auth_add_stdout
}
def remove_auth_source(self, source_id):
'''
Removes the authentication source with the given id from the config.
'''
args_list = [
'podman', 'exec',
'-u', '1000:1000',
'-it', 'forgejo-app',
'forgejo', 'admin', 'auth', 'delete',
'--id', str(source_id),
]
auth_delete_rc, auth_delete_stdout, _ = self.module.run_command(args_list)
if auth_delete_rc == 0:
return {
'failed': False,
'changed': True,
'msg': f'OAuth2 authorization source {self.name} removed.'
}
return {
'failed': True,
'msg': auth_delete_stdout
}
def update_oauth_source(self, source_id):
'''
Updates the authentication source with the given id to match the passed config.
'''
args_list = [
'podman', 'exec',
'-u', '1000:1000',
'-it', 'forgejo-app',
'forgejo', 'admin', 'auth', 'update-oauth',
'--id', str(source_id),
]
args_list += self.create_command_arguments()
auth_update_rc, auth_update_stdout, _ = self.module.run_command(args_list)
if auth_update_rc == 0:
return {
'failed': False,
'changed': True,
'msg': f'OAuth2 authorization source {self.name} has been updated'
}
return {
'failed': True,
'msg': auth_update_stdout
}
def create_command_arguments(self):
'''
Create the command arguments for cli commands from the passed config.
'''
result = [
'--name', self.name,
'--provider', self.module.params.get('provider'),
'--key', self.module.params.get('key'),
'--secret', self.module.params.get('secret'),
'--auto-discover-url',
(self.module.params.get('auto_discover_url')
if self.module.params.get('auto_discover_url') else ""),
'--custom-tenant-id',
(self.module.params.get('custom_tenant_id')
if self.module.params.get('custom_tenant_id') else ""),
'--custom-auth-url',
(self.module.params.get('custom_auth_url')
if self.module.params.get('custom_auth_url') else ""),
'--custom-token-url',
(self.module.params.get('custom_token_url')
if self.module.params.get('custom_token_url') else ""),
'--custom-profile-url',
(self.module.params.get('custom_profile_url')
if self.module.params.get('custom_profile_url') else ""),
'--custom-email-url',
(self.module.params.get('custom_email_url')
if self.module.params.get('custom_email_url') else ""),
'--icon-url',
(self.module.params.get('icon_url')
if self.module.params.get('icon_url') else ""),
'--required-claim-name',
(self.module.params.get('required_claim_name')
if self.module.params.get('required_claim_name') else ""),
'--required-claim-value',
(self.module.params.get('required_claim_value')
if self.module.params.get('required_claim_value') else ""),
'--group-claim-name',
(self.module.params.get('group_claim_name')
if self.module.params.get('group_claim_name') else ""),
'--admin-group',
(self.module.params.get('admin_group')
if self.module.params.get('admin_group') else ""),
'--restricted-group',
(self.module.params.get('restricted_group')
if self.module.params.get('restricted_group') else ""),
'--group-team-map',
(self.module.params.get('group_team_map')
if self.module.params.get('group_team_map') else ""),
]
for scope in self.module.params.get('scopes').split():
result.append('--scopes')
result.append(scope)
if self.module.params.get('use_custom_urls'):
result.append('--use-custom-urls')
if self.module.params.get('skip_local_2fa'):
result.append('--skip-local-2fa')
if self.module.params.get('group_team_map_removal'):
result.append('--group-team-map-removal')
return result
def main():
'''
Run the module code.
'''
module_args = {
'state': {'type': str, 'default': 'present', 'choices': ['present', 'absent']},
'update': {'type': bool, 'default': False},
'name': {'type': str, 'required': True},
'provider': {'type': str, 'required': True},
'key': {'type': str, 'required': True},
'secret': {'type': str, 'required': True},
'auto_discover_url': {'type': str, 'required': False},
'use_custom_urls': {'type': bool, 'default': False},
'custom_tenant_id': {'type': str, 'required': False},
'custom_auth_url': {'type': str, 'required': False},
'custom_token_url': {'type': str, 'required': False},
'custom_profile_url': {'type': str, 'required': False},
'custom_email_url': {'type': str, 'required': False},
'icon_url': {'type': str, 'required': False},
'skip_local_2fa': {'type': bool, 'default': False},
'scopes': {'type': str, 'required': False},
'required_claim_name': {'type': str, 'required': False},
'required_claim_value': {'type': str, 'required': False},
'group_claim_name': {'type': str, 'required': False},
'admin_group': {'type': str, 'required': False},
'restricted_group': {'type': str, 'required': False},
'group_team_map': {'type': str, 'required': False},
'group_team_map_removal': {'type': bool, 'default': False},
}
module = AnsibleModule(
argument_spec = module_args,
supports_check_mode = False,
)
forgejo_auth_module = ForgejoOAuth(module)
result = forgejo_auth_module.run()
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,21 @@
---
forgejo_db_image_name: 'docker.io/library/postgres'
forgejo_db_image_tag: '16'
forgejo_db_volume_name: 'forgejo_db_storage'
forgejo_network_name: 'forgejo'
forgejo_app_image_name: 'codeberg.org/forgejo/forgejo'
forgejo_app_image_tag: '7'
forgejo_app_volume_name: 'forgejo_app_storage'
forgejo_db_user: 'forgejo'
forgejo_db_password: 'forgejo'
forgejo_db_database: 'forgejo'
forgejo_ssh_port: 222

View file

@ -0,0 +1,69 @@
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn)}}
{{template "base/alert" .}}
{{end}}
<h4 class="ui top attached header center">
{{if .LinkAccountMode}}
{{ctx.Locale.Tr "auth.oauth_signin_title"}}
{{else}}
{{ctx.Locale.Tr "auth.login_userpass"}}
{{end}}
</h4>
<div class="ui attached segment">
<form class="ui form tw-max-w-2xl tw-m-auto" action="{{.SignInLink}}" method="post">
{{.CsrfTokenHtml}}
{{if (not .OAuth2Providers)}}
<div class="required field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
<label for="user_name">{{ctx.Locale.Tr "home.uname_holder"}}</label>
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required>
</div>
{{if or (not .DisablePassword) .LinkAccountMode}}
<div class="required field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
<label for="password">{{ctx.Locale.Tr "password"}}</label>
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="current-password" required>
</div>
{{end}}
{{if not .LinkAccountMode}}
<div class="inline field">
<div class="ui checkbox">
<label>{{ctx.Locale.Tr "auth.remember_me"}}</label>
<input name="remember" type="checkbox">
</div>
</div>
{{end}}
{{template "user/auth/captcha" .}}
<div class="field">
<button class="ui primary button">
{{if .LinkAccountMode}}
{{ctx.Locale.Tr "auth.oauth_signin_submit"}}
{{else}}
{{ctx.Locale.Tr "sign_in"}}
{{end}}
</button>
<a href="{{AppSubUrl}}/user/forgot_password">{{ctx.Locale.Tr "auth.forgot_password"}}</a>
</div>
{{if .ShowRegistrationButton}}
<div class="field">
<a href="{{AppSubUrl}}/user/sign_up">{{ctx.Locale.Tr "auth.sign_up_now"}}</a>
</div>
{{end}}
{{end}}
{{if .OAuth2Providers}}
<div id="oauth2-login-navigator" class="tw-py-1">
<div class="tw-flex tw-flex-col tw-justify-center">
<div id="oauth2-login-navigator-inner" class="tw-flex tw-flex-col tw-flex-wrap tw-items-center tw-gap-2">
{{range $provider := .OAuth2Providers}}
<a class="{{$provider.Name}} ui button tw-flex tw-items-center tw-justify-center tw-py-2 tw-w-full oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}">
{{$provider.IconHTML 28}}
{{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}}
</a>
{{end}}
</div>
</div>
</div>
{{end}}
</form>
</div>

View file

@ -0,0 +1,7 @@
---
- name: 'Restart forgejo container'
become: true
ansible.builtin.service:
name: 'container-forgejo-app'
state: 'restarted'
daemon_reload: true

View file

@ -0,0 +1,120 @@
---
# Basic stuff
- name: 'Install required software'
become: true
ansible.builtin.apt:
name: 'podman'
state: 'present'
- name: 'Create podman network for forgejo deployment'
become: true
containers.podman.podman_network:
name: '{{ forgejo_network_name }}'
state: 'present'
- name: 'Create the volume for database storage'
become: true
containers.podman.podman_volume:
name: '{{ item }}'
state: 'present'
loop:
- '{{ forgejo_db_volume_name }}'
- '{{ forgejo_app_volume_name }}'
# Database
- name: 'Create config for database for forgejo'
become: true
ansible.builtin.template:
src: 'systemd/container-forgejo-db.service.j2'
dest: '/etc/systemd/system/container-forgejo-db.service'
owner: 'root'
group: 'root'
mode: 'u=rw,g=r,o=r'
- name: 'Start and enable forgejo-db service'
become: true
ansible.builtin.systemd:
name: 'container-forgejo-db.service'
state: 'started'
enabled: true
daemon_reload: true
# Application
- name: 'Prepare config location'
become: true
ansible.builtin.file:
path: '{{ item }}'
state: 'directory'
owner: 'root'
group: 'root'
mode: 'u=rwx,g=rx,o=rx'
loop:
- '/srv/git/conf/'
- '/srv/git/custom/templates/user/auth'
- name: 'Deploy application config'
become: true
ansible.builtin.template:
src: 'forgejo/forgejo.ini'
dest: '/srv/git/conf/forgejo.ini'
owner: 'root'
group: 'root'
mode: 'u=rw,g=r,o=r'
notify: 'Restart forgejo container'
- name: 'Write customized login page'
become: true
ansible.builtin.copy:
src: 'forgejo/templates/user/auth/signin_inner.tmpl'
dest: '/srv/git/custom/templates/user/auth/signin_inner.tmpl'
owner: 'root'
group: 'root'
mode: 'u=rw,g=r,o=r'
notify: 'Restart forgejo container'
- name: 'Create service for forgejo'
become: true
ansible.builtin.template:
src: 'systemd/container-forgejo-app.service.j2'
dest: '/etc/systemd/system/container-forgejo-app.service'
owner: 'root'
group: 'root'
mode: 'u=rw,g=r,o=r'
notify: 'Restart forgejo container'
- name: 'Start and enable forgejo-app service'
become: true
ansible.builtin.systemd:
name: 'container-forgejo-app.service'
state: 'started'
enabled: true
daemon_reload: true
# Authentication source
- name: 'Configure forgejo authentication source'
become: true
when: forgejo_sso_create_source
enbewe.forgejo.forgejo_oauth:
state: 'present'
update: '{{ forgejo_sso_update | default(false) }}'
name: '{{ forgejo_sso_name }}'
provider: '{{ forgejo_sso_provider }}'
key: '{{ forgejo_sso_key }}'
secret: '{{ forgejo_sso_secret }}'
auto_discover_url: '{{ forgejo_sso_auto_discover_url | default("") }}'
use_custom_urls: '{{ forgejo_sso_use_custom_urls | default(false) }}'
custom_tenant_id: '{{ forgejo_sso_custom_tenant_id | default("") }}'
custom_auth_url: '{{ forgejo_sso_custom_auth_url | default("") }}'
custom_token_url: '{{ forgejo_sso_custom_token_url | default("") }}'
custom_profile_url: '{{ forgejo_sso_custom_profile_url | default("") }}'
custom_email_url: '{{ forgejo_sso_custom_email_url | default("") }}'
icon_url: '{{ forgejo_sso_icon_url | default("") }}'
skip_local_2fa: '{{ forgejo_sso_skip_local_2fa | default(true) }}'
scopes: '{{ forgejo_sso_scopes | default("") }}'
required_claim_name: '{{ forgejo_sso_required_claim_name | default("") }}'
required_claim_value: '{{ forgejo_sso_required_claim_value | default("") }}'
group_claim_name: '{{ forgejo_sso_group_claim_name | default("") }}'
admin_group: '{{ forgejo_sso_admin_group | default("") }}'
restricted_group: '{{ forgejo_sso_restricted_group | default("") }}'
group_team_map: '{{ forgejo_sso_group_team_map | default("") }}'
group_team_map_removal: '{{ forgejo_sso_group_team_map_removal | default(false) }}'

View file

@ -0,0 +1,124 @@
APP_NAME = {{ forgejo_general_app_name }}
RUN_MODE = {{ forgejo_general_run_mode | default('prod') }}
RUN_USER = git
WORK_PATH = /data/gitea
[repository]
ROOT = /data/git/repositories
DEFAULT_PRIVATE = {{ forgejo_repostiory_default_private | default('last') }}
DISABLE_HTTP_GIT = {{ forgejo_repostiory_disable_http_git | default(false) }}
ENABLE_PUSH_CREATE_USER = {{ forgejo_repostiory_enable_push_create_user | default(false) }}
ENABLE_PUSH_CREATE_ORG = {{ forgejo_repostiory_enable_push_create_org | default(false) }}
[repository.pull-request]
DEFAULT_MERGE_STYLE = merge
[repository.upload]
TEMP_PATH = /data/gitea/uploads
[repository.signing]
DEFAULT_TRUST_MODEL = committer
[repository.local]
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo
[server]
APP_DATA_PATH = /data/gitea
DOMAIN = {{ forgejo_server_domain }}
SSH_DOMAIN = {{ forgejo_server_ssh_domain | default(forgejo_server_domain) }}
HTTP_PORT = 3000
ROOT_URL = {{ forgejo_server_root_url }}
DISABLE_SSH = false
SSH_PORT = {{ forgejo_ssh_port }}
SSH_LISTEN_PORT = 22
LFS_START_SERVER = true
LFS_JWT_SECRET = {{ forgejo_server_lfs_jwt_secret }}
OFFLINE_MODE = {{ forgejo_server_offline_mode | default(true) }}
LANDING_PAGE = {{ forgejo_server_landing_page | default('home') }}
[database]
DB_TYPE = postgres
HOST = forgejo-db:5432
NAME = {{ forgejo_db_database }}
USER = {{ forgejo_db_user }}
PASSWD = {{ forgejo_db_password }}
SCHEMA =
SSL_MODE = disable
LOG_SQL = false
[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
[admin]
DEFAULT_EMAIL_NOTIFICATIONS = {{ forgejo_admin_default_email_notification | default('enabled') }}
DISABLE_REGULAR_ORG_CREATION = {{ forgejo_admin_disable_regular_org_creation | default(false) }}
SEND_NOTIFICATION_EMAIL_ON_NEW_USER = {{ forgejo_admin_send_notification_email_on_new_user | default(false) }}
[security]
INSTALL_LOCK = true
SECRET_KEY = {{ forgejo_security_secret_key }}
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
INTERNAL_TOKEN = {{ forgejo_security_internal_token }}
PASSWORD_HASH_ALGO = pbkdf2_hi
[openid]
ENABLE_OPENID_SIGNIN = {{ forgejo_openid_enable_openid_signin | default(true) }}
ENABLE_OPENID_SIGNUP = {{ forgejo_openid_enable_openid_signup | default(false) }}
[oauth2_client]
ENABLE_AUTO_REGISTRATION = {{ forgejo_oauth2_enable_auto_registration | default(false) }}
[service]
REGISTER_EMAIL_CONFIRM = {{ forgejo_service_register_email_confirm | default(false) }}
REGISTER_MANUAL_CONFIRM = {{ forgejo_service_register_manual_confirm | default(false) }}
DISABLE_REGISTRATION = {{ forgejo_service_disable_registration | default(false) }}
REQUIRE_SIGNIN_VIEW = {{ forgejo_service_require_signin_view | default(true) }}
ENABLE_NOTIFY_MAIL = {{ forgejo_service_enable_notify_mail | default(false) }}
ENABLE_CAPTCHA = false
DEFAULT_KEEP_EMAIL_PRIVATE = {{ forgejo_service_default_keep_email_private | default(true) }}
DEFAULT_ALLOW_CREATE_ORGANIZATION = {{ forgejo_service_default_allow_create_organization | default(false) }}
DEFAULT_ENABLE_TIMETRACKING = false
ALLOW_ONLY_INTERNAL_REGISTRATION = {{ forgejo_service_allow_only_internal_registration | default(false) }}
ALLOW_ONLY_EXTERNAL_REGISTRATION = {{ forgejo_service_allow_only_external_registration | default(false) }}
[service.explore]
REQUIRE_SIGNIN_VIEW = {{ forgejo_service_explore_require_signin_view | default(false) }}
DISABLE_USERS_PAGE = {{ forgejo_service_explore_disable_users_page | default(true) }}
[mailer]
ENABLED = false
[session]
PROVIDER = file
PROVIDER_CONFIG = /data/gitea/sessions
[picture]
AVATAR_UPLOAD_PATH = /data/gitea/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars
[attachment]
PATH = /data/gitea/attachments
[log]
MODE = console
LEVEL = {{ forgejo_log_level | default('info') }}
ROOT_PATH = /data/gitea/log
[cron.update_checker]
ENABLED = true
[oauth2]
ENABLED = {{ forgejo_oauth2_provider_enabled | default(false) }}
JWT_SECRET = {{ forgejo_oauth2_jwt_secret }}
[federation]
ENABLED = {{ forgejo_federation_enabled | default(false) }}
SHARE_USER_STATISTICS = {{ forgejo_federation_share_user_statistics | default(true) }}
MAX_SIZE = {{ forgejo_federation_max_size | default(4) }}
[lfs]
PATH = /data/git/lfs
[actions]
ENABLED = {{ forgejo_actions_enabled | default(false) }}

View file

@ -0,0 +1,52 @@
[Unit]
Description=Podman container-forgejo-app.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=70
Type=notify
NotifyAccess=all
ExecStartPre=/bin/rm -f %t/%n.ctr-id
ExecStart=/usr/bin/podman run \
--cidfile=%t/%n.ctr-id \
--cgroups=no-conmon \
--rm \
--sdnotify=conmon \
--replace \
--publish 127.0.0.1:8082:3000/tcp \
--publish {{ forgejo_ssh_port }}:22/tcp \
--detach \
--tty \
--network={{ forgejo_network_name }} \
--volume {{ forgejo_app_volume_name }}:/data/:Z \
--volume /etc/timezone:/etc/timezone:ro \
--volume /etc/localtime:/etc/localtime:ro \
--volume /srv/git/conf/forgejo.ini:/data/gitea/conf/app.ini:ro \
--volume /srv/git/custom/templates:/data/gitea/templates:ro \
--env USER_UID=1000 \
--env USER_GID=1000 \
--env FORGEJO__database__DB_TYPE=postgres \
--env FORGEJO__database__HOST=forgejo-db:5432 \
--env FORGEJO__database__NAME={{ forgejo_db_database }} \
--env FORGEJO__database__USER={{ forgejo_db_user }} \
--env FORGEJO__database__PASSWD={{ forgejo_db_password }} \
--name=forgejo-app \
{{ forgejo_app_image_name }}:{{ forgejo_app_image_tag }}
ExecStop=/usr/bin/podman stop \
--ignore \
--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
--force --ignore \
--cidfile=%t/%n.ctr-id
[Install]
WantedBy=default.target

View file

@ -0,0 +1,42 @@
[Unit]
Description=Podman container-forgejo-db.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=70
Type=notify
NotifyAccess=all
ExecStartPre=/bin/rm -f %t/%n.ctr-id
ExecStart=/usr/bin/podman run \
--cidfile=%t/%n.ctr-id \
--cgroups=no-conmon \
--rm \
--sdnotify=conmon \
--replace \
--detach \
--tty \
--network={{ forgejo_network_name }} \
--volume {{ forgejo_db_volume_name }}:/var/lib/postgresql/data/:Z \
--env POSTGRES_USER={{ forgejo_db_user }} \
--env POSTGRES_PASSWORD={{ forgejo_db_password }} \
--env POSTGRES_DB={{ forgejo_db_database }} \
--name=forgejo-db \
{{ forgejo_db_image_name }}:{{ forgejo_db_image_tag }}
ExecStop=/usr/bin/podman stop \
--ignore \
--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
--force --ignore \
--cidfile=%t/%n.ctr-id
[Install]
WantedBy=default.target