feature/gitea-actions #2
No files matched your search
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user