Merge branch 'main' into feature/add-process-and-file-version-for-services

Resolves conflicts from main's IOC-on-runner refactor landing after this
branch's deploy-and-restart-service.sh -> deploy-service.sh rename:
- deploy-service.sh: keep the rename/VERSION-DEPLOYED-PV/conditional-restart
  changes, but adopt main's common.sh-based init_service_env() instead of
  duplicating the env/suffix logic inline.
- add-new-service.yml: keep the deploy-service.sh call for the new-service
  step, and keep main's real Install/Start New IOC steps (this branch still
  had them as stale commented-out placeholders from before that work landed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9GjKvXCmfgJpKzbQdZ7UA
This commit is contained in:
Benjamin LabrecqueandClaude Sonnet 5 committed 2026-08-27 11:39:57 +02:00
commit 7c9c0ead54
82 files changed
+478 -822

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# Shared helpers for the scripts run by the gitea runner.
# Source it as: source "$(dirname "$0")/common.sh"
# Overridable so the scripts can be exercised outside of the runner
HLA_ROOT=${HLA_ROOT:-/sls/bd/hla}
# Sets AGEBD_ENV_SUFFIX_UPPER/LOWER for the given environment.
# Usage: set_env_suffix <dev|prod>
set_env_suffix() {
local ENV=$1
if [ "${ENV}" = "dev" ]; then
AGEBD_ENV_SUFFIX_UPPER="-DEV"
AGEBD_ENV_SUFFIX_LOWER="-dev"
elif [ "${ENV}" = "prod" ]; then
AGEBD_ENV_SUFFIX_UPPER=""
AGEBD_ENV_SUFFIX_LOWER=""
else
echo "Unknown environment '${ENV}', expected 'dev' or 'prod'" >&2
exit 1
fi
}
# Sets the naming and path variables used by the deploy and ioc scripts.
# Usage: init_service_env <service-name> <dev|prod>
init_service_env() {
local SERVICE_NAME=$1
AGEBD_ENV=$2
if [ -z "${SERVICE_NAME}" ] || [ -z "${AGEBD_ENV}" ]; then
echo "Usage: $0 <service-name> <dev|prod>" >&2
exit 1
fi
set_env_suffix "${AGEBD_ENV}"
SERVICE_NAME_LOWER=$(echo "${SERVICE_NAME}" | tr '[:upper:]' '[:lower:]')
SERVICE_NAME_UPPER=$(echo "${SERVICE_NAME}" | tr '[:lower:]' '[:upper:]')
SERVICE_DIR=${HLA_ROOT}/${AGEBD_ENV}/services/${SERVICE_NAME_LOWER}/current
IOC_DIR=${SERVICE_DIR}/ioc
IOC_NAME=AGEBD-CPCL-${SERVICE_NAME_UPPER}${AGEBD_ENV_SUFFIX_UPPER}
# Host running shellbox, i.e. the host the ioc itself runs on
IOC_HOST=sls-vserv-bd-01${AGEBD_ENV_SUFFIX_LOWER}
}
+21 -16
View File
@@ -1,30 +1,22 @@
#!/bin/bash
# Deploys a service to /sls/bd/hla and (re)starts its systemd unit.
#
# Usage: ./.gitea/scripts/deploy-service.sh <service-name> <dev|prod> [restart]
SERVICE_NAME=$1
AGEBD_ENV=$2
source "$(dirname "$0")/common.sh"
init_service_env "${1:-}" "${2:-}"
DO_RESTART_SERVICE=$3
NOW=$(date +%Y-%m-%dT%H:%M:%S%Z)
echo "=========================================="
echo "Deploying service: $SERVICE_NAME (${AGEBD_ENV})"
echo "Deploying service: $SERVICE_NAME_LOWER (${AGEBD_ENV})"
echo "=========================================="
NOW=$(date +%Y-%m-%dT%H:%M:%S%Z)
SERVICE_NAME_LOWER=$(echo "$SERVICE_NAME" | tr '[:upper:]' '[:lower:]')
SERVICE_NAME_UPPER=$(echo "$SERVICE_NAME" | tr '[:lower:]' '[:upper:]')
SERVICE_DIR=/sls/bd/hla/${AGEBD_ENV}/services/${SERVICE_NAME_LOWER}/current
SYSTEMD_UNIT_NAME=AGEBD-SERVICE-${SERVICE_NAME_UPPER}.service
SYSTEMD_UNIT_FILE_PATH=${SERVICE_DIR}/systemd/${SYSTEMD_UNIT_NAME}
IOC_DIR=${SERVICE_DIR}/ioc
IOC_PORT=$(uv run --directory scripts ci/get_ioc_port.py ${SERVICE_NAME_LOWER})
if [ "${AGEBD_ENV}" = "dev" ]; then
AGEBD_ENV_SUFFIX_UPPER="-DEV"
AGEBD_ENV_SUFFIX_LOWER="-dev"
else
AGEBD_ENV_SUFFIX_UPPER=""
AGEBD_ENV_SUFFIX_LOWER=""
fi
# TODO: see https://gitea.psi.ch/sls/hla_framework_bd/issues/50
# rm -rf "${SERVICE_DIR}"
# mkdir -p ${SERVICE_DIR}
@@ -45,6 +37,19 @@ sed -i "s/{{ *agebd_env_suffix_upper* }}/${AGEBD_ENV_SUFFIX_UPPER}/g" "${IOC_DIR
sed -i "s/{{ *agebd_env_suffix_lower* }}/${AGEBD_ENV_SUFFIX_LOWER}/g" "${IOC_DIR}/"*
sed -i "s/{{ *ioc_port* }}/${IOC_PORT}/g" "${IOC_DIR}/"*
### RENAMING
### The ioc files carry the environment suffix in their name, e.g. in dev
### AGEBD-CPCL-NTURNS_main.subs -> AGEBD-CPCL-NTURNS-DEV_main.subs
### (rsync --delete restores the original names on every deploy, so this runs every time)
if [ -n "${AGEBD_ENV_SUFFIX_UPPER}" ]; then
for FILE_SUFFIX in "_main.subs" "_parameters.yaml"; do
ORIG_FILE=${IOC_DIR}/AGEBD-CPCL-${SERVICE_NAME_UPPER}${FILE_SUFFIX}
if [ -f "${ORIG_FILE}" ]; then
mv "${ORIG_FILE}" "${IOC_DIR}/AGEBD-CPCL-${SERVICE_NAME_UPPER}${AGEBD_ENV_SUFFIX_UPPER}${FILE_SUFFIX}"
fi
done
fi
### SYSTEMD
if [ -f "${SYSTEMD_UNIT_FILE_PATH}" ]; then
sed -i "s/{{ *agebd_env* }}/${AGEBD_ENV}/g" "${SYSTEMD_UNIT_FILE_PATH}"
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
# Runs an ioc action for a single service or for all services.
#
# Usage: ./.gitea/scripts/ioc-for-services.sh <install|start|restart> <service-name|all> <dev|prod>
set -euo pipefail
IOC_ACTION=${1:-}
SERVICE=${2:-}
AGEBD_ENV=${3:-}
SCRIPT_DIR=$(dirname "$0")
IOC_SCRIPT=${SCRIPT_DIR}/ioc-${IOC_ACTION}.sh
if [ ! -f "${IOC_SCRIPT}" ]; then
echo "Unknown ioc action '${IOC_ACTION}', expected 'install', 'start' or 'restart'" >&2
exit 1
fi
if [ "${SERVICE}" = "all" ]; then
SERVICES=$(ls -d services/*/ | xargs -n1 basename)
else
SERVICES=${SERVICE}
fi
for SERVICE_NAME in ${SERVICES}; do
"${IOC_SCRIPT}" "${SERVICE_NAME}" "${AGEBD_ENV}"
done
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
# Installs the ioc of a service from its deployed ioc dir on /sls.
# Requires the service to have been deployed first (deploy-and-restart-service.sh),
# which is what puts the substituted and environment-renamed ioc files in place.
#
# Usage: ./.gitea/scripts/ioc-install.sh <service-name> <dev|prod>
set -euo pipefail
source "$(dirname "$0")/common.sh"
init_service_env "${1:-}" "${2:-}"
echo "=========================================="
echo "Installing ioc: ${IOC_NAME} (${AGEBD_ENV})"
echo "=========================================="
if [ ! -d "${IOC_DIR}" ]; then
echo "Ioc dir '${IOC_DIR}' not found, deploy the service first" >&2
exit 1
fi
STDERR_FILE=$(mktemp)
trap 'rm -f "${STDERR_FILE}"' EXIT
set +e
(cd "${IOC_DIR}" && ioc install -V --facility sls --ioc "${IOC_NAME}" --clean) 2>"${STDERR_FILE}"
INSTALL_RC=$?
set -e
cat "${STDERR_FILE}" >&2
# 'ioc install ...' returns exit code 0 even on failure, so its stderr is
# inspected for error keywords to force a pipeline failure.
# Note: -a because some responses of the 'ioc ...' cli are non-utf8.
if [ ${INSTALL_RC} -ne 0 ] || grep -qa -e "\[error\]" -e "Unable to write" "${STDERR_FILE}"; then
echo "Failed to install ioc ${IOC_NAME}" >&2
exit 1
fi
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# Restarts the ioc of a service.
#
# Usage: ./.gitea/scripts/ioc-restart.sh <service-name> <dev|prod>
set -euo pipefail
source "$(dirname "$0")/common.sh"
init_service_env "${1:-}" "${2:-}"
echo "=========================================="
echo "Restarting ioc: ${IOC_NAME} (${AGEBD_ENV})"
echo "=========================================="
cd "${IOC_DIR}"
ioc restart "${IOC_NAME}"
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# Starts the ioc of a service through shellbox on the ioc host.
# Needed once after the initial install of a new ioc.
#
# Usage: ./.gitea/scripts/ioc-start.sh <service-name> <dev|prod>
set -euo pipefail
source "$(dirname "$0")/common.sh"
init_service_env "${1:-}" "${2:-}"
IOC_PORT=$(uv run --directory scripts ci/get_ioc_port.py "${SERVICE_NAME_LOWER}")
echo "=========================================="
echo "Starting ioc: ${IOC_NAME} on ${IOC_HOST}:${IOC_PORT}"
echo "=========================================="
ssh -o BatchMode=yes "${IOC_HOST}" bash -s <<REMOTE
set -e
sudo shellbox status
sudo shellbox reload
sudo shellbox status
sudo shellbox start ${IOC_PORT}
REMOTE
+19 -7
View File
@@ -9,7 +9,7 @@ env:
UV_CACHE_DIR: /var/cache/uv
UV_LINK_MODE: hardlink
# TODO: when to deploy to prod? Manual (i.e. cli command)?
# TODO: when to deploy to prod? Manual (i.e. the 'Ioc' workflow)?
jobs:
deploy:
runs-on: hla-dev
@@ -31,19 +31,31 @@ jobs:
SERVICE_NAME="${BRANCH#feature/add-service-}"
echo "service_name_lower=$SERVICE_NAME" >> $GITHUB_OUTPUT
# - name: Install Master IOC
# - name: Restart Master IOC
# The ioc steps read the deployed ioc files on /sls, so the service
# has to be deployed before its ioc is installed.
- name: Deploy and Restart Master Service
run: |
./.gitea/scripts/deploy-service.sh "master" "dev" true
# - name: Install New IOC
- name: Install Master IOC
run: |
./.gitea/scripts/ioc-install.sh "master" "dev"
# - name: Start New IOC
- name: Restart Master IOC
run: |
./.gitea/scripts/ioc-restart.sh "master" "dev"
- name: Deploy and Restart New Service
run: |
SERVICE_NAME=${{ steps.parse_branch.outputs.service_name_lower }}
./.gitea/scripts/deploy-service.sh "${SERVICE_NAME}" "dev" true
- name: Install New IOC
run: |
SERVICE_NAME=${{ steps.parse_branch.outputs.service_name_lower }}
./.gitea/scripts/ioc-install.sh "${SERVICE_NAME}" "dev"
- name: Start New IOC
run: |
SERVICE_NAME=${{ steps.parse_branch.outputs.service_name_lower }}
./.gitea/scripts/ioc-start.sh "${SERVICE_NAME}" "dev"
+6
View File
@@ -116,6 +116,12 @@ jobs:
--exclude='.pytest_cache/' \
./qt/* "${DEST_DIR}/"
### SUBSTITUTIONS
source "${{ github.workspace }}/.gitea/scripts/common.sh"
set_env_suffix "${AGEBD_ENV}"
sed -i "s/{{ *agebd_env_suffix_upper* }}/${AGEBD_ENV_SUFFIX_UPPER}/g" "${DEST_DIR}/"*
sed -i "s/{{ *agebd_env_suffix_lower* }}/${AGEBD_ENV_SUFFIX_LOWER}/g" "${DEST_DIR}/"*
# -----------------------------------------------------------------
# Services Detection, Testing, and Deployment
# -----------------------------------------------------------------
+56
View File
@@ -0,0 +1,56 @@
name: Ioc
# Manually install / start / restart iocs.
# The ioc files are read from the deployed dir on /sls,
# so the service has to be deployed (see the 'Deploy' workflow) beforehand.
on:
workflow_dispatch:
inputs:
action:
description: 'What to do with the ioc(s)'
type: choice
required: true
default: 'install'
options:
- install
- restart
- start
service:
description: "Service name in lower case, or 'all' for every service"
type: string
required: true
agebd_env:
description: 'Environment'
type: choice
required: true
default: 'dev'
options:
- dev
- prod
env:
UV_CACHE_DIR: /var/cache/uv
UV_LINK_MODE: hardlink
jobs:
ioc-dev:
if: ${{ inputs.agebd_env == 'dev' }}
runs-on: hla-dev
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run ioc ${{ inputs.action }}
run: |
./.gitea/scripts/ioc-for-services.sh "${{ inputs.action }}" "${{ inputs.service }}" "dev"
ioc-prod:
if: ${{ inputs.agebd_env == 'prod' }}
runs-on: hla-prod
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run ioc ${{ inputs.action }}
run: |
./.gitea/scripts/ioc-for-services.sh "${{ inputs.action }}" "${{ inputs.service }}" "prod"
-8
View File
@@ -1,8 +0,0 @@
[defaults]
remote_tmp = /tmp/ansible-$USER
# some responses of 'ioc ...' cli are non-utf8
module_strict_utf8_response = False
# ./ refers to dir in which this file is located
collections_path = ./installed-collections
-3
View File
@@ -1,3 +0,0 @@
collections:
- name: ansible.posix
version: 1.6.0
-19
View File
@@ -1,19 +0,0 @@
all:
vars:
agebd_env: "dev"
env_suffix_upper: "-DEV"
env_suffix_lower: "-dev"
children:
lc:
hosts:
sls-lc:
ansible_remote_tmp: /tmp/.ansible_${ansible_user}_tmp
vserv:
hosts:
sls-vserv-bd-01-dev:
ansible_ssh_pipelining: true
ansible_ssh_common_args: '-o ForwardAgent=yes'
vserv-hla:
hosts:
sls-vserv-bd-hla01-dev:
-18
View File
@@ -1,18 +0,0 @@
all:
vars:
agebd_env: "prod"
env_suffix_upper: ""
env_suffix_lower: ""
children:
lc:
hosts:
sls-lca:
ansible_remote_tmp: /tmp/.ansible_${ansible_user}_tmp
vserv:
hosts:
sls-vserv-bd-01:
ansible_ssh_pipelining: true
vserv-hla:
hosts:
sls-vserv-bd-hla01:
-42
View File
@@ -1,42 +0,0 @@
---
- name: Add a new service
hosts: vserv-hla
connection: local
gather_facts: true # Gather once here, available to all included tasks!
vars:
repo_root: "{{ playbook_dir }}/../.."
tasks:
# - name: Install Master IOC
# vars:
# preferred_service_name_lower: master
# branch_name: "feature/add-service-{{ service_name_lower }}"
# import_tasks: ../tasks/ioc/ioc-install.yml
# - name: Restart Master IOC
# vars:
# preferred_service_name_lower: master
# branch_name: "feature/add-service-{{ service_name_lower }}"
# import_tasks: ../tasks/ioc/ioc-restart.yml
- name: Deploy and Restart Master Service
vars:
preferred_service_name_lower: master
branch_name: "feature/add-service-{{ service_name_lower }}"
import_tasks: ../tasks/service/service-deploy-and-restart.yml
# - name: Install New IOC
# vars:
# branch_name: "feature/add-service-{{ service_name_lower }}"
# import_tasks: ../tasks/ioc/ioc-install.yml
# - name: Start New IOC
# vars:
# branch_name: "feature/add-service-{{ service_name_lower }}"
# import_tasks: ../tasks/ioc/ioc-start.yml
- name: Deploy and Restart New Service
vars:
branch_name: "feature/add-service-{{ service_name_lower }}"
import_tasks: ../tasks/service/service-deploy-and-restart.yml
@@ -1,24 +0,0 @@
---
- name: Deploy and Restart Modified Services
hosts: vserv-hla
connection: local
vars:
# Passed from the Gitea workflow, e.g., "master playground"
changed_services_raw: ""
agebd_env: "dev"
repo_root: "{{ playbook_dir }}/../.."
tasks:
# Fail early if no services were modified
- name: Check if there are services to deploy
ansible.builtin.meta: end_play
when: changed_services_raw | trim == ""
# Main Loop: loop through the list of modified service names
- name: Deploy and Restart Modified Services
include_tasks: ../tasks/service/service-deploy-and-restart.yml
loop: "{{ changed_services_raw.split() }}"
loop_control:
loop_var: service_name_lower
extended: yes
-11
View File
@@ -1,11 +0,0 @@
---
- name: Install an IOC
hosts: lc
gather_facts: false
vars:
workspace_dir: "/tmp/hla_framework_bd"
tasks:
- name: Run ioc install tasks
ansible.builtin.import_tasks: ../tasks/ioc/ioc-install.yml
-11
View File
@@ -1,11 +0,0 @@
---
- name: Restart an IOC
hosts: lc
gather_facts: false
vars:
workspace_dir: "/tmp/hla_framework_bd"
tasks:
- name: Run ioc restart tasks
ansible.builtin.include_tasks: ../tasks/ioc/ioc-restart.yml
-14
View File
@@ -1,14 +0,0 @@
# TODO: ansible seems to be very slow on this machine
# each step takes quite a while (e.g. cloning repo, which
# is significantly faster on other machines).
---
- name: Start an IOC
hosts: vserv
gather_facts: false
vars:
workspace_dir: "/tmp/hla_framework_bd"
tasks:
- name: Run ioc start tasks
ansible.builtin.import_tasks: ../tasks/ioc/ioc-start.yml
-63
View File
@@ -1,63 +0,0 @@
---
- name: Init service name
ansible.builtin.import_tasks: ../utils/init-service-name.yml
- name: Clone repo
ansible.builtin.import_tasks: ../utils/clone-repo.yml
- name: Get and set ioc_port
ansible.builtin.import_tasks: ../utils/get-ioc-port.yml
- name: "[{{ target_service_name_lower }}] Establish environment naming conventions"
ansible.builtin.set_fact:
service_name_upper: "{{ target_service_name_lower | upper }}"
- name: "[{{ target_service_name_lower }}] Define file paths"
ansible.builtin.set_fact:
ioc_base_dir: "{{ workspace_dir }}/services/{{ target_service_name_lower }}/current/ioc"
orig_subs_name: "AGEBD-CPCL-{{ service_name_upper }}_main.subs"
target_subs_name: "AGEBD-CPCL-{{ service_name_upper }}{{ env_suffix_upper }}_main.subs"
orig_params_name: "AGEBD-CPCL-{{ service_name_upper }}_parameters.yaml"
target_params_name: "AGEBD-CPCL-{{ service_name_upper }}{{ env_suffix_upper }}_parameters.yaml"
# Rename the subs file if we are in DEV
- name: "[{{ target_service_name_lower }}] Rename substitution file for environment"
ansible.builtin.command:
cmd: "mv {{ ioc_base_dir }}/{{ orig_subs_name }} {{ ioc_base_dir }}/{{ target_subs_name }}"
removes: "{{ ioc_base_dir }}/{{ orig_subs_name }}" # Only runs if the original file exists
# Rename the paramters file if we are in DEV
- name: "[{{ target_service_name_lower }}] Rename parameters file for environment"
ansible.builtin.command:
cmd: "mv {{ ioc_base_dir }}/{{ orig_params_name }} {{ ioc_base_dir }}/{{ target_params_name }}"
removes: "{{ ioc_base_dir }}/{{ orig_params_name }}" # Only runs if the original file exists
# Replace {{ agebd_env_suffix_upper }} inside the newly renamed subs file
- name: "[{{ target_service_name_lower }}] Substitute environment tokens inside subs file"
ansible.builtin.replace:
path: "{{ ioc_base_dir }}/{{ target_subs_name }}"
regexp: '\{\{\s*agebd_env_suffix_upper\s*\}\}'
replace: "{{ env_suffix_upper }}"
# Replace {{ agebd_env_suffix_lower }} and {{ ioc_port }} inside the newly renamed params file
- name: "[{{ target_service_name_lower }}] Substitute environment token inside params file"
ansible.builtin.replace:
path: "{{ ioc_base_dir }}/{{ target_params_name }}"
regexp: "{{ item.regexp }}"
replace: "{{ item.replace | string }}"
with_items:
- { regexp: '\{\{\s*agebd_env_suffix_lower\s*\}\}', replace: "{{ env_suffix_lower }}" }
- { regexp: '\{\{\s*ioc_port\s*\}\}', replace: "{{ ioc_port }}" }
- name: "[{{ target_service_name_lower }}] Run ioc install"
ansible.builtin.shell:
cmd: "ioc install -V --facility sls --ioc AGEBD-CPCL-{{ service_name_upper }}{{ env_suffix_upper }} --clean"
chdir: "{{ ioc_base_dir }}"
changed_when: true
register: ioc_install_result
# Force a pipeline failure if the command output contains error keywords
# (ioc install ... returns exit code 0 even on failure...)
failed_when: >
ioc_install_result.rc != 0 or
"[error]" in ioc_install_result.stderr or
"Unable to write" in ioc_install_result.stderr
-20
View File
@@ -1,20 +0,0 @@
---
- name: Init service name
ansible.builtin.import_tasks: ../utils/init-service-name.yml
- name: Clone repo
ansible.builtin.import_tasks: ../utils/clone-repo.yml
- name: "[{{ target_service_name_lower }}] Establish environment naming conventions"
ansible.builtin.set_fact:
service_name_upper: "{{ target_service_name_lower | upper }}"
- name: "[{{ target_service_name_lower }}] Define file paths"
ansible.builtin.set_fact:
ioc_base_dir: "{{ workspace_dir }}/services/{{ target_service_name_lower }}/current/ioc"
- name: "[{{ target_service_name_lower }}] Restart IOC"
ansible.builtin.shell:
cmd: "ioc restart AGEBD-CPCL-{{ service_name_upper }}{{ env_suffix_upper }}"
chdir: "{{ ioc_base_dir }}"
changed_when: true
-17
View File
@@ -1,17 +0,0 @@
---
- name: Init service name
ansible.builtin.import_tasks: ../utils/init-service-name.yml
- name: Clone repo
ansible.builtin.import_tasks: ../utils/clone-repo.yml
- name: Get and set ioc_port
ansible.builtin.import_tasks: ../utils/get-ioc-port.yml
- name: "[{{ ioc_port }}] Start IOC"
ansible.builtin.shell: |
sudo shellbox status
sudo shellbox reload
sudo shellbox status
sudo shellbox start {{ ioc_port }}
changed_when: true
@@ -1,53 +0,0 @@
---
- name: Initialize service name
ansible.builtin.set_fact:
# Uses preferred_service_name_lower if defined in vars, otherwise falls back to the CLI extra-vars
target_service_name_lower: "{{ preferred_service_name_lower | default(service_name_lower) }}"
- name: "[{{ target_service_name_lower }}] Define service specific paths and names"
ansible.builtin.set_fact:
svc_current_dir: "/sls/bd/hla/{{ agebd_env }}/services/{{ target_service_name_lower }}/current"
service_unit_name: "AGEBD-SERVICE-{{ target_service_name_lower | upper }}.service"
- name: "[{{ target_service_name_lower }}] Ensure target directory structure exists"
ansible.builtin.file:
path: "{{ svc_current_dir }}"
state: directory
# ansible.builtin.shell: |
# mkdir -p "{{ svc_current_dir }}"
# TODO: make sure this is what we want (essentially wipes existing dest dir). Does it remove .venv?
- name: "[{{ target_service_name_lower }}] Sync service deployment files"
ansible.posix.synchronize:
src: "{{ repo_root }}/services/{{ target_service_name_lower }}/current/"
dest: "{{ svc_current_dir }}/"
delete: yes # This removes stale files on the target that no longer exist locally
recursive: yes
- name: "[{{ target_service_name_lower }}] Substitute environment variable inline"
ansible.builtin.replace:
path: "{{ svc_current_dir }}/systemd/{{ service_unit_name }}"
regexp: '\{\{\s*agebd_env\s*\}\}'
replace: "{{ agebd_env }}"
# systemctl --user link --force "./{{ service_unit_name }}"
- name: "[{{ target_service_name_lower }}] Symlink systemd unit file (Absolute Path)"
ansible.builtin.file:
src: "{{ svc_current_dir }}/systemd/{{ service_unit_name }}"
dest: "~/.config/systemd/user/{{ service_unit_name }}"
state: link
force: yes # This overwrites the broken/existing symlink cleanly without error
# export XDG_RUNTIME_DIR=/run/user/$(id -u)
# systemctl --user daemon-reload
# systemctl --user enable "{{ service_unit_name }}"
# systemctl --user restart "{{ service_unit_name }}"
- name: "[{{ target_service_name_lower }}] Enable and Restart Systemd Service"
ansible.builtin.systemd_service:
name: "{{ service_unit_name }}"
scope: user
daemon_reload: yes
enabled: yes
state: restarted
environment:
XDG_RUNTIME_DIR: "/run/user/{{ ansible_user_uid }}"
-8
View File
@@ -1,8 +0,0 @@
---
- name: Clone this repository
ansible.builtin.git:
repo: "git@gitea.psi.ch:sls/hla_framework_bd.git"
dest: "{{ workspace_dir }}"
accept_hostkey: true
version: "{{ branch_name }}"
force: true
-18
View File
@@ -1,18 +0,0 @@
---
- name: Read the service registry file
ansible.builtin.slurp:
src: "{{ workspace_dir }}/config/services_registry.yml"
register: remote_registry_file
- name: Parse the service registry YAML content into a fact
ansible.builtin.set_fact:
services_registry: "{{ remote_registry_file.content | b64decode | from_yaml }}"
- name: Extract the port from the services registry and save it as a persistent fact
ansible.builtin.set_fact:
ioc_port: "{{ services_registry.services | selectattr('name', 'equalto', target_service_name_lower) | map(attribute='ioc_port') | first | default(none) }}"
- name: Fail if the service or port wasn't found in the registry
ansible.builtin.fail:
msg: "Service '{{ target_service_name_lower }}' not found in services registry or port not set"
when: ioc_port | trim == "" or ioc_port is none
@@ -1,5 +0,0 @@
---
- name: Initialize service name
ansible.builtin.set_fact:
# Uses preferred_service_name_lower if defined in vars, otherwise falls back to the CLI extra-vars
target_service_name_lower: "{{ preferred_service_name_lower | default(service_name_lower) }}"
+1 -4
View File
@@ -5,11 +5,8 @@ AGEBD_ENV=$2 # 'dev' for dev environment, any other string launches prod.
REMAINING_ARGS=( "${@:3}" )
if [ "$AGEBD_ENV" = "dev" ]; then
AGEBD_ENV_SUFFIX="-DEV"
EPICS_CA_ADDR_LIST="$EPICS_CA_ADDR_LIST 129.129.146.255"
else
AGEBD_ENV_SUFFIX=""
fi
export EPICS_CA_ADDR_LIST
caqtdm -macro "AGEBD_ENV_SUFFIX=${AGEBD_ENV_SUFFIX}" "${REMAINING_ARGS[@]}" "${UI_FILENAME}"
caqtdm "${REMAINING_ARGS[@]}" "${UI_FILENAME}"
+10 -6
View File
@@ -3,9 +3,6 @@
SVC_NAME=$1
set --
# TODO: remove?
# PSI_FACILITY=SLS
SERVICES_DIR=/sls/bd/hla/${AGEBD_ENV}/services
SVC_NAME_LOWER=${SVC_NAME,,}
SVC_NAME_LOWER_UNDERSCORES="${SVC_NAME_LOWER//-/_}"
@@ -14,11 +11,19 @@ SVC_DIR=${SERVICES_DIR}/${SVC_NAME_LOWER}
# gfa_12_epics.sh died on the 16.04.2026 due to required AFS write access
#. /etc/profile.d/gfa_12_epics.sh
#. /sls/bd/exchange/BeamData/user/felix/workarounds/epicsRHEL8.rc # temporary fix
# cas_12_epics.sh can only fall back to the CA gateway when its broadcast-address
# auto-detection fails (e.g. non-accelerator-network hosts) if PSI_GFA_FACILITY is
# set; systemd --user units don't inherit it from an interactive login shell so we need
# to set PSI_GFA_FACILITY=SLS explicitely.
export PSI_GFA_FACILITY=SLS
. /etc/profile.d/cas_12_epics.sh
if [ "${AGEBD_ENV}" = "dev" ]; then
export EPICS_CA_ADDR_LIST="$EPICS_CA_ADDR_LIST 129.129.146.255"
fi
echo "EPICS_CA_ADDR_LIST:"
echo "$EPICS_CA_ADDR_LIST"
# activate conda environment
. /opt/gfa/python-3.10/latest/bin/activate
@@ -40,7 +45,6 @@ uv sync --frozen --project ${APP_DIR}
# activate .venv, this works in conjunction with the conda environment
source ${APP_DIR}/.venv/bin/activate
# TODO: use `exec python3 ...` here?
python -u -B -m agebd_${SVC_NAME_LOWER_UNDERSCORES}.main
exec python -u -B -m agebd_${SVC_NAME_LOWER_UNDERSCORES}.main
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
-2
View File
@@ -9,8 +9,6 @@ authors = [
dependencies = [
"agebd",
"ansible>=8.7.0",
"ansible-runner>=2.4.3",
"copier>=9.10.3",
"pydantic>=2.13.4",
"pydantic-settings>=2.14.2",
-40
View File
@@ -1,40 +0,0 @@
import os
import sys
import ansible_runner
import typer
from agebd.utils import get_git_root
from core.enums import IOC_ENV
ioc = typer.Typer(no_args_is_help=True)
REPO_ROOT = get_git_root(__file__)
def run_playbook(playbook: str, env: IOC_ENV, vars: list[str]):
env_str = str(env)
if env_str not in ["dev", "prod"]:
raise ValueError("env must be 'dev' or 'prod'")
current_env = os.environ.copy()
current_env["ANSIBLE_CONFIG"] = f"{REPO_ROOT}/ansible/ansible.cfg"
extra_vars = [f"--extra-vars={var}" for var in vars]
out, err, rc = ansible_runner.run_command(
executable_cmd=f"ansible-playbook",
cmdline_args=[
playbook,
"-i",
f"{REPO_ROOT}/ansible/inventories/{env_str}/hosts.yml",
*extra_vars,
"-v",
],
envvars=current_env,
input_fd=sys.stdin,
output_fd=sys.stdout,
error_fd=sys.stderr,
)
print("rc: {}".format(rc))
print("out: {}".format(out))
print("err: {}".format(err))
-8
View File
@@ -6,14 +6,6 @@ class ServiceStatus(str, Enum):
DISABLED = "disabled"
class IOC_ENV(str, Enum):
PROD = "prod"
DEV = "dev"
def __str__(self) -> str:
return self.value
class GuiOption(str, Enum):
NEW = "new"
EXISTING = "existing"
-114
View File
@@ -1,114 +0,0 @@
import typer
from agebd.utils import get_git_root
from config.paths import Paths
from core.ansible import run_playbook
from core.enums import IOC_ENV
from models import ServiceRegistry
ioc = typer.Typer(no_args_is_help=True)
REPO_ROOT = get_git_root(__file__)
ANSIBLE_PLAYBOOKS_DIR = REPO_ROOT / "ansible" / "playbooks"
IOC_PLAYBOOK_INSTALL = ANSIBLE_PLAYBOOKS_DIR / "ioc-install.yml"
IOC_PLAYBOOK_RESTART = ANSIBLE_PLAYBOOKS_DIR / "ioc-restart.yml"
IOC_PLAYBOOK_START = ANSIBLE_PLAYBOOKS_DIR / "ioc-start.yml"
SERVICE_NAME_HELP = "Service name in lower case"
@ioc.command(no_args_is_help=True)
def install(
env: IOC_ENV = typer.Option(..., "--env"),
service_name_lower: str = typer.Option(..., "--service-name", "-s", help=SERVICE_NAME_HELP),
branch_name: str | None = typer.Option(
None, "--branch-name", "-b", help="Default is 'feature/add-service-{service-name}'"
),
):
if branch_name is None:
branch_name = f"feature/add-service-{service_name_lower}"
branch_var = f"branch_name={branch_name}"
service_name_var = f"service_name_lower={service_name_lower}"
run_playbook(playbook=str(IOC_PLAYBOOK_INSTALL), env=env, vars=[branch_var, service_name_var])
@ioc.command(no_args_is_help=True)
def install_all(
env: IOC_ENV = typer.Option(..., "--env"),
):
paths = Paths()
registry = ServiceRegistry.read_from_file(paths.service_registry_file)
services = registry.get_services()
branch_var = "branch_name=main"
for service in services:
service_name_var = f"service_name_lower={service.name_lower}"
run_playbook(
playbook=str(IOC_PLAYBOOK_INSTALL), env=env, vars=[branch_var, service_name_var]
)
@ioc.command(no_args_is_help=True)
def start(
env: IOC_ENV = typer.Option(..., "--env"),
service_name_lower: str = typer.Option(..., "--service-name", "-s", help=SERVICE_NAME_HELP),
branch_name: str | None = typer.Option(
None, "--branch-name", "-b", help="Default is 'feature/add-service-{service-name}'"
),
):
if branch_name is None:
branch_name = f"feature/add-service-{service_name_lower}"
branch_var = f"branch_name={branch_name}"
service_name_var = f"service_name_lower={service_name_lower}"
run_playbook(playbook=str(IOC_PLAYBOOK_START), env=env, vars=[service_name_var, branch_var])
@ioc.command()
def start_all(
env: IOC_ENV = typer.Option(..., "--env"),
):
paths = Paths()
registry = ServiceRegistry.read_from_file(paths.service_registry_file)
services = registry.get_services()
for service in services:
service_name_var = f"service_name_lower={service.name_lower}"
run_playbook(playbook=str(IOC_PLAYBOOK_START), env=env, vars=[service_name_var])
@ioc.command(no_args_is_help=True)
def restart(
env: IOC_ENV = typer.Option(..., "--env"),
service_name_lower: str = typer.Option(..., "--service-name", "-s", help=SERVICE_NAME_HELP),
branch_name: str | None = typer.Option(
None, "--branch-name", "-b", help="Default is 'feature/add-service-{service-name}'"
),
):
if branch_name is None:
branch_name = f"feature/add-service-{service_name_lower}"
branch_var = f"branch_name={branch_name}"
service_name_var = f"service_name_lower={service_name_lower}"
run_playbook(playbook=str(IOC_PLAYBOOK_RESTART), env=env, vars=[branch_var, service_name_var])
@ioc.command(no_args_is_help=True)
def restart_all(
env: IOC_ENV = typer.Option(..., "--env"),
):
paths = Paths()
registry = ServiceRegistry.read_from_file(paths.service_registry_file)
services = registry.get_services()
branch_var = "branch_name=main"
for service in services:
service_name_var = f"service_name_lower={service.name_lower}"
run_playbook(
playbook=str(IOC_PLAYBOOK_RESTART), env=env, vars=[branch_var, service_name_var]
)
-2
View File
@@ -2,7 +2,6 @@ import logging
import typer
import ioc
import service
logger = logging.getLogger(__name__)
@@ -10,7 +9,6 @@ logger = logging.getLogger(__name__)
cli = typer.Typer(no_args_is_help=True, add_completion=False)
cli.add_typer(service.service, name="service")
cli.add_typer(ioc.ioc, name="ioc")
if __name__ == "__main__":
@@ -1,3 +1,4 @@
<!--TODO: remove: cicd test 57-->
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
Generated
-147
View File
@@ -37,8 +37,6 @@ version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "agebd" },
{ name = "ansible" },
{ name = "ansible-runner" },
{ name = "copier" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
@@ -60,8 +58,6 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "agebd", editable = "../packages/agebd" },
{ name = "ansible", specifier = ">=8.7.0" },
{ name = "ansible-runner", specifier = ">=2.4.3" },
{ name = "copier", specifier = ">=9.10.3" },
{ name = "pydantic", specifier = ">=2.13.4" },
{ name = "pydantic-settings", specifier = ">=2.14.2" },
@@ -98,49 +94,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "ansible"
version = "10.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ansible-core" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d4/64/29fdff6fe7682342adb54802c1cd90b2272d382e1743089af88f90a1d986/ansible-10.7.0.tar.gz", hash = "sha256:59d29e3de1080e740dfa974517d455217601b16d16880314d9be26145c68dc22", size = 41256974, upload-time = "2024-12-03T18:04:25.794Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f3/95/cb8944902a2cdd94b1e19ff73695548679a388b9c473dc63c8dc64ffea3a/ansible-10.7.0-py3-none-any.whl", hash = "sha256:0089f08e047ceb70edd011be009f5c6273add613fbe491e9697c0556c989d8ea", size = 51576038, upload-time = "2024-12-03T18:04:20.065Z" },
]
[[package]]
name = "ansible-core"
version = "2.17.14"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
{ name = "jinja2" },
{ name = "packaging" },
{ name = "pyyaml" },
{ name = "resolvelib" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ff/80/2925a0564f6f99a8002c3be3885b83c3a1dc5f57ebf00163f528889865f5/ansible_core-2.17.14.tar.gz", hash = "sha256:7c17fee39f8c29d70e3282a7e9c10bd70d5cd4fd13ddffc5dcaa52adbd142ff8", size = 3119687, upload-time = "2025-09-08T18:28:03.158Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/86/29/d694562f1a875b50aa74f691521fe493704f79cf1938cd58f28f7e2327d2/ansible_core-2.17.14-py3-none-any.whl", hash = "sha256:34a49582a57c2f2af17ede2cefd3b3602a2d55d22089f3928570d52030cafa35", size = 2189656, upload-time = "2025-09-08T18:28:00.375Z" },
]
[[package]]
name = "ansible-runner"
version = "2.4.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
{ name = "pexpect" },
{ name = "python-daemon" },
{ name = "pyyaml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/68/4f/62222b42b52cc771db51ccc77ffac19cc5f0196ff61de7c93585845a819b/ansible_runner-2.4.3.tar.gz", hash = "sha256:5f3025529bb968fdc3b627457dd8d418dbf9d53bc73735d50e69c28544d53031", size = 154148, upload-time = "2026-03-16T18:42:14.861Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e3/89/fa7f81db6134caa455c9351373ee934b5fdeed212eb939d6c42d80d683d7/ansible_runner-2.4.3-py3-none-any.whl", hash = "sha256:cdac6daa151a50084ffda710e769db23fa975fc0507796191d7708831b286e37", size = 80257, upload-time = "2026-03-16T18:42:13.72Z" },
]
[[package]]
name = "asttokens"
version = "3.0.2"
@@ -150,29 +103,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" },
]
[[package]]
name = "cffi"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c0/e9/6d7724983b3d5a0908dbf74f64038ade77c18646ff6636ec7894fd392ce1/cffi-2.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0", size = 183837, upload-time = "2026-07-06T21:32:09.655Z" },
{ url = "https://files.pythonhosted.org/packages/69/aa/24580a278de21fd7322635556334d9b535f1cbc00b0a3919447cdf464c65/cffi-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd", size = 184226, upload-time = "2026-07-06T21:32:11.196Z" },
{ url = "https://files.pythonhosted.org/packages/88/a9/02cae418ec4beb282ace11958d9d4737793439d561fadc7e6d56f2e2b354/cffi-2.1.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46", size = 211107, upload-time = "2026-07-06T21:32:12.328Z" },
{ url = "https://files.pythonhosted.org/packages/3b/30/c806937ed5e4c2c7ac30d9d6b76b5dc57ff8b75d83800d9bb11a8253cf2a/cffi-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2", size = 218733, upload-time = "2026-07-06T21:32:13.67Z" },
{ url = "https://files.pythonhosted.org/packages/f9/cf/398272b8bbfd58aa314fda5a7f1cdbb26d1d78ae324a11211521315dd1f0/cffi-2.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd", size = 205543, upload-time = "2026-07-06T21:32:15.148Z" },
{ url = "https://files.pythonhosted.org/packages/45/ca/f91641185cdd90c36d317a9dc7f85e88ef8682d8b300977baff5e23c35d8/cffi-2.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3", size = 205460, upload-time = "2026-07-06T21:32:16.479Z" },
{ url = "https://files.pythonhosted.org/packages/38/66/04781a77b411f0bb5b234d62c1814754ab75ebe455ccff1b08e8d7aae98f/cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0", size = 218760, upload-time = "2026-07-06T21:32:17.98Z" },
{ url = "https://files.pythonhosted.org/packages/d0/9a/bb1d5ed9c3fcae158e9f6391bf309c95d98c2ac37ed56573228471d0af5e/cffi-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43", size = 221230, upload-time = "2026-07-06T21:32:19.407Z" },
{ url = "https://files.pythonhosted.org/packages/41/aa/3c1409cdd26094efacd1c36c66e0a6eb9d4296e4fd4f9901b8b2042f4323/cffi-2.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c", size = 213524, upload-time = "2026-07-06T21:32:20.828Z" },
{ url = "https://files.pythonhosted.org/packages/fa/75/74dfb7c3fc6ebbd408038476bd4c1d7e925c62614e7b9c534ecc34218288/cffi-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd", size = 220341, upload-time = "2026-07-06T21:32:21.9Z" },
{ url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" },
{ url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
@@ -207,44 +137,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/22/2c/68dec88ee26f96af2d4d8ee9d8567313a0826f4fd6bc5ae4527be670d354/copier-9.17.0-py3-none-any.whl", hash = "sha256:fe4e7b59faf4c0e1386eccbb79c6797e6df33ca20d5de49a8372571d8669f2ad", size = 65954, upload-time = "2026-07-13T14:33:03.301Z" },
]
[[package]]
name = "cryptography"
version = "49.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" },
{ url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" },
{ url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" },
{ url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" },
{ url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" },
{ url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" },
{ url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" },
{ url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" },
{ url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" },
{ url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" },
{ url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" },
{ url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" },
{ url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" },
{ url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" },
{ url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" },
{ url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" },
{ url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" },
{ url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" },
{ url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" },
{ url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" },
{ url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" },
{ url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" },
{ url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" },
{ url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" },
{ url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" },
{ url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" },
]
[[package]]
name = "decorator"
version = "5.3.1"
@@ -406,15 +298,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl", hash = "sha256:e1082f5564917649c76fed239117820610516ec10f87735d0338688800a55b34", size = 18975, upload-time = "2022-06-30T14:08:49.571Z" },
]
[[package]]
name = "lockfile"
version = "0.12.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/17/47/72cb04a58a35ec495f96984dddb48232b551aafb95bde614605b754fe6f7/lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", size = 20874, upload-time = "2015-11-25T18:29:58.279Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/22/9460e311f340cb62d26a38c419b1381b8593b0bb6b5d1f056938b086d362/lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa", size = 13564, upload-time = "2015-11-25T18:29:51.462Z" },
]
[[package]]
name = "markdown-it-py"
version = "4.2.0"
@@ -597,15 +480,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" },
]
[[package]]
name = "pycparser"
version = "3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
]
[[package]]
name = "pydantic"
version = "2.13.4"
@@ -748,18 +622,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" },
]
[[package]]
name = "python-daemon"
version = "3.1.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "lockfile" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/37/4f10e37bdabc058a32989da2daf29e57dc59dbc5395497f3d36d5f5e2694/python_daemon-3.1.2.tar.gz", hash = "sha256:f7b04335adc473de877f5117e26d5f1142f4c9f7cd765408f0877757be5afbf4", size = 71576, upload-time = "2024-12-03T08:41:07.843Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/3c/b88167e2d6785c0e781ee5d498b07472aeb9b6765da3b19e7cc9e0813841/python_daemon-3.1.2-py3-none-any.whl", hash = "sha256:b906833cef63502994ad48e2eab213259ed9bb18d54fa8774dcba2ff7864cec6", size = 30872, upload-time = "2024-12-03T08:41:03.322Z" },
]
[[package]]
name = "python-dotenv"
version = "1.2.2"
@@ -798,15 +660,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" },
]
[[package]]
name = "resolvelib"
version = "1.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ce/10/f699366ce577423cbc3df3280063099054c23df70856465080798c6ebad6/resolvelib-1.0.1.tar.gz", hash = "sha256:04ce76cbd63fded2078ce224785da6ecd42b9564b1390793f64ddecbe997b309", size = 21065, upload-time = "2023-03-09T05:10:38.292Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/fc/e9ccf0521607bcd244aa0b3fbd574f71b65e9ce6a112c83af988bbbe2e23/resolvelib-1.0.1-py2.py3-none-any.whl", hash = "sha256:d2da45d1a8dfee81bdd591647783e340ef3bcb104b54c383f70d422ef5cc7dbf", size = 17194, upload-time = "2023-03-09T05:10:36.214Z" },
]
[[package]]
name = "rich"
version = "15.0.0"
-36
View File
@@ -1,36 +0,0 @@
# Ansible
## Running a playbook manually
Set vars on command line:
```
ansible-playbook ansible/playbooks/add-new-service.yml \
-i ansible/hosts.yml \
--extra-vars="repo_root=${{ github.workspace }} agebd_env=dev service_name_lower=<service dir name>" \
-v
```
### Install an IOC
```
ansible-playbook ansible/playbooks/ioc-install.yml \
-i ansible/hosts.yml \
--extra-vars="agebd_env=dev branch_name=feature/add-service-playground service_name_lower=playground ioc_port=50003" \
-v
```
### Start an IOC
```
ansible-playbook ansible/playbooks/ioc-start.yml \
-i ansible/hosts.yml \
--extra-vars="service_name_lower=playground ioc_port=50003" \
-v
```
### Restart an IOC
```
ansible-playbook ansible/playbooks/ioc-restart.yml \
-i ansible/hosts.yml \
--extra-vars="agebd_env=dev branch_name=feature/add-service-playground service_name_lower=playground" \
-v
```
+29 -2
View File
@@ -3,9 +3,11 @@
## Hosts
- `sls-vserv-bd-01(-dev)`
- Runs shellbox commands (and `ioc install ...`?)
- Runs the shellbox commands, which the runner triggers over `ssh`
(see `.gitea/scripts/ioc-start.sh`)
- `sls-vserv-bd-hla01(-dev)`
- Runs everything else
- Runs the runner itself, and therefore everything else: the deployments
to `/sls/bd/hla/<env>/` and the `ioc ...` commands
## Setup
@@ -182,3 +184,28 @@ The gitea actions run as user `svcusr-sls2hla`
### #TODO: this is a temporary workaround
I created an ssh key on `sls-vserv-bd-hla01-dev` (where the runner runs), and added the public key to my
gitea profile ssh keys. That way the `svcusr` can clone the repos I can clone.
## Setup `sls-vserv-bd-01-dev`
On `sls-vserv-bd-hla01-dev`
```
sudo su - svcusr-sls2hla
cat ~/.ssh/id_ed25519.pub
```
If it does not exist yet:
```
sudo su - svcusr-sls2hla
ssh-keygen -t ed25519 -N "" -C "svcusr-sls2hla@sls-vserv-bd-hla01-dev" -f ~/.ssh/id_ed25519
cat ~/.ssh/id_ed25519.pub
```
```
ssh sls-vserv-bd-01-dev # lands as your own user
sudo install -d -m 700 -o svcusr-sls2hla -g unx-nogroup /home/svcusr-sls2hla /home/svcusr-sls2hla/.ssh
echo '<paste the pubkey>' | sudo tee /home/svcusr-sls2hla/.ssh/authorized_keys
sudo chown svcusr-sls2hla:unx-nogroup /home/svcusr-sls2hla/.ssh/authorized_keys
sudo chmod 600 /home/svcusr-sls2hla/.ssh/authorized_keys
```
+27 -1
View File
@@ -1,6 +1,8 @@
# IOC
The following instructions explain the basics of starting and managing IOCs on the IOC host.
The following instructions explain the basics of starting and managing IOCs on the IOC host.
They describe the manual steps. In practice the CI/CD runner does this for you, see
[Automation](#automation) below.
- TODO: some iocs not associated to a service, where to put?
- TODO: need to dev/prod template them too
@@ -92,3 +94,27 @@ sudo shellbox start 50045 # start ioc
sudo shellbox log 50045 -f # log file (tail)
exit # exit ssh
```
## Automation
Installing, starting and restarting IOCs is done by the gitea runner, not by hand:
- `.gitea/scripts/ioc-install.sh <service-name> <dev|prod>`
- `.gitea/scripts/ioc-start.sh <service-name> <dev|prod>` (shellbox, over `ssh`)
- `.gitea/scripts/ioc-restart.sh <service-name> <dev|prod>`
The scripts operate on the deployed IOC files in
`/sls/bd/hla/<env>/services/<service-name>/current/ioc`, so a service has to be deployed
before its IOC can be installed.
They run automatically when a new service is added (`.gitea/workflows/add-new-service.yml`).
## Manual CICD Workflow trigger
IOC commands can be triggered manually for any service (or `all` services) through the `Ioc` workflow
(`.gitea/workflows/ioc.yml`) in the gitea Actions tab:
```
Gitea Repo > Actions tab > Ioc (row on left side) > Run Workflow
```
+27 -2
View File
@@ -62,9 +62,34 @@ Type "help", "copyright", "credits" or "license" for more information.
## Systemd Services
When ssh-ing into a machine, `/etc/profile.d/*` automatically gets sourced.
When ssh-ing into a machine, `/etc/profile.d/*` automatically gets sourced. And thus we have:
```
[sls-vserv-bd-hla01-dev ~]$ env | grep EPICS
EPICS_CA_ADDR_LIST=sls-cagw.psi.ch:5062
```
When running a `systemd` service, this does not happen.
Note -- it seems that the following does not work as expected:
```
That's why we need to source `/etc/profile.d/cas_12_epics.sh` in our startup script
to make sure that `EPICS` is configured properly.
to make sure that `EPICS` is configured properly.
```
After sourcing the script, we still have: `EPICS_CA_ADDR_LIST=` (i.e. the script does not set `EPICS_CA_ADDR_LIST=sls-cagw.psi.ch:5062`) in `systemd` service.
Whereas in the shell `unset EPICS_CA_ADDR_LIST` and then `. /etc/profile/cas_12_epics.sh` properly sets `EPICS_CA_ADDR_LIST=sls-cagw.psi.ch:5062`.
Check which env variables a `systemd` service is running with:
```
[sls-vserv-bd-hla01-dev ~]$ systemctl --user restart AGEBD-SERVICE-SCRUBBING.service && sleep 1 && PID=$(systemctl --user show -p MainPID --value AGEBD-SERVICE-SCRUBBING.service)
[sls-vserv-bd-hla01-dev ~]$ tr '\0' '\n' < /proc/$PID/environ | grep EPICS
EPICS_CAS_IGNORE_ADDR_LIST=
EPICS_CA_ADDR_LIST= 129.129.146.255
EPICS_HOST_ARCH=linux-x86_64
EPICS_BASE_HOST_BIN=/opt/gfa/python-3.10/20220602/epics/bin/linux-x86_64
PYEPICS_LIBCA=/opt/gfa/python-3.10/20220602/epics/lib/linux-x86_64/libca.so
EPICS_BASE_VERSION=7.0.5.0
EPICS_BASE=/opt/gfa/python-3.10/20220602/epics
```
+2 -2
View File
@@ -16,8 +16,8 @@ In `dev`:
./bin/sls_hla_launch_gui.sh qt/A_BD_InjectionGuard.ui dev
```
If you need to pass another macro variable in `dev`, you will need to set `AGEBD_ENV_SUFFIX` explicitely:
Any extra arguments are passed straight through to `caqtdm`, e.g. to set a macro:
```
./bin/sls_hla_launch_gui.sh qt/A_BD_Tune.ui dev -macro "AGEBD_ENV_SUFFIX=-DEV, OTHER_ENV_VAR=HEYHEY"
./bin/sls_hla_launch_gui.sh qt/A_BD_Tune.ui dev -macro "OTHER_ENV_VAR=HEYHEY"
```
+7 -1
View File
@@ -12,6 +12,9 @@ class PVLink:
def __init__(self, pvname, **kwargs):
self.link = self.connect(pvname, **kwargs)
self.value = np.nan
self.timestamp = time()
# sleep(0.1)
# print(self.pvname, self.link.connected)
@@ -60,7 +63,10 @@ class PVLink:
sleep(5)
self.link.reconnect()
try:
self.link.reconnect()
except Exception:
print("{:} reconnect attempt raised an error, retrying ...".format(self.pvname))
sleep(1)
+82 -80
View File
@@ -9,107 +9,109 @@ from agebd.service.base import BaseService
logger = logging.getLogger(__name__)
class CallbackRunner:
class _BaseRunner:
"""
Shared control loop: poll abort/onoff PVs, dispatch to the active or
paused tick, ping the alive watchdog, and stop cleanly on exception.
"""
def __init__(self, service: BaseService):
self.svc = service
self.pvs = service.pvs
def start(self):
logger.debug(f"Starting {type(self).__name__} for service: {self.svc.name}")
while not self.pvs.abort.get():
try:
# if service is not paused
if self.pvs.onoff.get():
self._tick_active()
# if service is paused
else:
self._tick_paused()
# inform watchdog of service alive state
self.pvs.alive.put(0)
except Exception as e:
self.svc.exception = e
self.svc.crashed = True
break
self._on_stop()
self.svc.finalize_termination()
def _tick_active(self):
raise NotImplementedError
def _tick_paused(self):
raise NotImplementedError
def _on_stop(self):
pass
class CallbackRunner(_BaseRunner):
"""
Executes service updates asynchronously, triggered by EPICS PV events.
"""
def __init__(self, service: BaseService):
self.svc = service
self.pvs = service.pvs
self._callback_active = False
def _tick_active(self):
# add callback for triggering the loop if not active
if not self.svc.CallbackActive:
self.svc.transition_to_running()
self.pvs.CallbackPV.add_callback(callback=self.svc.CallbackFun, index=1)
self.svc.CallbackActive = 1
def start(self):
logger.debug(f"Starting CallbackRunner for service: {self.svc.name}")
while not self.pvs.abort.get():
try:
# if service is not paused
if self.pvs.onoff.get():
# add callback for triggering the loop if not active
if not self._callback_active:
self.svc.transition_to_running()
self.pvs.CallbackPV.add_callback(callback=self.svc.CallbackFun, index=1)
self.svc.CallbackActive = 1
# run mainloop of service
self.svc.update()
# run mainloop of service
self.svc.update()
# inform watchdog of service run state
self.pvs.running.put(0)
# inform watchdog of service run state
self.pvs.running.put(0)
# wait for callback to fire
poll(evt=1.0e-5, iot=0.1)
# wait for callback to fire
poll(evt=1.0e-5, iot=0.1)
def _tick_paused(self):
# remove callback for triggering the loop
if self.svc.CallbackActive:
self.svc.transition_to_paused()
self.pvs.CallbackPV.remove_callback(index=1)
self.svc.CallbackActive = 0
self.svc.CallbackFired = 0
# if service is paused
else:
# remove callback for triggering the loop
if self.svc.CallbackActive:
self.svc.transition_to_paused()
self.pvs.CallbackPV.remove_callback(index=1)
self.svc.CallbackActive = 0
self.svc.CallbackFired = 0
# wait until service is unpaused
sleep(0.1)
# inform watchdog of service alive state
self.pvs.alive.put(0)
except Exception as e:
self.svc.exception = e
self.svc.crashed = True
break
# wait until service is unpaused
sleep(0.1)
def _on_stop(self):
# remove callback(s) from event(s)
self.pvs.CallbackPV.remove_callback(index=1)
self.svc.finalize_termination()
class PeriodicRunner:
class PeriodicRunner(_BaseRunner):
"""
Executes service updates at a fixed frequency.
"""
def __init__(self, service: BaseService):
self.svc = service
self.pvs = service.pvs
def _tick_active(self):
if not self.svc.status == ServiceStatus.RUNNING:
self.svc.transition_to_running()
def start(self):
logger.debug(f"Starting PeriodicRunner for service: {self.svc.name}")
while not self.pvs.abort.get():
try:
# if service is not paused
if self.pvs.onoff.get():
if not self.svc.status == ServiceStatus.RUNNING:
self.svc.transition_to_running()
# run mainloop of service
self.svc.update()
# run mainloop of service
self.svc.update()
# inform watchdog of service run state
self.pvs.running.put(0)
# inform watchdog of service run state
self.pvs.running.put(0)
# wait for timer
sleep(self.svc.sleep_interval)
# wait for timer
sleep(self.svc.sleep_interval)
def _tick_paused(self):
if not self.svc.status == ServiceStatus.PAUSED:
self.svc.transition_to_paused()
# if service is paused
else:
if not self.svc.status == ServiceStatus.PAUSED:
self.svc.transition_to_paused()
self.svc.CallbackFired = 0
self.svc.CallbackFired = 0
# wait until service is unpaused
sleep(0.1)
# inform watchdog of service alive state
self.pvs.alive.put(0)
except Exception as e:
self.svc.exception = e
self.svc.crashed = True
break
self.svc.finalize_termination()
# wait until service is unpaused
sleep(0.1)
+1 -1
View File
@@ -1,6 +1,6 @@
from agebd.pv import PVLink
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
class BasePVs:
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+2 -2
View File
@@ -986,7 +986,7 @@ caLabel[statusTip="Operation"]{
<bool>false</bool>
</property>
<property name="scriptCommand">
<string notr="true">ssh svcusr-sls2hla@sls-vserv-bd-hla01 systemctl --user restart AGEBD-SERVICE-MASTER.service</string>
<string notr="true">ssh svcusr-sls2hla@sls-vserv-bd-hla01{{ agebd_env_suffix_lower }} systemctl --user restart AGEBD-SERVICE-MASTER.service</string>
</property>
</widget>
<widget class="caScriptButton" name="cascriptbutton_2">
@@ -1015,7 +1015,7 @@ caLabel[statusTip="Operation"]{
<bool>false</bool>
</property>
<property name="scriptCommand">
<string notr="true">terminator -e "ssh svcusr-sls2hla@sls-vserv-bd-hla01"</string>
<string notr="true">terminator -e "ssh svcusr-sls2hla@sls-vserv-bd-hla01{{ agebd_env_suffix_lower }}"</string>
</property>
</widget>
</widget>
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--TODO: remove: cicd test 57-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_dbpm3curr import PVs, Service
from agebd_dbpm3curr.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_injectionguard import PVs, Service
from agebd_injectionguard.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_master import PVs, Service
from agebd_master.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_plots import PVs, Service
from agebd_plots.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_postmortemlog import PVs, Service
from agebd_postmortemlog.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd_scrubbing import PVs, Service
from agebd_scrubbing.runner import Runner
from agebd_scrubbing.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_shifttool import PVs, Service
from agebd_shifttool.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_taubpm import PVs, Service
from agebd_taubpm.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_taupct import PVs, Service
from agebd_taupct.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_timing import PVs, Service
from agebd_timing.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd_topuptool import PVs, Service
from agebd_topuptool.runner import Runner
from agebd_topuptool.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_tune import PVs, Service
from agebd_tune.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_tunebump import PVs, Service
from agebd_tunebump.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -6,7 +6,7 @@ from agebd.utils import init_logging
from agebd_tunefbx import PVs, Service
from agebd_tunefbx.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
@@ -7,7 +7,7 @@ from agebd_tunefby import PVs, Service
from agebd_tunefby.service import SERVICE_NAME
# TODO: remove: cicd test 54
# TODO: remove: cicd test 57
def main(
log_level: LogLevel = typer.Option(
@@ -1,3 +1,4 @@
<!--TODO: remove: cicd test 57-->
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>