feature/gitea-actions #2

Merged
mariusz.pacek merged 2 commits from feature/gitea-actions into master 2025-12-27 13:44:23 +00:00
3 changed files with 297 additions and 0 deletions

No files matched your search

+141
View File
@@ -0,0 +1,141 @@
# Gitea Actions: quick notes 🔧
## What this adds
- `.gitea/workflows/ci.yml` — runs backend Maven tests and builds the frontend on every `push`.
## Requirements (quick checklist)
1. **Enable Actions** for this repository: Repository Settings → Actions → Enable Repository Actions.
2. **Runner (act_runner)**: you must register at least one runner so jobs can execute.
- Prefer running the runner on a separate machine from the Gitea server.
- You can use the `gitea/act_runner` docker image or the binaries from Gitea downloads.
### Example runner registration (local machine)
1. On your host, download or pull the runner image: `docker pull gitea/act_runner:latest`.
2. Start a container for registration only (refer to docs for token and instance):
- Get a token from Gitea UI: **Settings** → **Actions** → **Runners** → **Generate token** (repo/org/instance level).
- Run the register command (example using container; adjust instance and token):
```bash
docker run --rm -it -v $PWD:/data gitea/act_runner register --instance https://your.gitea.host --token <TOKEN>
```
3. Start the runner daemon (on the machine where you registered):
```bash
./act_runner daemon
# or use the docker image as a long-lived container after registration
```
## Secrets & env
- If tests or builds need credentials, add them to **Settings → Secrets** at repo/org/instance level and reference via `${{ secrets.MY_SECRET }}`.
## Notes & tips
- The workflow uses `actions/setup-java` and `actions/setup-node` which fetch from public actions. If your runner has restricted network access, adjust steps to install local toolchains instead.
- If you want to run integration tests or deploy artifacts, tell me which target host or container you'll use and I can add deploy steps.
---
## Deploying the runner to all workers (recommended for docker-executor)
If you want the runner to run on every worker node, use the provided `gitea-act-runner-stack.yml` which runs the service in `mode: global` with a placement constraint for workers:
```yaml
deploy:
mode: global
placement:
constraints:
- node.role == worker
```
Notes:
- This will start one runner instance on each worker node. If you need fewer runners, switch to `mode: replicated` and use `replicas` with a node label constraint (e.g., `node.labels.runner == true`).
- Mounting `/var/run/docker.sock` gives the runner the ability to run job containers (docker-executor) but is a security-sensitive choice — only run this on trusted worker nodes.
### Quick deploy
```bash
# from a manager node
docker stack deploy -c gitea-act-runner-stack.yml act-runner
```
### Optional: label nodes instead of using node.role
If you prefer to run on a subset of workers, add a label to desired nodes and update the `placement.constraints`:
```bash
docker node update --label-add runner=true <node-id-or-hostname>
```
Then use:
```yaml
placement:
constraints:
- node.labels.runner == true
```
---
## Registration examples & troubleshooting 🔍
If you see errors like `instance address is empty`, it means the runner registration command wasn't given an instance URL (via `--instance` or `GITEA_INSTANCE_URL`). Use one of these approaches depending on your environment.
1) One-shot CLI (internal Swarm network)
```bash
docker run --rm -it --network gitea -v act_runner-data:/data gitea/act_runner register \
--instance http://gitea_server:3000 --token <TOKEN> --no-interactive --name swarm-runner
```
Use the internal service name (`gitea_server:3000`) when registering from inside the Swarm `gitea` network.
2) One-shot (public/Traefik URL)
```bash
docker run --rm -it -v act_runner-data:/data gitea/act_runner register \
--instance https://git.mariuszpacek.pl --token <TOKEN> --no-interactive --name swarm-runner
```
If you use an HTTPS URL, ensure the container trusts the cert (or use the internal HTTP address if available).
3) Auto-register & run with env vars (recommended for simple Docker usage)
This will register (if needed) and start the runner in one step:
```bash
docker run -d --name gitea-runner \
-v act_runner-data:/data -v /var/run/docker.sock:/var/run/docker.sock \
-e GITEA_INSTANCE_URL=https://git.mariuszpacek.pl \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<TOKEN> \
-e GITEA_RUNNER_NAME=swarm-runner \
gitea/act_runner:latest
```
4) Portainer notes
- For a temporary registration container, set image `gitea/act_runner:latest`, **Command** to:
`register --instance http://gitea_server:3000 --token <TOKEN> --no-interactive`
- Or set environment variables `GITEA_INSTANCE_URL` and `GITEA_RUNNER_REGISTRATION_TOKEN` and start the container (detached) to auto-register.
5) Debugging checks
- Test reachability from the registrator container:
```bash
docker run --rm --network gitea curlimages/curl curl -sS http://gitea_server:3000/api/v1/version
```
- Verify `.runner` written to the volume:
```bash
docker run --rm -v act_runner-data:/data alpine sh -c "ls -la /data && cat /data/.runner"
```
- Check logs of the register container / service for precise errors.
If the error persists, paste the exact register command you ran and its container logs and I'll inspect them.
---
## Portainer: secure register + deploy (recommended)
Use a one-shot *registration stack* (deploy it, wait for logs, then remove it) and then deploy the long-running daemon stack that uses a Docker secret for the registration token.
1) **Create a secret** in Portainer (or via CLI):
- Portainer: *Secrets → Add secret* → name: `registration_token` → value: `<your_token>`
- CLI: `echo "<your_token>" | docker secret create registration_token -`
2) **Register (one-shot)** — Stack: `.gitea/act-runner-register-stack.yml`
- Deploy in Portainer → set stack variables:
- `INSTANCE_URL` = `http://gitea_server:3000` (or `https://git.mariuszpacek.pl`)
- `REGISTRATION_TOKEN` = *your token* (or use a temporary secret and modify command to read it)
- Check service logs for success, then remove this stack.
3) **Deploy daemon (global on workers)** — Stack: `.gitea/act-runner-deploy-stack.yml`
- Ensure secret `registration_token` exists (see step 1).
- Deploy stack and set `INSTANCE_URL` and `RUNNER_NAME` as environment variables in Portainer.
- Runner will start as a global service on workers and use `/run/secrets/registration_token` (no token stored in repo).
Notes:
- **Do not commit tokens to the repo.** Use Docker secrets or Portainer's secret store.
- If you prefer a quick test, the register stack accepts `REGISTRATION_TOKEN` as an env var (less secure). After successful registration, remove the env token and rely on the Docker secret for the daemon stack.
+67
View File
@@ -0,0 +1,67 @@
name: CI
on:
push:
branches: ["**"]
jobs:
backend:
name: Backend tests (Maven)
runs-on: ['self-hosted','swarm-runner']
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Show runner info
run: |
echo "Runner name: ${{ runner.name }}"
echo "Runner labels: ${{ toJSON(runner.labels) }}"
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Cache Maven
uses: actions/cache@v4
with:
path: |
~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Run Maven tests
run: mvn -B test
frontend:
name: Frontend build (node)
runs-on: ['self-hosted','swarm-runner']
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Show runner info
run: |
echo "Runner name: ${{ runner.name }}"
echo "Runner labels: ${{ toJSON(runner.labels) }}"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Cache node modules
uses: actions/cache@v4
with:
path: frontend/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('frontend/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install & Build
run: |
cd frontend
npm ci
npm run build
@@ -0,0 +1,89 @@
package home.automation.modbus;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import static org.junit.jupiter.api.Assertions.*;
class ModbusParserTests {
private ModbusRtuTcpRepository createRepo() {
return new ModbusRtuTcpRepository(new Connection() {
@Override
public com.serotonin.modbus4j.ModbusMaster getConnection() {
throw new UnsupportedOperationException("not used in parser tests");
}
}, java.util.Collections.emptyList());
}
private String invokeDecode(ModbusRtuTcpRepository repo, short[] data, String dataType, Double scale, Boolean legacy) throws Exception {
Method m = ModbusRtuTcpRepository.class.getDeclaredMethod("decodeValueFromShorts", short[].class, String.class, Double.class, Boolean.class);
m.setAccessible(true);
return (String) m.invoke(repo, (Object) data, dataType, scale, legacy);
}
@Test
void decodeFourByteFloat() throws Exception {
ModbusRtuTcpRepository repo = createRepo();
float value = 12.34f;
int bits = Float.floatToIntBits(value);
short high = (short) ((bits >>> 16) & 0xFFFF);
short low = (short) (bits & 0xFFFF);
String decoded = invokeDecode(repo, new short[]{high, low}, "FOUR_BYTE_FLOAT", 1.0, null);
assertEquals("12.34", decoded);
}
@Test
void decodeFourByteFloatSwapped() throws Exception {
ModbusRtuTcpRepository repo = createRepo();
float value = -5.5f;
int bits = Float.floatToIntBits(value);
short high = (short) ((bits >>> 16) & 0xFFFF);
short low = (short) (bits & 0xFFFF);
// swapped: pass low first, then high and use SWAPPED dataType
String decoded = invokeDecode(repo, new short[]{low, high}, "FOUR_BYTE_FLOAT_SWAPPED", 1.0, null);
// rounding to 2 decimals expected by implementation
assertEquals("-5.5", decoded);
}
@Test
void decodeFourByteUnsignedInt() throws Exception {
ModbusRtuTcpRepository repo = createRepo();
long unsignedVal = 3000000000L; // > 2^31
int bits = (int) unsignedVal; // low 32 bits
short high = (short) ((bits >>> 16) & 0xFFFF);
short low = (short) (bits & 0xFFFF);
String decoded = invokeDecode(repo, new short[]{high, low}, "FOUR_BYTE_INT_UNSIGNED", 1.0, null);
assertEquals((double) unsignedVal, Double.parseDouble(decoded), 0.0001);
}
@Test
void decodeFourByteSignedInt() throws Exception {
ModbusRtuTcpRepository repo = createRepo();
int signedVal = -12345678;
int bits = signedVal;
short high = (short) ((bits >>> 16) & 0xFFFF);
short low = (short) (bits & 0xFFFF);
String decoded = invokeDecode(repo, new short[]{high, low}, "FOUR_BYTE_INT_SIGNED", 1.0, null);
assertEquals((double) signedVal, Double.parseDouble(decoded), 0.0001);
}
@Test
void decodeTwoByteSignedLegacyWrap() throws Exception {
ModbusRtuTcpRepository repo = createRepo();
short s = (short) (-32760); // near legacy wrap sentinel
String decoded = invokeDecode(repo, new short[]{s}, "TWO_BYTE_INT_SIGNED", 1.0, true);
// calculation: -(s + 32768) * scale -> -( -32760 + 32768) = -8
assertEquals("-8.0", decoded);
}
@Test
void decodeTwoByteNormal() throws Exception {
ModbusRtuTcpRepository repo = createRepo();
short s = (short) (123);
String decoded = invokeDecode(repo, new short[]{s}, "TWO_BYTE_INT_SIGNED", 0.1, false);
// 123 * 0.1 = 12.3
assertEquals("12.3", decoded);
}
}