|
|
||
|---|---|---|
| defaults | ||
| handlers | ||
| meta | ||
| tasks | ||
| templates | ||
| tests | ||
| vars | ||
| .ansible-lint | ||
| .gitignore | ||
| LICENSE | ||
| README.md | ||
README.md
Forgejo
A role that installs forgejo, the
self-hosted lightweight software forge. The role downloads the forgejo
binary, creates the service user and the directory layout, renders
app.ini, installs the systemd unit from a template so that
optional dependencies (memcached, redis, a local database) can be
declared as Wants=/After= directives, and
manages the OAuth2/OIDC and LDAP authentication sources.
Requirements
None. git and git-lfs are installed by the
role.
Role Variables
The most important variables are listed below:
forgejo_version: 10.0.0
forgejo_arch: linux-amd64
forgejo_download_url: 'https://codeberg.org/forgejo/forgejo/releases/download/{{ forgejo_tag }}/forgejo-{{ forgejo_version }}-{{ forgejo_arch }}'
# The binary is installed versioned and forgejo_bin_path is a symlink to it, so
# that bumping forgejo_version really installs the new binary and a rollback is
# a symlink flip. The checksum is verified against the published .sha256
forgejo_versioned_bin_path: '/usr/local/lib/forgejo/forgejo-{{ forgejo_version }}-{{ forgejo_arch }}'
forgejo_bin_path: /usr/local/bin/forgejo
forgejo_force_binary_download: false
forgejo_keep_previous_binaries: true
# The service user. forgejo upstream uses the 'git' user
git_create_service_user: true
git_user: git
git_group: '{{ git_user }}'
git_home: /home/git
# FORGEJO_WORK_DIR, the bare repositories, and where app.ini lives
forgejo_work_dir: /var/lib/forgejo
forgejo_repository_data: '{{ forgejo_work_dir }}/repositories'
forgejo_conf_dir: /etc/forgejo
forgejo_enabled: true
forgejo_service_name: forgejoapp.ini
app.ini is rendered from flat
variables, one per configuration key. The variable name is the
section name with every . and - replaced by
_, an underscore, and the key:
| app.ini | variable |
|---|---|
[server] HTTP_PORT |
server_HTTP_PORT |
[cron.archive_cleanup] ENABLED |
cron_archive_cleanup_ENABLED |
[repo-archive] PATH |
repo_archive_PATH |
| a key before the first section | _default_APP_NAME |
A key is written only when its variable is defined and not
empty, so app.ini contains exactly what was
configured. The set of keys forgejo understands is taken from the
app.example.ini of the pinned version: the file is fetched
to the controller (forgejo_appconf_example_path)
and parsed in memory. Nothing is written inside the role directory,
which ansible-galaxy install --force-with-deps would wipe
on every run.
forgejo_appconf_example_url: 'https://codeberg.org/forgejo/forgejo/raw/tag/{{ forgejo_tag }}/custom/conf/app.example.ini'
forgejo_appconf_example_path: '{{ lookup("env", "HOME") }}/.ansible/tmp/forgejo-app.example-{{ forgejo_version }}.ini'
# Set it to false to reuse a file already at that path (offline controller)
forgejo_appconf_example_download: trueapp.example.ini does not document everything: the named
storage sections ([storage.<name>], the documented
way to point some subsystems at object storage) and
[avatar]/[repo-avatar] are read by the code
but absent from the example file.
forgejo_ini_extra_sections writes them verbatim, after
everything else, and is not checked against the key
list:
forgejo_ini_extra_sections:
storage.s3:
STORAGE_TYPE: minio
MINIO_ENDPOINT: 's3.example.org:8080'
MINIO_BUCKET: forgejo
MINIO_USE_SSL: 'true'
MINIO_BUCKET_LOOKUP: path
avatar:
STORAGE_TYPE: s3Authentication sources
OIDC and LDAP sources cannot be declared in
app.ini: forgejo keeps them in the
login_source table of its own database. The role drives the
forgejo admin auth CLI instead, matching each source
by name against
forgejo admin auth list:
- not there yet: created with
add-oauth/add-ldap; - already there: updated with
update-oauth --id/update-ldap --id, but only whenforgejo_auth_sources_updateis true.
Updating is opt-in because the CLI cannot read a source back to
compare, so an unconditional update would rewrite the row and report
changed on every run. Push a change explicitly:
--tags forgejo_auth -e forgejo_auth_sources_update=trueforgejo_manage_auth_sources: true
forgejo_oauth_sources:
- name: 'keycloak' # also the callback path: /user/oauth2/keycloak/callback
provider: openidConnect
key: 'forgejo'
secret: '{{ vault_forgejo_oidc_client_secret }}'
auto_discover_url: 'https://accounts.example.org/realms/R/.well-known/openid-configuration'
scopes: 'openid email profile'
required_claim_name: groups # login refused unless the claim
required_claim_value: users # contains this value
group_claim_name: groups
admin_group: admins
forgejo_ldap_sources:
- name: 'directory'
security_protocol: ldaps
host: ldap.example.org
port: 636
bind_dn: 'uid=svc,...'
bind_password: '{{ vault_forgejo_ldap_bind_pwd }}'
user_search_base: 'cn=users,...'
user_filter: '(&(memberof=cn=users,...)(uid=%s))'
admin_filter: '(memberof=cn=admins,...)'
synchronize_users: trueRenaming a source invalidates the redirect URI registered with the identity provider, because the name is part of the callback path.
An LDAP source used only to synchronize users must
still be active: the sync_external_users cron
walks the active sources only. To keep the password sign-in form from
appearing, set service_ENABLE_INTERNAL_SIGNIN: 'false'
rather than deactivating the source. Note that this also removes the
only way back in if the OIDC provider is unavailable: recovery means
editing app.ini and restarting.
The CLI takes the client secret and the bind password on the command
line, so they are visible in the process list of the target while the
command runs. The tasks are no_log by default
(forgejo_auth_no_log).
Systemd unit tuning:
# Raise it if you have repositories with a lot of files and you get HTTP 500
# errors because of that
forgejo_limit_nofile: ''
# Set it when forgejo listens on a unix socket: systemd creates /run/<dir>
forgejo_runtime_dir: ''
# Set it if git and/or git-lfs are not installed inside the default PATH
forgejo_path_env: ''
# Set it to true to let forgejo bind ports below 1024
forgejo_bind_privileged_ports: falseSystemd sandbox. It is a drop-in,
/etc/systemd/system/forgejo.service.d/10-hardening.conf, so
the unit stays readable and the sandbox can be taken out of the way by
removing one file:
forgejo_systemd_hardening: false
# On top of forgejo_work_dir and forgejo_repository_data, which are always
# listed. Each path must EXIST or the service dies with 226/NAMESPACE
forgejo_systemd_read_write_paths: []
forgejo_systemd_inaccessible_paths: []
forgejo_systemd_protect_home: "{{ 'true' if builtin ssh server else 'false' }}"
forgejo_systemd_private_users: true
# Extra [Service] lines, verbatim
forgejo_systemd_hardening_adjunct: []Three things about it were established by testing the release binary rather than by reading documentation, and they are the reason the file is not a copy of the usual hardening boilerplate:
@resourcesis denied with:EPERM, not plainly. The go runtime raisesRLIMIT_NOFILEto the hard limit at startup, and a plain deny answers thatsetrlimitwithSIGSYS: forgejo core-dumps before printing a line. It survives a plain deny only whileLimitNOFILE=happens to set soft equal to hard.ProtectHome=is tied to the builtin SSH server. With it on, forgejo never writes<git_home>/.ssh/authorized_keys.ReadWritePaths=does not punch a hole throughProtectHome=, so the home directory is either hidden or it is not.AF_NETLINKis not inRestrictAddressFamilies=. It is not needed while/etc/nsswitch.confsayshosts: files dns, which keeps name resolution inside the go resolver; a host joined to sssd or nss-resolve sends go through glibc’sgetaddrinfoand needs it.
PrivateTmp=true gives the service a private
/var/tmp as well, so a ReadWritePaths= under
it does not exist inside the namespace.
Service dependencies. Every service enabled below is added to the
systemd unit as a Wants=/After= pair:
forgejo_local_postgresql: false
forgejo_local_mysql: false
forgejo_local_mariadb: false
forgejo_local_memcache: false
forgejo_local_redis: false
# Any other unit, by name
forgejo_service_dependencies_adjunct: []The unit names can be overridden when they differ from the
distribution defaults: forgejo_redis_service_name,
forgejo_memcached_service_name,
forgejo_postgresql_service_name,
forgejo_mysql_service_name,
forgejo_mariadb_service_name.
Tags
forgejo: everythingforgejo_binary: the binary download and the symlink onlyforgejo_service: the systemd unit and the service state onlyforgejo_prepare: fetch theapp.example.iniof the pinned version and build the map of the keys forgejo understandsforgejo_conf: renderapp.ini(needsforgejo_prepare, which is why both tags run the prepare tasks)forgejo_auth: the OAuth2/OIDC and LDAP authentication sources only
Dependencies
The memcached and redis roles, installed
only when forgejo_local_memcache and/or
forgejo_local_redis are true.
Example Playbook
- hosts: forgejo-servers
roles:
- role: forgejo
forgejo_version: 10.0.0
forgejo_local_postgresql: true
forgejo_local_redis: true
forgejo_local_memcache: trueLicense
EUPL-1.2
Author Information
Andrea Dell’Amico, andrea.dellamico@isti.cnr.it Franca Debole, franca.debole@isti.cnr.it