Initial commit
This commit is contained in:
commit
3e5aaab433
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"id": "org.forgejo.cloudronapp",
|
||||
"title": "Forgejo (unofficial)",
|
||||
"author": "Codeberg e.V.",
|
||||
"description": "file://DESCRIPTION.md",
|
||||
"tagline": "A self-hosted lightweight software forge.",
|
||||
"version": "0.0.7",
|
||||
"upstreamVersion": "1.21.7-0",
|
||||
"healthCheckPath": "/explore",
|
||||
"httpPort": 3000,
|
||||
"memoryLimit": 536870912,
|
||||
"addons": {
|
||||
"mysql": { },
|
||||
"sendmail": { "supportsDisplayName": true },
|
||||
"localstorage": { },
|
||||
"oidc": { "loginRedirectUri": "/user/oauth2/cloudron/callback" }
|
||||
},
|
||||
"tcpPorts": {
|
||||
"SSH_PORT": {
|
||||
"title": "SSH Port",
|
||||
"description": "SSH Port over which repos can be pushed & pulled",
|
||||
"defaultValue": 29418
|
||||
}
|
||||
},
|
||||
"manifestVersion": 2,
|
||||
"website": "https://forgejo.org",
|
||||
"contactEmail": "contact@forgejo.org",
|
||||
"icon": "file://logo.png",
|
||||
"optionalSso": true,
|
||||
"mediaLinks": [
|
||||
"https://forgejo.org/_astro/hello-world.f0327754_ZgSUOs.webp",
|
||||
"https://forgejo.org/_astro/pull-request-button.3d7a9576_Z1RfA9x.webp",
|
||||
"https://forgejo.org/_astro/pull-request-review-global.49c41d42_1OeOAb.webp",
|
||||
"https://forgejo.org/_astro/project.06d8d4dc_2aiHpd.webp",
|
||||
"https://docs.codeberg.org/assets/images/collaborating/citable-code/releases2.webp"
|
||||
],
|
||||
"tags": [ "version control", "git", "code hosting", "development", "github", "bitbucket", "gitlab" ],
|
||||
"changelog": "file://CHANGELOG.md",
|
||||
"postInstallMessage": "file://POSTINSTALL.md",
|
||||
"minBoxVersion": "7.5.1",
|
||||
"forumUrl": "https://matrix.to/#/#forgejo:matrix.org",
|
||||
"documentationUrl": "https://forgejo.org/docs/latest/"
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
Forgejo is a self-hosted lightweight software forge. Easy to install and low maintenance, it just does the job. It is similar to Gitea, GitHub, or Gitlab.
|
||||
|
||||
**Note:** _This app is not developed by the Forgejo team and was created by me, Lucid Cocoon aka Anthony Hughes. I am simply integrating the Forgejo Docker image into a Cloudron app and making no changes to the Forgejo image itself. You may contact me for issues with installing and managing the app through Cloudron, but for issues with Forgejo itself please contact the Forgejo team._
|
||||
|
||||
### Forge great software with Forgejo
|
||||
Take back control of your software development process, self-host your projects and get everyone involved in delivering quality software on the same page.
|
||||
|
||||
#### Simple software project management
|
||||
Ease of use is important to get things done efficiently. Forgejo’s user experience is designed for collaboration and productivity.
|
||||
|
||||
#### Self-hosted alternative to GitHub
|
||||
With a rich feature set, Forgejo still has a low server profile and requires an order of magnitude less resources than other forges.
|
||||
|
||||
#### Self-hosted alternative to GitHub
|
||||
Liberate your software from proprietary shackles. Forgejo offers a familiar environment to GitHub users, allowing smooth transition to a platform you own.
|
||||
|
||||
#### Guaranteed 100% Free Software
|
||||
Forgejo will always be Free and Open Source Software. Furthermore we exclusively use Free Software for our own project development.
|
||||
|
||||
#### Easy to install and maintain
|
||||
Hosting your own software forge does not require expert skills. With Forgejo you can control your server with minimal effort.
|
||||
|
||||
#### Beyond coding, we forge ahead
|
||||
An exciting future awaits. We will innovate the Software Forge and enable collaborative software development facilitated by decentralized platforms.
|
||||
|
||||
### Bug reports
|
||||
The Forgejo Cloudron App integration is an **unofficial** app created by Lucid Cocoon (aka Anthony Hughes aka me) and not supported by the Forgejo team. Please open bugs for it at [Lucid Cocoon](https://forge.lucidcocoon.scot/forgejo-cloudron-app/issues) and I will do my best to resolve it. For issues with Forgejo itself please open bugs at [Codeberg](https://codeberg.org/forgejo/forgejo/issues).
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
FROM cloudron/base:4.2.0@sha256:46da2fffb36353ef714f97ae8e962bd2c212ca091108d768ba473078319a47f4
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y openssh-server git asciidoctor pandoc && \
|
||||
rm -rf /etc/ssh_host_* && \
|
||||
rm -r /var/cache/apt /var/lib/apt/lists
|
||||
RUN pip3 install jupyter
|
||||
|
||||
ADD supervisor/ /etc/supervisor/conf.d/
|
||||
|
||||
RUN adduser --disabled-login --gecos 'Gitea' git
|
||||
# by default, git account is created as inactive which prevents login via openssh
|
||||
# https://github.com/gitlabhq/gitlabhq/issues/5304
|
||||
RUN passwd -d git
|
||||
|
||||
RUN mkdir -p /home/git/gitea
|
||||
WORKDIR /home/git
|
||||
|
||||
# for autosign feature
|
||||
ENV GNUPGHOME="/app/data/gnupg"
|
||||
|
||||
ARG VERSION="1.21.7-0"
|
||||
|
||||
# RUN curl -L https://dl.gitea.io/gitea/${VERSION}/gitea-${VERSION}-linux-amd64 -o /home/git/gitea/gitea \
|
||||
# && chmod +x /home/git/gitea/gitea
|
||||
|
||||
RUN curl -L https://codeberg.org/forgejo/forgejo/releases/download/v${VERSION}/forgejo-${VERSION}-linux-amd64 -o /home/git/gitea/gitea \
|
||||
&& chmod +x /home/git/gitea/gitea
|
||||
|
||||
# setup config paths
|
||||
ADD app.ini.template /home/git/app.ini.template
|
||||
|
||||
# setup log paths
|
||||
RUN mkdir -p /run/gitea && chown -R git:git /run/gitea
|
||||
RUN sed -e 's,^logfile=.*$,logfile=/run/gitea/supervisord.log,' -i /etc/supervisor/supervisord.conf
|
||||
|
||||
RUN ln -s /app/data/ssh /home/git/.ssh
|
||||
RUN ln -s /app/data/gitconfig /home/git/.gitconfig
|
||||
|
||||
ADD start.sh /home/git/start.sh
|
||||
|
||||
COPY sshd_config /etc/ssh/sshd_config
|
||||
|
||||
CMD [ "/home/git/start.sh" ]
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
MIT License (MIT)
|
||||
Copyright (c) 2016 Cloudron UG
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
This app is pre-setup with an admin account. The initial credentials are:
|
||||
|
||||
**Username**: root<br/>
|
||||
**Password**: changeme<br/>
|
||||
|
||||
Please change the admin password immediately.
|
||||
|
||||
<sso>
|
||||
Use the `Local` authentication source for logging in as admin.
|
||||
</sso>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# Gitea Cloudron App
|
||||
|
||||
This repository contains the Cloudron app package source for [Gitea](http://gitea.io/).
|
||||
|
||||
## Installation
|
||||
|
||||
[](https://cloudron.io/button.html?app=io.gitea.cloudronapp)
|
||||
|
||||
or using the [Cloudron command line tooling](https://cloudron.io/references/cli.html)
|
||||
|
||||
```
|
||||
cloudron install --appstore-id io.gitea.cloudronapp
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
The app package can be built using the [Cloudron command line tooling](https://cloudron.io/references/cli.html).
|
||||
|
||||
```
|
||||
cd gitea-app
|
||||
|
||||
cloudron build
|
||||
cloudron install
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The e2e tests are located in the `test/` folder and require [nodejs](http://nodejs.org/). They are creating a fresh build, install the app on your Cloudron, perform tests, backup, restore and test if the repos are still ok. The tests expect port 29418 to be available.
|
||||
|
||||
```
|
||||
cd gitea-app/test
|
||||
|
||||
npm install
|
||||
PATH=$PATH:node_modules/.bin USERNAME=<cloudron username> PASSWORD=<cloudron password> mocha --bail test.js
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
APP_NAME = Gitea
|
||||
RUN_USER = git
|
||||
RUN_MODE = prod
|
||||
|
||||
|
||||
[database]
|
||||
; those settings are protected and can't be modified
|
||||
DB_TYPE = mysql
|
||||
HOST = ##MYSQL_HOST:##MYSQL_PORT
|
||||
NAME = ##MYSQL_DATABASE
|
||||
USER = ##MYSQL_USERNAME
|
||||
PASSWD = ##MYSQL_PASSWORD
|
||||
SSL_MODE = disable
|
||||
PATH =
|
||||
|
||||
|
||||
[server]
|
||||
; those settings are protected and can't be modified
|
||||
PROTOCOL = http
|
||||
DOMAIN = ##DOMAIN
|
||||
ROOT_URL = https://%(DOMAIN)s/
|
||||
HTTP_ADDR =
|
||||
HTTP_PORT = 3000
|
||||
DISABLE_SSH = ##DISABLE_SSH
|
||||
SSH_PORT = ##SSH_PORT
|
||||
APP_DATA_PATH = /app/data/appdata
|
||||
|
||||
; Landing page for non-logged users, can be "home" or "explore"
|
||||
LANDING_PAGE = explore
|
||||
|
||||
|
||||
[repository]
|
||||
; this setting is protected and can't be modified
|
||||
ROOT = /app/data/repository
|
||||
|
||||
SCRIPT_TYPE = bash
|
||||
|
||||
|
||||
[repository.upload]
|
||||
ENABLED = true
|
||||
|
||||
; this setting is protected and can't be modified
|
||||
TEMP_PATH = /run/gitea/tmp/uploads
|
||||
|
||||
|
||||
[release.attachment]
|
||||
ENABLED = true
|
||||
; APP_DATA_PATH/attachments
|
||||
PATH =
|
||||
|
||||
[oauth2_client]
|
||||
ENABLE_AUTO_REGISTRATION = true
|
||||
USERNAME = sub
|
||||
UPDATE_AVATAR = false
|
||||
ACCOUNT_LINKING = auto
|
||||
|
||||
[mailer]
|
||||
ENABLED = true
|
||||
|
||||
; those settings are protected and can't be modified
|
||||
SMTP_ADDR = ##MAIL_SERVER
|
||||
SMTP_PORT = ##MAIL_PORT
|
||||
USER = ##MAIL_SMTP_USERNAME
|
||||
PASSWD = ##MAIL_SMTP_PASSWORD
|
||||
FROM = ##MAIL_FROM
|
||||
PROTOCOL = smtps
|
||||
FORCE_TRUST_SERVER_CERT = true
|
||||
|
||||
|
||||
[security]
|
||||
; those settings are protected and can't be modified
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = ##SECRET_KEY
|
||||
REVERSE_PROXY_LIMIT = 1
|
||||
REVERSE_PROXY_TRUSTED_PROXIES = *
|
||||
|
||||
[service]
|
||||
DISABLE_REGISTRATION = false
|
||||
SHOW_REGISTRATION_BUTTON = false
|
||||
ENABLE_NOTIFY_MAIL = true
|
||||
|
||||
|
||||
[log]
|
||||
; those settings are protected and can't be modified
|
||||
MODE = console
|
||||
; used for xorm.log
|
||||
ROOT_PATH = /run/gitea
|
||||
|
||||
|
||||
[picture]
|
||||
; APP_DATA_PATH/avatars
|
||||
AVATAR_UPLOAD_PATH =
|
||||
GRAVATAR_SOURCE = gravatar
|
||||
DISABLE_GRAVATAR = false
|
||||
|
||||
|
||||
[attachment]
|
||||
ENABLE = true
|
||||
; APP_DATA_PATH/attachments
|
||||
PATH =
|
||||
|
||||
|
||||
[indexer]
|
||||
; this setting is protected and can't be modified
|
||||
ISSUE_INDEXER_PATH = /app/data/appdata/indexers/issues.bleve
|
||||
|
||||
[session]
|
||||
PROVIDER = file
|
||||
PROVIDER_CONFIG = /run/gitea/sessions
|
||||
COOKIE_SECURE = true
|
||||
COOKIE_NAME = cloudron_gitea
|
||||
GC_INTERVAL_TIME = 2592000
|
||||
|
||||
[markup.asciidoc]
|
||||
ENABLED = true
|
||||
FILE_EXTENSIONS = .adoc,.asciidoc
|
||||
RENDER_COMMAND = "asciidoctor -s -a showtitle --out-file=- -"
|
||||
; Input is not a standard input but a file
|
||||
IS_INPUT_FILE = false
|
||||
|
||||
[markup.restructuredtext]
|
||||
ENABLED = true
|
||||
FILE_EXTENSIONS = .rst
|
||||
RENDER_COMMAND = "timeout 30s pandoc +RTS -M512M -RTS -f rst"
|
||||
IS_INPUT_FILE = false
|
||||
|
||||
[markup.jupyter]
|
||||
ENABLED = true
|
||||
FILE_EXTENSIONS = .ipynb
|
||||
RENDER_COMMAND = "jupyter nbconvert --stdin --stdout --to html --template basic"
|
||||
IS_INPUT_FILE = false
|
||||
|
||||
[markup.sanitizer.jupyter.img]
|
||||
ALLOW_DATA_URI_IMAGES = true
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# Log in to Docker
|
||||
sudo docker login
|
||||
<username>
|
||||
<password>
|
||||
|
||||
# Build the docker image:
|
||||
sudo docker build -t <username>/<repository>:<tag> .
|
||||
|
||||
# Push to Docker
|
||||
sudo docker push <repository>:<tag>
|
||||
|
||||
# Install to Cloudron
|
||||
cloudron install --image <username>/<repository>:<tag>
|
||||
Location: <enter web address for the app>
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
# Package generated configuration file
|
||||
# See the sshd_config(5) manpage for details
|
||||
|
||||
# What ports, IPs and protocols we listen for
|
||||
Port 29418
|
||||
# Use these options to restrict which interfaces/protocols sshd will bind to
|
||||
ListenAddress 0.0.0.0
|
||||
ListenAddress ::
|
||||
Protocol 2
|
||||
# HostKeys for protocol version 2
|
||||
HostKey /app/data/sshd/ssh_host_rsa_key
|
||||
HostKey /app/data/sshd/ssh_host_dsa_key
|
||||
HostKey /app/data/sshd/ssh_host_ecdsa_key
|
||||
HostKey /app/data/sshd/ssh_host_ed25519_key
|
||||
|
||||
# Logging
|
||||
SyslogFacility AUTH
|
||||
LogLevel INFO
|
||||
|
||||
# Authentication:
|
||||
LoginGraceTime 120
|
||||
PermitRootLogin prohibit-password
|
||||
StrictModes yes
|
||||
|
||||
PubkeyAuthentication yes
|
||||
#AuthorizedKeysFile %h/.ssh/authorized_keys
|
||||
|
||||
# Don't read the user's ~/.rhosts and ~/.shosts files
|
||||
IgnoreRhosts yes
|
||||
# similar for protocol version 2
|
||||
HostbasedAuthentication no
|
||||
# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
|
||||
#IgnoreUserKnownHosts yes
|
||||
|
||||
# To enable empty passwords, change to yes (NOT RECOMMENDED)
|
||||
PermitEmptyPasswords no
|
||||
|
||||
# Change to yes to enable challenge-response passwords (beware issues with
|
||||
# some PAM modules and threads)
|
||||
ChallengeResponseAuthentication no
|
||||
|
||||
# Change to no to disable tunnelled clear text passwords
|
||||
#PasswordAuthentication yes
|
||||
|
||||
# Kerberos options
|
||||
#KerberosAuthentication no
|
||||
#KerberosGetAFSToken no
|
||||
#KerberosOrLocalPasswd yes
|
||||
#KerberosTicketCleanup yes
|
||||
|
||||
# GSSAPI options
|
||||
#GSSAPIAuthentication no
|
||||
#GSSAPICleanupCredentials yes
|
||||
|
||||
X11Forwarding yes
|
||||
X11DisplayOffset 10
|
||||
PrintMotd no
|
||||
PrintLastLog yes
|
||||
TCPKeepAlive yes
|
||||
#UseLogin no
|
||||
|
||||
#MaxStartups 10:30:60
|
||||
#Banner /etc/issue.net
|
||||
|
||||
# Allow client to pass locale environment variables
|
||||
AcceptEnv LANG LC_*
|
||||
|
||||
Subsystem sftp /usr/lib/openssh/sftp-server
|
||||
|
||||
# Set this to 'yes' to enable PAM authentication, account processing,
|
||||
# and session processing. If this is enabled, PAM authentication will
|
||||
# be allowed through the ChallengeResponseAuthentication and
|
||||
# PasswordAuthentication. Depending on your PAM configuration,
|
||||
# PAM authentication via ChallengeResponseAuthentication may bypass
|
||||
# the setting of "PermitRootLogin without-password".
|
||||
# If you just want the PAM account and session checks to run without
|
||||
# PAM authentication, then enable this but set PasswordAuthentication
|
||||
# and ChallengeResponseAuthentication to 'no'.
|
||||
UsePAM no
|
||||
UseDNS no
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
mkdir -p /run/gitea/tmp/uploads /run/sshd /run/gitea/sessions
|
||||
|
||||
setup_oidc_source() {
|
||||
set -eu
|
||||
|
||||
echo "==> Setup OIDC source"
|
||||
|
||||
now=$(date +%s)
|
||||
mysql -u"${CLOUDRON_MYSQL_USERNAME}" -p"${CLOUDRON_MYSQL_PASSWORD}" -h mysql --database="${CLOUDRON_MYSQL_DATABASE}" -e \
|
||||
"REPLACE INTO login_source (id, type, name, is_active, cfg, created_unix, updated_unix) VALUES (1,6,'cloudron', 1,'{\"Provider\":\"openidConnect\",\"ClientID\":\"${CLOUDRON_OIDC_CLIENT_ID}\",\"ClientSecret\":\"${CLOUDRON_OIDC_CLIENT_SECRET}\",\"OpenIDConnectAutoDiscoveryURL\":\"${CLOUDRON_OIDC_ISSUER}/.well-known/openid-configuration\",\"CustomURLMapping\":null,\"IconURL\":\"\",\"Scopes\":[\"openid email profile\"],\"RequiredClaimName\":\"\",\"RequiredClaimValue\":\"\",\"GroupClaimName\":\"\",\"AdminGroup\":\"\",\"GroupTeamMap\":\"\",\"GroupTeamMapRemoval\":false,\"RestrictedGroup\":\"\"}','${now}','${now}')"
|
||||
}
|
||||
|
||||
setup_root_user() {
|
||||
set -eu
|
||||
|
||||
if sudo -H -u git /home/git/gitea/gitea admin user create --username root --password changeme --email admin@cloudron.local --admin -c /run/gitea/app.ini; then
|
||||
echo "==> root user added"
|
||||
else
|
||||
echo "==> Failed to add root user"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
setup_auth() {
|
||||
set -eu
|
||||
|
||||
# Wait for gitea to finish db setup, before we do any db operations
|
||||
while ! curl --fail http://localhost:3000/explore; do
|
||||
echo "==> Waiting for gitea to come up"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "==> Gitea is up, setting up auth"
|
||||
|
||||
if [[ -n "${CLOUDRON_OIDC_ISSUER:-}" ]]; then
|
||||
setup_oidc_source
|
||||
fi
|
||||
|
||||
user_count=$(mysql -u"${CLOUDRON_MYSQL_USERNAME}" -p"${CLOUDRON_MYSQL_PASSWORD}" -h mysql --database="${CLOUDRON_MYSQL_DATABASE}" -N -B -e "SELECT count(*) FROM user")
|
||||
# be careful, not to create root user for existing LDAP based installs
|
||||
if [[ "${user_count}" == "0" ]]; then
|
||||
echo "==> Setting up root user for first run"
|
||||
setup_root_user
|
||||
fi
|
||||
}
|
||||
|
||||
# SSH_PORT can be unset to disable SSH
|
||||
disable_ssh="false"
|
||||
if [[ -z "${SSH_PORT:-}" ]]; then
|
||||
echo "SSH disabled"
|
||||
SSH_PORT=29418 # arbitrary port to keep sshd happy
|
||||
disable_ssh="true"
|
||||
fi
|
||||
|
||||
if [[ ! -f "/app/data/sshd/ssh_host_ed25519_key" ]]; then
|
||||
echo "Generating ssh host keys"
|
||||
mkdir -p /app/data/sshd
|
||||
ssh-keygen -qt rsa -N '' -f /app/data/sshd/ssh_host_rsa_key
|
||||
ssh-keygen -qt dsa -N '' -f /app/data/sshd/ssh_host_dsa_key
|
||||
ssh-keygen -qt ecdsa -N '' -f /app/data/sshd/ssh_host_ecdsa_key
|
||||
ssh-keygen -qt ed25519 -N '' -f /app/data/sshd/ssh_host_ed25519_key
|
||||
else
|
||||
echo "Reusing existing host keys"
|
||||
fi
|
||||
|
||||
chmod 0600 /app/data/sshd/*_key
|
||||
chmod 0644 /app/data/sshd/*.pub
|
||||
|
||||
sed -e "s/^Port .*/Port ${SSH_PORT}/" /etc/ssh/sshd_config > /run/gitea/sshd_config
|
||||
|
||||
if [[ ! -f /app/data/app.ini ]]; then
|
||||
echo -e "; Add customizations here - https://docs.gitea.io/en-us/config-cheat-sheet/" > /app/data/app.ini
|
||||
|
||||
echo "==> Generating new SECRET_KEY"
|
||||
crudini --set "/app/data/app.ini" security SECRET_KEY $(pwgen -1 -s)
|
||||
fi
|
||||
|
||||
# merge user config file
|
||||
cp /home/git/app.ini.template "/run/gitea/app.ini"
|
||||
crudini --merge "/run/gitea/app.ini" < "/app/data/app.ini"
|
||||
|
||||
# override important values
|
||||
crudini --set "/run/gitea/app.ini" database DB_TYPE mysql
|
||||
crudini --set "/run/gitea/app.ini" database HOST "${CLOUDRON_MYSQL_HOST}:${CLOUDRON_MYSQL_PORT}"
|
||||
crudini --set "/run/gitea/app.ini" database NAME "${CLOUDRON_MYSQL_DATABASE}"
|
||||
crudini --set "/run/gitea/app.ini" database USER "${CLOUDRON_MYSQL_USERNAME}"
|
||||
crudini --set "/run/gitea/app.ini" database PASSWD "${CLOUDRON_MYSQL_PASSWORD}"
|
||||
crudini --set "/run/gitea/app.ini" database SSL_MODE "disable"
|
||||
crudini --set "/run/gitea/app.ini" server PROTOCOL "http"
|
||||
crudini --set "/run/gitea/app.ini" server DOMAIN "${CLOUDRON_APP_DOMAIN}"
|
||||
crudini --set "/run/gitea/app.ini" server ROOT_URL "https://%(DOMAIN)s/"
|
||||
crudini --set "/run/gitea/app.ini" server HTTP_ADDR ""
|
||||
crudini --set "/run/gitea/app.ini" server HTTP_PORT "3000"
|
||||
crudini --set "/run/gitea/app.ini" server DISABLE_SSH "${disable_ssh}"
|
||||
crudini --set "/run/gitea/app.ini" server SSH_PORT "${SSH_PORT}"
|
||||
crudini --set "/run/gitea/app.ini" server APP_DATA_PATH "/app/data/appdata"
|
||||
crudini --set "/run/gitea/app.ini" repository ROOT "/app/data/repository"
|
||||
crudini --set "/run/gitea/app.ini" repository.upload TEMP_PATH "/run/gitea/tmp/uploads"
|
||||
crudini --set "/run/gitea/app.ini" mailer SMTP_ADDR "${CLOUDRON_MAIL_SMTP_SERVER}"
|
||||
crudini --set "/run/gitea/app.ini" mailer SMTP_PORT "${CLOUDRON_MAIL_SMTPS_PORT}"
|
||||
crudini --set "/run/gitea/app.ini" mailer PROTOCOL smtps
|
||||
crudini --set "/run/gitea/app.ini" mailer USER "${CLOUDRON_MAIL_SMTP_USERNAME}"
|
||||
crudini --set "/run/gitea/app.ini" mailer PASSWD "${CLOUDRON_MAIL_SMTP_PASSWORD}"
|
||||
crudini --set "/run/gitea/app.ini" mailer FROM "${CLOUDRON_MAIL_FROM_DISPLAY_NAME:-Gitea} <${CLOUDRON_MAIL_FROM}>"
|
||||
crudini --set "/run/gitea/app.ini" mailer FORCE_TRUST_SERVER_CERT "true"
|
||||
crudini --set "/run/gitea/app.ini" security INSTALL_LOCK "true"
|
||||
crudini --set "/run/gitea/app.ini" security REVERSE_PROXY_LIMIT 1
|
||||
crudini --set "/run/gitea/app.ini" security REVERSE_PROXY_TRUSTED_PROXIES "*"
|
||||
crudini --set "/run/gitea/app.ini" log MODE "console"
|
||||
crudini --set "/run/gitea/app.ini" log ROOT_PATH "/run/gitea"
|
||||
crudini --set "/run/gitea/app.ini" log LEVEL "debug"
|
||||
crudini --set "/run/gitea/app.ini" indexer ISSUE_INDEXER_PATH "/app/data/appdata/indexers/issues.bleve"
|
||||
|
||||
echo "==> Creating dirs and changing permissions"
|
||||
mkdir -p /app/data/repository /app/data/ssh /app/data/custom /app/data/gnupg
|
||||
chown -R git:git /app/data /run/gitea
|
||||
|
||||
# this expects app.ini to be available
|
||||
( setup_auth ) &
|
||||
|
||||
exec /usr/bin/supervisord --configuration /etc/supervisor/supervisord.conf --nodaemon -i Gitea
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[program:gitea]
|
||||
directory=/home/git/gitea
|
||||
command=/home/git/gitea/gitea web -c /run/gitea/app.ini -p 3000
|
||||
user=git
|
||||
autostart=true
|
||||
autorestart=true
|
||||
; https://veithen.github.io/2015/01/08/supervisord-redirecting-stdout.html
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
environment=HOME="/home/git",USER="git",GITEA_CUSTOM="/app/data/custom"
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
[program:sshd]
|
||||
directory=/
|
||||
command=/usr/sbin/sshd -f /run/gitea/sshd_config -D
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
ssh -o IdentitiesOnly=yes -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ${SCRIPT_DIR}/id_ed25519 "$@"
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACDECyFdxcmgOemNvs0wUhkgzfj9IS2OTG6bU5AXfNkXfgAAAJAoNQg/KDUI
|
||||
PwAAAAtzc2gtZWQyNTUxOQAAACDECyFdxcmgOemNvs0wUhkgzfj9IS2OTG6bU5AXfNkXfg
|
||||
AAAEC9nIZlzus9hn/b99E/cnSE2Vpycx0invItrrzgOX9qwMQLIV3FyaA56Y2+zTBSGSDN
|
||||
+P0hLY5MbptTkBd82Rd+AAAADW5lYnVsb25AbHVuYXI=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQLIV3FyaA56Y2+zTBSGSDN+P0hLY5MbptTkBd82Rd+ nebulon@lunar
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "test",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "test.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chromedriver": "^122.0.5",
|
||||
"expect.js": "^0.3.1",
|
||||
"mocha": "^10.3.0",
|
||||
"selenium-webdriver": "^4.18.1",
|
||||
"superagent": "^8.1.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/* jshint esversion: 8 */
|
||||
/* global it:false */
|
||||
/* global xit:false */
|
||||
/* global describe:false */
|
||||
/* global before:false */
|
||||
/* global after:false */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('chromedriver');
|
||||
|
||||
const execSync = require('child_process').execSync,
|
||||
expect = require('expect.js'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
superagent = require('superagent'),
|
||||
{ Builder, By, until } = require('selenium-webdriver'),
|
||||
{ Options } = require('selenium-webdriver/chrome');
|
||||
|
||||
if (!process.env.USERNAME || !process.env.PASSWORD || !process.env.EMAIL) {
|
||||
console.log('USERNAME, PASSWORD and EMAIL env vars need to be set');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
describe('Application life cycle test', function () {
|
||||
this.timeout(0);
|
||||
|
||||
const TIMEOUT = parseInt(process.env.TIMEOUT, 10) || 5000;
|
||||
const EXEC_ARGS = { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' };
|
||||
const LOCATION = 'test';
|
||||
const SSH_PORT = 29420;
|
||||
|
||||
let app, browser;
|
||||
var athenticated_by_oidc = false;
|
||||
|
||||
const repodir = '/tmp/testrepo';
|
||||
const reponame = 'testrepo';
|
||||
|
||||
const username = process.env.USERNAME;
|
||||
const password = process.env.PASSWORD;
|
||||
const email = process.env.EMAIL;
|
||||
|
||||
before(function () {
|
||||
browser = new Builder().forBrowser('chrome').setChromeOptions(new Options().windowSize({ width: 1280, height: 1024 })).build();
|
||||
});
|
||||
|
||||
after(function () {
|
||||
browser.quit();
|
||||
fs.rmSync(repodir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function getAppInfo() {
|
||||
var inspect = JSON.parse(execSync('cloudron inspect'));
|
||||
app = inspect.apps.filter(function (a) { return a.location.indexOf(LOCATION) === 0; })[0];
|
||||
expect(app).to.be.an('object');
|
||||
}
|
||||
|
||||
async function waitForElement(elem) {
|
||||
await browser.wait(until.elementLocated(elem), TIMEOUT);
|
||||
await browser.wait(until.elementIsVisible(browser.findElement(elem)), TIMEOUT);
|
||||
}
|
||||
|
||||
function sleep(millis) {
|
||||
return new Promise(resolve => setTimeout(resolve, millis));
|
||||
}
|
||||
|
||||
async function setAvatar() {
|
||||
await browser.get('https://' + app.fqdn + '/user/settings');
|
||||
|
||||
var button = await browser.findElement(By.xpath('//label[contains(text(), "Use Custom Avatar")]'));
|
||||
await browser.executeScript('arguments[0].scrollIntoView(false)', button);
|
||||
await browser.findElement(By.xpath('//label[contains(text(), "Use Custom Avatar")]')).click();
|
||||
await browser.findElement(By.xpath('//input[@type="file" and @name="avatar"]')).sendKeys(path.resolve(__dirname, '../logo.png'));
|
||||
await browser.findElement(By.xpath('//button[contains(text(), "Update Avatar")]')).click();
|
||||
await browser.wait(until.elementLocated(By.xpath('//p[contains(text(),"Your avatar has been updated.")]')), TIMEOUT);
|
||||
}
|
||||
|
||||
async function checkAvatar() {
|
||||
await browser.get(`https://${app.fqdn}/${username}`);
|
||||
|
||||
const avatarSrc = await browser.findElement(By.xpath('//div[@id="profile-avatar"]/a/img')).getAttribute('src');
|
||||
|
||||
const response = await superagent.get(avatarSrc);
|
||||
expect(response.statusCode).to.equal(200);
|
||||
}
|
||||
|
||||
async function login(username, password) {
|
||||
await browser.get('https://' + app.fqdn + '/user/login');
|
||||
|
||||
await browser.findElement(By.id('user_name')).sendKeys(username);
|
||||
await browser.findElement(By.id('password')).sendKeys(password);
|
||||
await browser.findElement(By.xpath('//form[@action="/user/login"]//button')).click();
|
||||
await browser.wait(until.elementLocated(By.xpath('//img[contains(@class, "avatar")]')), TIMEOUT);
|
||||
}
|
||||
|
||||
async function adminLogin() {
|
||||
await login('root', 'changeme');
|
||||
}
|
||||
|
||||
async function loginOIDC(username, password) {
|
||||
browser.manage().deleteAllCookies();
|
||||
await browser.get(`https://${app.fqdn}/user/login`);
|
||||
await browser.sleep(2000);
|
||||
|
||||
|
||||
await browser.findElement(By.xpath('//a[contains(@class, "openidConnect") and contains(., "Sign in with cloudron")]')).click();
|
||||
await browser.sleep(2000);
|
||||
|
||||
if (!athenticated_by_oidc) {
|
||||
await waitForElement(By.xpath('//input[@name="username"]'));
|
||||
await browser.findElement(By.xpath('//input[@name="username"]')).sendKeys(username);
|
||||
await browser.findElement(By.xpath('//input[@name="password"]')).sendKeys(password);
|
||||
await browser.sleep(2000);
|
||||
await browser.findElement(By.id('loginSubmitButton')).click();
|
||||
await browser.sleep(2000);
|
||||
|
||||
athenticated_by_oidc = true;
|
||||
}
|
||||
|
||||
await waitForElement(By.xpath('//img[contains(@class, "avatar")]'));
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
await browser.get('https://' + app.fqdn);
|
||||
|
||||
await browser.findElement(By.xpath('//img[contains(@class, "avatar")]')).click();
|
||||
await sleep(2000);
|
||||
await browser.findElement(By.xpath('//a[@data-url="/user/logout"]')).click();
|
||||
await sleep(2000);
|
||||
}
|
||||
|
||||
async function addPublicKey() {
|
||||
var publicKey = fs.readFileSync(__dirname + '/id_ed25519.pub', 'utf8');
|
||||
|
||||
await browser.get('https://' + app.fqdn + '/user/settings/keys');
|
||||
|
||||
await browser.wait(until.elementLocated(By.id('add-ssh-button')), TIMEOUT);
|
||||
await browser.findElement(By.id('add-ssh-button')).click();
|
||||
await browser.findElement(By.id('ssh-key-title')).sendKeys('testkey');
|
||||
await browser.findElement(By.id('ssh-key-content')).sendKeys(publicKey.trim()); // #3480
|
||||
var button = browser.findElement(By.xpath('//button[contains(text(), "Add Key")]'));
|
||||
await browser.executeScript('arguments[0].scrollIntoView(false)', button);
|
||||
await browser.findElement(By.xpath('//form//button[contains(text(),"Add Key")]')).click();
|
||||
|
||||
await browser.wait(until.elementLocated(By.xpath('//p[contains(text(), "has been added.")]')), TIMEOUT);
|
||||
}
|
||||
|
||||
async function addPublicKeyOld() {
|
||||
var publicKey = fs.readFileSync(__dirname + '/id_ed25519.pub', 'utf8');
|
||||
|
||||
await browser.get('https://' + app.fqdn + '/user/settings/keys');
|
||||
|
||||
await browser.wait(until.elementLocated(By.id('add-ssh-button')), TIMEOUT);
|
||||
await browser.findElement(By.id('add-ssh-button')).click();
|
||||
await browser.findElement(By.id('ssh-key-title')).sendKeys('testkey');
|
||||
await browser.findElement(By.id('ssh-key-content')).sendKeys(publicKey.trim()); // #3480
|
||||
var button = browser.findElement(By.xpath('//button[contains(text(), "Add Key")]'));
|
||||
await browser.executeScript('arguments[0].scrollIntoView(false)', button);
|
||||
await browser.findElement(By.xpath('//button[contains(text(), "Add Key") and contains(@class, "green")]')).click();
|
||||
|
||||
await browser.wait(until.elementLocated(By.xpath('//p[contains(text(), "has been added.")]')), TIMEOUT);
|
||||
}
|
||||
|
||||
async function createRepo() {
|
||||
var getRepoPage = await browser.get('https://' + app.fqdn + '/repo/create');
|
||||
|
||||
await browser.findElement(By.id('repo_name')).sendKeys(reponame);
|
||||
var button = browser.findElement(By.xpath('//button[contains(text(), "Create Repository")]'));
|
||||
await browser.executeScript('arguments[0].scrollIntoView(true)', button);
|
||||
await browser.findElement(By.id('auto-init')).click();
|
||||
await browser.findElement(By.xpath('//button[contains(text(), "Create Repository")]')).click();
|
||||
|
||||
await browser.wait(function () {
|
||||
return browser.getCurrentUrl().then(function (url) {
|
||||
return url === 'https://' + app.fqdn + '/' + username + '/' + reponame;
|
||||
});
|
||||
}, TIMEOUT);
|
||||
}
|
||||
|
||||
async function checkCloneUrl() {
|
||||
await browser.get('https://' + app.fqdn + '/' + username + '/' + reponame);
|
||||
await browser.findElement(By.id('repo-clone-ssh')).click();
|
||||
|
||||
var cloneUrl = await browser.findElement(By.id('repo-clone-url')).getAttribute('value');
|
||||
expect(cloneUrl).to.be(`ssh://git@${app.fqdn}:${SSH_PORT}/${username}/${reponame}.git`);
|
||||
}
|
||||
|
||||
function cloneRepo() {
|
||||
fs.rmSync(repodir, { recursive: true, force: true });
|
||||
var env = Object.create(process.env);
|
||||
env.GIT_SSH = __dirname + '/git_ssh_wrapper.sh';
|
||||
execSync(`git clone ssh://git@${app.fqdn}:${SSH_PORT}/${username}/${reponame}.git ${repodir}`, { env: env });
|
||||
}
|
||||
|
||||
function pushFile() {
|
||||
const env = Object.create(process.env);
|
||||
env.GIT_SSH = __dirname + '/git_ssh_wrapper.sh';
|
||||
execSync(`touch newfile && git add newfile && git commit -a -mx && git push ssh://git@${app.fqdn}:${SSH_PORT}/${username}/${reponame} main`,
|
||||
{ env: env, cwd: repodir });
|
||||
fs.rmSync(repodir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
function fileExists() {
|
||||
expect(fs.existsSync(repodir + '/newfile')).to.be(true);
|
||||
}
|
||||
|
||||
async function sendMail() {
|
||||
await browser.get(`https://${app.fqdn}/admin/config`);
|
||||
|
||||
var button = await browser.findElement(By.xpath('//button[contains(text(), "Send")]'));
|
||||
await browser.executeScript('arguments[0].scrollIntoView(true)', button);
|
||||
await browser.findElement(By.xpath('//input[@name="email"]')).sendKeys('test@cloudron.io');
|
||||
await browser.findElement(By.xpath('//button[contains(text(), "Send")]')).click();
|
||||
await browser.wait(until.elementLocated(By.xpath('//p[contains(text(), "A testing email has been sent")]')), TIMEOUT);
|
||||
}
|
||||
|
||||
xit('build app', function () { execSync('cloudron build', EXEC_ARGS); });
|
||||
it('install app', function () { execSync(`cloudron install --location ${LOCATION} -p SSH_PORT=${SSH_PORT}`, EXEC_ARGS); });
|
||||
|
||||
it('can get app information', getAppInfo);
|
||||
|
||||
it('can admin login', adminLogin);
|
||||
it('can send mail', sendMail);
|
||||
it('can logout', logout);
|
||||
|
||||
it('can login', loginOIDC.bind(null, username, password));
|
||||
it('can set avatar', setAvatar);
|
||||
it('can get avatar', checkAvatar);
|
||||
|
||||
it('can add public key', addPublicKey);
|
||||
|
||||
it('can create repo', createRepo);
|
||||
|
||||
it('displays correct clone url', checkCloneUrl);
|
||||
|
||||
it('can clone the url', cloneRepo);
|
||||
|
||||
it('can add and push a file', pushFile);
|
||||
|
||||
it('can restart app', function () { execSync('cloudron restart --app ' + app.id); });
|
||||
|
||||
xit('can login', loginOIDC.bind(null, username, password)); // no need to relogin since session persists
|
||||
it('displays correct clone url', checkCloneUrl);
|
||||
it('can clone the url', cloneRepo);
|
||||
it('file exists in repo', fileExists);
|
||||
|
||||
it('backup app', function () { execSync('cloudron backup create --app ' + app.id, EXEC_ARGS); });
|
||||
it('restore app', function () { execSync('cloudron restore --app ' + app.id, EXEC_ARGS); });
|
||||
|
||||
it('can login', loginOIDC.bind(null, username, password));
|
||||
it('can get avatar', checkAvatar);
|
||||
it('can clone the url', cloneRepo);
|
||||
it('file exists in repo', function () { expect(fs.existsSync(repodir + '/newfile')).to.be(true); });
|
||||
|
||||
it('move to different location', async function () {
|
||||
//browser.manage().deleteAllCookies(); // commented because of error "'Network.deleteCookie' wasn't found"
|
||||
// ensure we don't hit NXDOMAIN in the mean time
|
||||
await browser.get('about:blank');
|
||||
|
||||
execSync('cloudron configure --location ' + LOCATION + '2 --app ' + app.id, EXEC_ARGS);
|
||||
});
|
||||
it('can get app information', getAppInfo);
|
||||
|
||||
it('can login', loginOIDC.bind(null, username, password));
|
||||
it('can get avatar', checkAvatar);
|
||||
it('displays correct clone url', checkCloneUrl);
|
||||
it('can clone the url', cloneRepo);
|
||||
it('file exists in repo', function () { expect(fs.existsSync(repodir + '/newfile')).to.be(true); });
|
||||
|
||||
it('uninstall app', async function () {
|
||||
// ensure we don't hit NXDOMAIN in the mean time
|
||||
await browser.get('about:blank');
|
||||
execSync('cloudron uninstall --app ' + app.id, EXEC_ARGS);
|
||||
});
|
||||
|
||||
// No SSO
|
||||
it('install app (no sso)', function () { execSync(`cloudron install --no-sso --location ${LOCATION} -p SSH_PORT=${SSH_PORT}`, EXEC_ARGS); });
|
||||
|
||||
it('can get app information', getAppInfo);
|
||||
it('can admin login (no sso)', adminLogin);
|
||||
it('can logout', logout);
|
||||
|
||||
it('uninstall app (no sso)', async function () {
|
||||
await browser.get('about:blank');
|
||||
execSync('cloudron uninstall --app ' + app.id, EXEC_ARGS);
|
||||
});
|
||||
|
||||
// test update
|
||||
it('can install app', function () { execSync(`cloudron install --appstore-id ${app.manifest.id} --location ${LOCATION} -p SSH_PORT=${SSH_PORT}`, EXEC_ARGS); });
|
||||
|
||||
it('can get app information', getAppInfo);
|
||||
it('can login', loginOIDC.bind(null, username, password));
|
||||
it('can set avatar', setAvatar);
|
||||
it('can get avatar', checkAvatar);
|
||||
it('can add public key', addPublicKey);
|
||||
it('can create repo', createRepo);
|
||||
it('can clone the url', cloneRepo);
|
||||
it('can add and push a file', pushFile);
|
||||
|
||||
it('can update', function () { execSync('cloudron update --app ' + app.id, EXEC_ARGS); });
|
||||
it('can get app information', getAppInfo);
|
||||
|
||||
it('can admin login', adminLogin);
|
||||
it('can send mail', sendMail);
|
||||
it('can logout', logout);
|
||||
|
||||
it('can login', loginOIDC.bind(null, username, password));
|
||||
it('can get avatar', checkAvatar);
|
||||
it('can clone the url', cloneRepo);
|
||||
it('file exists in cloned repo', fileExists);
|
||||
|
||||
it('uninstall app', async function () {
|
||||
// ensure we don't hit NXDOMAIN in the mean time
|
||||
await browser.get('about:blank');
|
||||
execSync('cloudron uninstall --app ' + app.id, EXEC_ARGS);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue