diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..485dee6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 03d9549..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index c756077..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index d843f34..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index 1ac928b..0000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- {}
-
\ No newline at end of file
diff --git a/.idea/zcli-playground.iml b/.idea/zcli-playground.iml
deleted file mode 100644
index 24ce78f..0000000
--- a/.idea/zcli-playground.iml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/gitea/README.md b/gitea/README.md
new file mode 100644
index 0000000..ef65bd3
--- /dev/null
+++ b/gitea/README.md
@@ -0,0 +1,57 @@
+# Gitea on Zerops
+
+Self-hosted Gitea backed by HA Postgres and SeaweedFS shared storage.
+
+## Prepare secrets
+
+Gitea validates its security secrets at startup and tries to rewrite `app.ini`
+if they're missing or malformed. Zerops' `<@generateRandomString>` preprocessor
+produces alphanumeric values that don't match Gitea's required formats
+(base64url-32 bytes for `JWT_SECRET` / `LFS_JWT_SECRET`, a signed JWT for
+`INTERNAL_TOKEN`), so generate them with the Gitea binary itself:
+
+```sh
+./generate-secrets.sh
+```
+
+It downloads the matching Gitea release and prints four `KEY=VALUE` lines.
+Paste each value into the gitea service's **secret env vars** in the Zerops GUI
+(or pre-fill `zerops-service-import.yaml` before importing).
+
+`DB_PASSWORD` is generated by the Zerops preprocessor (`<@generateRandomString>`)
+during import — cross-service expansion like `${db_password}` is not evaluated
+inside the import.yaml, so the gitea service generates its own value and the
+Postgres service must be initialized with the same one.
+
+## Bootstrap
+
+1. Paste `zerops-service-import.yaml` into the Zerops GUI:
+ project → **Import service** → fill in the four secrets from step above.
+2. Push the gitea config and build recipe:
+ ```sh
+ zcli push gitea
+ ```
+3. Create the admin user from the running container (service console):
+ ```sh
+ sudo -u git -E /usr/local/bin/gitea admin user create \
+ --config /etc/gitea/app.ini \
+ --admin \
+ --username admin \
+ --email you@example.com \
+ --password 'choose-a-strong-one' \
+ --must-change-password=false
+ ```
+4. Log in at the service's HTTPS URL and add your SSH public key under
+ **Settings → SSH/GPG Keys**.
+
+## SSH access
+
+Gitea's built-in SSH listens on **port 2222** in the container. Zerops custom
+domains only carry HTTPS, so SSH needs one of:
+
+- **Public IPv4/IPv6 on the gitea service** — Zerops GUI → service →
+ *Public Routing* → add IP, open port 2222. Map `:2222 → :2222`.
+
+After SSH works, set `SSH_DOMAIN` / `ROOT_URL` overrides if you use a custom
+hostname, otherwise Gitea will print clone URLs using the autogenerated
+Zerops subdomain.
diff --git a/gitea/generate-secrets.sh b/gitea/generate-secrets.sh
index 7af1c7b..efc5fc6 100755
--- a/gitea/generate-secrets.sh
+++ b/gitea/generate-secrets.sh
@@ -11,6 +11,6 @@ wget -qO "$BIN" "https://dl.gitea.com/gitea/${GITEA_VERSION}/gitea-${GITEA_VERSI
chmod +x "$BIN"
echo "JWT_SECRET=$("$BIN" generate secret JWT_SECRET)"
+echo "LFS_JWT_SECRET=$("$BIN" generate secret LFS_JWT_SECRET)"
echo "SECRET_KEY=$("$BIN" generate secret SECRET_KEY)"
echo "INTERNAL_TOKEN=$("$BIN" generate secret INTERNAL_TOKEN)"
-echo "LFS_JWT_SECRET=$("$BIN" generate secret LFS_JWT_SECRET)"
diff --git a/gitea/zerops-service-import.yaml b/gitea/zerops-service-import.yaml
new file mode 100644
index 0000000..b7c7fda
--- /dev/null
+++ b/gitea/zerops-service-import.yaml
@@ -0,0 +1,22 @@
+#zeropsPreprocessor=on
+services:
+ - hostname: db
+ type: postgresql:ha@18
+ priority: 10
+ verticalAutoscaling:
+ minFreeRamGB: 0.25
+ - hostname: volume
+ type: seaweedfs:ha@3
+ priority: 10
+ - hostname: gitea
+ type: ubuntu@24.04
+ envSecrets:
+ DB_PASSWORD: <@generateRandomString(<32>)>
+ JWT_SECRET:
+ LFS_JWT_SECRET:
+ SECRET_KEY:
+ INTERNAL_TOKEN:
+ minContainers: 1
+ maxContainers: 1
+ mount:
+ - volume